antechamber 1.0.26 → 1.0.28
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/CHANGELOG.md +14 -0
- package/dist/mcp.d.ts +8 -1
- package/dist/mcp.js +138 -35
- package/dist/server.js +2 -1
- package/extensions/omo-compat/package.json +1 -1
- package/extensions/omo-dag/package.json +1 -1
- package/extensions/omo-work/package.json +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project uses [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [1.0.28] - 2026-10-28
|
|
11
|
+
|
|
12
|
+
- fix(mcp): sign in to MCP servers without a running OmO session (#79)
|
|
13
|
+
|
|
14
|
+
## [1.0.27] - 2026-10-28
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- Sign in to an MCP server from OpenChamber without a running OmO session in that directory. The sign-in now runs in
|
|
19
|
+
an agent started for it alone, which stops once the sign-in completes, is cancelled or expires, and every running
|
|
20
|
+
session reconnects the server afterwards so it picks up the new token.
|
|
21
|
+
- Find OmO's builtin `/mcp` command when an extension such as `pi-mcp-adapter` also registers `mcp` and OmO renames
|
|
22
|
+
the builtin to `mcp:1`. MCP sign-in used to fail there with "Unknown command /mcp".
|
|
23
|
+
|
|
10
24
|
## [1.0.26] - 2026-10-28
|
|
11
25
|
|
|
12
26
|
### Fixed
|
package/dist/mcp.d.ts
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* Everything therefore targets the most recently used LIVE session in the directory; a
|
|
6
6
|
* dormant session is never revived just to answer an MCP request. Status reads for a
|
|
7
7
|
* directory without one fall back to the most recently used live session elsewhere.
|
|
8
|
-
* Authorization
|
|
8
|
+
* Authorization in a directory without one runs in an agent spawned for that flow alone
|
|
9
|
+
* (`--no-session`), disposed once the flow completes, fails, is cancelled or expires.
|
|
10
|
+
* OmO stores tokens globally, so a completed sign-in reconnects the server in every other
|
|
11
|
+
* live session.
|
|
9
12
|
*
|
|
10
13
|
* Status comes from the read-only `get_loaded_surfaces` RPC. Authorization drives OmO's
|
|
11
14
|
* documented `/mcp` subcommands (docs/mcp.md "Auth"):
|
|
@@ -17,6 +20,8 @@
|
|
|
17
20
|
* Commands are sent straight to the agent, outside the conversation queue: they neither
|
|
18
21
|
* wait behind a running turn nor create a user message, and the session is held active so
|
|
19
22
|
* the reaper cannot stop the process (and its listener or pending state) mid-flow.
|
|
23
|
+
* An extension that registers its own `mcp` command makes OmO rename the builtin (`mcp:1`),
|
|
24
|
+
* so the builtin is found by its source, not by name.
|
|
20
25
|
*/
|
|
21
26
|
import type { RouteServices } from "./route-services.js";
|
|
22
27
|
type OCMcpStatus = {
|
|
@@ -67,6 +72,8 @@ export declare function authorizationUrl(text: string, name: string): string | u
|
|
|
67
72
|
* authorization request's own `redirect_uri` and `state`.
|
|
68
73
|
*/
|
|
69
74
|
export declare function redirectInput(authorization: string, input: string): string | undefined;
|
|
75
|
+
/** Stops the agents spawned for sign-ins; live sessions are stopped by their own manager. */
|
|
76
|
+
export declare function disposeMcpAgents(): Promise<void>;
|
|
70
77
|
export declare function mcpRoute(services: RouteServices, method: string, path: string, directory: string, body: () => Promise<unknown>): Promise<RouteResult | undefined>;
|
|
71
78
|
export declare function mcpRouteV2(services: RouteServices, method: string, path: string, directory: string, body: () => Promise<unknown>): Promise<RouteResult | undefined>;
|
|
72
79
|
export {};
|
package/dist/mcp.js
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
* Everything therefore targets the most recently used LIVE session in the directory; a
|
|
6
6
|
* dormant session is never revived just to answer an MCP request. Status reads for a
|
|
7
7
|
* directory without one fall back to the most recently used live session elsewhere.
|
|
8
|
-
* Authorization
|
|
8
|
+
* Authorization in a directory without one runs in an agent spawned for that flow alone
|
|
9
|
+
* (`--no-session`), disposed once the flow completes, fails, is cancelled or expires.
|
|
10
|
+
* OmO stores tokens globally, so a completed sign-in reconnects the server in every other
|
|
11
|
+
* live session.
|
|
9
12
|
*
|
|
10
13
|
* Status comes from the read-only `get_loaded_surfaces` RPC. Authorization drives OmO's
|
|
11
14
|
* documented `/mcp` subcommands (docs/mcp.md "Auth"):
|
|
@@ -17,9 +20,12 @@
|
|
|
17
20
|
* Commands are sent straight to the agent, outside the conversation queue: they neither
|
|
18
21
|
* wait behind a running turn nor create a user message, and the session is held active so
|
|
19
22
|
* the reaper cannot stop the process (and its listener or pending state) mid-flow.
|
|
23
|
+
* An extension that registers its own `mcp` command makes OmO rename the builtin (`mcp:1`),
|
|
24
|
+
* so the builtin is found by its source, not by name.
|
|
20
25
|
*/
|
|
21
26
|
import { randomUUID } from "node:crypto";
|
|
22
27
|
import { errorBody } from "./http.js";
|
|
28
|
+
import { PiRpcClient } from "./pi-rpc.js";
|
|
23
29
|
/** Bounds how long an HTTP caller waits on a read-only RPC; the transport keeps its own budget. */
|
|
24
30
|
const MCP_READ_WAIT_MS = 5_000;
|
|
25
31
|
/** OmO announces the authorization URL before it waits for the browser. */
|
|
@@ -32,6 +38,7 @@ const MCP_AUTH_ATTEMPT_TTL_MS = 10 * 60_000;
|
|
|
32
38
|
const SERVER_NAME = /^[^\s"'\\]+$/;
|
|
33
39
|
const INTEGRATION_PREFIX = "mcp_";
|
|
34
40
|
const OAUTH_METHOD_ID = "oauth";
|
|
41
|
+
const BUILTIN_MCP_PATH = "<builtin:mcp>";
|
|
35
42
|
const flows = new Map();
|
|
36
43
|
const admissions = new Map();
|
|
37
44
|
function isRecord(value) {
|
|
@@ -95,11 +102,15 @@ async function directoryServers(sessions, directory) {
|
|
|
95
102
|
const live = liveSession(sessions, directory) ?? (fallback === undefined ? undefined : { pi: fallback });
|
|
96
103
|
return live === undefined ? [] : loadedServers(live.pi).catch(() => []);
|
|
97
104
|
}
|
|
98
|
-
|
|
105
|
+
/** The builtin MCP command's name in this agent; builds without `sourceInfo` only know it as `mcp`. */
|
|
106
|
+
async function mcpCommandName(pi) {
|
|
99
107
|
const response = await pi.probe({ type: "get_commands" }, undefined, AbortSignal.timeout(MCP_READ_WAIT_MS));
|
|
100
108
|
if (!response.success || !isRecord(response.data) || !Array.isArray(response.data.commands))
|
|
101
|
-
return
|
|
102
|
-
|
|
109
|
+
return undefined;
|
|
110
|
+
const commands = response.data.commands.flatMap((command) => isRecord(command) && command.source === "extension" && typeof command.name === "string"
|
|
111
|
+
? [{ name: command.name, builtin: isRecord(command.sourceInfo) && command.sourceInfo.path === BUILTIN_MCP_PATH }]
|
|
112
|
+
: []);
|
|
113
|
+
return (commands.find((command) => command.builtin) ?? commands.find((command) => command.name === "mcp"))?.name;
|
|
103
114
|
}
|
|
104
115
|
/** The URL OmO prints on its own line after naming the server (`auth` and `auth-start` both do). */
|
|
105
116
|
export function authorizationUrl(text, name) {
|
|
@@ -151,7 +162,7 @@ function sameCallback(target, authorization) {
|
|
|
151
162
|
const expected = new URL(announced);
|
|
152
163
|
return target.origin === expected.origin && target.pathname === expected.pathname;
|
|
153
164
|
}
|
|
154
|
-
function runMcpCommand(sessions, s, pi, name, args, observe = {}) {
|
|
165
|
+
function runMcpCommand(sessions, s, pi, name, commandName, args, observe = {}) {
|
|
155
166
|
const errors = [];
|
|
156
167
|
const onEvent = (event) => {
|
|
157
168
|
const entry = event.entry;
|
|
@@ -174,8 +185,8 @@ function runMcpCommand(sessions, s, pi, name, args, observe = {}) {
|
|
|
174
185
|
};
|
|
175
186
|
pi.onEvent(onEvent);
|
|
176
187
|
pi.onExtensionUI(onNotify);
|
|
177
|
-
|
|
178
|
-
|
|
188
|
+
const send = () => pi.command({ type: "prompt", message: `/${commandName} ${args}`, expandPromptTemplates: true }, MCP_AUTH_COMMAND_TIMEOUT_MS);
|
|
189
|
+
return (s === undefined ? send() : sessions.lifecycle.use(s, send))
|
|
179
190
|
.then((response) => {
|
|
180
191
|
if (!response.success)
|
|
181
192
|
throw new Error(response.error ?? "OmO rejected the /mcp command");
|
|
@@ -193,11 +204,57 @@ function runMcpCommand(sessions, s, pi, name, args, observe = {}) {
|
|
|
193
204
|
function announce(services, directory, name) {
|
|
194
205
|
services.broadcast(directory, { type: "mcp.tools.changed", properties: { server: name } });
|
|
195
206
|
}
|
|
207
|
+
/** Other live sessions keep their own connection, which still lacks the new token until it reconnects. */
|
|
208
|
+
function authorized(services, directory, name, flow) {
|
|
209
|
+
announce(services, directory, name);
|
|
210
|
+
const reconnects = services.sessions.liveSessions().flatMap((s) => {
|
|
211
|
+
const pi = s.pi;
|
|
212
|
+
if (pi === undefined || pi === flow.pi)
|
|
213
|
+
return [];
|
|
214
|
+
return [
|
|
215
|
+
mcpCommandName(pi)
|
|
216
|
+
.then((commandName) => commandName === undefined
|
|
217
|
+
? undefined
|
|
218
|
+
: runMcpCommand(services.sessions, s, pi, name, commandName, `reconnect ${name}`))
|
|
219
|
+
.catch((error) => console.error(`[mcp] reconnecting ${name} in session ${s.info.id} failed: ${errorMessage(error)}`)),
|
|
220
|
+
];
|
|
221
|
+
});
|
|
222
|
+
if (reconnects.length > 0)
|
|
223
|
+
void Promise.all(reconnects).then(() => announce(services, directory, name));
|
|
224
|
+
}
|
|
225
|
+
function agentCurrent(flow) {
|
|
226
|
+
return flow.s === undefined ? !flow.released : flow.s.pi === flow.pi;
|
|
227
|
+
}
|
|
228
|
+
function release(flow) {
|
|
229
|
+
if (flow.s !== undefined)
|
|
230
|
+
return;
|
|
231
|
+
flow.released = true;
|
|
232
|
+
clearTimeout(flow.expiry);
|
|
233
|
+
void flow.pi
|
|
234
|
+
.dispose()
|
|
235
|
+
.catch((error) => console.error(`[mcp] stopping an auth agent failed: ${errorMessage(error)}`));
|
|
236
|
+
}
|
|
237
|
+
/** Stops the agents spawned for sign-ins; live sessions are stopped by their own manager. */
|
|
238
|
+
export async function disposeMcpAgents() {
|
|
239
|
+
const dedicated = [...flows.values()].filter((flow) => flow.s === undefined && !flow.released);
|
|
240
|
+
for (const flow of dedicated) {
|
|
241
|
+
flow.released = true;
|
|
242
|
+
clearTimeout(flow.expiry);
|
|
243
|
+
}
|
|
244
|
+
await Promise.all(dedicated.map((flow) => flow.pi.dispose()));
|
|
245
|
+
}
|
|
246
|
+
async function flowStatus(flow, name) {
|
|
247
|
+
if (flow.server !== undefined || !agentCurrent(flow))
|
|
248
|
+
return flow.server;
|
|
249
|
+
return currentStatus(flow.pi, name);
|
|
250
|
+
}
|
|
196
251
|
function pruneFlows() {
|
|
197
252
|
const now = Date.now();
|
|
198
253
|
for (const [key, flow] of flows)
|
|
199
|
-
if (!flow.running && (flow
|
|
254
|
+
if (!flow.running && (!agentCurrent(flow) || now - flow.created > MCP_AUTH_ATTEMPT_TTL_MS)) {
|
|
200
255
|
flows.delete(key);
|
|
256
|
+
release(flow);
|
|
257
|
+
}
|
|
201
258
|
}
|
|
202
259
|
/** Concurrent clicks share one admission, so a second `/mcp auth` never races the first. */
|
|
203
260
|
function admitFlow(services, directory, name, command) {
|
|
@@ -216,29 +273,40 @@ const inProgress = (name) => ({
|
|
|
216
273
|
async function startFlow(services, directory, name, command) {
|
|
217
274
|
if (!SERVER_NAME.test(name))
|
|
218
275
|
return { error: { status: 400, body: errorBody("InvalidRequestError", "invalid MCP server name") } };
|
|
219
|
-
const live = liveSession(services.sessions, directory);
|
|
220
|
-
if (live === undefined)
|
|
221
|
-
return {
|
|
222
|
-
error: {
|
|
223
|
-
status: 400,
|
|
224
|
-
body: errorBody("McpUnavailableError", "MCP authorization needs a running OmO session in this directory"),
|
|
225
|
-
},
|
|
226
|
-
};
|
|
227
|
-
const { s, pi } = live;
|
|
228
276
|
const key = flowKey(directory, name);
|
|
229
277
|
pruneFlows();
|
|
230
278
|
const existing = flows.get(key);
|
|
231
|
-
if (existing?.running && existing
|
|
279
|
+
if (existing?.running && agentCurrent(existing))
|
|
232
280
|
return existing.command === command ? { flow: existing } : inProgress(name);
|
|
233
|
-
const
|
|
281
|
+
const live = liveSession(services.sessions, directory);
|
|
282
|
+
const s = live?.s;
|
|
283
|
+
const pi = live?.pi ?? new PiRpcClient({ cwd: directory, args: ["--no-session"] });
|
|
284
|
+
const refuse = async (status, error, message) => {
|
|
285
|
+
if (live === undefined)
|
|
286
|
+
await pi.dispose();
|
|
287
|
+
return { error: { status, body: errorBody(error, message) } };
|
|
288
|
+
};
|
|
289
|
+
let servers;
|
|
290
|
+
let commandName;
|
|
291
|
+
try {
|
|
292
|
+
// A spawned agent answers its first RPC only once it has started.
|
|
293
|
+
if (live === undefined)
|
|
294
|
+
await pi.command({ type: "get_state" });
|
|
295
|
+
servers = await loadedServers(pi);
|
|
296
|
+
commandName = await mcpCommandName(pi);
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
return refuse(400, "McpUnavailableError", `OmO could not start for MCP authorization: ${errorMessage(error)}`);
|
|
300
|
+
}
|
|
234
301
|
if (!servers.some((server) => server.name === name))
|
|
235
|
-
return
|
|
236
|
-
if (
|
|
237
|
-
return
|
|
302
|
+
return refuse(404, "McpServerNotFoundError", `MCP server not found: ${name}`);
|
|
303
|
+
if (commandName === undefined)
|
|
304
|
+
return refuse(400, "UnsupportedError", "This OmO build has no /mcp command");
|
|
238
305
|
const url = Promise.withResolvers();
|
|
239
306
|
const flow = {
|
|
240
307
|
s,
|
|
241
308
|
pi,
|
|
309
|
+
commandName,
|
|
242
310
|
command,
|
|
243
311
|
attemptID: randomUUID(),
|
|
244
312
|
created: Date.now(),
|
|
@@ -246,8 +314,16 @@ async function startFlow(services, directory, name, command) {
|
|
|
246
314
|
done: Promise.resolve(),
|
|
247
315
|
running: true,
|
|
248
316
|
authorized: false,
|
|
317
|
+
released: false,
|
|
249
318
|
};
|
|
250
|
-
|
|
319
|
+
if (live === undefined) {
|
|
320
|
+
pi.onExit(() => {
|
|
321
|
+
flow.released = true;
|
|
322
|
+
});
|
|
323
|
+
flow.expiry = setTimeout(() => release(flow), MCP_AUTH_ATTEMPT_TTL_MS);
|
|
324
|
+
flow.expiry.unref();
|
|
325
|
+
}
|
|
326
|
+
flow.done = runMcpCommand(services.sessions, s, pi, name, commandName, `${command} ${name}`, {
|
|
251
327
|
url: (found) => {
|
|
252
328
|
flow.authorizationUrl ??= found;
|
|
253
329
|
url.resolve(found);
|
|
@@ -256,12 +332,20 @@ async function startFlow(services, directory, name, command) {
|
|
|
256
332
|
flow.authorized = true;
|
|
257
333
|
},
|
|
258
334
|
})
|
|
259
|
-
.then(() => {
|
|
260
|
-
if (flow.authorized)
|
|
261
|
-
|
|
335
|
+
.then(async () => {
|
|
336
|
+
if (!flow.authorized)
|
|
337
|
+
return;
|
|
338
|
+
if (live === undefined) {
|
|
339
|
+
const server = await currentStatus(pi, name).catch(() => undefined);
|
|
340
|
+
if (server !== undefined)
|
|
341
|
+
flow.server = server;
|
|
342
|
+
release(flow);
|
|
343
|
+
}
|
|
344
|
+
authorized(services, directory, name, flow);
|
|
262
345
|
})
|
|
263
346
|
.catch((error) => {
|
|
264
347
|
flow.error = errorMessage(error);
|
|
348
|
+
release(flow);
|
|
265
349
|
throw error;
|
|
266
350
|
})
|
|
267
351
|
.finally(() => {
|
|
@@ -269,6 +353,8 @@ async function startFlow(services, directory, name, command) {
|
|
|
269
353
|
});
|
|
270
354
|
// The caller that started the flow may already have its URL; a later failure is still reported.
|
|
271
355
|
void flow.done.catch((error) => console.error(`[mcp] authorization of ${name} failed: ${errorMessage(error)}`));
|
|
356
|
+
if (existing !== undefined)
|
|
357
|
+
release(existing);
|
|
272
358
|
flows.set(key, flow);
|
|
273
359
|
return { flow };
|
|
274
360
|
}
|
|
@@ -301,10 +387,12 @@ async function completeFlow(services, directory, name, input) {
|
|
|
301
387
|
const fail = (status, name_, message) => ({
|
|
302
388
|
error: { status, body: errorBody(name_, message) },
|
|
303
389
|
});
|
|
304
|
-
if (flow === undefined ||
|
|
390
|
+
if (flow === undefined || Date.now() - flow.created > MCP_AUTH_ATTEMPT_TTL_MS)
|
|
305
391
|
return fail(400, "McpAuthError", `No pending authorization for ${name}; start it again`);
|
|
306
392
|
if (flow.authorized)
|
|
307
|
-
return { server: await
|
|
393
|
+
return { server: await flowStatus(flow, name) };
|
|
394
|
+
if (!agentCurrent(flow))
|
|
395
|
+
return fail(400, "McpAuthError", `No pending authorization for ${name}; start it again`);
|
|
308
396
|
if (flow.error !== undefined)
|
|
309
397
|
return fail(400, "McpAuthError", `Authorization of ${name} failed (${flow.error}); start it again`);
|
|
310
398
|
if (typeof input !== "string" || flow.authorizationUrl === undefined)
|
|
@@ -312,11 +400,15 @@ async function completeFlow(services, directory, name, input) {
|
|
|
312
400
|
const redirect = redirectInput(flow.authorizationUrl, input);
|
|
313
401
|
if (redirect === undefined)
|
|
314
402
|
return fail(400, "InvalidRequestError", "code must be an authorization code or redirect URL");
|
|
403
|
+
// OmO consumes the pending state on any answer, so a dedicated agent has nothing left to wait for.
|
|
404
|
+
let attempted = false;
|
|
405
|
+
let server;
|
|
315
406
|
try {
|
|
316
407
|
if (flow.running && flow.command === "auth") {
|
|
317
408
|
const target = new URL(redirect);
|
|
318
409
|
if (!sameCallback(target, flow.authorizationUrl))
|
|
319
410
|
return fail(400, "McpAuthError", `OmO is waiting for ${name} on its loopback callback; paste that redirect`);
|
|
411
|
+
attempted = true;
|
|
320
412
|
const delivered = await fetch(target, { redirect: "manual", signal: AbortSignal.timeout(MCP_READ_WAIT_MS) });
|
|
321
413
|
if (!delivered.ok) {
|
|
322
414
|
// OmO ends the flow on a rejected callback; settle it so a retry cannot join the dead flow.
|
|
@@ -326,21 +418,30 @@ async function completeFlow(services, directory, name, input) {
|
|
|
326
418
|
await flow.done;
|
|
327
419
|
}
|
|
328
420
|
else {
|
|
329
|
-
|
|
421
|
+
attempted = true;
|
|
422
|
+
await runMcpCommand(services.sessions, flow.s, flow.pi, name, flow.commandName, `auth-complete ${name} ${redirect}`, {
|
|
330
423
|
authorized: () => {
|
|
331
424
|
flow.authorized = true;
|
|
332
425
|
},
|
|
333
426
|
});
|
|
334
|
-
|
|
427
|
+
if (flow.authorized)
|
|
428
|
+
authorized(services, directory, name, flow);
|
|
429
|
+
else
|
|
430
|
+
announce(services, directory, name);
|
|
335
431
|
}
|
|
432
|
+
if (!flow.authorized)
|
|
433
|
+
return fail(400, "McpAuthError", flow.error ?? `MCP server ${name} did not complete authorization`);
|
|
434
|
+
server = await flowStatus(flow, name);
|
|
336
435
|
}
|
|
337
436
|
catch (error) {
|
|
338
437
|
flow.error = errorMessage(error);
|
|
339
438
|
return fail(400, "McpAuthError", flow.error);
|
|
340
439
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
440
|
+
finally {
|
|
441
|
+
if (attempted)
|
|
442
|
+
release(flow);
|
|
443
|
+
}
|
|
444
|
+
return { server };
|
|
344
445
|
}
|
|
345
446
|
function decodeName(raw) {
|
|
346
447
|
try {
|
|
@@ -388,7 +489,7 @@ export async function mcpRoute(services, method, path, directory, body) {
|
|
|
388
489
|
status: 400,
|
|
389
490
|
body: errorBody("McpAuthError", `MCP server ${name} is waiting for its authorization code; paste it to finish`),
|
|
390
491
|
};
|
|
391
|
-
const server = await
|
|
492
|
+
const server = await flowStatus(flow, name);
|
|
392
493
|
return { status: 200, body: server === undefined ? { status: "disabled" } : openCodeMcpStatus(server) };
|
|
393
494
|
}
|
|
394
495
|
const announced = await awaitAuthorizationUrl(flow, name);
|
|
@@ -480,8 +581,10 @@ export async function mcpRouteV2(services, method, path, directory, body) {
|
|
|
480
581
|
return "error" in result ? result.error : { status: 204 };
|
|
481
582
|
}
|
|
482
583
|
if (method === "DELETE") {
|
|
483
|
-
if (flow !== undefined)
|
|
584
|
+
if (flow !== undefined) {
|
|
484
585
|
flows.delete(flowKey(directory, name));
|
|
586
|
+
release(flow);
|
|
587
|
+
}
|
|
485
588
|
return { status: 204 };
|
|
486
589
|
}
|
|
487
590
|
if (method !== "GET")
|
package/dist/server.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** HTTP/SSE composition root for the OpenCode API backed by pi. */
|
|
2
2
|
import { createHash, randomBytes } from "node:crypto";
|
|
3
3
|
import { createServer } from "node:http";
|
|
4
|
+
import { disposeMcpAgents } from "./mcp.js";
|
|
4
5
|
import { debugLog } from "./misc-routes.js";
|
|
5
6
|
import { SessionManager } from "./sessions.js";
|
|
6
7
|
import { loadCatalog } from "./catalog.js";
|
|
@@ -64,7 +65,7 @@ export class AdapterServer {
|
|
|
64
65
|
socket.destroy();
|
|
65
66
|
this.webSocketClients.clear();
|
|
66
67
|
try {
|
|
67
|
-
await this.sessions.disposeAll();
|
|
68
|
+
await Promise.all([this.sessions.disposeAll(), disposeMcpAgents()]);
|
|
68
69
|
}
|
|
69
70
|
finally {
|
|
70
71
|
// An in-flight HTTP request must not hold shutdown open after teardown.
|