diff --git a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
index b5b1aaa64..a513aca9a 100644
--- a/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
+++ b/MCPForUnity/Editor/Constants/EditorPrefKeys.cs
@@ -66,6 +66,7 @@ internal static class EditorPrefKeys
internal const string ApiKey = "MCPForUnity.ApiKey";
+ internal const string AutoStartOnLoad = "MCPForUnity.AutoStartOnLoad";
internal const string BatchExecuteMaxCommands = "MCPForUnity.BatchExecute.MaxCommands";
internal const string LogRecordEnabled = "MCPForUnity.LogRecordEnabled";
}
diff --git a/MCPForUnity/Editor/Services/HttpAutoStartHandler.cs b/MCPForUnity/Editor/Services/HttpAutoStartHandler.cs
new file mode 100644
index 000000000..981dc2141
--- /dev/null
+++ b/MCPForUnity/Editor/Services/HttpAutoStartHandler.cs
@@ -0,0 +1,175 @@
+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;
+using UnityEngine;
+
+namespace MCPForUnity.Editor.Services
+{
+ ///
+ /// Automatically starts the HTTP MCP bridge on editor load when the user has opted in
+ /// via the "Auto-Start on Editor Load" toggle in Advanced Settings.
+ /// This complements HttpBridgeReloadHandler (which only resumes after domain reloads).
+ ///
+ [InitializeOnLoad]
+ internal static class HttpAutoStartHandler
+ {
+ private const string SessionInitKey = "HttpAutoStartHandler.SessionInitialized";
+
+ static HttpAutoStartHandler()
+ {
+ // SessionState resets on editor process start but persists across domain reloads.
+ // Only run once per session — let HttpBridgeReloadHandler handle reload-resume cases.
+ if (SessionState.GetBool(SessionInitKey, false)) return;
+
+ if (Application.isBatchMode &&
+ string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("UNITY_MCP_ALLOW_BATCH")))
+ {
+ return;
+ }
+
+ // Only check lightweight EditorPrefs here — services like EditorConfigurationCache
+ // and MCPServiceLocator may not be initialized yet on fresh editor launch.
+ bool autoStartEnabled = EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false);
+ if (!autoStartEnabled) return;
+
+ SessionState.SetBool(SessionInitKey, true);
+
+ // Delay to let the editor and services finish initialization.
+ EditorApplication.delayCall += OnEditorReady;
+ }
+
+ private static void OnEditorReady()
+ {
+ try
+ {
+ bool autoStartEnabled = EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false);
+ if (!autoStartEnabled) return;
+
+ bool useHttp = EditorConfigurationCache.Instance.UseHttpTransport;
+ if (!useHttp) return;
+
+ // Don't auto-start if bridge is already running.
+ if (MCPServiceLocator.TransportManager.IsRunning(TransportMode.Http)) return;
+
+ _ = AutoStartAsync();
+ }
+ catch (Exception ex)
+ {
+ McpLog.Debug($"[HTTP Auto-Start] Deferred check failed: {ex.Message}");
+ }
+ }
+
+ private static async Task AutoStartAsync()
+ {
+ try
+ {
+ bool isLocal = !HttpEndpointUtility.IsRemoteScope();
+
+ if (isLocal)
+ {
+ // For HTTP Local: launch the server process first, then connect the bridge.
+ // This mirrors what the UI "Start Server" button does.
+ if (!HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(
+ HttpEndpointUtility.GetLocalBaseUrl(), out string policyError))
+ {
+ McpLog.Debug($"[HTTP Auto-Start] Local URL blocked by security policy: {policyError}");
+ return;
+ }
+
+ // Check if server is already reachable (e.g. user started it externally).
+ if (!MCPServiceLocator.Server.IsLocalHttpServerReachable())
+ {
+ bool serverStarted = MCPServiceLocator.Server.StartLocalHttpServer(quiet: true);
+ if (!serverStarted)
+ {
+ McpLog.Warn("[HTTP Auto-Start] Failed to start local HTTP server");
+ return;
+ }
+ }
+
+ // Wait for the server to become reachable, then connect.
+ await WaitForServerAndConnectAsync();
+ }
+ else
+ {
+ // For HTTP Remote: server is external, just connect the bridge.
+ await ConnectBridgeAsync();
+ }
+ }
+ catch (Exception ex)
+ {
+ McpLog.Warn($"[HTTP Auto-Start] Failed: {ex.Message}");
+ }
+ }
+
+ ///
+ /// Waits for the local HTTP server to accept connections, then connects the bridge.
+ /// Mirrors TryAutoStartSessionAsync in McpConnectionSection.
+ ///
+ private static async Task WaitForServerAndConnectAsync()
+ {
+ const int maxAttempts = 30;
+ var shortDelay = TimeSpan.FromMilliseconds(500);
+ var longDelay = TimeSpan.FromSeconds(3);
+
+ for (int attempt = 0; attempt < maxAttempts; attempt++)
+ {
+ // Abort if user changed settings while we were waiting.
+ if (!EditorPrefs.GetBool(EditorPrefKeys.AutoStartOnLoad, false)) return;
+ if (!EditorConfigurationCache.Instance.UseHttpTransport) return;
+ if (MCPServiceLocator.TransportManager.IsRunning(TransportMode.Http)) return;
+
+ bool reachable = MCPServiceLocator.Server.IsLocalHttpServerReachable();
+
+ if (reachable)
+ {
+ bool started = await MCPServiceLocator.Bridge.StartAsync();
+ if (started)
+ {
+ McpLog.Info("[HTTP Auto-Start] Bridge started successfully");
+ MCPForUnityEditorWindow.RequestHealthVerification();
+ return;
+ }
+ }
+ else if (attempt >= 20 && (attempt - 20) % 3 == 0)
+ {
+ // Last-resort: try connecting even if not detected (process detection may fail).
+ bool started = await MCPServiceLocator.Bridge.StartAsync();
+ if (started)
+ {
+ McpLog.Info("[HTTP Auto-Start] Bridge started successfully (late connect)");
+ MCPForUnityEditorWindow.RequestHealthVerification();
+ return;
+ }
+ }
+
+ var delay = attempt < 6 ? shortDelay : longDelay;
+ try { await Task.Delay(delay); }
+ catch { return; }
+ }
+
+ McpLog.Warn("[HTTP Auto-Start] Server did not become reachable after launch");
+ }
+
+ ///
+ /// Connects the bridge directly (for remote HTTP where the server is already running).
+ ///
+ private static async Task ConnectBridgeAsync()
+ {
+ bool started = await MCPServiceLocator.Bridge.StartAsync();
+ if (started)
+ {
+ McpLog.Info("[HTTP Auto-Start] Bridge started successfully (remote)");
+ MCPForUnityEditorWindow.RequestHealthVerification();
+ }
+ else
+ {
+ McpLog.Warn("[HTTP Auto-Start] Failed to connect to remote HTTP server");
+ }
+ }
+ }
+}
diff --git a/MCPForUnity/Editor/Services/HttpAutoStartHandler.cs.meta b/MCPForUnity/Editor/Services/HttpAutoStartHandler.cs.meta
new file mode 100644
index 000000000..29a8a5f9e
--- /dev/null
+++ b/MCPForUnity/Editor/Services/HttpAutoStartHandler.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 3d8f1790992fe0742938d8a879056ee6
\ No newline at end of file
diff --git a/MCPForUnity/Editor/Services/IServerManagementService.cs b/MCPForUnity/Editor/Services/IServerManagementService.cs
index 2da66738f..dea0c6ddd 100644
--- a/MCPForUnity/Editor/Services/IServerManagementService.cs
+++ b/MCPForUnity/Editor/Services/IServerManagementService.cs
@@ -15,8 +15,9 @@ public interface IServerManagementService
/// Start the local HTTP server in a new terminal window.
/// Stops any existing server on the port and clears the uvx cache first.
///
+ /// When true, skip confirmation dialogs (used by auto-start).
/// True if server was started successfully, false otherwise
- bool StartLocalHttpServer();
+ bool StartLocalHttpServer(bool quiet = false);
///
/// Stop the local HTTP server by finding the process listening on the configured port
diff --git a/MCPForUnity/Editor/Services/ServerManagementService.cs b/MCPForUnity/Editor/Services/ServerManagementService.cs
index 0b7799fad..d1f6a0fc2 100644
--- a/MCPForUnity/Editor/Services/ServerManagementService.cs
+++ b/MCPForUnity/Editor/Services/ServerManagementService.cs
@@ -233,17 +233,20 @@ private string GetPlatformSpecificPathPrepend()
/// Start the local HTTP server in a separate terminal window.
/// Stops any existing server on the port and clears the uvx cache first.
///
- public bool StartLocalHttpServer()
+ public bool StartLocalHttpServer(bool quiet = false)
{
/// Clean stale Python build artifacts when using a local dev server path
AssetPathUtility.CleanLocalServerBuildArtifacts();
if (!TryGetLocalHttpServerCommandParts(out _, out _, out var displayCommand, out var error))
{
- EditorUtility.DisplayDialog(
- "Cannot Start HTTP Server",
- error ?? "The server command could not be constructed with the current settings.",
- "OK");
+ if (!quiet)
+ {
+ EditorUtility.DisplayDialog(
+ "Cannot Start HTTP Server",
+ error ?? "The server command could not be constructed with the current settings.",
+ "OK");
+ }
return false;
}
@@ -259,12 +262,15 @@ public bool StartLocalHttpServer()
var remaining = GetListeningProcessIdsForPort(uri.Port);
if (remaining.Count > 0)
{
- EditorUtility.DisplayDialog(
- "Port In Use",
- $"Cannot start the local HTTP server because port {uri.Port} is already in use by PID(s): " +
- $"{string.Join(", ", remaining)}\n\n" +
- "MCP For Unity will not terminate unrelated processes. Stop the owning process manually or change the HTTP URL.",
- "OK");
+ if (!quiet)
+ {
+ EditorUtility.DisplayDialog(
+ "Port In Use",
+ $"Cannot start the local HTTP server because port {uri.Port} is already in use by PID(s): " +
+ $"{string.Join(", ", remaining)}\n\n" +
+ "MCP For Unity will not terminate unrelated processes. Stop the owning process manually or change the HTTP URL.",
+ "OK");
+ }
return false;
}
}
@@ -286,50 +292,53 @@ public bool StartLocalHttpServer()
launchCommand = $"{displayCommand} --pidfile {QuoteIfNeeded(pidFilePath)} --unity-instance-token {instanceToken}";
}
- if (EditorUtility.DisplayDialog(
+ if (!quiet && !EditorUtility.DisplayDialog(
"Start Local HTTP Server",
$"This will start the MCP server in HTTP mode in a new terminal window:\n\n{launchCommand}\n\n" +
"Continue?",
"Start Server",
"Cancel"))
{
+ return false;
+ }
+
+ try
+ {
+ // Clear any stale handshake state from prior launches.
+ ClearLocalServerPidTracking();
+
+ // Best-effort: delete stale pidfile if it exists.
try
{
- // Clear any stale handshake state from prior launches.
- ClearLocalServerPidTracking();
-
- // Best-effort: delete stale pidfile if it exists.
- try
+ if (!string.IsNullOrEmpty(pidFilePath) && File.Exists(pidFilePath))
{
- if (!string.IsNullOrEmpty(pidFilePath) && File.Exists(pidFilePath))
- {
- DeletePidFile(pidFilePath);
- }
+ DeletePidFile(pidFilePath);
}
- catch { }
+ }
+ catch { }
- // Launch the server in a new terminal window (keeps user-visible logs).
- var startInfo = CreateTerminalProcessStartInfo(launchCommand);
- System.Diagnostics.Process.Start(startInfo);
- if (!string.IsNullOrEmpty(pidFilePath))
- {
- StoreLocalHttpServerHandshake(pidFilePath, instanceToken);
- }
- McpLog.Info($"Started local HTTP server in terminal: {launchCommand}");
- return true;
+ // Launch the server in a new terminal window (keeps user-visible logs).
+ var startInfo = CreateTerminalProcessStartInfo(launchCommand);
+ System.Diagnostics.Process.Start(startInfo);
+ if (!string.IsNullOrEmpty(pidFilePath))
+ {
+ StoreLocalHttpServerHandshake(pidFilePath, instanceToken);
}
- catch (Exception ex)
+ McpLog.Info($"Started local HTTP server in terminal: {launchCommand}");
+ return true;
+ }
+ catch (Exception ex)
+ {
+ McpLog.Error($"Failed to start server: {ex.Message}");
+ if (!quiet)
{
- McpLog.Error($"Failed to start server: {ex.Message}");
EditorUtility.DisplayDialog(
"Error",
$"Failed to start server: {ex.Message}",
"OK");
- return false;
}
+ return false;
}
-
- return false;
}
///
diff --git a/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs b/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs
index 89261c074..6d5de0efb 100644
--- a/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs
+++ b/MCPForUnity/Editor/Windows/Components/Advanced/McpAdvancedSection.cs
@@ -24,6 +24,7 @@ public class McpAdvancedSection
private TextField gitUrlOverride;
private Button browseGitUrlButton;
private Button clearGitUrlButton;
+ private Toggle autoStartOnLoadToggle;
private Toggle debugLogsToggle;
private Toggle logRecordToggle;
private Toggle devModeForceRefreshToggle;
@@ -66,6 +67,7 @@ private void CacheUIElements()
gitUrlOverride = Root.Q("git-url-override");
browseGitUrlButton = Root.Q