From 83dd9c0d9a0225a424079e8d509bf0509a85442b Mon Sep 17 00:00:00 2001 From: Marcus Sanatan Date: Thu, 27 Nov 2025 21:35:44 -0400 Subject: [PATCH 1/3] 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. --- MCPForUnity/Editor/Config.meta | 8 ++++ .../Editor/Config/McpDistributionSettings.cs | 48 +++++++++++++++++++ .../Config/McpDistributionSettings.cs.meta | 11 +++++ .../Editor/Helpers/HttpEndpointUtility.cs | 3 +- .../Transports/WebSocketTransportClient.cs | 3 +- .../Editor/Setup/SetupWindowService.cs | 11 +++++ 6 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 MCPForUnity/Editor/Config.meta create mode 100644 MCPForUnity/Editor/Config/McpDistributionSettings.cs create mode 100644 MCPForUnity/Editor/Config/McpDistributionSettings.cs.meta diff --git a/MCPForUnity/Editor/Config.meta b/MCPForUnity/Editor/Config.meta new file mode 100644 index 00000000..ae74d0cb --- /dev/null +++ b/MCPForUnity/Editor/Config.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 85b87f3586d7f4ab9a4c6299dd6f8e0b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MCPForUnity/Editor/Config/McpDistributionSettings.cs b/MCPForUnity/Editor/Config/McpDistributionSettings.cs new file mode 100644 index 00000000..0f765ebe --- /dev/null +++ b/MCPForUnity/Editor/Config/McpDistributionSettings.cs @@ -0,0 +1,48 @@ +using System; +using UnityEngine; + +namespace MCPForUnity.Editor.Config +{ + /// + /// Distribution controls so we can ship different defaults (Asset Store vs. git) without forking code. + /// + [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) + && !defaultHttpBaseUrl.Contains("localhost", StringComparison.OrdinalIgnoreCase) + && !defaultHttpBaseUrl.Contains("127.0.0.1", StringComparison.OrdinalIgnoreCase); + } + + 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(ResourcePath); + if (_cached != null) + { + return _cached; + } + + // No asset present (git/dev installs) - fall back to baked-in defaults. + _cached = ScriptableObject.CreateInstance(); + _cached.name = "McpDistributionSettings (Runtime Defaults)"; + return _cached; + } + } + } +} diff --git a/MCPForUnity/Editor/Config/McpDistributionSettings.cs.meta b/MCPForUnity/Editor/Config/McpDistributionSettings.cs.meta new file mode 100644 index 00000000..afde627d --- /dev/null +++ b/MCPForUnity/Editor/Config/McpDistributionSettings.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 076640e601dc44b7ea64105223d6f0d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs b/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs index bda33cbb..53a1c843 100644 --- a/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs +++ b/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs @@ -1,6 +1,7 @@ using System; using UnityEditor; using MCPForUnity.Editor.Constants; +using MCPForUnity.Editor.Config; namespace MCPForUnity.Editor.Helpers { @@ -12,7 +13,7 @@ namespace MCPForUnity.Editor.Helpers public static class HttpEndpointUtility { private const string PrefKey = EditorPrefKeys.HttpBaseUrl; - private const string DefaultBaseUrl = "http://localhost:8080"; + private static string DefaultBaseUrl => McpDistribution.Settings.defaultHttpBaseUrl; /// /// Returns the normalized base URL currently stored in EditorPrefs. diff --git a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs b/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs index 14ff7259..35011a80 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs @@ -6,6 +6,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using MCPForUnity.Editor.Config; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Services.Transport; using Newtonsoft.Json; @@ -667,7 +668,7 @@ private static Uri BuildWebSocketUri(string baseUrl) { if (string.IsNullOrWhiteSpace(baseUrl)) { - baseUrl = "http://localhost:8080"; + baseUrl = McpDistribution.Settings.defaultHttpBaseUrl; } if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var httpUri)) diff --git a/MCPForUnity/Editor/Setup/SetupWindowService.cs b/MCPForUnity/Editor/Setup/SetupWindowService.cs index b1c5ed6d..1bf4ad91 100644 --- a/MCPForUnity/Editor/Setup/SetupWindowService.cs +++ b/MCPForUnity/Editor/Setup/SetupWindowService.cs @@ -4,6 +4,7 @@ using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Windows; using MCPForUnity.Editor.Constants; +using MCPForUnity.Editor.Config; using UnityEditor; using UnityEngine; @@ -44,6 +45,16 @@ private static void CheckSetupNeeded() // Check if setup was already completed or dismissed in previous sessions bool setupCompleted = EditorPrefs.GetBool(SETUP_COMPLETED_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 if (!(setupCompleted || setupDismissed)) From 4aa13d7e197b3dce1458edb294c8bc1bfa7f2f7c Mon Sep 17 00:00:00 2001 From: Marcus Sanatan Date: Thu, 27 Nov 2025 21:50:21 -0400 Subject: [PATCH 2/3] 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. --- .../Editor/Config/McpDistributionSettings.cs | 61 ++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/MCPForUnity/Editor/Config/McpDistributionSettings.cs b/MCPForUnity/Editor/Config/McpDistributionSettings.cs index 0f765ebe..1cdac946 100644 --- a/MCPForUnity/Editor/Config/McpDistributionSettings.cs +++ b/MCPForUnity/Editor/Config/McpDistributionSettings.cs @@ -1,4 +1,6 @@ using System; +using System.Net; +using System.Net.Sockets; using UnityEngine; namespace MCPForUnity.Editor.Config @@ -14,8 +16,7 @@ public class McpDistributionSettings : ScriptableObject internal bool IsRemoteDefault => !string.IsNullOrWhiteSpace(defaultHttpBaseUrl) - && !defaultHttpBaseUrl.Contains("localhost", StringComparison.OrdinalIgnoreCase) - && !defaultHttpBaseUrl.Contains("127.0.0.1", StringComparison.OrdinalIgnoreCase); + && !IsLocalAddress(defaultHttpBaseUrl); } internal static class McpDistribution @@ -44,5 +45,61 @@ internal static McpDistributionSettings Settings return _cached; } } + + 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; + } } } From 51249f09267cf3b1991757c63dd65de9096d0e36 Mon Sep 17 00:00:00 2001 From: Marcus Sanatan Date: Thu, 27 Nov 2025 22:01:53 -0400 Subject: [PATCH 3/3] Fix error --- .../Editor/Config/McpDistributionSettings.cs | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/MCPForUnity/Editor/Config/McpDistributionSettings.cs b/MCPForUnity/Editor/Config/McpDistributionSettings.cs index 1cdac946..11616964 100644 --- a/MCPForUnity/Editor/Config/McpDistributionSettings.cs +++ b/MCPForUnity/Editor/Config/McpDistributionSettings.cs @@ -17,34 +17,6 @@ public class McpDistributionSettings : ScriptableObject internal bool IsRemoteDefault => !string.IsNullOrWhiteSpace(defaultHttpBaseUrl) && !IsLocalAddress(defaultHttpBaseUrl); - } - - 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(ResourcePath); - if (_cached != null) - { - return _cached; - } - - // No asset present (git/dev installs) - fall back to baked-in defaults. - _cached = ScriptableObject.CreateInstance(); - _cached.name = "McpDistributionSettings (Runtime Defaults)"; - return _cached; - } - } private static bool IsLocalAddress(string url) { @@ -102,4 +74,32 @@ private static bool IsLocalAddress(string url) 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(ResourcePath); + if (_cached != null) + { + return _cached; + } + + // No asset present (git/dev installs) - fall back to baked-in defaults. + _cached = ScriptableObject.CreateInstance(); + _cached.name = "McpDistributionSettings (Runtime Defaults)"; + return _cached; + } + } + } }