diff --git a/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs b/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
index 94e2f3be9..8678676d8 100644
--- a/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
+++ b/MCPForUnity/Editor/Helpers/HttpEndpointUtility.cs
@@ -47,10 +47,13 @@ public static void SaveBaseUrl(string userValue)
}
///
- /// Returns the normalized local HTTP base URL (always reads local pref).
+ /// Returns the normalized local HTTP base URL. UNITY_MCP_HTTP_PORT wins when set,
+ /// so a CI editor keeps its own port instead of the one a sibling last wrote.
///
public static string GetLocalBaseUrl()
{
+ if (McpHttpCiBoot.TryGetCiPort(out int ciPort)) return $"http://127.0.0.1:{ciPort}";
+
string stored = EditorPrefs.GetString(LocalPrefKey, DefaultLocalBaseUrl);
return NormalizeBaseUrl(stored, DefaultLocalBaseUrl, remoteScope: false);
}
@@ -128,9 +131,12 @@ public static string GetRegisterToolsUrl()
///
/// Returns true if the active HTTP transport scope is "remote".
+ /// Always false under UNITY_MCP_HTTP_PORT: the CI endpoint is local by construction.
///
public static bool IsRemoteScope()
{
+ if (McpHttpCiBoot.TryGetCiPort(out _)) return false;
+
string scope = EditorConfigurationCache.Instance.HttpTransportScope;
return string.Equals(scope, "remote", StringComparison.OrdinalIgnoreCase);
}
diff --git a/MCPForUnity/Editor/McpHttpCiBoot.cs b/MCPForUnity/Editor/McpHttpCiBoot.cs
new file mode 100644
index 000000000..6366ac501
--- /dev/null
+++ b/MCPForUnity/Editor/McpHttpCiBoot.cs
@@ -0,0 +1,49 @@
+using System;
+using MCPForUnity.Editor.Helpers;
+using MCPForUnity.Editor.Services;
+using MCPForUnity.Editor.Services.Transport;
+using UnityEditor;
+
+namespace MCPForUnity.Editor
+{
+ // HTTP is a plugin-hub pull model: the editor dials ws://127.0.0.1:/hub/plugin
+ // StartStdioForCi only listens, so an editor booted that way never registers
+ public static class McpHttpCiBoot
+ {
+ private const string PortEnv = "UNITY_MCP_HTTP_PORT";
+
+ // Never persisted: sibling checkouts share one EditorPrefs file, so the last writer wins
+ public static bool TryGetCiPort(out int port)
+ {
+ port = 0;
+ string raw = Environment.GetEnvironmentVariable(PortEnv);
+ return !string.IsNullOrWhiteSpace(raw)
+ && int.TryParse(raw, out port)
+ && port > 0
+ && port <= 65535;
+ }
+
+ public static void StartHttpForCi()
+ {
+ if (!TryGetCiPort(out _))
+ {
+ McpLog.Error($"[MCPForUnity] StartHttpForCi: {PortEnv} not set or invalid; cannot start HTTP transport");
+ return;
+ }
+
+ // Defer the connect so MCPServiceLocator and friends are initialized.
+ EditorApplication.delayCall += async () =>
+ {
+ try
+ {
+ if (!await MCPServiceLocator.TransportManager.StartAsync(TransportMode.Http))
+ McpLog.Error("[MCPForUnity] StartHttpForCi: HTTP transport failed to start");
+ }
+ catch (Exception e)
+ {
+ McpLog.Error($"[MCPForUnity] StartHttpForCi: HTTP transport threw on start: {e}");
+ }
+ };
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/McpHttpCiBoot.cs.meta b/MCPForUnity/Editor/McpHttpCiBoot.cs.meta
new file mode 100644
index 000000000..1bd4658ac
--- /dev/null
+++ b/MCPForUnity/Editor/McpHttpCiBoot.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3fff0044fbf5420d8be6c5bb79828276
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant: