diff --git a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs index a310c6e1..70eae154 100644 --- a/MCPForUnity/Editor/Helpers/AssetPathUtility.cs +++ b/MCPForUnity/Editor/Helpers/AssetPathUtility.cs @@ -156,10 +156,10 @@ public static string GetMcpServerGitUrl() if (version == "unknown") { // Fall back to main repo without pinned version so configs remain valid in test scenarios - return "git+https://github.com/CoplayDev/unity-mcp#subdirectory=Server"; + return "git+https://github.com/prophecygamestudio/unity-mcp#subdirectory=Server"; } - return $"git+https://github.com/CoplayDev/unity-mcp@v{version}#subdirectory=Server"; + return $"git+https://github.com/prophecygamestudio/unity-mcp@v{version}#subdirectory=Server"; } /// diff --git a/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs b/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs index 10f05248..45e4fafa 100644 --- a/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs +++ b/MCPForUnity/Editor/MenuItems/MCPForUnityMenu.cs @@ -1,4 +1,3 @@ -using MCPForUnity.Editor.Setup; using MCPForUnity.Editor.Windows; using UnityEditor; using UnityEngine; @@ -7,12 +6,6 @@ namespace MCPForUnity.Editor.MenuItems { public static class MCPForUnityMenu { - [MenuItem("Window/MCP For Unity/Setup Window", priority = 1)] - public static void ShowSetupWindow() - { - SetupWindowService.ShowSetupWindow(); - } - [MenuItem("Window/MCP For Unity/Toggle MCP Window %#m", priority = 2)] public static void ToggleMCPWindow() { diff --git a/MCPForUnity/Editor/Migrations/LegacyServerSrcMigration.cs b/MCPForUnity/Editor/Migrations/LegacyServerSrcMigration.cs index e90019d8..cf8d5de9 100644 --- a/MCPForUnity/Editor/Migrations/LegacyServerSrcMigration.cs +++ b/MCPForUnity/Editor/Migrations/LegacyServerSrcMigration.cs @@ -38,15 +38,8 @@ private static void RunMigrationIfNeeded() try { - McpLog.Info("Detected legacy embedded MCP server configuration. Updating all client configs..."); - - var summary = MCPServiceLocator.Client.ConfigureAllDetectedClients(); - - if (summary.FailureCount > 0 || summary.SuccessCount == 0) - { - McpLog.Warn($"Legacy configuration migration incomplete ({summary.GetSummaryMessage()}). Will retry next session."); - return; - } + // Client configuration is no longer supported - just clean up legacy keys + McpLog.Info("Detected legacy embedded MCP server configuration. Cleaning up legacy preferences..."); if (hasServerSrc) { @@ -60,7 +53,7 @@ private static void RunMigrationIfNeeded() McpLog.Info(" ✓ Removed legacy key: MCPForUnity.UseEmbeddedServer"); } - McpLog.Info($"Legacy configuration migration complete ({summary.GetSummaryMessage()})"); + McpLog.Info("Legacy configuration migration complete. Note: Client configuration is now handled by the MCP client (e.g., Cursor's mcp.json)."); } catch (Exception ex) { diff --git a/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs b/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs index 84b63f32..f1d3747c 100644 --- a/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs +++ b/MCPForUnity/Editor/Migrations/StdIoVersionMigration.cs @@ -43,53 +43,17 @@ private static void RunMigrationIfNeeded() if (string.Equals(lastUpgradeVersion, currentVersion, StringComparison.OrdinalIgnoreCase)) { - return; // Already refreshed for this package version - } - - bool hadFailures = false; - bool touchedAny = false; - - var configurators = McpClientRegistry.All.OfType().ToList(); - foreach (var configurator in configurators) - { - try - { - if (!ConfigUsesStdIo(configurator.Client)) - continue; - - if (!configurator.SupportsAutoConfigure) - continue; - - MCPServiceLocator.Client.ConfigureClient(configurator); - touchedAny = true; - } - catch (Exception ex) - { - hadFailures = true; - McpLog.Warn($"Failed to refresh stdio config for {configurator.DisplayName}: {ex.Message}"); - } - } - - if (!touchedAny) - { - // Nothing needed refreshing; still record version so we don't rerun every launch - try { EditorPrefs.SetString(LastUpgradeKey, currentVersion); } catch { } - return; - } - - if (hadFailures) - { - McpLog.Warn("Stdio MCP upgrade encountered errors; will retry next session."); - return; + return; // Already processed for this package version } + // Client configuration is no longer supported - just record the version try { EditorPrefs.SetString(LastUpgradeKey, currentVersion); } catch { } - McpLog.Info($"Updated stdio MCP configs to package version {currentVersion}."); + McpLog.Info($"Package version {currentVersion} detected. Note: Client configuration is now handled by the MCP client (e.g., Cursor's mcp.json)."); } private static bool ConfigUsesStdIo(McpClient client) diff --git a/MCPForUnity/Editor/Resources/Editor/Selection.cs b/MCPForUnity/Editor/Resources/Editor/Selection.cs index 022d9c48..406b9c8f 100644 --- a/MCPForUnity/Editor/Resources/Editor/Selection.cs +++ b/MCPForUnity/Editor/Resources/Editor/Selection.cs @@ -21,7 +21,7 @@ public static object HandleCommand(JObject @params) activeObject = UnityEditor.Selection.activeObject?.name, activeGameObject = UnityEditor.Selection.activeGameObject?.name, activeTransform = UnityEditor.Selection.activeTransform?.name, - activeInstanceID = UnityEditor.Selection.activeInstanceID, + activeInstanceID = UnityEditor.Selection.activeObject?.GetInstanceID() ?? 0, count = UnityEditor.Selection.count, objects = UnityEditor.Selection.objects .Select(obj => new diff --git a/MCPForUnity/Editor/Services/BridgeControlService.cs b/MCPForUnity/Editor/Services/BridgeControlService.cs index 0786de05..33153ec5 100644 --- a/MCPForUnity/Editor/Services/BridgeControlService.cs +++ b/MCPForUnity/Editor/Services/BridgeControlService.cs @@ -1,39 +1,29 @@ using System; using System.Threading.Tasks; -using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Services.Transport; using MCPForUnity.Editor.Services.Transport.Transports; -using UnityEditor; namespace MCPForUnity.Editor.Services { /// - /// Bridges the editor UI to the active transport (HTTP with WebSocket push, or stdio). + /// Bridges the editor UI to the stdio transport. /// public class BridgeControlService : IBridgeControlService { private readonly TransportManager _transportManager; - private TransportMode _preferredMode = TransportMode.Http; public BridgeControlService() { _transportManager = MCPServiceLocator.TransportManager; } - private TransportMode ResolvePreferredMode() + private static BridgeVerificationResult BuildVerificationResult(TransportState state, bool pingSucceeded, string messageOverride = null, bool? handshakeOverride = null) { - bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); - _preferredMode = useHttp ? TransportMode.Http : TransportMode.Stdio; - return _preferredMode; - } - - private static BridgeVerificationResult BuildVerificationResult(TransportState state, TransportMode mode, bool pingSucceeded, string messageOverride = null, bool? handshakeOverride = null) - { - bool handshakeValid = handshakeOverride ?? (mode == TransportMode.Stdio ? state.IsConnected : true); + bool handshakeValid = handshakeOverride ?? state.IsConnected; string transportLabel = string.IsNullOrWhiteSpace(state.TransportName) - ? mode.ToString().ToLowerInvariant() + ? "stdio" : state.TransportName; string detailSuffix = string.IsNullOrWhiteSpace(state.Details) ? string.Empty : $" [{state.Details}]"; string message = messageOverride @@ -53,8 +43,7 @@ public bool IsRunning { get { - var mode = ResolvePreferredMode(); - return _transportManager.IsRunning(mode); + return _transportManager.IsRunning(); } } @@ -62,36 +51,32 @@ public int CurrentPort { get { - var mode = ResolvePreferredMode(); - var state = _transportManager.GetState(mode); + var state = _transportManager.GetState(); if (state.Port.HasValue) { return state.Port.Value; } - // Legacy fallback while the stdio bridge is still in play return StdioBridgeHost.GetCurrentPort(); } } public bool IsAutoConnectMode => StdioBridgeHost.IsAutoConnectMode(); - public TransportMode? ActiveMode => ResolvePreferredMode(); public async Task StartAsync() { - var mode = ResolvePreferredMode(); try { - bool started = await _transportManager.StartAsync(mode); + bool started = await _transportManager.StartAsync(); if (!started) { - McpLog.Warn($"Failed to start MCP transport: {mode}"); + McpLog.Warn("Failed to start MCP stdio transport"); } return started; } catch (Exception ex) { - McpLog.Error($"Error starting MCP transport {mode}: {ex.Message}"); + McpLog.Error($"Error starting MCP stdio transport: {ex.Message}"); return false; } } @@ -100,8 +85,7 @@ public async Task StopAsync() { try { - var mode = ResolvePreferredMode(); - await _transportManager.StopAsync(mode); + await _transportManager.StopAsync(); } catch (Exception ex) { @@ -111,28 +95,21 @@ public async Task StopAsync() public async Task VerifyAsync() { - var mode = ResolvePreferredMode(); - bool pingSucceeded = await _transportManager.VerifyAsync(mode); - var state = _transportManager.GetState(mode); - return BuildVerificationResult(state, mode, pingSucceeded); + bool pingSucceeded = await _transportManager.VerifyAsync(); + var state = _transportManager.GetState(); + return BuildVerificationResult(state, pingSucceeded); } public BridgeVerificationResult Verify(int port) { - var mode = ResolvePreferredMode(); - bool pingSucceeded = _transportManager.VerifyAsync(mode).GetAwaiter().GetResult(); - var state = _transportManager.GetState(mode); - - if (mode == TransportMode.Stdio) - { - bool handshakeValid = state.IsConnected && port == CurrentPort; - string message = handshakeValid - ? $"STDIO transport listening on port {CurrentPort}" - : $"STDIO transport port mismatch (expected {CurrentPort}, got {port})"; - return BuildVerificationResult(state, mode, pingSucceeded && handshakeValid, message, handshakeValid); - } - - return BuildVerificationResult(state, mode, pingSucceeded); + bool pingSucceeded = _transportManager.VerifyAsync().GetAwaiter().GetResult(); + var state = _transportManager.GetState(); + + bool handshakeValid = state.IsConnected && port == CurrentPort; + string message = handshakeValid + ? $"STDIO transport listening on port {CurrentPort}" + : $"STDIO transport port mismatch (expected {CurrentPort}, got {port})"; + return BuildVerificationResult(state, pingSucceeded && handshakeValid, message, handshakeValid); } } diff --git a/MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs b/MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs deleted file mode 100644 index 2f5ef681..00000000 --- a/MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Threading.Tasks; -using MCPForUnity.Editor.Constants; -using MCPForUnity.Editor.Helpers; -using MCPForUnity.Editor.Services.Transport; -using MCPForUnity.Editor.Windows; -using UnityEditor; - -namespace MCPForUnity.Editor.Services -{ - /// - /// Ensures HTTP transports resume after domain reloads similar to the legacy stdio bridge. - /// - [InitializeOnLoad] - internal static class HttpBridgeReloadHandler - { - static HttpBridgeReloadHandler() - { - AssemblyReloadEvents.beforeAssemblyReload += OnBeforeAssemblyReload; - AssemblyReloadEvents.afterAssemblyReload += OnAfterAssemblyReload; - } - - private static void OnBeforeAssemblyReload() - { - try - { - var transport = MCPServiceLocator.TransportManager; - bool shouldResume = transport.IsRunning(TransportMode.Http); - - if (shouldResume) - { - EditorPrefs.SetBool(EditorPrefKeys.ResumeHttpAfterReload, true); - } - else - { - EditorPrefs.DeleteKey(EditorPrefKeys.ResumeHttpAfterReload); - } - - if (shouldResume) - { - var stopTask = transport.StopAsync(TransportMode.Http); - stopTask.ContinueWith(t => - { - if (t.IsFaulted && t.Exception != null) - { - McpLog.Warn($"Error stopping MCP bridge before reload: {t.Exception.GetBaseException().Message}"); - } - }, TaskScheduler.Default); - } - } - catch (Exception ex) - { - McpLog.Warn($"Failed to evaluate HTTP bridge reload state: {ex.Message}"); - } - } - - private static void OnAfterAssemblyReload() - { - bool resume = false; - try - { - // Only resume HTTP if it is still the selected transport. - bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); - resume = useHttp && EditorPrefs.GetBool(EditorPrefKeys.ResumeHttpAfterReload, false); - if (resume) - { - EditorPrefs.DeleteKey(EditorPrefKeys.ResumeHttpAfterReload); - } - } - catch (Exception ex) - { - McpLog.Warn($"Failed to read HTTP bridge reload flag: {ex.Message}"); - resume = false; - } - - if (!resume) - { - return; - } - - // If the editor is not compiling, attempt an immediate restart without relying on editor focus. - bool isCompiling = EditorApplication.isCompiling; - try - { - var pipeline = Type.GetType("UnityEditor.Compilation.CompilationPipeline, UnityEditor"); - var prop = pipeline?.GetProperty("isCompiling", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static); - if (prop != null) isCompiling |= (bool)prop.GetValue(null); - } - catch { } - - if (!isCompiling) - { - try - { - var startTask = MCPServiceLocator.TransportManager.StartAsync(TransportMode.Http); - startTask.ContinueWith(t => - { - if (t.IsFaulted) - { - var baseEx = t.Exception?.GetBaseException(); - McpLog.Warn($"Failed to resume HTTP MCP bridge after domain reload: {baseEx?.Message}"); - return; - } - bool started = t.Result; - if (!started) - { - McpLog.Warn("Failed to resume HTTP MCP bridge after domain reload"); - } - else - { - MCPForUnityEditorWindow.RequestHealthVerification(); - } - }, TaskScheduler.Default); - return; - } - catch (Exception ex) - { - McpLog.Error($"Error resuming HTTP MCP bridge: {ex.Message}"); - return; - } - } - - // Fallback when compiling: schedule on the editor loop - EditorApplication.delayCall += async () => - { - try - { - bool started = await MCPServiceLocator.TransportManager.StartAsync(TransportMode.Http); - if (!started) - { - McpLog.Warn("Failed to resume HTTP MCP bridge after domain reload"); - } - else - { - MCPForUnityEditorWindow.RequestHealthVerification(); - } - } - catch (Exception ex) - { - McpLog.Error($"Error resuming HTTP MCP bridge: {ex.Message}"); - } - }; - } - } -} diff --git a/MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs.meta b/MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs.meta deleted file mode 100644 index ae5e9edd..00000000 --- a/MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 4c0cf970a7b494a659be151dc0124296 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MCPForUnity/Editor/Services/IBridgeControlService.cs b/MCPForUnity/Editor/Services/IBridgeControlService.cs index 7cc593e0..1f62fcdd 100644 --- a/MCPForUnity/Editor/Services/IBridgeControlService.cs +++ b/MCPForUnity/Editor/Services/IBridgeControlService.cs @@ -1,5 +1,4 @@ using System.Threading.Tasks; -using MCPForUnity.Editor.Services.Transport; namespace MCPForUnity.Editor.Services { @@ -23,11 +22,6 @@ public interface IBridgeControlService /// bool IsAutoConnectMode { get; } - /// - /// Gets the currently active transport mode, if any - /// - TransportMode? ActiveMode { get; } - /// /// Starts the MCP for Unity Bridge asynchronously /// @@ -47,7 +41,7 @@ public interface IBridgeControlService BridgeVerificationResult Verify(int port); /// - /// Verifies the connection asynchronously (works for both HTTP and stdio transports) + /// Verifies the connection asynchronously /// /// Verification result with detailed status Task VerifyAsync(); diff --git a/MCPForUnity/Editor/Services/MCPServiceLocator.cs b/MCPForUnity/Editor/Services/MCPServiceLocator.cs index d537182f..e6ec8d38 100644 --- a/MCPForUnity/Editor/Services/MCPServiceLocator.cs +++ b/MCPForUnity/Editor/Services/MCPServiceLocator.cs @@ -11,23 +11,19 @@ namespace MCPForUnity.Editor.Services public static class MCPServiceLocator { private static IBridgeControlService _bridgeService; - private static IClientConfigurationService _clientService; private static IPathResolverService _pathService; private static ITestRunnerService _testRunnerService; private static IPackageUpdateService _packageUpdateService; private static IPlatformService _platformService; private static IToolDiscoveryService _toolDiscoveryService; - private static IServerManagementService _serverManagementService; private static TransportManager _transportManager; public static IBridgeControlService Bridge => _bridgeService ??= new BridgeControlService(); - public static IClientConfigurationService Client => _clientService ??= new ClientConfigurationService(); public static IPathResolverService Paths => _pathService ??= new PathResolverService(); public static ITestRunnerService Tests => _testRunnerService ??= new TestRunnerService(); public static IPackageUpdateService Updates => _packageUpdateService ??= new PackageUpdateService(); public static IPlatformService Platform => _platformService ??= new PlatformService(); public static IToolDiscoveryService ToolDiscovery => _toolDiscoveryService ??= new ToolDiscoveryService(); - public static IServerManagementService Server => _serverManagementService ??= new ServerManagementService(); public static TransportManager TransportManager => _transportManager ??= new TransportManager(); /// @@ -39,8 +35,6 @@ public static void Register(T implementation) where T : class { if (implementation is IBridgeControlService b) _bridgeService = b; - else if (implementation is IClientConfigurationService c) - _clientService = c; else if (implementation is IPathResolverService p) _pathService = p; else if (implementation is ITestRunnerService t) @@ -51,8 +45,6 @@ public static void Register(T implementation) where T : class _platformService = ps; else if (implementation is IToolDiscoveryService td) _toolDiscoveryService = td; - else if (implementation is IServerManagementService sm) - _serverManagementService = sm; else if (implementation is TransportManager tm) _transportManager = tm; } @@ -63,23 +55,19 @@ public static void Register(T implementation) where T : class public static void Reset() { (_bridgeService as IDisposable)?.Dispose(); - (_clientService as IDisposable)?.Dispose(); (_pathService as IDisposable)?.Dispose(); (_testRunnerService as IDisposable)?.Dispose(); (_packageUpdateService as IDisposable)?.Dispose(); (_platformService as IDisposable)?.Dispose(); (_toolDiscoveryService as IDisposable)?.Dispose(); - (_serverManagementService as IDisposable)?.Dispose(); (_transportManager as IDisposable)?.Dispose(); _bridgeService = null; - _clientService = null; _pathService = null; _testRunnerService = null; _packageUpdateService = null; _platformService = null; _toolDiscoveryService = null; - _serverManagementService = null; _transportManager = null; } } diff --git a/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs b/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs index bb70ffb0..9ce5987f 100644 --- a/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs +++ b/MCPForUnity/Editor/Services/StdioBridgeReloadHandler.cs @@ -1,14 +1,12 @@ using System; using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; -using MCPForUnity.Editor.Services.Transport; -using MCPForUnity.Editor.Services.Transport.Transports; using UnityEditor; namespace MCPForUnity.Editor.Services { /// - /// Ensures the legacy stdio bridge resumes after domain reloads, mirroring the HTTP handler. + /// Ensures the stdio bridge resumes after domain reloads. /// [InitializeOnLoad] internal static class StdioBridgeReloadHandler @@ -23,17 +21,13 @@ private static void OnBeforeAssemblyReload() { try { - // Only persist resume intent when stdio is the active transport and the bridge is running. - bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); - bool isRunning = MCPServiceLocator.TransportManager.IsRunning(TransportMode.Stdio); - bool shouldResume = !useHttp && isRunning; - - if (shouldResume) + // Persist resume intent when the bridge is running + bool isRunning = MCPServiceLocator.TransportManager.IsRunning(); + if (isRunning) { EditorPrefs.SetBool(EditorPrefKeys.ResumeStdioAfterReload, true); - // Stop only the stdio bridge; leave HTTP untouched if it is running concurrently. - var stopTask = MCPServiceLocator.TransportManager.StopAsync(TransportMode.Stdio); + var stopTask = MCPServiceLocator.TransportManager.StopAsync(); stopTask.ContinueWith(t => { if (t.IsFaulted && t.Exception != null) @@ -59,8 +53,6 @@ private static void OnAfterAssemblyReload() try { resume = EditorPrefs.GetBool(EditorPrefKeys.ResumeStdioAfterReload, false); - bool useHttp = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); - resume = resume && !useHttp; if (resume) { EditorPrefs.DeleteKey(EditorPrefKeys.ResumeStdioAfterReload); @@ -82,7 +74,7 @@ private static void OnAfterAssemblyReload() private static void TryStartBridgeImmediate() { - var startTask = MCPServiceLocator.TransportManager.StartAsync(TransportMode.Stdio); + var startTask = MCPServiceLocator.TransportManager.StartAsync(); startTask.ContinueWith(t => { if (t.IsFaulted) @@ -96,8 +88,6 @@ private static void TryStartBridgeImmediate() McpLog.Warn("Failed to resume stdio bridge after domain reload"); return; } - - MCPForUnity.Editor.Windows.MCPForUnityEditorWindow.RequestHealthVerification(); }, System.Threading.Tasks.TaskScheduler.Default); } } diff --git a/MCPForUnity/Editor/Services/Transport/TransportManager.cs b/MCPForUnity/Editor/Services/Transport/TransportManager.cs index d221ab83..5e6422ca 100644 --- a/MCPForUnity/Editor/Services/Transport/TransportManager.cs +++ b/MCPForUnity/Editor/Services/Transport/TransportManager.cs @@ -6,150 +6,81 @@ namespace MCPForUnity.Editor.Services.Transport { /// - /// Coordinates the active transport client and exposes lifecycle helpers. + /// Coordinates the stdio transport client and exposes lifecycle helpers. /// public class TransportManager { - private IMcpTransportClient _httpClient; private IMcpTransportClient _stdioClient; - private TransportState _httpState = TransportState.Disconnected("http"); private TransportState _stdioState = TransportState.Disconnected("stdio"); - private Func _webSocketFactory; - private Func _stdioFactory; public TransportManager() { - Configure( - () => new WebSocketTransportClient(MCPServiceLocator.ToolDiscovery), - () => new StdioTransportClient()); + _stdioClient = new StdioTransportClient(); } - public IMcpTransportClient ActiveTransport => null; // Deprecated single-transport accessor - public TransportMode? ActiveMode => null; // Deprecated single-transport accessor + public IMcpTransportClient ActiveTransport => _stdioClient; - public void Configure( - Func webSocketFactory, - Func stdioFactory) + public async Task StartAsync() { - _webSocketFactory = webSocketFactory ?? throw new ArgumentNullException(nameof(webSocketFactory)); - _stdioFactory = stdioFactory ?? throw new ArgumentNullException(nameof(stdioFactory)); - } - - private IMcpTransportClient GetOrCreateClient(TransportMode mode) - { - return mode switch - { - TransportMode.Http => _httpClient ??= _webSocketFactory(), - TransportMode.Stdio => _stdioClient ??= _stdioFactory(), - _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported transport mode"), - }; - } - - private IMcpTransportClient GetClient(TransportMode mode) - { - return mode switch + if (_stdioClient == null) { - TransportMode.Http => _httpClient, - TransportMode.Stdio => _stdioClient, - _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported transport mode"), - }; - } - - public async Task StartAsync(TransportMode mode) - { - IMcpTransportClient client = GetOrCreateClient(mode); + _stdioClient = new StdioTransportClient(); + } - bool started = await client.StartAsync(); + bool started = await _stdioClient.StartAsync(); if (!started) { try { - await client.StopAsync(); + await _stdioClient.StopAsync(); } catch (Exception ex) { - McpLog.Warn($"Error while stopping transport {client.TransportName}: {ex.Message}"); + McpLog.Warn($"Error while stopping transport {_stdioClient.TransportName}: {ex.Message}"); } - UpdateState(mode, TransportState.Disconnected(client.TransportName, "Failed to start")); + _stdioState = TransportState.Disconnected(_stdioClient.TransportName, "Failed to start"); return false; } - UpdateState(mode, client.State ?? TransportState.Connected(client.TransportName)); + _stdioState = _stdioClient.State ?? TransportState.Connected(_stdioClient.TransportName); return true; } - public async Task StopAsync(TransportMode? mode = null) + public async Task StopAsync() { - async Task StopClient(IMcpTransportClient client, TransportMode clientMode) - { - if (client == null) return; - try { await client.StopAsync(); } - catch (Exception ex) { McpLog.Warn($"Error while stopping transport {client.TransportName}: {ex.Message}"); } - finally { UpdateState(clientMode, TransportState.Disconnected(client.TransportName)); } + if (_stdioClient == null) return; + try + { + await _stdioClient.StopAsync(); } - - if (mode == null) - { - await StopClient(_httpClient, TransportMode.Http); - await StopClient(_stdioClient, TransportMode.Stdio); - return; + catch (Exception ex) + { + McpLog.Warn($"Error while stopping transport {_stdioClient.TransportName}: {ex.Message}"); } - - if (mode == TransportMode.Http) - { - await StopClient(_httpClient, TransportMode.Http); - } - else - { - await StopClient(_stdioClient, TransportMode.Stdio); + finally + { + _stdioState = TransportState.Disconnected(_stdioClient.TransportName); } } - public async Task VerifyAsync(TransportMode mode) + public async Task VerifyAsync() { - IMcpTransportClient client = GetClient(mode); - if (client == null) + if (_stdioClient == null) { return false; } - bool ok = await client.VerifyAsync(); - var state = client.State ?? TransportState.Disconnected(client.TransportName, "No state reported"); - UpdateState(mode, state); + bool ok = await _stdioClient.VerifyAsync(); + var state = _stdioClient.State ?? TransportState.Disconnected(_stdioClient.TransportName, "No state reported"); + _stdioState = state; return ok; } - public TransportState GetState(TransportMode mode) + public TransportState GetState() { - return mode switch - { - TransportMode.Http => _httpState, - TransportMode.Stdio => _stdioState, - _ => throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported transport mode"), - }; + return _stdioState; } - public bool IsRunning(TransportMode mode) => GetState(mode).IsConnected; - - private void UpdateState(TransportMode mode, TransportState state) - { - switch (mode) - { - case TransportMode.Http: - _httpState = state; - break; - case TransportMode.Stdio: - _stdioState = state; - break; - default: - throw new ArgumentOutOfRangeException(nameof(mode), mode, "Unsupported transport mode"); - } - } - } - - public enum TransportMode - { - Http, - Stdio + public bool IsRunning() => _stdioState.IsConnected; } } diff --git a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs index 31de7311..187e3bd5 100644 --- a/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs +++ b/MCPForUnity/Editor/Services/Transport/Transports/StdioBridgeHost.cs @@ -208,15 +208,8 @@ private static void ScheduleInitRetry() private static bool ShouldAutoStartBridge() { - try - { - bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true); - return !useHttpTransport; - } - catch - { - return true; - } + // Always auto-start stdio bridge when editor starts + return true; } private static void EnsureStartedOnEditorIdle() diff --git a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs b/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs deleted file mode 100644 index 35011a80..00000000 --- a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs +++ /dev/null @@ -1,691 +0,0 @@ -using System; -using System.Buffers; -using System.Collections.Generic; -using System.IO; -using System.Net.WebSockets; -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; -using Newtonsoft.Json.Linq; -using UnityEngine; - -namespace MCPForUnity.Editor.Services.Transport.Transports -{ - /// - /// Maintains a persistent WebSocket connection to the MCP server plugin hub. - /// Handles registration, keep-alives, and command dispatch back into Unity via - /// . - /// - public class WebSocketTransportClient : IMcpTransportClient, IDisposable - { - private const string TransportDisplayName = "websocket"; - private static readonly TimeSpan[] ReconnectSchedule = - { - TimeSpan.Zero, - TimeSpan.FromSeconds(1), - TimeSpan.FromSeconds(3), - TimeSpan.FromSeconds(5), - TimeSpan.FromSeconds(10), - TimeSpan.FromSeconds(30) - }; - - private static readonly TimeSpan DefaultKeepAliveInterval = TimeSpan.FromSeconds(15); - private static readonly TimeSpan DefaultCommandTimeout = TimeSpan.FromSeconds(30); - - private readonly IToolDiscoveryService _toolDiscoveryService; - private ClientWebSocket _socket; - private CancellationTokenSource _lifecycleCts; - private CancellationTokenSource _connectionCts; - private Task _receiveTask; - private Task _keepAliveTask; - private readonly SemaphoreSlim _sendLock = new(1, 1); - - private Uri _endpointUri; - private string _sessionId; - private string _projectHash; - private string _projectName; - private string _unityVersion; - private TimeSpan _keepAliveInterval = DefaultKeepAliveInterval; - private TimeSpan _socketKeepAliveInterval = DefaultKeepAliveInterval; - private volatile bool _isConnected; - private int _isReconnectingFlag; - private TransportState _state = TransportState.Disconnected(TransportDisplayName, "Transport not started"); - private bool _disposed; - - public WebSocketTransportClient(IToolDiscoveryService toolDiscoveryService = null) - { - _toolDiscoveryService = toolDiscoveryService; - } - - public bool IsConnected => _isConnected; - public string TransportName => TransportDisplayName; - public TransportState State => _state; - - public async Task StartAsync() - { - // Capture identity values on the main thread before any async context switching - _projectName = ProjectIdentityUtility.GetProjectName(); - _projectHash = ProjectIdentityUtility.GetProjectHash(); - _unityVersion = Application.unityVersion; - - await StopAsync(); - - _lifecycleCts = new CancellationTokenSource(); - _endpointUri = BuildWebSocketUri(HttpEndpointUtility.GetBaseUrl()); - _sessionId = null; - - if (!await EstablishConnectionAsync(_lifecycleCts.Token)) - { - await StopAsync(); - return false; - } - - // State is connected but session ID might be pending until 'registered' message - _state = TransportState.Connected(TransportDisplayName, sessionId: "pending", details: _endpointUri.ToString()); - _isConnected = true; - return true; - } - - public async Task StopAsync() - { - if (_lifecycleCts == null) - { - return; - } - - try - { - _lifecycleCts.Cancel(); - } - catch { } - - await StopConnectionLoopsAsync().ConfigureAwait(false); - - if (_socket != null) - { - try - { - if (_socket.State == WebSocketState.Open || _socket.State == WebSocketState.CloseReceived) - { - await _socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Shutdown", CancellationToken.None).ConfigureAwait(false); - } - } - catch { } - finally - { - _socket.Dispose(); - _socket = null; - } - } - - _isConnected = false; - _state = TransportState.Disconnected(TransportDisplayName); - - _lifecycleCts.Dispose(); - _lifecycleCts = null; - } - - public async Task VerifyAsync() - { - if (_socket == null || _socket.State != WebSocketState.Open) - { - return false; - } - - if (_lifecycleCts == null) - { - return false; - } - - try - { - using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(_lifecycleCts.Token); - timeoutCts.CancelAfter(TimeSpan.FromSeconds(5)); - await SendPongAsync(timeoutCts.Token).ConfigureAwait(false); - return true; - } - catch (Exception ex) - { - McpLog.Warn($"[WebSocket] Verify ping failed: {ex.Message}"); - return false; - } - } - - public void Dispose() - { - if (_disposed) - { - return; - } - - try - { - // Ensure background loops are stopped before disposing shared resources - StopAsync().GetAwaiter().GetResult(); - } - catch (Exception ex) - { - McpLog.Warn($"[WebSocket] Dispose failed to stop cleanly: {ex.Message}"); - } - - _sendLock?.Dispose(); - _socket?.Dispose(); - _lifecycleCts?.Dispose(); - _disposed = true; - } - - private async Task EstablishConnectionAsync(CancellationToken token) - { - await StopConnectionLoopsAsync().ConfigureAwait(false); - - _connectionCts?.Dispose(); - _connectionCts = CancellationTokenSource.CreateLinkedTokenSource(token); - CancellationToken connectionToken = _connectionCts.Token; - - _socket?.Dispose(); - _socket = new ClientWebSocket(); - _socket.Options.KeepAliveInterval = _socketKeepAliveInterval; - - try - { - await _socket.ConnectAsync(_endpointUri, connectionToken).ConfigureAwait(false); - } - catch (Exception ex) - { - McpLog.Error($"[WebSocket] Connection failed: {ex.Message}"); - return false; - } - - StartBackgroundLoops(connectionToken); - - try - { - await SendRegisterAsync(connectionToken).ConfigureAwait(false); - } - catch (Exception ex) - { - McpLog.Error($"[WebSocket] Registration failed: {ex.Message}"); - return false; - } - - return true; - } - - /// - /// Stops the connection loops and disposes of the connection CTS. - /// Particularly useful when reconnecting, we want to ensure that background loops are cancelled correctly before starting new oens - /// - /// Whether to await the receive and keep alive tasks before disposing. - private async Task StopConnectionLoopsAsync(bool awaitTasks = true) - { - if (_connectionCts != null && !_connectionCts.IsCancellationRequested) - { - try { _connectionCts.Cancel(); } catch { } - } - - if (_receiveTask != null) - { - if (awaitTasks) - { - try { await _receiveTask.ConfigureAwait(false); } catch { } - _receiveTask = null; - } - else if (_receiveTask.IsCompleted) - { - _receiveTask = null; - } - } - - if (_keepAliveTask != null) - { - if (awaitTasks) - { - try { await _keepAliveTask.ConfigureAwait(false); } catch { } - _keepAliveTask = null; - } - else if (_keepAliveTask.IsCompleted) - { - _keepAliveTask = null; - } - } - - if (_connectionCts != null) - { - _connectionCts.Dispose(); - _connectionCts = null; - } - } - - private void StartBackgroundLoops(CancellationToken token) - { - if ((_receiveTask != null && !_receiveTask.IsCompleted) || (_keepAliveTask != null && !_keepAliveTask.IsCompleted)) - { - return; - } - - _receiveTask = Task.Run(() => ReceiveLoopAsync(token), CancellationToken.None); - _keepAliveTask = Task.Run(() => KeepAliveLoopAsync(token), CancellationToken.None); - } - - private async Task ReceiveLoopAsync(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - try - { - string message = await ReceiveMessageAsync(token).ConfigureAwait(false); - if (message == null) - { - continue; - } - await HandleMessageAsync(message, token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - catch (WebSocketException wse) - { - McpLog.Warn($"[WebSocket] Receive loop error: {wse.Message}"); - await HandleSocketClosureAsync(wse.Message).ConfigureAwait(false); - break; - } - catch (Exception ex) - { - McpLog.Warn($"[WebSocket] Unexpected receive error: {ex.Message}"); - await HandleSocketClosureAsync(ex.Message).ConfigureAwait(false); - break; - } - } - } - - private async Task ReceiveMessageAsync(CancellationToken token) - { - if (_socket == null) - { - return null; - } - - byte[] rentedBuffer = ArrayPool.Shared.Rent(8192); - var buffer = new ArraySegment(rentedBuffer); - using var ms = new MemoryStream(8192); - - try - { - while (!token.IsCancellationRequested) - { - WebSocketReceiveResult result = await _socket.ReceiveAsync(buffer, token).ConfigureAwait(false); - - if (result.MessageType == WebSocketMessageType.Close) - { - await HandleSocketClosureAsync(result.CloseStatusDescription ?? "Server closed connection").ConfigureAwait(false); - return null; - } - - if (result.Count > 0) - { - ms.Write(buffer.Array!, buffer.Offset, result.Count); - } - - if (result.EndOfMessage) - { - break; - } - } - - if (ms.Length == 0) - { - return null; - } - - return Encoding.UTF8.GetString(ms.ToArray()); - } - finally - { - ArrayPool.Shared.Return(rentedBuffer); - } - } - - private async Task HandleMessageAsync(string message, CancellationToken token) - { - JObject payload; - try - { - payload = JObject.Parse(message); - } - catch (Exception ex) - { - McpLog.Warn($"[WebSocket] Invalid JSON payload: {ex.Message}"); - return; - } - - string messageType = payload.Value("type") ?? string.Empty; - - switch (messageType) - { - case "welcome": - ApplyWelcome(payload); - break; - case "registered": - await HandleRegisteredAsync(payload, token).ConfigureAwait(false); - break; - case "execute": - await HandleExecuteAsync(payload, token).ConfigureAwait(false); - break; - case "ping": - await SendPongAsync(token).ConfigureAwait(false); - break; - default: - // No-op for unrecognised types (keep-alives, telemetry, etc.) - break; - } - } - - private void ApplyWelcome(JObject payload) - { - int? keepAliveSeconds = payload.Value("keepAliveInterval"); - if (keepAliveSeconds.HasValue && keepAliveSeconds.Value > 0) - { - _keepAliveInterval = TimeSpan.FromSeconds(keepAliveSeconds.Value); - _socketKeepAliveInterval = _keepAliveInterval; - } - - int? serverTimeoutSeconds = payload.Value("serverTimeout"); - if (serverTimeoutSeconds.HasValue) - { - int sourceSeconds = keepAliveSeconds ?? serverTimeoutSeconds.Value; - int safeSeconds = Math.Max(5, Math.Min(serverTimeoutSeconds.Value, sourceSeconds)); - _socketKeepAliveInterval = TimeSpan.FromSeconds(safeSeconds); - } - } - - private async Task HandleRegisteredAsync(JObject payload, CancellationToken token) - { - string newSessionId = payload.Value("session_id"); - if (!string.IsNullOrEmpty(newSessionId)) - { - _sessionId = newSessionId; - ProjectIdentityUtility.SetSessionId(_sessionId); - _state = TransportState.Connected(TransportDisplayName, sessionId: _sessionId, details: _endpointUri.ToString()); - McpLog.Info($"[WebSocket] Registered with session ID: {_sessionId}"); - - await SendRegisterToolsAsync(token).ConfigureAwait(false); - } - } - - private async Task SendRegisterToolsAsync(CancellationToken token) - { - if (_toolDiscoveryService == null) return; - - var tools = _toolDiscoveryService.DiscoverAllTools(); - var toolsArray = new JArray(); - - foreach (var tool in tools) - { - var toolObj = new JObject - { - ["name"] = tool.Name, - ["description"] = tool.Description, - ["structured_output"] = tool.StructuredOutput, - ["requires_polling"] = tool.RequiresPolling, - ["poll_action"] = tool.PollAction - }; - - var paramsArray = new JArray(); - if (tool.Parameters != null) - { - foreach (var p in tool.Parameters) - { - paramsArray.Add(new JObject - { - ["name"] = p.Name, - ["description"] = p.Description, - ["type"] = p.Type, - ["required"] = p.Required, - ["default_value"] = p.DefaultValue - }); - } - } - toolObj["parameters"] = paramsArray; - toolsArray.Add(toolObj); - } - - var payload = new JObject - { - ["type"] = "register_tools", - ["tools"] = toolsArray - }; - - await SendJsonAsync(payload, token).ConfigureAwait(false); - McpLog.Info($"[WebSocket] Sent {tools.Count} tools registration"); - } - - private async Task HandleExecuteAsync(JObject payload, CancellationToken token) - { - string commandId = payload.Value("id"); - string commandName = payload.Value("name"); - JObject parameters = payload.Value("params") ?? new JObject(); - int timeoutSeconds = payload.Value("timeout") ?? (int)DefaultCommandTimeout.TotalSeconds; - - if (string.IsNullOrEmpty(commandId) || string.IsNullOrEmpty(commandName)) - { - McpLog.Warn("[WebSocket] Invalid execute payload (missing id or name)"); - return; - } - - var commandEnvelope = new JObject - { - ["type"] = commandName, - ["params"] = parameters - }; - - string responseJson; - try - { - using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(token); - timeoutCts.CancelAfter(TimeSpan.FromSeconds(Math.Max(1, timeoutSeconds))); - responseJson = await TransportCommandDispatcher.ExecuteCommandJsonAsync(commandEnvelope.ToString(Formatting.None), timeoutCts.Token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - responseJson = JsonConvert.SerializeObject(new - { - status = "error", - error = $"Command '{commandName}' timed out after {timeoutSeconds} seconds" - }); - } - catch (Exception ex) - { - responseJson = JsonConvert.SerializeObject(new - { - status = "error", - error = ex.Message - }); - } - - JToken resultToken; - try - { - resultToken = JToken.Parse(responseJson); - } - catch - { - resultToken = new JObject - { - ["status"] = "error", - ["error"] = "Invalid response payload" - }; - } - - var responsePayload = new JObject - { - ["type"] = "command_result", - ["id"] = commandId, - ["result"] = resultToken - }; - - await SendJsonAsync(responsePayload, token).ConfigureAwait(false); - } - - private async Task KeepAliveLoopAsync(CancellationToken token) - { - while (!token.IsCancellationRequested) - { - try - { - await Task.Delay(_keepAliveInterval, token).ConfigureAwait(false); - if (_socket == null || _socket.State != WebSocketState.Open) - { - break; - } - await SendPongAsync(token).ConfigureAwait(false); - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - McpLog.Warn($"[WebSocket] Keep-alive failed: {ex.Message}"); - await HandleSocketClosureAsync(ex.Message).ConfigureAwait(false); - break; - } - } - } - - private async Task SendRegisterAsync(CancellationToken token) - { - var registerPayload = new JObject - { - ["type"] = "register", - // session_id is now server-authoritative; omitted here or sent as null - ["project_name"] = _projectName, - ["project_hash"] = _projectHash, - ["unity_version"] = _unityVersion - }; - - await SendJsonAsync(registerPayload, token).ConfigureAwait(false); - } - - private Task SendPongAsync(CancellationToken token) - { - var payload = new JObject - { - ["type"] = "pong", - }; - return SendJsonAsync(payload, token); - } - - private async Task SendJsonAsync(JObject payload, CancellationToken token) - { - if (_socket == null) - { - throw new InvalidOperationException("WebSocket is not initialised"); - } - - string json = payload.ToString(Formatting.None); - byte[] bytes = Encoding.UTF8.GetBytes(json); - var buffer = new ArraySegment(bytes); - - await _sendLock.WaitAsync(token).ConfigureAwait(false); - try - { - if (_socket.State != WebSocketState.Open) - { - throw new InvalidOperationException("WebSocket is not open"); - } - - await _socket.SendAsync(buffer, WebSocketMessageType.Text, true, token).ConfigureAwait(false); - } - finally - { - _sendLock.Release(); - } - } - - private async Task HandleSocketClosureAsync(string reason) - { - if (_lifecycleCts == null || _lifecycleCts.IsCancellationRequested) - { - return; - } - - if (Interlocked.CompareExchange(ref _isReconnectingFlag, 1, 0) != 0) - { - return; - } - - _isConnected = false; - _state = _state.WithError(reason ?? "Connection closed"); - McpLog.Warn($"[WebSocket] Connection closed: {reason}"); - - await StopConnectionLoopsAsync(awaitTasks: false).ConfigureAwait(false); - - _ = Task.Run(() => AttemptReconnectAsync(_lifecycleCts.Token), CancellationToken.None); - } - - private async Task AttemptReconnectAsync(CancellationToken token) - { - try - { - await StopConnectionLoopsAsync().ConfigureAwait(false); - - foreach (TimeSpan delay in ReconnectSchedule) - { - if (token.IsCancellationRequested) - { - return; - } - - if (delay > TimeSpan.Zero) - { - try { await Task.Delay(delay, token).ConfigureAwait(false); } - catch (OperationCanceledException) { return; } - } - - if (await EstablishConnectionAsync(token).ConfigureAwait(false)) - { - _state = TransportState.Connected(TransportDisplayName, sessionId: _sessionId, details: _endpointUri.ToString()); - _isConnected = true; - McpLog.Info("[WebSocket] Reconnected to MCP server"); - return; - } - } - } - finally - { - Interlocked.Exchange(ref _isReconnectingFlag, 0); - } - - _state = TransportState.Disconnected(TransportDisplayName, "Failed to reconnect"); - } - - private static Uri BuildWebSocketUri(string baseUrl) - { - if (string.IsNullOrWhiteSpace(baseUrl)) - { - baseUrl = McpDistribution.Settings.defaultHttpBaseUrl; - } - - if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out var httpUri)) - { - throw new InvalidOperationException($"Invalid MCP base URL: {baseUrl}"); - } - - string scheme = httpUri.Scheme.Equals("https", StringComparison.OrdinalIgnoreCase) ? "wss" : "ws"; - string builder = $"{scheme}://{httpUri.Authority}"; - if (!string.IsNullOrEmpty(httpUri.AbsolutePath) && httpUri.AbsolutePath != "/") - { - builder += httpUri.AbsolutePath.TrimEnd('/'); - } - - builder += "/hub/plugin"; - - return new Uri(builder); - } - } -} diff --git a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs.meta b/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs.meta deleted file mode 100644 index 91b98e00..00000000 --- a/MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 044c8f7beb4af4a77a14d677190c21dc -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs index 781d1c0e..010aa434 100644 --- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs +++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs @@ -49,7 +49,9 @@ public class McpClientConfigSection public McpClientConfigSection(VisualElement root) { Root = root; - configurators = MCPServiceLocator.Client.GetAllClients().ToList(); + // Client configuration service is no longer available + // This section should not be loaded, but handle gracefully if it is + configurators = new List(); CacheUIElements(); InitializeUI(); RegisterCallbacks(); @@ -184,51 +186,18 @@ private void UpdateClaudeCliPathVisibility() private void OnConfigureAllClientsClicked() { - try - { - var summary = MCPServiceLocator.Client.ConfigureAllDetectedClients(); - - string message = summary.GetSummaryMessage() + "\n\n"; - foreach (var msg in summary.Messages) - { - message += msg + "\n"; - } - - EditorUtility.DisplayDialog("Configure All Clients", message, "OK"); - - if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count) - { - UpdateClientStatus(); - UpdateManualConfiguration(); - } - } - catch (Exception ex) - { - EditorUtility.DisplayDialog("Configuration Failed", ex.Message, "OK"); - } + EditorUtility.DisplayDialog("Client Configuration", + "Client configuration is no longer supported by this plugin.\n\n" + + "Please configure your MCP client (e.g., Cursor) directly using its configuration file (e.g., mcp.json).", + "OK"); } private void OnConfigureClicked() { - if (selectedClientIndex < 0 || selectedClientIndex >= configurators.Count) - return; - - var client = configurators[selectedClientIndex]; - - try - { - MCPServiceLocator.Client.ConfigureClient(client); - lastStatusChecks.Remove(client); - RefreshClientStatus(client, forceImmediate: true); - UpdateManualConfiguration(); - } - catch (Exception ex) - { - clientStatusLabel.text = "Error"; - clientStatusLabel.style.color = Color.red; - McpLog.Error($"Configuration failed: {ex.Message}"); - EditorUtility.DisplayDialog("Configuration Failed", ex.Message, "OK"); - } + EditorUtility.DisplayDialog("Client Configuration", + "Client configuration is no longer supported by this plugin.\n\n" + + "Please configure your MCP client (e.g., Cursor) directly using its configuration file (e.g., mcp.json).", + "OK"); } private void OnBrowseClaudeClicked() @@ -301,78 +270,24 @@ public void RefreshSelectedClient() private void RefreshClientStatus(IMcpClientConfigurator client, bool forceImmediate = false) { - if (client is ClaudeCliMcpConfigurator) - { - RefreshClaudeCliStatus(client, forceImmediate); - return; - } - - if (forceImmediate || ShouldRefreshClient(client)) + // Client configuration service is no longer available + // Just show not configured status + if (client is McpClientConfiguratorBase baseConfigurator) { - MCPServiceLocator.Client.CheckClientStatus(client); - lastStatusChecks[client] = DateTime.UtcNow; + baseConfigurator.Client.SetStatus(McpStatus.NotConfigured, "Client configuration not supported"); } - ApplyStatusToUi(client); } private void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImmediate) { - if (forceImmediate) - { - MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); - lastStatusChecks[client] = DateTime.UtcNow; - ApplyStatusToUi(client); - return; - } - - bool hasStatus = lastStatusChecks.ContainsKey(client); - bool needsRefresh = !hasStatus || ShouldRefreshClient(client); - - if (!hasStatus) - { - ApplyStatusToUi(client, showChecking: true); - } - else - { - ApplyStatusToUi(client); - } - - if (needsRefresh && !statusRefreshInFlight.Contains(client)) + // Client configuration service is no longer available + // Just show not configured status + if (client is McpClientConfiguratorBase baseConfigurator) { - statusRefreshInFlight.Add(client); - ApplyStatusToUi(client, showChecking: true); - - Task.Run(() => - { - MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false); - }).ContinueWith(t => - { - bool faulted = false; - string errorMessage = null; - if (t.IsFaulted && t.Exception != null) - { - var baseException = t.Exception.GetBaseException(); - errorMessage = baseException?.Message ?? "Status check failed"; - McpLog.Error($"Failed to refresh Claude CLI status: {errorMessage}"); - faulted = true; - } - - EditorApplication.delayCall += () => - { - statusRefreshInFlight.Remove(client); - lastStatusChecks[client] = DateTime.UtcNow; - if (faulted) - { - if (client is McpClientConfiguratorBase baseConfigurator) - { - baseConfigurator.Client.SetStatus(McpStatus.Error, errorMessage ?? "Status check failed"); - } - } - ApplyStatusToUi(client); - }; - }); + baseConfigurator.Client.SetStatus(McpStatus.NotConfigured, "Client configuration not supported"); } + ApplyStatusToUi(client); } private bool ShouldRefreshClient(IMcpClientConfigurator client) diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs index 9b2cc933..bdc45377 100644 --- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs +++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs @@ -1,49 +1,21 @@ -using System; using System.Threading.Tasks; -using MCPForUnity.Editor.Constants; using MCPForUnity.Editor.Helpers; using MCPForUnity.Editor.Services; -using MCPForUnity.Editor.Services.Transport; -using UnityEditor; -using UnityEditor.UIElements; -using UnityEngine; using UnityEngine.UIElements; namespace MCPForUnity.Editor.Windows.Components.Connection { /// /// Controller for the Connection section of the MCP For Unity editor window. - /// Handles transport protocol, HTTP/stdio configuration, connection status, and health checks. + /// Handles stdio transport connection status and health checks. /// public class McpConnectionSection { - // Transport protocol enum - private enum TransportProtocol - { - HTTP, - Stdio - } - // UI Elements - private EnumField transportDropdown; - private VisualElement httpUrlRow; - private VisualElement httpServerCommandSection; - private TextField httpServerCommandField; - private Button copyHttpServerCommandButton; - private Label httpServerCommandHint; - private TextField httpUrlField; - private Button startHttpServerButton; - private Button stopHttpServerButton; - private VisualElement unitySocketPortRow; - private TextField unityPortField; - private VisualElement statusIndicator; - private Label connectionStatusLabel; - private Button connectionToggleButton; private VisualElement healthIndicator; private Label healthStatusLabel; private Button testConnectionButton; - private bool connectionToggleInProgress; private Task verificationTask; private string lastHealthStatus; @@ -53,9 +25,6 @@ private enum TransportProtocol private const string HealthStatusPingFailed = "Ping Failed"; private const string HealthStatusUnhealthy = "Unhealthy"; - // Events - public event Action OnManualConfigUpdateRequested; - public VisualElement Root { get; private set; } public McpConnectionSection(VisualElement root) @@ -68,20 +37,6 @@ public McpConnectionSection(VisualElement root) private void CacheUIElements() { - transportDropdown = Root.Q("transport-dropdown"); - httpUrlRow = Root.Q("http-url-row"); - httpServerCommandSection = Root.Q("http-server-command-section"); - httpServerCommandField = Root.Q("http-server-command"); - copyHttpServerCommandButton = Root.Q