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..11616964 --- /dev/null +++ b/MCPForUnity/Editor/Config/McpDistributionSettings.cs @@ -0,0 +1,105 @@ +using System; +using System.Net; +using System.Net.Sockets; +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) + && !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(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))