From 05b0bffd0714e26fbfaf57a6e164c709a06a39cb Mon Sep 17 00:00:00 2001
From: gtinneyprophecy <104222421+gtinneyprophecy@users.noreply.github.com>
Date: Thu, 4 Dec 2025 22:02:07 -0500
Subject: [PATCH 01/12] Update repository URL in README
Changed from parent github to prophecygamesstudio fork
---
README.md | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/README.md b/README.md
index 97510019..67797ee7 100644
--- a/README.md
+++ b/README.md
@@ -144,13 +144,13 @@ MCP for Unity connects your tools using two components:
3. Click `+` -> `Add package from git URL...`.
4. Enter:
```
- https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity
+ https://github.com/prophecygamestudio/unity-mcp.git?path=/MCPForUnity
```
5. Click `Add`.
**Need a fixed version?** Use a tagged URL instead (updates require uninstalling and re-installing):
```
-https://github.com/CoplayDev/unity-mcp.git?path=/MCPForUnity#v8.0.0
+https://github.com/prophecygamestudio/unity-mcp.git?path=/MCPForUnity#v8.0.0
```
#### To install via OpenUPM
@@ -177,7 +177,7 @@ HTTP transport is enabled out of the box. The Unity window can launch the FastMC
You can also start the server yourself from a terminal—useful for CI or when you want to see raw logs:
```bash
-uvx --from "git+https://github.com/CoplayDev/unity-mcp@v8.1.0#subdirectory=Server" mcp-for-unity --transport http --http-url http://localhost:8080
+uvx --from "git+https://github.com/prophecygamestudio/unity-mcp@v8.1.0#subdirectory=Server" mcp-for-unity --transport http --http-url http://localhost:8080
```
Keep the process running while clients are connected.
@@ -196,8 +196,8 @@ For **Claude Desktop** Users, try using our manually scrapped Unity_Skills by do
Client-specific troubleshooting
- **VSCode**: uses `Code/User/mcp.json` with top-level `servers.unityMCP`, `"type": "http"`, and the URL from Step 2. On Windows, MCP for Unity still prefers an absolute `uv.exe` path when you switch back to stdio.
- - **Cursor / Windsurf** [(**help link**)](https://github.com/CoplayDev/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf): if `uv` is missing, the MCP for Unity window shows "uv Not Found" with a quick [HELP] link and a "Choose `uv` Install Location" button.
- - **Claude Code** [(**help link**)](https://github.com/CoplayDev/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code): if `claude` isn't found, the window shows "Claude Not Found" with [HELP] and a "Choose Claude Location" button. Unregister now updates the UI immediately.
+ - **Cursor / Windsurf** [(**help link**)](https://github.com/prophecygamestudio/unity-mcp/wiki/1.-Fix-Unity-MCP-and-Cursor,-VSCode-&-Windsurf): if `uv` is missing, the MCP for Unity window shows "uv Not Found" with a quick [HELP] link and a "Choose `uv` Install Location" button.
+ - **Claude Code** [(**help link**)](https://github.com/prophecygamestudio/unity-mcp/wiki/2.-Fix-Unity-MCP-and-Claude-Code): if `claude` isn't found, the window shows "Claude Not Found" with [HELP] and a "Choose Claude Location" button. Unregister now updates the UI immediately.
**Option B: Manual Configuration**
@@ -420,7 +420,7 @@ Your privacy matters to us. All telemetry is optional and designed to respect yo
-Still stuck? [Open an Issue](https://github.com/CoplayDev/unity-mcp/issues) or [Join the Discord](https://discord.gg/y4p8KfzrN4)!
+Still stuck? [Open an Issue](https://github.com/prophecygamestudio/unity-mcp/issues) or [Join the Discord](https://discord.gg/y4p8KfzrN4)!
---
From 43b8e136a4817cfe602a3f88e874d1a78c916634 Mon Sep 17 00:00:00 2001
From: gtinney
Date: Thu, 4 Dec 2025 22:34:45 -0500
Subject: [PATCH 02/12] Ack! Looks like original README.md for starting MCP was
wrong. Trying to correct it.
---
Server/README.md | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/Server/README.md b/Server/README.md
index a18554be..57c67aa6 100644
--- a/Server/README.md
+++ b/Server/README.md
@@ -73,10 +73,10 @@ git clone https://github.com/CoplayDev/unity-mcp.git
cd unity-mcp/Server
# Run with uv (HTTP)
-uv run server.py --transport http --http-url http://localhost:8080
+uv run -m src.main --transport http --http-url http://localhost:8080
# Run with uv (stdio)
-uv run server.py --transport stdio
+uv run -m src.main --transport stdio
```
**MCP Client Configuration (HTTP):**
@@ -100,7 +100,8 @@ uv run server.py --transport stdio
"run",
"--directory",
"C:\\path\\to\\unity-mcp\\Server",
- "server.py",
+ "-m",
+ "src.main",
"--transport",
"stdio"
]
@@ -119,7 +120,8 @@ uv run server.py --transport stdio
"run",
"--directory",
"/path/to/unity-mcp/Server",
- "server.py",
+ "-m",
+ "src.main",
"--transport",
"stdio"
]
From 719d8caf2c0ddb18600fbd74de1b9ae926f1fcf9 Mon Sep 17 00:00:00 2001
From: gtinney
Date: Thu, 4 Dec 2025 22:41:50 -0500
Subject: [PATCH 03/12] Update repository URL in Server README
---
Server/README.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/Server/README.md b/Server/README.md
index 57c67aa6..57c04101 100644
--- a/Server/README.md
+++ b/Server/README.md
@@ -11,7 +11,7 @@ Model Context Protocol server for Unity Editor integration. Control Unity throug
💬 **Join our community:** [Discord Server](https://discord.gg/y4p8KfzrN4)
-**Required:** Install the [Unity MCP Plugin](https://github.com/CoplayDev/unity-mcp?tab=readme-ov-file#-step-1-install-the-unity-package) to connect Unity Editor with this MCP server.
+**Required:** Install the [Unity MCP Plugin](https://github.com/prophecygamestudio/unity-mcp?tab=readme-ov-file#-step-1-install-the-unity-package) to connect Unity Editor with this MCP server.
---
@@ -23,11 +23,11 @@ Run directly from GitHub without installation:
```bash
# HTTP (default)
-uvx --from git+https://github.com/CoplayDev/unity-mcp@v8.1.6#subdirectory=Server \
+uvx --from git+https://github.com/prophecygamestudio/unity-mcp@v8.1.6#subdirectory=Server \
mcp-for-unity --transport http --http-url http://localhost:8080
# Stdio
-uvx --from git+https://github.com/CoplayDev/unity-mcp@v8.1.6#subdirectory=Server \
+uvx --from git+https://github.com/prophecygamestudio/unity-mcp@v8.1.6#subdirectory=Server \
mcp-for-unity --transport stdio
```
@@ -52,7 +52,7 @@ uvx --from git+https://github.com/CoplayDev/unity-mcp@v8.1.6#subdirectory=Server
"command": "uvx",
"args": [
"--from",
- "git+https://github.com/CoplayDev/unity-mcp@v8.1.6#subdirectory=Server",
+ "git+https://github.com/prophecygamestudio/unity-mcp@v8.1.6#subdirectory=Server",
"mcp-for-unity",
"--transport",
"stdio"
@@ -69,7 +69,7 @@ For local development or custom installations:
```bash
# Clone the repository
-git clone https://github.com/CoplayDev/unity-mcp.git
+git clone https://github.com/prophecygamestudio/unity-mcp.git
cd unity-mcp/Server
# Run with uv (HTTP)
@@ -168,7 +168,7 @@ Once connected, try these commands in your AI assistant:
For complete documentation, troubleshooting, and advanced usage:
-📖 **[Full Documentation](https://github.com/CoplayDev/unity-mcp#readme)**
+📖 **[Full Documentation](https://github.com/prophecygamestudio/unity-mcp#readme)**
---
@@ -182,4 +182,4 @@ For complete documentation, troubleshooting, and advanced usage:
## License
-MIT License - See [LICENSE](https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE)
+MIT License - See [LICENSE](https://github.com/prophecygamestudio/unity-mcp/blob/main/LICENSE)
From c2be2f9f0490b8027b56d0ed91597e6dcce0ceff Mon Sep 17 00:00:00 2001
From: gtinney
Date: Fri, 5 Dec 2025 01:53:31 -0500
Subject: [PATCH 04/12] Try to update Unity Package install to use local clone
of prophecy's unit-mcp fork
---
MCPForUnity/package.json | 6 +++---
TestProjects/UnityMCPTests/Packages/manifest.json | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/MCPForUnity/package.json b/MCPForUnity/package.json
index 6602d974..130a71fe 100644
--- a/MCPForUnity/package.json
+++ b/MCPForUnity/package.json
@@ -1,11 +1,11 @@
{
- "name": "com.coplaydev.unity-mcp",
+ "name": "com.prophecygamestudio.unity-mcp",
"version": "8.1.6",
"displayName": "MCP for Unity",
"description": "A bridge that connects AI assistants to Unity via the MCP (Model Context Protocol). Allows AI clients like Claude Code, Cursor, and VSCode to directly control your Unity Editor for enhanced development workflows.\n\nFeatures automated setup wizard, cross-platform support, and seamless integration with popular AI development tools.\n\nJoin Our Discord: https://discord.gg/y4p8KfzrN4",
"unity": "2021.3",
- "documentationUrl": "https://github.com/CoplayDev/unity-mcp",
- "licensesUrl": "https://github.com/CoplayDev/unity-mcp/blob/main/LICENSE",
+ "documentationUrl": "https://github.com/prophecygamestudio/unity-mcp",
+ "licensesUrl": "https://github.com/prophecygamestudio/unity-mcp/blob/main/LICENSE",
"dependencies": {
"com.unity.nuget.newtonsoft-json": "3.0.2"
},
diff --git a/TestProjects/UnityMCPTests/Packages/manifest.json b/TestProjects/UnityMCPTests/Packages/manifest.json
index 0bd78a67..a30cab4b 100644
--- a/TestProjects/UnityMCPTests/Packages/manifest.json
+++ b/TestProjects/UnityMCPTests/Packages/manifest.json
@@ -1,6 +1,6 @@
{
"dependencies": {
- "com.coplaydev.unity-mcp": "file:../../../MCPForUnity",
+ "com.prophecygamestudio.unity-mcp": "file:../../../MCPForUnity",
"com.unity.ai.navigation": "1.1.4",
"com.unity.collab-proxy": "2.5.2",
"com.unity.feature.development": "1.0.1",
From 7acccf2908ea21c94fc847afadab70fb64ef691f Mon Sep 17 00:00:00 2001
From: gtinney
Date: Fri, 5 Dec 2025 02:51:14 -0500
Subject: [PATCH 05/12] Try to point Unity installed package to point to
prophecygamestudio mcp repo
---
MCPForUnity/Editor/Helpers/AssetPathUtility.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
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";
}
///
From 0e9a4e65688ffcc4fc424fa8aec0e80d1c2b5d62 Mon Sep 17 00:00:00 2001
From: gtinney
Date: Fri, 5 Dec 2025 22:45:20 -0500
Subject: [PATCH 06/12] Removed a lot http MCP, forcing start of stdio.
Removing MCP UI elements
---
.../Migrations/LegacyServerSrcMigration.cs | 13 +-
.../Migrations/StdIoVersionMigration.cs | 42 +-
.../Editor/Services/BridgeControlService.cs | 65 +-
.../Services/HttpBridgeReloadHandler.cs | 145 ----
.../Services/HttpBridgeReloadHandler.cs.meta | 11 -
.../Editor/Services/IBridgeControlService.cs | 8 +-
.../Editor/Services/MCPServiceLocator.cs | 12 -
.../Services/StdioBridgeReloadHandler.cs | 20 +-
.../Services/Transport/TransportManager.cs | 131 +---
.../Transport/Transports/StdioBridgeHost.cs | 11 +-
.../Transports/WebSocketTransportClient.cs | 691 ------------------
.../WebSocketTransportClient.cs.meta | 11 -
.../ClientConfig/McpClientConfigSection.cs | 125 +---
.../Connection/McpConnectionSection.cs | 221 +-----
.../Editor/Windows/MCPForUnityEditorWindow.cs | 19 -
15 files changed, 88 insertions(+), 1437 deletions(-)
delete mode 100644 MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs
delete mode 100644 MCPForUnity/Editor/Services/HttpBridgeReloadHandler.cs.meta
delete mode 100644 MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs
delete mode 100644 MCPForUnity/Editor/Services/Transport/Transports/WebSocketTransportClient.cs.meta
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/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..e9c65dec 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)
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..ed840cec 100644
--- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
+++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
@@ -3,9 +3,7 @@
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;
@@ -13,27 +11,11 @@ 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;
@@ -53,9 +35,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,15 +47,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