diff --git a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
index eb4ba2b52..43562e5c8 100644
--- a/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
+++ b/MCPForUnity/Editor/Clients/McpClientConfiguratorBase.cs
@@ -334,7 +334,24 @@ public ClaudeCliMcpConfigurator(McpClient client) : base(client) { }
public override string GetConfigPath() => "Managed via Claude CLI";
+ ///
+ /// Checks the Claude CLI registration status.
+ /// MUST be called from the main Unity thread due to EditorPrefs and Application.dataPath access.
+ ///
public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
+ {
+ // Capture main-thread-only values before delegating to thread-safe method
+ string projectDir = Path.GetDirectoryName(Application.dataPath);
+ bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
+ return CheckStatusWithProjectDir(projectDir, useHttpTransport, attemptAutoRewrite);
+ }
+
+ ///
+ /// Internal thread-safe version of CheckStatus.
+ /// Can be called from background threads because all main-thread-only values are passed as parameters.
+ /// Both projectDir and useHttpTransport are REQUIRED (non-nullable) to enforce thread safety at compile time.
+ ///
+ internal McpStatus CheckStatusWithProjectDir(string projectDir, bool useHttpTransport, bool attemptAutoRewrite = true)
{
try
{
@@ -347,8 +364,11 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
return client.status;
}
- string args = "mcp list";
- string projectDir = Path.GetDirectoryName(Application.dataPath);
+ // projectDir is required - no fallback to Application.dataPath
+ if (string.IsNullOrEmpty(projectDir))
+ {
+ throw new ArgumentNullException(nameof(projectDir), "Project directory must be provided for thread-safe execution");
+ }
string pathPrepend = null;
if (Application.platform == RuntimePlatform.OSXEditor)
@@ -372,10 +392,35 @@ public override McpStatus CheckStatus(bool attemptAutoRewrite = true)
}
catch { }
- if (ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out _, 10000, pathPrepend))
+ // Check if UnityMCP exists
+ if (ExecPath.TryRun(claudePath, "mcp list", projectDir, out var listStdout, out var listStderr, 10000, pathPrepend))
{
- if (!string.IsNullOrEmpty(stdout) && stdout.IndexOf("UnityMCP", StringComparison.OrdinalIgnoreCase) >= 0)
+ if (!string.IsNullOrEmpty(listStdout) && listStdout.IndexOf("UnityMCP", StringComparison.OrdinalIgnoreCase) >= 0)
{
+ // UnityMCP is registered - now verify transport mode matches
+ // useHttpTransport parameter is required (non-nullable) to ensure thread safety
+ bool currentUseHttp = useHttpTransport;
+
+ // Get detailed info about the registration to check transport type
+ if (ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out var getStdout, out var getStderr, 7000, pathPrepend))
+ {
+ // Parse the output to determine registered transport mode
+ // The CLI output format contains "Type: http" or "Type: stdio"
+ bool registeredWithHttp = getStdout.Contains("Type: http", StringComparison.OrdinalIgnoreCase);
+ bool registeredWithStdio = getStdout.Contains("Type: stdio", StringComparison.OrdinalIgnoreCase);
+
+ // Check for transport mismatch
+ if ((currentUseHttp && registeredWithStdio) || (!currentUseHttp && registeredWithHttp))
+ {
+ string registeredTransport = registeredWithHttp ? "HTTP" : "stdio";
+ string currentTransport = currentUseHttp ? "HTTP" : "stdio";
+ string errorMsg = $"Transport mismatch: Claude Code is registered with {registeredTransport} but current setting is {currentTransport}. Click Configure to re-register.";
+ client.SetStatus(McpStatus.Error, errorMsg);
+ McpLog.Warn(errorMsg);
+ return client.status;
+ }
+ }
+
client.SetStatus(McpStatus.Configured);
return client.status;
}
@@ -452,26 +497,29 @@ private void Register()
}
catch { }
- bool already = false;
- if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend))
+ // Check if UnityMCP already exists and remove it first to ensure clean registration
+ // This ensures we always use the current transport mode setting
+ bool serverExists = ExecPath.TryRun(claudePath, "mcp get UnityMCP", projectDir, out _, out _, 7000, pathPrepend);
+ if (serverExists)
{
- string combined = ($"{stdout}\n{stderr}") ?? string.Empty;
- if (combined.IndexOf("already exists", StringComparison.OrdinalIgnoreCase) >= 0)
+ McpLog.Info("Existing UnityMCP registration found - removing to ensure transport mode is up-to-date");
+ if (!ExecPath.TryRun(claudePath, "mcp remove UnityMCP", projectDir, out var removeStdout, out var removeStderr, 10000, pathPrepend))
{
- already = true;
- }
- else
- {
- throw new InvalidOperationException($"Failed to register with Claude Code:\n{stderr}\n{stdout}");
+ McpLog.Warn($"Failed to remove existing UnityMCP registration: {removeStderr}. Attempting to register anyway...");
}
}
- if (!already)
+ // Now add the registration with the current transport mode
+ if (!ExecPath.TryRun(claudePath, args, projectDir, out var stdout, out var stderr, 15000, pathPrepend))
{
- McpLog.Info("Successfully registered with Claude Code.");
+ throw new InvalidOperationException($"Failed to register with Claude Code:\n{stderr}\n{stdout}");
}
- CheckStatus();
+ McpLog.Info($"Successfully registered with Claude Code using {(useHttpTransport ? "HTTP" : "stdio")} transport.");
+
+ // Set status to Configured immediately after successful registration
+ // The UI will trigger an async verification check separately to avoid blocking
+ client.SetStatus(McpStatus.Configured);
}
private void Unregister()
@@ -514,7 +562,7 @@ private void Unregister()
}
client.SetStatus(McpStatus.NotConfigured);
- CheckStatus();
+ // Status is already set - no need for blocking CheckStatus() call
}
public override string GetManualSnippet()
diff --git a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs
index 781d1c0ed..9c2dd795b 100644
--- a/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs
+++ b/MCPForUnity/Editor/Windows/Components/ClientConfig/McpClientConfigSection.cs
@@ -6,6 +6,7 @@
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using MCPForUnity.Editor.Clients;
+using MCPForUnity.Editor.Constants;
using MCPForUnity.Editor.Helpers;
using MCPForUnity.Editor.Models;
using MCPForUnity.Editor.Services;
@@ -288,12 +289,14 @@ private void OnCopyJsonClicked()
McpLog.Info("Configuration copied to clipboard");
}
- public void RefreshSelectedClient()
+ public void RefreshSelectedClient(bool forceImmediate = false)
{
if (selectedClientIndex >= 0 && selectedClientIndex < configurators.Count)
{
var client = configurators[selectedClientIndex];
- RefreshClientStatus(client, forceImmediate: true);
+ // Force immediate for non-Claude CLI, or when explicitly requested
+ bool shouldForceImmediate = forceImmediate || client is not ClaudeCliMcpConfigurator;
+ RefreshClientStatus(client, shouldForceImmediate);
UpdateManualConfiguration();
UpdateClaudeCliPathVisibility();
}
@@ -318,14 +321,6 @@ private void RefreshClientStatus(IMcpClientConfigurator client, bool forceImmedi
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);
@@ -338,14 +333,21 @@ private void RefreshClaudeCliStatus(IMcpClientConfigurator client, bool forceImm
ApplyStatusToUi(client);
}
- if (needsRefresh && !statusRefreshInFlight.Contains(client))
+ if ((forceImmediate || needsRefresh) && !statusRefreshInFlight.Contains(client))
{
statusRefreshInFlight.Add(client);
ApplyStatusToUi(client, showChecking: true);
+ // Capture main-thread-only values before async task
+ string projectDir = Path.GetDirectoryName(Application.dataPath);
+ bool useHttpTransport = EditorPrefs.GetBool(EditorPrefKeys.UseHttpTransport, true);
+
Task.Run(() =>
{
- MCPServiceLocator.Client.CheckClientStatus(client, attemptAutoRewrite: false);
+ // This method is only called for Claude CLI configurators, so we can safely cast
+ // Use thread-safe version with captured main-thread values
+ var claudeConfigurator = (ClaudeCliMcpConfigurator)client;
+ claudeConfigurator.CheckStatusWithProjectDir(projectDir, useHttpTransport, attemptAutoRewrite: false);
}).ContinueWith(t =>
{
bool faulted = false;
diff --git a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
index 9b2cc9335..d19bab2b4 100644
--- a/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
+++ b/MCPForUnity/Editor/Windows/Components/Connection/McpConnectionSection.cs
@@ -55,6 +55,7 @@ private enum TransportProtocol
// Events
public event Action OnManualConfigUpdateRequested;
+ public event Action OnTransportChanged;
public VisualElement Root { get; private set; }
@@ -115,6 +116,7 @@ private void RegisterCallbacks()
UpdateHttpFieldVisibility();
RefreshHttpUi();
OnManualConfigUpdateRequested?.Invoke();
+ OnTransportChanged?.Invoke();
McpLog.Info($"Transport changed to: {evt.newValue}");
});
diff --git a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
index b82f03c12..d818edd05 100644
--- a/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
+++ b/MCPForUnity/Editor/Windows/MCPForUnityEditorWindow.cs
@@ -175,6 +175,8 @@ public void CreateGUI()
connectionSection = new McpConnectionSection(connectionRoot);
connectionSection.OnManualConfigUpdateRequested += () =>
clientConfigSection?.UpdateManualConfiguration();
+ connectionSection.OnTransportChanged += () =>
+ clientConfigSection?.RefreshSelectedClient(forceImmediate: true);
}
// Load and initialize Client Configuration section