@webmcp-bridge/local-mcp 0.5.4 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bridge.d.ts +4 -4
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +146 -581
- package/dist/bridge.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +14 -0
- package/dist/cli.js.map +1 -1
- package/dist/overlays.d.ts +72 -0
- package/dist/overlays.d.ts.map +1 -0
- package/dist/overlays.js +323 -0
- package/dist/overlays.js.map +1 -0
- package/dist/profiles.d.ts +8 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +27 -0
- package/dist/profiles.js.map +1 -0
- package/dist/runtime.d.ts +2 -1
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +21 -5
- package/dist/runtime.js.map +1 -1
- package/dist/server.d.ts +10 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +340 -0
- package/dist/server.js.map +1 -1
- package/dist/session.d.ts +3 -83
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +3 -602
- package/dist/session.js.map +1 -1
- package/package.json +8 -7
package/dist/bridge.js
CHANGED
|
@@ -1,23 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* This module composes runtime startup with the stdio MCP server into one lifecycle handle.
|
|
3
|
-
* It depends on
|
|
2
|
+
* This module composes site/runtime startup with the stdio MCP server into one lifecycle handle.
|
|
3
|
+
* It depends on agent-browser-core orchestration, site resolution, and server wiring so local-mcp stays a thin MCP facade over one browser session.
|
|
4
4
|
*/
|
|
5
|
+
import { assertAuthSensitiveBrowserSupport, resolveAuthPolicy, startBrowserSessionController, } from "@webmcp-bridge/agent-browser-core";
|
|
5
6
|
import { createLocalMcpStdioServer, } from "./server.js";
|
|
6
7
|
import { resolveCdpConnectUrl, startLocalMcpRuntime, } from "./runtime.js";
|
|
7
|
-
import {
|
|
8
|
+
import { evaluateDebugScript, evaluateOverlayTool, OverlayStore, } from "./overlays.js";
|
|
9
|
+
import { resolveDefaultUserDataDir } from "./profiles.js";
|
|
8
10
|
import { createNativeSiteDefinition, resolveSiteSource, } from "./sites.js";
|
|
9
|
-
const BOOTSTRAP_BROWSER_CLOSE_TIMEOUT_MS = 5_000;
|
|
10
|
-
const BOOTSTRAP_PROFILE_RELEASE_DELAY_MS = 500;
|
|
11
|
-
function readAuthState(value) {
|
|
12
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
13
|
-
return "unknown";
|
|
14
|
-
}
|
|
15
|
-
const state = value.state;
|
|
16
|
-
if (state === "authenticated" || state === "auth_required" || state === "challenge_required") {
|
|
17
|
-
return state;
|
|
18
|
-
}
|
|
19
|
-
return "unknown";
|
|
20
|
-
}
|
|
21
11
|
async function resolveSiteDefinitionFromBridgeOptions(options) {
|
|
22
12
|
const hasAdapterSource = Boolean(options.site || options.adapterModule);
|
|
23
13
|
if (hasAdapterSource) {
|
|
@@ -41,7 +31,8 @@ async function resolveSiteDefinitionFromBridgeOptions(options) {
|
|
|
41
31
|
async function startRuntime(options) {
|
|
42
32
|
return await startLocalMcpRuntime(options);
|
|
43
33
|
}
|
|
44
|
-
function buildRuntimeStartOptions(baseOptions, siteDefinition, controlMode, preferredPresentationMode, browserUrl) {
|
|
34
|
+
function buildRuntimeStartOptions(baseOptions, siteDefinition, userDataDir, controlMode, preferredPresentationMode, browserUrl) {
|
|
35
|
+
const configuredBrowserUrl = baseOptions.browserUrl?.trim() || undefined;
|
|
45
36
|
const nextOptions = {
|
|
46
37
|
siteDefinition,
|
|
47
38
|
preferredPresentationMode,
|
|
@@ -52,8 +43,8 @@ function buildRuntimeStartOptions(baseOptions, siteDefinition, controlMode, pref
|
|
|
52
43
|
if (baseOptions.browser !== undefined) {
|
|
53
44
|
nextOptions.browser = baseOptions.browser;
|
|
54
45
|
}
|
|
55
|
-
if (
|
|
56
|
-
nextOptions.userDataDir =
|
|
46
|
+
if (userDataDir !== undefined) {
|
|
47
|
+
nextOptions.userDataDir = userDataDir;
|
|
57
48
|
}
|
|
58
49
|
if (baseOptions.preferNative !== undefined) {
|
|
59
50
|
nextOptions.preferNative = baseOptions.preferNative;
|
|
@@ -65,7 +56,7 @@ function buildRuntimeStartOptions(baseOptions, siteDefinition, controlMode, pref
|
|
|
65
56
|
if (!browserUrl) {
|
|
66
57
|
throw new Error("CONFIG_ERROR: attach mode requires a browserUrl");
|
|
67
58
|
}
|
|
68
|
-
const explicitAttach =
|
|
59
|
+
const explicitAttach = configuredBrowserUrl !== undefined;
|
|
69
60
|
if (explicitAttach && baseOptions.browserChannel !== undefined) {
|
|
70
61
|
throw new Error("CONFIG_ERROR: --browser-url cannot be combined with --browser-channel");
|
|
71
62
|
}
|
|
@@ -84,6 +75,9 @@ function buildRuntimeStartOptions(baseOptions, siteDefinition, controlMode, pref
|
|
|
84
75
|
}
|
|
85
76
|
return nextOptions;
|
|
86
77
|
}
|
|
78
|
+
function toLocalBridgeState(status) {
|
|
79
|
+
return status;
|
|
80
|
+
}
|
|
87
81
|
export async function startLocalMcpBridge(options) {
|
|
88
82
|
const siteDefinition = await resolveSiteDefinitionFromBridgeOptions(options);
|
|
89
83
|
const authPolicy = resolveAuthPolicy(siteDefinition.manifest);
|
|
@@ -91,625 +85,194 @@ export async function startLocalMcpBridge(options) {
|
|
|
91
85
|
if (!targetUrl) {
|
|
92
86
|
throw new Error("CONFIG_ERROR: no target url provided (missing --url and manifest.defaultUrl)");
|
|
93
87
|
}
|
|
88
|
+
const configuredBrowserUrl = options.browserUrl?.trim() || undefined;
|
|
89
|
+
const managedUserDataDir = configuredBrowserUrl === undefined
|
|
90
|
+
? (options.userDataDir ?? resolveDefaultUserDataDir(siteDefinition, targetUrl))
|
|
91
|
+
: undefined;
|
|
94
92
|
if (authPolicy.mode === "bootstrap_then_attach") {
|
|
95
|
-
assertAuthSensitiveBrowserSupport(options.browser,
|
|
93
|
+
assertAuthSensitiveBrowserSupport(options.browser, managedUserDataDir);
|
|
96
94
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
let controlMode = "none";
|
|
100
|
-
let ownership = "none";
|
|
101
|
-
let authState = "unknown";
|
|
102
|
-
let sessionState = authPolicy.mode === "bootstrap_then_attach" ? "profile_missing" : "runtime_active";
|
|
103
|
-
let browserUrl = options.browserUrl;
|
|
104
|
-
let browserPid;
|
|
105
|
-
const configuredPreferredPresentationMode = options.preferredPresentationMode;
|
|
106
|
-
let preferredPresentationMode = options.preferredPresentationMode ?? "headed";
|
|
107
|
-
let presentationMode = preferredPresentationMode;
|
|
108
|
-
let lastBackupPath;
|
|
109
|
-
let metadata;
|
|
95
|
+
const overlayStore = new OverlayStore(siteDefinition.id, managedUserDataDir);
|
|
96
|
+
await overlayStore.load();
|
|
110
97
|
let server;
|
|
111
|
-
let
|
|
112
|
-
let
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
? {
|
|
119
|
-
site: siteDefinition.id,
|
|
120
|
-
targetUrl,
|
|
121
|
-
authPolicy,
|
|
122
|
-
}
|
|
123
|
-
: undefined;
|
|
124
|
-
const refreshStatus = () => {
|
|
125
|
-
const state = {
|
|
126
|
-
site: siteDefinition.id,
|
|
127
|
-
targetUrl,
|
|
128
|
-
controlMode,
|
|
129
|
-
...(browserUrl !== undefined ? { browserUrl } : {}),
|
|
130
|
-
mode: runtimeMode,
|
|
131
|
-
presentationMode,
|
|
132
|
-
preferredPresentationMode,
|
|
133
|
-
authPolicyMode: authPolicy.mode,
|
|
134
|
-
authState,
|
|
135
|
-
sessionState,
|
|
136
|
-
ownership,
|
|
137
|
-
...(profilePath !== undefined ? { profilePath } : {}),
|
|
138
|
-
...(browserPid !== undefined ? { browserPid } : {}),
|
|
139
|
-
...(lastBackupPath !== undefined ? { lastBackupPath } : {}),
|
|
140
|
-
};
|
|
141
|
-
return state;
|
|
142
|
-
};
|
|
143
|
-
const syncFromMetadata = (nextMetadata) => {
|
|
144
|
-
metadata = nextMetadata;
|
|
145
|
-
controlMode = nextMetadata.controlMode;
|
|
146
|
-
browserUrl = nextMetadata.browserUrl;
|
|
147
|
-
browserPid = nextMetadata.browserPid;
|
|
148
|
-
authState = nextMetadata.authState;
|
|
149
|
-
sessionState = nextMetadata.sessionState;
|
|
150
|
-
ownership = nextMetadata.ownership;
|
|
151
|
-
presentationMode = nextMetadata.presentationMode;
|
|
152
|
-
preferredPresentationMode = configuredPreferredPresentationMode ?? nextMetadata.preferredPresentationMode;
|
|
153
|
-
if (nextMetadata.lastBackupPath !== undefined) {
|
|
154
|
-
lastBackupPath = nextMetadata.lastBackupPath;
|
|
155
|
-
}
|
|
156
|
-
if (runtime === undefined) {
|
|
157
|
-
runtimeMode = "control-only";
|
|
158
|
-
presentationMode = nextMetadata.controlMode === "bootstrap" ? "headed" : nextMetadata.presentationMode;
|
|
159
|
-
}
|
|
160
|
-
};
|
|
161
|
-
const writeMetadata = async (patch) => {
|
|
162
|
-
if (!profilePath || !metadataFallback) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const nextMetadata = await updateSessionMetadata(profilePath, metadataFallback, patch);
|
|
166
|
-
syncFromMetadata(nextMetadata);
|
|
167
|
-
};
|
|
168
|
-
const hasRunningBootstrapBrowser = async (sourceMetadata) => {
|
|
169
|
-
const activeMetadata = sourceMetadata ?? metadata;
|
|
170
|
-
if (!activeMetadata) {
|
|
171
|
-
return false;
|
|
172
|
-
}
|
|
173
|
-
if (authPolicy.mode !== "bootstrap_then_attach" ||
|
|
174
|
-
activeMetadata.controlMode !== "bootstrap" ||
|
|
175
|
-
activeMetadata.ownership !== "external") {
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
if (await isProcessRunning(activeMetadata.browserPid)) {
|
|
179
|
-
return true;
|
|
180
|
-
}
|
|
181
|
-
if (!profilePath) {
|
|
182
|
-
return false;
|
|
183
|
-
}
|
|
184
|
-
const discoveredPid = await findBrowserProcessForProfile(profilePath);
|
|
185
|
-
if (!discoveredPid) {
|
|
186
|
-
return false;
|
|
187
|
-
}
|
|
188
|
-
if (discoveredPid !== activeMetadata.browserPid) {
|
|
189
|
-
await writeMetadata({
|
|
190
|
-
browserPid: discoveredPid,
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
return true;
|
|
194
|
-
};
|
|
195
|
-
const bindRuntime = (nextRuntime, nextOwnership) => {
|
|
196
|
-
runtime = nextRuntime;
|
|
197
|
-
runtimeMode = nextRuntime.mode;
|
|
198
|
-
controlMode = nextRuntime.controlMode;
|
|
199
|
-
presentationMode = nextRuntime.presentationMode;
|
|
200
|
-
ownership = nextOwnership;
|
|
201
|
-
unsubscribeRuntimeResourceUpdates?.();
|
|
202
|
-
unsubscribeRuntimeResourceUpdates = nextRuntime.gateway.onResourceUpdated((uri) => {
|
|
203
|
-
for (const listener of resourceUpdatedListeners) {
|
|
204
|
-
listener(uri);
|
|
205
|
-
}
|
|
206
|
-
});
|
|
207
|
-
ownerSessionGeneration += 1;
|
|
208
|
-
const generation = ownerSessionGeneration;
|
|
209
|
-
void nextRuntime.ownerSessionEnded.then(() => {
|
|
210
|
-
if (closed || generation !== ownerSessionGeneration) {
|
|
211
|
-
return;
|
|
98
|
+
let closeRequested = false;
|
|
99
|
+
let closeResources = async () => { };
|
|
100
|
+
const toolsetListeners = new Set();
|
|
101
|
+
const notifyToolsetMayHaveChanged = () => {
|
|
102
|
+
for (const listener of toolsetListeners) {
|
|
103
|
+
try {
|
|
104
|
+
listener();
|
|
212
105
|
}
|
|
213
|
-
|
|
106
|
+
catch (error) {
|
|
214
107
|
options.onError?.(error);
|
|
215
|
-
}
|
|
216
|
-
});
|
|
217
|
-
};
|
|
218
|
-
const clearRuntime = () => {
|
|
219
|
-
runtime = undefined;
|
|
220
|
-
runtimeMode = "control-only";
|
|
221
|
-
unsubscribeRuntimeResourceUpdates?.();
|
|
222
|
-
unsubscribeRuntimeResourceUpdates = undefined;
|
|
223
|
-
};
|
|
224
|
-
const closeRuntime = async () => {
|
|
225
|
-
if (!runtime) {
|
|
226
|
-
return;
|
|
108
|
+
}
|
|
227
109
|
}
|
|
228
|
-
const activeRuntime = runtime;
|
|
229
|
-
clearRuntime();
|
|
230
|
-
await activeRuntime.close();
|
|
231
110
|
};
|
|
232
|
-
const
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
try {
|
|
237
|
-
const result = await activeRuntime.gateway.callTool(authPolicy.authProbeTool, {});
|
|
238
|
-
return readAuthState(result);
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
return "unknown";
|
|
111
|
+
const requestClose = () => {
|
|
112
|
+
closeRequested = true;
|
|
113
|
+
if (server === undefined) {
|
|
114
|
+
return;
|
|
242
115
|
}
|
|
116
|
+
void closeResources().catch((error) => {
|
|
117
|
+
options.onError?.(error);
|
|
118
|
+
});
|
|
243
119
|
};
|
|
244
|
-
const
|
|
120
|
+
const controller = await startBrowserSessionController({
|
|
121
|
+
site: siteDefinition.id,
|
|
122
|
+
targetUrl,
|
|
123
|
+
authPolicy,
|
|
124
|
+
...(managedUserDataDir !== undefined ? { profilePath: managedUserDataDir } : {}),
|
|
125
|
+
...(options.browserChannel !== undefined ? { browserChannel: options.browserChannel } : {}),
|
|
126
|
+
...(options.browserUrl !== undefined ? { browserUrl: options.browserUrl } : {}),
|
|
127
|
+
...(options.preferredPresentationMode !== undefined
|
|
128
|
+
? { preferredPresentationMode: options.preferredPresentationMode }
|
|
129
|
+
: {}),
|
|
130
|
+
runtimeFactory: async ({ controlMode, preferredPresentationMode, browserUrl }) => await startRuntime(buildRuntimeStartOptions(options, siteDefinition, managedUserDataDir, controlMode, preferredPresentationMode, browserUrl)),
|
|
131
|
+
browserUrlHealthCheck: async (browserUrl) => {
|
|
132
|
+
await resolveCdpConnectUrl(browserUrl);
|
|
133
|
+
},
|
|
134
|
+
onCloseRequested: requestClose,
|
|
135
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
136
|
+
});
|
|
137
|
+
const unsubscribeControllerToolset = controller.onToolsetMayHaveChanged(() => {
|
|
138
|
+
notifyToolsetMayHaveChanged();
|
|
139
|
+
});
|
|
140
|
+
let closed = false;
|
|
141
|
+
let lastState = toLocalBridgeState(controller.getState());
|
|
142
|
+
closeResources = async () => {
|
|
245
143
|
if (closed) {
|
|
246
144
|
return;
|
|
247
145
|
}
|
|
248
146
|
closed = true;
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const
|
|
252
|
-
|
|
253
|
-
const results = await Promise.allSettled([
|
|
254
|
-
server?.close(),
|
|
255
|
-
activeRuntime?.close(),
|
|
256
|
-
activeMetadata?.ownership === "managed" ? stopManagedBrowser(activeMetadata) : undefined,
|
|
257
|
-
]);
|
|
147
|
+
lastState = toLocalBridgeState(controller.getState());
|
|
148
|
+
unsubscribeControllerToolset();
|
|
149
|
+
const activeServer = server;
|
|
150
|
+
server = undefined;
|
|
151
|
+
const results = await Promise.allSettled([activeServer?.close(), controller.close()]);
|
|
258
152
|
const firstFailure = results.find((result) => result.status === "rejected");
|
|
259
153
|
if (firstFailure) {
|
|
260
154
|
throw firstFailure.reason;
|
|
261
155
|
}
|
|
262
156
|
};
|
|
263
|
-
const runLifecycleTransition = async (operation) => {
|
|
264
|
-
const previousTransition = lifecycleTransition;
|
|
265
|
-
let releaseTransition;
|
|
266
|
-
lifecycleTransition = new Promise((resolve) => {
|
|
267
|
-
releaseTransition = resolve;
|
|
268
|
-
});
|
|
269
|
-
await previousTransition.catch(() => {
|
|
270
|
-
// Ignore previous transition failures so later lifecycle operations can still proceed.
|
|
271
|
-
});
|
|
272
|
-
try {
|
|
273
|
-
return await operation();
|
|
274
|
-
}
|
|
275
|
-
finally {
|
|
276
|
-
releaseTransition();
|
|
277
|
-
}
|
|
278
|
-
};
|
|
279
|
-
const closeResources = async () => {
|
|
280
|
-
await runLifecycleTransition(async () => {
|
|
281
|
-
await closeResourcesInternal();
|
|
282
|
-
});
|
|
283
|
-
};
|
|
284
|
-
const bootstrapSessionInternal = async (nextAuthState = "unknown") => {
|
|
285
|
-
if (authPolicy.mode !== "bootstrap_then_attach" || !profilePath) {
|
|
286
|
-
throw new Error("UNSUPPORTED_SESSION_CONTROL: bootstrap is available only for auth-sensitive managed sessions");
|
|
287
|
-
}
|
|
288
|
-
await closeRuntime();
|
|
289
|
-
if (await hasRunningBootstrapBrowser()) {
|
|
290
|
-
return refreshStatus();
|
|
291
|
-
}
|
|
292
|
-
if (metadata?.ownership === "managed") {
|
|
293
|
-
await stopManagedBrowser(metadata);
|
|
294
|
-
}
|
|
295
|
-
await ensureManagedProfile(profilePath);
|
|
296
|
-
const bootstrapOptions = {
|
|
297
|
-
targetUrl,
|
|
298
|
-
userDataDir: profilePath,
|
|
299
|
-
};
|
|
300
|
-
if (options.browserChannel !== undefined) {
|
|
301
|
-
bootstrapOptions.browserChannel = options.browserChannel;
|
|
302
|
-
}
|
|
303
|
-
const launchResult = await launchBootstrapBrowser(bootstrapOptions);
|
|
304
|
-
const bootstrapPatch = {
|
|
305
|
-
presentationMode: "headed",
|
|
306
|
-
preferredPresentationMode,
|
|
307
|
-
sessionState: nextAuthState === "unknown" ? "bootstrap_active" : describeSessionStateFromAuth(nextAuthState),
|
|
308
|
-
authState: nextAuthState,
|
|
309
|
-
controlMode: "bootstrap",
|
|
310
|
-
ownership: "external",
|
|
311
|
-
browserUrl: null,
|
|
312
|
-
browserPid: null,
|
|
313
|
-
};
|
|
314
|
-
if (launchResult.pid !== undefined) {
|
|
315
|
-
bootstrapPatch.browserPid = launchResult.pid;
|
|
316
|
-
}
|
|
317
|
-
await writeMetadata(bootstrapPatch);
|
|
318
|
-
return refreshStatus();
|
|
319
|
-
};
|
|
320
|
-
const adoptManagedAttachBrowserAsBootstrap = async (nextAuthState, nextBrowserPid) => {
|
|
321
|
-
await closeRuntime();
|
|
322
|
-
const bootstrapPatch = {
|
|
323
|
-
presentationMode: "headed",
|
|
324
|
-
preferredPresentationMode: "headed",
|
|
325
|
-
sessionState: describeSessionStateFromAuth(nextAuthState),
|
|
326
|
-
authState: nextAuthState,
|
|
327
|
-
controlMode: "bootstrap",
|
|
328
|
-
ownership: "external",
|
|
329
|
-
browserUrl: null,
|
|
330
|
-
browserPid: null,
|
|
331
|
-
};
|
|
332
|
-
if (nextBrowserPid !== undefined) {
|
|
333
|
-
bootstrapPatch.browserPid = nextBrowserPid;
|
|
334
|
-
}
|
|
335
|
-
preferredPresentationMode = "headed";
|
|
336
|
-
await writeMetadata(bootstrapPatch);
|
|
337
|
-
return refreshStatus();
|
|
338
|
-
};
|
|
339
|
-
const activateRuntime = async (nextRuntime, nextOwnership, nextBrowserUrl, nextBrowserPid) => {
|
|
340
|
-
const nextAuthState = await probeRuntimeAuthState(nextRuntime);
|
|
341
|
-
bindRuntime(nextRuntime, nextOwnership);
|
|
342
|
-
browserUrl = nextBrowserUrl;
|
|
343
|
-
browserPid = nextBrowserPid;
|
|
344
|
-
authState = nextAuthState;
|
|
345
|
-
sessionState = "runtime_active";
|
|
346
|
-
presentationMode = nextRuntime.presentationMode;
|
|
347
|
-
if (profilePath && metadataFallback) {
|
|
348
|
-
const runtimePatch = {
|
|
349
|
-
presentationMode: nextRuntime.presentationMode,
|
|
350
|
-
preferredPresentationMode,
|
|
351
|
-
sessionState: "runtime_active",
|
|
352
|
-
authState: nextAuthState,
|
|
353
|
-
controlMode: nextRuntime.controlMode,
|
|
354
|
-
ownership: nextOwnership,
|
|
355
|
-
};
|
|
356
|
-
if (nextBrowserUrl !== undefined) {
|
|
357
|
-
runtimePatch.browserUrl = nextBrowserUrl;
|
|
358
|
-
}
|
|
359
|
-
else {
|
|
360
|
-
runtimePatch.browserUrl = null;
|
|
361
|
-
}
|
|
362
|
-
if (nextBrowserPid !== undefined) {
|
|
363
|
-
runtimePatch.browserPid = nextBrowserPid;
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
runtimePatch.browserPid = null;
|
|
367
|
-
}
|
|
368
|
-
await writeMetadata(runtimePatch);
|
|
369
|
-
}
|
|
370
|
-
return refreshStatus();
|
|
371
|
-
};
|
|
372
|
-
const attachSessionInternal = async (requestedBrowserUrl, requestedPresentationMode = preferredPresentationMode) => {
|
|
373
|
-
const explicitBrowserUrl = requestedBrowserUrl?.trim() || options.browserUrl?.trim();
|
|
374
|
-
const relaunchManagedAttachBrowser = !explicitBrowserUrl &&
|
|
375
|
-
controlMode === "attach" &&
|
|
376
|
-
ownership === "managed" &&
|
|
377
|
-
requestedPresentationMode !== presentationMode;
|
|
378
|
-
const activeBrowserUrl = explicitBrowserUrl || (relaunchManagedAttachBrowser ? undefined : browserUrl);
|
|
379
|
-
const nextOwnership = explicitBrowserUrl ? "external" : "managed";
|
|
380
|
-
if (runtime) {
|
|
381
|
-
await closeRuntime();
|
|
382
|
-
}
|
|
383
|
-
if (relaunchManagedAttachBrowser) {
|
|
384
|
-
const managedBrowserPid = browserPid ?? (profilePath ? await findBrowserProcessForProfile(profilePath) : undefined);
|
|
385
|
-
if (profilePath && metadataFallback) {
|
|
386
|
-
await updateSessionMetadata(profilePath, metadataFallback, {
|
|
387
|
-
controlMode: "none",
|
|
388
|
-
ownership: "none",
|
|
389
|
-
browserUrl: null,
|
|
390
|
-
browserPid: null,
|
|
391
|
-
});
|
|
392
|
-
}
|
|
393
|
-
await stopBrowserProcess(managedBrowserPid);
|
|
394
|
-
if (managedBrowserPid) {
|
|
395
|
-
const didExit = await waitForProcessExit(managedBrowserPid, BOOTSTRAP_BROWSER_CLOSE_TIMEOUT_MS);
|
|
396
|
-
if (!didExit) {
|
|
397
|
-
throw new Error(`BROWSER_CLOSE_TIMEOUT: timed out waiting for managed browser ${String(managedBrowserPid)} to exit`);
|
|
398
|
-
}
|
|
399
|
-
await new Promise((resolve) => setTimeout(resolve, BOOTSTRAP_PROFILE_RELEASE_DELAY_MS));
|
|
400
|
-
}
|
|
401
|
-
browserUrl = undefined;
|
|
402
|
-
browserPid = undefined;
|
|
403
|
-
}
|
|
404
|
-
let managedAttachPid;
|
|
405
|
-
let attachBrowserUrl = activeBrowserUrl;
|
|
406
|
-
if (!explicitBrowserUrl && attachBrowserUrl && ownership === "managed") {
|
|
407
|
-
const managedBrowserPid = browserPid ?? (profilePath ? await findBrowserProcessForProfile(profilePath) : undefined);
|
|
408
|
-
const managedBrowserRunning = await isProcessRunning(managedBrowserPid);
|
|
409
|
-
let managedBrowserUrlHealthy = managedBrowserRunning;
|
|
410
|
-
if (managedBrowserRunning) {
|
|
411
|
-
try {
|
|
412
|
-
await resolveCdpConnectUrl(attachBrowserUrl);
|
|
413
|
-
}
|
|
414
|
-
catch {
|
|
415
|
-
managedBrowserUrlHealthy = false;
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
if (!managedBrowserRunning || !managedBrowserUrlHealthy) {
|
|
419
|
-
if (managedBrowserRunning) {
|
|
420
|
-
await stopBrowserProcess(managedBrowserPid);
|
|
421
|
-
if (managedBrowserPid) {
|
|
422
|
-
const didExit = await waitForProcessExit(managedBrowserPid, BOOTSTRAP_BROWSER_CLOSE_TIMEOUT_MS);
|
|
423
|
-
if (!didExit) {
|
|
424
|
-
throw new Error(`BROWSER_CLOSE_TIMEOUT: timed out waiting for stale managed browser ${String(managedBrowserPid)} to exit`);
|
|
425
|
-
}
|
|
426
|
-
await new Promise((resolve) => setTimeout(resolve, BOOTSTRAP_PROFILE_RELEASE_DELAY_MS));
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
if (profilePath && metadataFallback) {
|
|
430
|
-
await updateSessionMetadata(profilePath, metadataFallback, {
|
|
431
|
-
controlMode: "none",
|
|
432
|
-
ownership: "none",
|
|
433
|
-
browserUrl: null,
|
|
434
|
-
browserPid: null,
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
attachBrowserUrl = undefined;
|
|
438
|
-
browserUrl = undefined;
|
|
439
|
-
browserPid = undefined;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
if (!attachBrowserUrl) {
|
|
443
|
-
if (authPolicy.mode !== "bootstrap_then_attach" || !profilePath) {
|
|
444
|
-
throw new Error("CONFIG_ERROR: bridge.session.attach requires browserUrl when no managed attach session exists");
|
|
445
|
-
}
|
|
446
|
-
if (!explicitBrowserUrl && (await hasRunningBootstrapBrowser())) {
|
|
447
|
-
const bootstrapPid = metadata?.browserPid ?? (profilePath ? await findBrowserProcessForProfile(profilePath) : undefined);
|
|
448
|
-
await stopBrowserProcess(bootstrapPid);
|
|
449
|
-
const didExit = await waitForProcessExit(bootstrapPid, BOOTSTRAP_BROWSER_CLOSE_TIMEOUT_MS);
|
|
450
|
-
if (!didExit) {
|
|
451
|
-
throw new Error(`BOOTSTRAP_BROWSER_CLOSE_TIMEOUT: timed out waiting for bootstrap browser ${String(bootstrapPid)} to exit`);
|
|
452
|
-
}
|
|
453
|
-
await writeMetadata({
|
|
454
|
-
controlMode: "none",
|
|
455
|
-
ownership: "none",
|
|
456
|
-
browserUrl: null,
|
|
457
|
-
browserPid: null,
|
|
458
|
-
});
|
|
459
|
-
await new Promise((resolve) => setTimeout(resolve, BOOTSTRAP_PROFILE_RELEASE_DELAY_MS));
|
|
460
|
-
}
|
|
461
|
-
const existingProfileBrowserPid = await findBrowserProcessForProfile(profilePath);
|
|
462
|
-
if (existingProfileBrowserPid) {
|
|
463
|
-
await stopBrowserProcess(existingProfileBrowserPid);
|
|
464
|
-
const didExit = await waitForProcessExit(existingProfileBrowserPid, BOOTSTRAP_BROWSER_CLOSE_TIMEOUT_MS);
|
|
465
|
-
if (!didExit) {
|
|
466
|
-
throw new Error(`BROWSER_CLOSE_TIMEOUT: timed out waiting for existing browser ${String(existingProfileBrowserPid)} to exit`);
|
|
467
|
-
}
|
|
468
|
-
await new Promise((resolve) => setTimeout(resolve, BOOTSTRAP_PROFILE_RELEASE_DELAY_MS));
|
|
469
|
-
}
|
|
470
|
-
await ensureManagedProfile(profilePath);
|
|
471
|
-
const attachOptions = {
|
|
472
|
-
targetUrl,
|
|
473
|
-
userDataDir: profilePath,
|
|
474
|
-
presentationMode: requestedPresentationMode,
|
|
475
|
-
};
|
|
476
|
-
if (options.browserChannel !== undefined) {
|
|
477
|
-
attachOptions.browserChannel = options.browserChannel;
|
|
478
|
-
}
|
|
479
|
-
const managedAttach = await launchManagedAttachBrowser(attachOptions);
|
|
480
|
-
attachBrowserUrl = managedAttach.browserUrl;
|
|
481
|
-
managedAttachPid = managedAttach.pid;
|
|
482
|
-
}
|
|
483
|
-
try {
|
|
484
|
-
const nextRuntime = await startRuntime(buildRuntimeStartOptions(options, siteDefinition, "attach", requestedPresentationMode, attachBrowserUrl));
|
|
485
|
-
const nextAuthState = await probeRuntimeAuthState(nextRuntime);
|
|
486
|
-
if (authPolicy.mode === "bootstrap_then_attach" &&
|
|
487
|
-
!explicitBrowserUrl &&
|
|
488
|
-
(nextAuthState === "auth_required" || nextAuthState === "challenge_required")) {
|
|
489
|
-
if (managedAttachPid && requestedPresentationMode === "headed") {
|
|
490
|
-
bindRuntime(nextRuntime, "managed");
|
|
491
|
-
return await adoptManagedAttachBrowserAsBootstrap(nextAuthState, managedAttachPid);
|
|
492
|
-
}
|
|
493
|
-
await nextRuntime.close();
|
|
494
|
-
return await bootstrapSessionInternal(nextAuthState);
|
|
495
|
-
}
|
|
496
|
-
preferredPresentationMode = requestedPresentationMode;
|
|
497
|
-
return await activateRuntime(nextRuntime, nextOwnership, attachBrowserUrl, managedAttachPid ?? browserPid);
|
|
498
|
-
}
|
|
499
|
-
catch (error) {
|
|
500
|
-
if (managedAttachPid && profilePath && metadataFallback) {
|
|
501
|
-
const cleanupMetadata = await updateSessionMetadata(profilePath, metadataFallback, {
|
|
502
|
-
controlMode: "none",
|
|
503
|
-
ownership: "none",
|
|
504
|
-
browserUrl: null,
|
|
505
|
-
browserPid: null,
|
|
506
|
-
});
|
|
507
|
-
await stopManagedBrowser({
|
|
508
|
-
...cleanupMetadata,
|
|
509
|
-
ownership: "managed",
|
|
510
|
-
browserPid: managedAttachPid,
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
await closeResourcesInternal().catch(options.onError);
|
|
514
|
-
throw error;
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
const setPresentationModeInternal = async (setModeOptions) => {
|
|
518
|
-
if (closed) {
|
|
519
|
-
throw new Error("SESSION_NOT_AVAILABLE: local-mcp bridge session is closed");
|
|
520
|
-
}
|
|
521
|
-
const requestedPresentationMode = setModeOptions.presentationMode;
|
|
522
|
-
const previousRuntime = runtime;
|
|
523
|
-
const previousState = refreshStatus();
|
|
524
|
-
const previousPreferredPresentationMode = preferredPresentationMode;
|
|
525
|
-
if (controlMode === "bootstrap") {
|
|
526
|
-
throw new Error("UNSUPPORTED_SESSION_CONTROL: bridge.session.mode.set is unavailable while the bridge is in bootstrap mode");
|
|
527
|
-
}
|
|
528
|
-
if (ownership === "external") {
|
|
529
|
-
throw new Error("UNSUPPORTED_SESSION_CONTROL: bridge.session.mode.set is unavailable for external attach sessions");
|
|
530
|
-
}
|
|
531
|
-
if (requestedPresentationMode === presentationMode) {
|
|
532
|
-
preferredPresentationMode = requestedPresentationMode;
|
|
533
|
-
if (profilePath && metadataFallback) {
|
|
534
|
-
await writeMetadata({
|
|
535
|
-
presentationMode,
|
|
536
|
-
preferredPresentationMode,
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
return refreshStatus();
|
|
540
|
-
}
|
|
541
|
-
if (controlMode === "attach") {
|
|
542
|
-
preferredPresentationMode = requestedPresentationMode;
|
|
543
|
-
return await attachSessionInternal(undefined, requestedPresentationMode);
|
|
544
|
-
}
|
|
545
|
-
await closeRuntime();
|
|
546
|
-
preferredPresentationMode = requestedPresentationMode;
|
|
547
|
-
try {
|
|
548
|
-
const nextRuntime = await startRuntime(buildRuntimeStartOptions(options, siteDefinition, "launch", requestedPresentationMode));
|
|
549
|
-
return await activateRuntime(nextRuntime, "managed");
|
|
550
|
-
}
|
|
551
|
-
catch (error) {
|
|
552
|
-
preferredPresentationMode = previousPreferredPresentationMode;
|
|
553
|
-
if (previousRuntime) {
|
|
554
|
-
try {
|
|
555
|
-
const recoveredRuntime = await startRuntime(buildRuntimeStartOptions(options, siteDefinition, "launch", previousState.presentationMode));
|
|
556
|
-
await activateRuntime(recoveredRuntime, previousState.ownership);
|
|
557
|
-
}
|
|
558
|
-
catch (recoveryError) {
|
|
559
|
-
options.onError?.(recoveryError);
|
|
560
|
-
await closeResourcesInternal().catch(options.onError);
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
await closeResourcesInternal().catch(options.onError);
|
|
565
|
-
}
|
|
566
|
-
throw error;
|
|
567
|
-
}
|
|
568
|
-
};
|
|
569
|
-
const setPresentationMode = async (setModeOptions) => {
|
|
570
|
-
return await runLifecycleTransition(async () => await setPresentationModeInternal(setModeOptions));
|
|
571
|
-
};
|
|
572
|
-
const resetProfileInternal = async () => {
|
|
573
|
-
if (!profilePath || !metadataFallback) {
|
|
574
|
-
throw new Error("UNSUPPORTED_SESSION_CONTROL: reset_profile requires a managed --user-data-dir");
|
|
575
|
-
}
|
|
576
|
-
await closeRuntime();
|
|
577
|
-
if (metadata?.ownership === "managed") {
|
|
578
|
-
await stopManagedBrowser(metadata);
|
|
579
|
-
}
|
|
580
|
-
const resetResult = await backupAndResetProfile(profilePath, metadataFallback);
|
|
581
|
-
syncFromMetadata(resetResult.metadata);
|
|
582
|
-
lastBackupPath = resetResult.backupPath;
|
|
583
|
-
if (authPolicy.mode === "bootstrap_then_attach") {
|
|
584
|
-
return await bootstrapSessionInternal();
|
|
585
|
-
}
|
|
586
|
-
return refreshStatus();
|
|
587
|
-
};
|
|
588
|
-
const initializeControlPlane = async () => {
|
|
589
|
-
if (authPolicy.mode !== "bootstrap_then_attach") {
|
|
590
|
-
const nextControlMode = options.browserUrl ? "attach" : "launch";
|
|
591
|
-
const nextRuntime = await startRuntime(buildRuntimeStartOptions(options, siteDefinition, nextControlMode, preferredPresentationMode, options.browserUrl));
|
|
592
|
-
await activateRuntime(nextRuntime, nextControlMode === "attach" ? "external" : "managed", options.browserUrl);
|
|
593
|
-
return;
|
|
594
|
-
}
|
|
595
|
-
const managedProfilePath = profilePath;
|
|
596
|
-
metadata = await readSessionMetadata(managedProfilePath, metadataFallback);
|
|
597
|
-
syncFromMetadata(metadata);
|
|
598
|
-
if (options.browserUrl) {
|
|
599
|
-
await attachSessionInternal(options.browserUrl);
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
const hasRunningBootstrapExternal = await hasRunningBootstrapBrowser(metadata);
|
|
603
|
-
if (hasRunningBootstrapExternal) {
|
|
604
|
-
return;
|
|
605
|
-
}
|
|
606
|
-
const hasRunningManagedAttach = metadata.controlMode === "attach" &&
|
|
607
|
-
metadata.browserUrl !== undefined &&
|
|
608
|
-
metadata.ownership === "managed" &&
|
|
609
|
-
(await isProcessRunning(metadata.browserPid));
|
|
610
|
-
if (hasRunningManagedAttach) {
|
|
611
|
-
await attachSessionInternal();
|
|
612
|
-
return;
|
|
613
|
-
}
|
|
614
|
-
if (metadata.authState === "authenticated") {
|
|
615
|
-
await attachSessionInternal();
|
|
616
|
-
return;
|
|
617
|
-
}
|
|
618
|
-
if (metadata.sessionState !== "profile_missing") {
|
|
619
|
-
await attachSessionInternal();
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
await bootstrapSessionInternal(metadata.authState === "auth_required" || metadata.authState === "challenge_required"
|
|
623
|
-
? metadata.authState
|
|
624
|
-
: "unknown");
|
|
625
|
-
};
|
|
626
|
-
await initializeControlPlane();
|
|
627
157
|
const gateway = {
|
|
628
158
|
listTools: async () => {
|
|
159
|
+
const runtime = controller.getRuntime();
|
|
629
160
|
if (!runtime) {
|
|
630
161
|
return [];
|
|
631
162
|
}
|
|
632
|
-
|
|
163
|
+
const pageTools = await runtime.gateway.listTools();
|
|
164
|
+
return [...pageTools, ...overlayStore.listEnabledToolDefinitions()];
|
|
633
165
|
},
|
|
634
166
|
callTool: async (name, input) => {
|
|
167
|
+
const runtime = controller.getRuntime();
|
|
635
168
|
if (!runtime) {
|
|
636
169
|
throw new Error("SESSION_NOT_AVAILABLE: page tools are unavailable while local-mcp is waiting for bootstrap or attach");
|
|
637
170
|
}
|
|
638
|
-
|
|
171
|
+
const overlayTool = overlayStore.getOverlayTool(name);
|
|
172
|
+
if (overlayTool) {
|
|
173
|
+
return await evaluateOverlayTool(runtime.page, overlayTool.overlay, overlayTool.tool, input);
|
|
174
|
+
}
|
|
175
|
+
return (await runtime.gateway.callTool(name, input));
|
|
639
176
|
},
|
|
640
177
|
listResources: async () => {
|
|
178
|
+
const runtime = controller.getRuntime();
|
|
641
179
|
if (!runtime) {
|
|
642
180
|
return [];
|
|
643
181
|
}
|
|
644
182
|
return await runtime.gateway.listResources();
|
|
645
183
|
},
|
|
646
184
|
readResource: async (uri) => {
|
|
185
|
+
const runtime = controller.getRuntime();
|
|
647
186
|
if (!runtime) {
|
|
648
187
|
throw new Error(`RESOURCE_NOT_FOUND: ${uri}`);
|
|
649
188
|
}
|
|
650
189
|
return await runtime.gateway.readResource(uri);
|
|
651
190
|
},
|
|
652
|
-
onResourceUpdated: (listener) =>
|
|
653
|
-
resourceUpdatedListeners.add(listener);
|
|
654
|
-
return () => {
|
|
655
|
-
resourceUpdatedListeners.delete(listener);
|
|
656
|
-
};
|
|
657
|
-
},
|
|
191
|
+
onResourceUpdated: (listener) => controller.onResourceUpdated(listener),
|
|
658
192
|
};
|
|
659
193
|
try {
|
|
660
194
|
const serverOptions = {
|
|
661
195
|
gateway,
|
|
662
196
|
bridgeControl: {
|
|
663
|
-
getState:
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
return await runtime.openWindow();
|
|
667
|
-
}
|
|
668
|
-
if (authPolicy.mode === "bootstrap_then_attach") {
|
|
669
|
-
if (await hasRunningBootstrapBrowser()) {
|
|
670
|
-
await focusBrowserWindow(options.browserChannel).catch(() => {
|
|
671
|
-
// Focusing an external browser is best-effort; reuse still avoids duplicate windows.
|
|
672
|
-
});
|
|
673
|
-
return "focused";
|
|
674
|
-
}
|
|
675
|
-
await runLifecycleTransition(async () => {
|
|
676
|
-
await bootstrapSessionInternal(authState);
|
|
677
|
-
});
|
|
678
|
-
return "opened";
|
|
679
|
-
}
|
|
680
|
-
throw new Error("SESSION_NOT_AVAILABLE: current page is closed");
|
|
197
|
+
getState: () => {
|
|
198
|
+
lastState = toLocalBridgeState(controller.getState());
|
|
199
|
+
return lastState;
|
|
681
200
|
},
|
|
201
|
+
openWindow: async () => await controller.openWindow(),
|
|
682
202
|
bootstrapSession: async () => {
|
|
683
|
-
|
|
203
|
+
lastState = toLocalBridgeState(await controller.bootstrapSession());
|
|
204
|
+
return lastState;
|
|
684
205
|
},
|
|
685
206
|
attachSession: async (requestedBrowserUrl) => {
|
|
686
|
-
|
|
207
|
+
lastState = toLocalBridgeState(await controller.attachSession(requestedBrowserUrl));
|
|
208
|
+
return lastState;
|
|
209
|
+
},
|
|
210
|
+
debugEval: async (script, args) => {
|
|
211
|
+
const runtime = controller.getRuntime();
|
|
212
|
+
if (!runtime) {
|
|
213
|
+
throw new Error("SESSION_NOT_AVAILABLE: debug eval requires an active browser runtime");
|
|
214
|
+
}
|
|
215
|
+
return await evaluateDebugScript(runtime.page, script, args);
|
|
216
|
+
},
|
|
217
|
+
listOverlays: async () => overlayStore.list(),
|
|
218
|
+
installOverlay: async (installOptions) => {
|
|
219
|
+
const overlay = await overlayStore.install(installOptions);
|
|
220
|
+
notifyToolsetMayHaveChanged();
|
|
221
|
+
return overlay;
|
|
222
|
+
},
|
|
223
|
+
updateOverlay: async (updateOptions) => {
|
|
224
|
+
const overlay = await overlayStore.update(updateOptions);
|
|
225
|
+
notifyToolsetMayHaveChanged();
|
|
226
|
+
return overlay;
|
|
227
|
+
},
|
|
228
|
+
enableOverlay: async (id) => {
|
|
229
|
+
const overlay = await overlayStore.enable(id);
|
|
230
|
+
notifyToolsetMayHaveChanged();
|
|
231
|
+
return overlay;
|
|
232
|
+
},
|
|
233
|
+
disableOverlay: async (id) => {
|
|
234
|
+
const overlay = await overlayStore.disable(id);
|
|
235
|
+
notifyToolsetMayHaveChanged();
|
|
236
|
+
return overlay;
|
|
237
|
+
},
|
|
238
|
+
deleteOverlay: async (id) => {
|
|
239
|
+
await overlayStore.delete(id);
|
|
240
|
+
notifyToolsetMayHaveChanged();
|
|
241
|
+
},
|
|
242
|
+
getPresentationMode: () => controller.getPresentationMode(),
|
|
243
|
+
setPresentationMode: async (setModeOptions) => {
|
|
244
|
+
lastState = toLocalBridgeState(await controller.setPresentationMode(setModeOptions));
|
|
245
|
+
return lastState;
|
|
687
246
|
},
|
|
688
|
-
getPresentationMode: () => refreshStatus().presentationMode,
|
|
689
|
-
setPresentationMode: async (setModeOptions) => await setPresentationMode(setModeOptions),
|
|
690
247
|
resetProfile: async () => {
|
|
691
|
-
|
|
248
|
+
lastState = toLocalBridgeState(await controller.resetProfile());
|
|
249
|
+
await overlayStore.load();
|
|
250
|
+
notifyToolsetMayHaveChanged();
|
|
251
|
+
return lastState;
|
|
692
252
|
},
|
|
693
253
|
closeBridge: async () => {
|
|
694
254
|
await closeResources();
|
|
695
255
|
},
|
|
696
256
|
},
|
|
697
257
|
serviceVersion: options.serviceVersion,
|
|
258
|
+
onToolsetMayHaveChanged: (listener) => {
|
|
259
|
+
toolsetListeners.add(listener);
|
|
260
|
+
return () => {
|
|
261
|
+
toolsetListeners.delete(listener);
|
|
262
|
+
};
|
|
263
|
+
},
|
|
264
|
+
...(options.input !== undefined ? { input: options.input } : {}),
|
|
265
|
+
...(options.output !== undefined ? { output: options.output } : {}),
|
|
266
|
+
...(options.onError !== undefined ? { onError: options.onError } : {}),
|
|
698
267
|
};
|
|
699
|
-
if (options.input !== undefined) {
|
|
700
|
-
serverOptions.input = options.input;
|
|
701
|
-
}
|
|
702
|
-
if (options.output !== undefined) {
|
|
703
|
-
serverOptions.output = options.output;
|
|
704
|
-
}
|
|
705
|
-
if (options.onError !== undefined) {
|
|
706
|
-
serverOptions.onError = options.onError;
|
|
707
|
-
}
|
|
708
268
|
server = createLocalMcpStdioServer(serverOptions);
|
|
709
269
|
await server.start();
|
|
270
|
+
if (closeRequested) {
|
|
271
|
+
await closeResources();
|
|
272
|
+
}
|
|
710
273
|
}
|
|
711
274
|
catch (error) {
|
|
712
|
-
await
|
|
275
|
+
await closeResources().catch(options.onError);
|
|
713
276
|
throw error;
|
|
714
277
|
}
|
|
715
278
|
const input = options.input ?? process.stdin;
|
|
@@ -721,22 +284,24 @@ export async function startLocalMcpBridge(options) {
|
|
|
721
284
|
input.once("end", handleInputEnded);
|
|
722
285
|
return {
|
|
723
286
|
get site() {
|
|
724
|
-
return
|
|
287
|
+
return lastState.site;
|
|
725
288
|
},
|
|
726
289
|
get targetUrl() {
|
|
727
|
-
return targetUrl;
|
|
290
|
+
return lastState.targetUrl;
|
|
728
291
|
},
|
|
729
292
|
get controlMode() {
|
|
730
|
-
return controlMode;
|
|
293
|
+
return lastState.controlMode;
|
|
731
294
|
},
|
|
732
295
|
get mode() {
|
|
733
|
-
return
|
|
296
|
+
return lastState.mode;
|
|
734
297
|
},
|
|
735
298
|
get presentationMode() {
|
|
736
|
-
|
|
299
|
+
lastState = toLocalBridgeState(controller.getState());
|
|
300
|
+
return lastState.presentationMode;
|
|
737
301
|
},
|
|
738
302
|
get preferredPresentationMode() {
|
|
739
|
-
|
|
303
|
+
lastState = toLocalBridgeState(controller.getState());
|
|
304
|
+
return lastState.preferredPresentationMode;
|
|
740
305
|
},
|
|
741
306
|
close: async () => {
|
|
742
307
|
input.removeListener("end", handleInputEnded);
|