Add distribution settings for Asset Store vs git defaults (#404)

* Add distribution settings for Asset Store vs git defaults

Introduce McpDistributionSettings ScriptableObject to configure different defaults for Asset Store and git distributions without code forking. Add skipSetupWindowWhenRemoteDefault flag to bypass setup wizard when shipping with hosted MCP URL. Replace hardcoded localhost:8080 defaults with configurable defaultHttpBaseUrl from distribution settings in HttpEndpointUtility and WebSocketTransportClient.

* Improve local address detection in McpDistributionSettings with comprehensive IP range checks

Replace simple string-based localhost/127.0.0.1 checks with robust IsLocalAddress method that validates loopback addresses, private IPv4 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16), IPv6 link-local and loopback addresses, and .local hostnames using proper URI parsing and IPAddress validation.

* Fix error
main
Marcus Sanatan 2025-11-27 22:10:21 -04:00 committed by GitHub
parent bd620e04be
commit c50e583300
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 139 additions and 2 deletions

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 85b87f3586d7f4ab9a4c6299dd6f8e0b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,105 @@
using System;
using System.Net;
using System.Net.Sockets;
using UnityEngine;
namespace MCPForUnity.Editor.Config
{
/// <summary>
/// Distribution controls so we can ship different defaults (Asset Store vs. git) without forking code.
/// </summary>
[CreateAssetMenu(menuName = "MCP/Distribution Settings", fileName = "McpDistributionSettings")]
public class McpDistributionSettings : ScriptableObject
{
[SerializeField] internal string defaultHttpBaseUrl = "http://localhost:8080";
[SerializeField] internal bool skipSetupWindowWhenRemoteDefault = false;
internal bool IsRemoteDefault =>
!string.IsNullOrWhiteSpace(defaultHttpBaseUrl)
&& !IsLocalAddress(defaultHttpBaseUrl);
private static bool IsLocalAddress(string url)
{
if (string.IsNullOrWhiteSpace(url))
{
return true;
}
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri))
{
return false;
}
string host = uri.Host;
if (string.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase))
{
return true;
}
if (IPAddress.TryParse(host, out var ip))
{
if (IPAddress.IsLoopback(ip))
{
return true;
}
if (ip.AddressFamily == AddressFamily.InterNetwork)
{
var bytes = ip.GetAddressBytes();
// 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.0.0/16
if (bytes[0] == 10) return true;
if (bytes[0] == 172 && bytes[1] >= 16 && bytes[1] <= 31) return true;
if (bytes[0] == 192 && bytes[1] == 168) return true;
if (bytes[0] == 169 && bytes[1] == 254) return true;
}
else if (ip.AddressFamily == AddressFamily.InterNetworkV6)
{
// ::1 loopback or fe80::/10 link-local
if (ip.IsIPv6LinkLocal || ip.Equals(IPAddress.IPv6Loopback))
{
return true;
}
}
return false;
}
// Hostname: treat *.local as local network; otherwise assume remote.
if (host.EndsWith(".local", StringComparison.OrdinalIgnoreCase))
{
return true;
}
return false;
}
}
internal static class McpDistribution
{
private const string ResourcePath = "McpDistributionSettings";
private static McpDistributionSettings _cached;
internal static McpDistributionSettings Settings
{
get
{
if (_cached != null)
{
return _cached;
}
_cached = UnityEngine.Resources.Load<McpDistributionSettings>(ResourcePath);
if (_cached != null)
{
return _cached;
}
// No asset present (git/dev installs) - fall back to baked-in defaults.
_cached = ScriptableObject.CreateInstance<McpDistributionSettings>();
_cached.name = "McpDistributionSettings (Runtime Defaults)";
return _cached;
}
}
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 076640e601dc44b7ea64105223d6f0d3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,6 +1,7 @@
using System; using System;
using UnityEditor; using UnityEditor;
using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Config;
namespace MCPForUnity.Editor.Helpers namespace MCPForUnity.Editor.Helpers
{ {
@ -12,7 +13,7 @@ namespace MCPForUnity.Editor.Helpers
public static class HttpEndpointUtility public static class HttpEndpointUtility
{ {
private const string PrefKey = EditorPrefKeys.HttpBaseUrl; private const string PrefKey = EditorPrefKeys.HttpBaseUrl;
private const string DefaultBaseUrl = "http://localhost:8080"; private static string DefaultBaseUrl => McpDistribution.Settings.defaultHttpBaseUrl;
/// <summary> /// <summary>
/// Returns the normalized base URL currently stored in EditorPrefs. /// Returns the normalized base URL currently stored in EditorPrefs.

View File

@ -6,6 +6,7 @@ using System.Net.WebSockets;
using System.Text; using System.Text;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using MCPForUnity.Editor.Config;
using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Services.Transport; using MCPForUnity.Editor.Services.Transport;
using Newtonsoft.Json; using Newtonsoft.Json;
@ -667,7 +668,7 @@ namespace MCPForUnity.Editor.Services.Transport.Transports
{ {
if (string.IsNullOrWhiteSpace(baseUrl)) if (string.IsNullOrWhiteSpace(baseUrl))
{ {
baseUrl = "http://localhost:8080"; baseUrl = McpDistribution.Settings.defaultHttpBaseUrl;
} }
if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var httpUri)) if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var httpUri))

View File

@ -4,6 +4,7 @@ using MCPForUnity.Editor.Dependencies.Models;
using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Windows; using MCPForUnity.Editor.Windows;
using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Config;
using UnityEditor; using UnityEditor;
using UnityEngine; using UnityEngine;
@ -44,6 +45,16 @@ namespace MCPForUnity.Editor.Setup
// Check if setup was already completed or dismissed in previous sessions // Check if setup was already completed or dismissed in previous sessions
bool setupCompleted = EditorPrefs.GetBool(SETUP_COMPLETED_KEY, false); bool setupCompleted = EditorPrefs.GetBool(SETUP_COMPLETED_KEY, false);
bool setupDismissed = EditorPrefs.GetBool(SETUP_DISMISSED_KEY, false); bool setupDismissed = EditorPrefs.GetBool(SETUP_DISMISSED_KEY, false);
bool userOverrodeHttpUrl = EditorPrefs.HasKey(EditorPrefKeys.HttpBaseUrl);
// In Asset Store builds with a remote default URL (and no user override), skip the local setup wizard.
if (!userOverrodeHttpUrl
&& McpDistribution.Settings.skipSetupWindowWhenRemoteDefault
&& McpDistribution.Settings.IsRemoteDefault)
{
McpLog.Info("Skipping Setup Window because this distribution ships with a hosted MCP URL. Open Window/MCP For Unity/Setup Window if you want to configure a local runtime.", always: false);
return;
}
// Only show Setup Window if it hasn't been completed or dismissed before // Only show Setup Window if it hasn't been completed or dismissed before
if (!(setupCompleted || setupDismissed)) if (!(setupCompleted || setupDismissed))