-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathHttpAutoStartHandler.cs
More file actions
179 lines (158 loc) · 7.26 KB
/
HttpAutoStartHandler.cs
File metadata and controls
179 lines (158 loc) · 7.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
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
{
/// <summary>
/// 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).
/// </summary>
[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.
string launchBaseUrl = HttpEndpointUtility.GetLocalServerLaunchBaseUrl();
string policyError;
bool launchAllowed = HttpEndpointUtility.IsLanScope()
? HttpEndpointUtility.IsHttpLanUrlAllowedForLaunch(launchBaseUrl, out policyError)
: HttpEndpointUtility.IsHttpLocalUrlAllowedForLaunch(launchBaseUrl, out policyError);
if (!launchAllowed)
{
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}");
}
}
/// <summary>
/// Waits for the local HTTP server to accept connections, then connects the bridge.
/// Mirrors TryAutoStartSessionAsync in McpConnectionSection.
/// </summary>
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");
}
/// <summary>
/// Connects the bridge directly (for remote HTTP where the server is already running).
/// </summary>
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");
}
}
}
}