@mongodb-js/mcp-http-runners 3.0.0-prerelease.2 → 3.0.0-prerelease.4
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/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/legacyMcpHttpHandler.d.ts +124 -0
- package/dist/legacyMcpHttpHandler.d.ts.map +1 -0
- package/dist/legacyMcpHttpHandler.js +437 -0
- package/dist/legacyMcpHttpHandler.js.map +1 -0
- package/dist/mcpHttpServer.d.ts +47 -48
- package/dist/mcpHttpServer.d.ts.map +1 -1
- package/dist/mcpHttpServer.js +103 -364
- package/dist/mcpHttpServer.js.map +1 -1
- package/dist/monitoringServer.js +1 -1
- package/dist/monitoringServer.js.map +1 -1
- package/dist/streamableHttpRunner.d.ts +3 -3
- package/dist/streamableHttpRunner.d.ts.map +1 -1
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -1
- package/package.json +9 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export type { StreamableHttpRunnerOptions } from "./streamableHttpRunner.js";
|
|
2
2
|
export { StreamableHttpRunner } from "./streamableHttpRunner.js";
|
|
3
3
|
export { MCPHttpServer, type MCPHttpServerOptions } from "./mcpHttpServer.js";
|
|
4
|
+
export { LegacyMcpHttpHandler, type LegacyMcpHttpHandlerOptions, type LegacySessionOptions, } from "./legacyMcpHttpHandler.js";
|
|
5
|
+
export { SessionStore, SessionLimitExceededError, SessionRejectedError, createDefaultSessionStore, type ISessionStore, type SessionStoreConstructorArgs, type CreateSessionStoreFn, } from "@mongodb-js/mcp-core";
|
|
4
6
|
export { MonitoringServer, type MonitoringServerOptions } from "./monitoringServer.js";
|
|
5
7
|
export { ExpressBasedHttpServer, type ExpressBasedHttpServerOptions } from "./expressBasedHttpServer.js";
|
|
6
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAG7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGjE,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGvF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,2BAA2B,EAAE,MAAM,2BAA2B,CAAC;AAG7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAGjE,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACH,oBAAoB,EACpB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC5B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACH,YAAY,EACZ,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,KAAK,aAAa,EAClB,KAAK,2BAA2B,EAChC,KAAK,oBAAoB,GAC5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,uBAAuB,CAAC;AAGvF,OAAO,EAAE,sBAAsB,EAAE,KAAK,6BAA6B,EAAE,MAAM,6BAA6B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
export { StreamableHttpRunner } from "./streamableHttpRunner.js";
|
|
3
3
|
// HTTP Servers
|
|
4
4
|
export { MCPHttpServer } from "./mcpHttpServer.js";
|
|
5
|
+
export { LegacyMcpHttpHandler, } from "./legacyMcpHttpHandler.js";
|
|
6
|
+
// Session lifecycle primitives live in @mongodb-js/mcp-core; re-export for convenience.
|
|
7
|
+
export { SessionStore, SessionLimitExceededError, SessionRejectedError, createDefaultSessionStore, } from "@mongodb-js/mcp-core";
|
|
5
8
|
export { MonitoringServer } from "./monitoringServer.js";
|
|
6
9
|
// Express HTTP Server base
|
|
7
10
|
export { ExpressBasedHttpServer } from "./expressBasedHttpServer.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,qCAAqC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,eAAe;AACf,OAAO,EAAE,aAAa,EAA6B,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC;AAEvF,2BAA2B;AAC3B,OAAO,EAAE,sBAAsB,EAAsC,MAAM,6BAA6B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,qCAAqC;AACrC,OAAO,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEjE,eAAe;AACf,OAAO,EAAE,aAAa,EAA6B,MAAM,oBAAoB,CAAC;AAC9E,OAAO,EACH,oBAAoB,GAGvB,MAAM,2BAA2B,CAAC;AACnC,wFAAwF;AACxF,OAAO,EACH,YAAY,EACZ,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,GAI5B,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,gBAAgB,EAAgC,MAAM,uBAAuB,CAAC;AAEvF,2BAA2B;AAC3B,OAAO,EAAE,sBAAsB,EAAsC,MAAM,6BAA6B,CAAC"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
|
2
|
+
import type express from "express";
|
|
3
|
+
import type { ILogger, TransportRequestContext, HttpServerOptions, BaseServer } from "@mongodb-js/mcp-types";
|
|
4
|
+
import { type ISessionStore } from "@mongodb-js/mcp-core";
|
|
5
|
+
/** Builds a fresh request-scoped server for a legacy request. */
|
|
6
|
+
export type LegacyServerFactory = (request: TransportRequestContext) => Promise<BaseServer>;
|
|
7
|
+
/** Tunables forwarded to the {@link SessionStore}. */
|
|
8
|
+
export type LegacySessionOptions = {
|
|
9
|
+
maxSessions?: number;
|
|
10
|
+
idleTimeoutMS?: number;
|
|
11
|
+
notificationTimeoutMS?: number;
|
|
12
|
+
evictionIdleGraceMS?: number;
|
|
13
|
+
/**
|
|
14
|
+
* When true, the legacy (2025-era) HTTP transport accepts a client-supplied
|
|
15
|
+
* `mcp-session-id` header on `initialize` instead of rejecting it, and
|
|
16
|
+
* reuses that id for the session. When no session is found for a supplied
|
|
17
|
+
* id, the handler attempts an implicit re-initialization and restores the
|
|
18
|
+
* client's previously negotiated capabilities from the session store (see
|
|
19
|
+
* {@link ISessionStore.saveNegotiatedClientState}); a store without durable
|
|
20
|
+
* storage restores nothing. Defaults to `false`.
|
|
21
|
+
*/
|
|
22
|
+
externallyManagedSessions?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type LegacyMcpHttpHandlerOptions = {
|
|
25
|
+
/** Builds a fresh request-scoped server for the legacy session. */
|
|
26
|
+
createServer: LegacyServerFactory;
|
|
27
|
+
logger: ILogger;
|
|
28
|
+
http: HttpServerOptions;
|
|
29
|
+
/** Session store backing the legacy sessionful path (auth-aware or durable). */
|
|
30
|
+
sessionStore: ISessionStore<NodeStreamableHTTPServerTransport>;
|
|
31
|
+
/**
|
|
32
|
+
* When true, accept a client-supplied `mcp-session-id` on `initialize` and
|
|
33
|
+
* restore negotiated client state for implicitly re-initialized sessions.
|
|
34
|
+
* Defaults to `false`.
|
|
35
|
+
*/
|
|
36
|
+
externallyManagedSessions?: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* The contract {@link MCPHttpServer.createLegacyHandler} returns for 2025-era
|
|
40
|
+
* serving. It is deliberately transport-shaped so alternative implementations
|
|
41
|
+
* (a different transport, a distributed session store, ...) can be returned by
|
|
42
|
+
* overriding `createLegacyHandler`.
|
|
43
|
+
*/
|
|
44
|
+
export interface LegacyMcpHandler {
|
|
45
|
+
/** Serves one 2025-era HTTP request (POST/GET/DELETE) to `/mcp`. */
|
|
46
|
+
handle(req: express.Request, res: express.Response): Promise<void>;
|
|
47
|
+
/** Releases any held resources (e.g. live sessions) on shutdown. */
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Serves 2025-era (legacy) MCP requests over streamable HTTP **sessionfully**.
|
|
52
|
+
*
|
|
53
|
+
* The 2026-07-28 protocol is stateless and carries client identity per request
|
|
54
|
+
* in the `_meta` envelope. The 2025 protocol is not: it declares the client's
|
|
55
|
+
* negotiated capabilities (incl. `elicitation`) in the `initialize` handshake
|
|
56
|
+
* and delivers elicitation as real server→client requests. Both need a live,
|
|
57
|
+
* correlated transport, which is exactly what the SDK's legacy elicitation
|
|
58
|
+
* shim relies on. The SDK's `legacy: 'stateless'` mode cannot do that (each
|
|
59
|
+
* request gets a fresh instance: no capabilities, no return channel).
|
|
60
|
+
*
|
|
61
|
+
* This handler owns that sessionful lifecycle: an `initialize` POST creates a
|
|
62
|
+
* session (a connected transport/server pair), later requests and the SSE idle
|
|
63
|
+
* stream reuse the session's transport (the shim's return channel), and a
|
|
64
|
+
* DELETE closes it. Sessions are bounded by the injected `sessionStore`.
|
|
65
|
+
* Keeping it isolated from {@link MCPHttpServer} leaves the modern stateless
|
|
66
|
+
* path clean.
|
|
67
|
+
*/
|
|
68
|
+
export declare class LegacyMcpHttpHandler implements LegacyMcpHandler {
|
|
69
|
+
private readonly createServer;
|
|
70
|
+
private readonly logger;
|
|
71
|
+
private readonly http;
|
|
72
|
+
private readonly sessions;
|
|
73
|
+
private readonly externallyManagedSessions;
|
|
74
|
+
/** Serializes concurrent initialization of the same session id (see {@link createSession}). */
|
|
75
|
+
private readonly pendingInitializations;
|
|
76
|
+
constructor({ createServer, logger, http, sessionStore, externallyManagedSessions, }: LegacyMcpHttpHandlerOptions);
|
|
77
|
+
/** Closes every live session (e.g. on server shutdown). */
|
|
78
|
+
close(): Promise<void>;
|
|
79
|
+
private buildTransportContext;
|
|
80
|
+
/** Reports a session error the way main did: same codes, messages, and statuses. */
|
|
81
|
+
private reportSessionError;
|
|
82
|
+
/**
|
|
83
|
+
* Keeps the session's SSE idle stream alive through proxies and reaps dead
|
|
84
|
+
* clients: pings every 30s and closes the transport after 3 consecutive
|
|
85
|
+
* failures. Not started in JSON response mode, where connections are
|
|
86
|
+
* short-lived and pings aren't needed.
|
|
87
|
+
*/
|
|
88
|
+
private startKeepAliveLoop;
|
|
89
|
+
/**
|
|
90
|
+
* Creates a fresh session (or returns the existing / in-flight one for an
|
|
91
|
+
* externally managed id). Concurrent initializations of the same session id
|
|
92
|
+
* are serialized so they don't race on `addSession` (see
|
|
93
|
+
* {@link createSessionInstance}).
|
|
94
|
+
*/
|
|
95
|
+
private createSession;
|
|
96
|
+
/** Builds the server/transport pair and registers it in the session store. */
|
|
97
|
+
private createSessionInstance;
|
|
98
|
+
/**
|
|
99
|
+
* Serves a 2025-era request. POSTs that carry no session begin a new session
|
|
100
|
+
* (the `initialize` handshake); POSTs/GETs/DELETEs that carry a session id
|
|
101
|
+
* are routed to the stored transport, preserving the client's negotiated
|
|
102
|
+
* state and providing the SSE return channel for server→client requests.
|
|
103
|
+
*/
|
|
104
|
+
handle(req: express.Request, res: express.Response): Promise<void>;
|
|
105
|
+
/** Routes a session-carrying request to its live transport; reports a session error when missing. */
|
|
106
|
+
private handleSessionRequest;
|
|
107
|
+
/**
|
|
108
|
+
* Restores the client state negotiated during the session's original
|
|
109
|
+
* `initialize` onto an implicitly re-initialized server. The session's
|
|
110
|
+
* server on this pod never saw the client's `initialize` request, so
|
|
111
|
+
* without this it would treat the client as capability-less — e.g.
|
|
112
|
+
* skipping confirmation elicitation for destructive tools. No-op when the
|
|
113
|
+
* session store does not persist negotiated client state.
|
|
114
|
+
*/
|
|
115
|
+
private restoreNegotiatedClientState;
|
|
116
|
+
/**
|
|
117
|
+
* Arranges for the client state negotiated by a real `initialize` exchange
|
|
118
|
+
* to be persisted through the session store, so implicit re-initializations
|
|
119
|
+
* of this session can restore it. Wraps the `oninitialized` callback
|
|
120
|
+
* installed by `server.connect`, hence must run after it.
|
|
121
|
+
*/
|
|
122
|
+
private captureNegotiatedClientStateOnInitialize;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=legacyMcpHttpHandler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacyMcpHttpHandler.d.ts","sourceRoot":"","sources":["../src/legacyMcpHttpHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iCAAiC,EAAE,MAAM,4BAA4B,CAAC;AAC/E,OAAO,KAAK,OAAO,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,EACR,OAAO,EACP,uBAAuB,EAEvB,iBAAiB,EACjB,UAAU,EAEb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA2BrF,iEAAiE;AACjE,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,uBAAuB,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;AAE5F,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;;;;;OAQG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GAAG;IACtC,mEAAmE;IACnE,YAAY,EAAE,mBAAmB,CAAC;IAClC,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,iBAAiB,CAAC;IACxB,gFAAgF;IAChF,YAAY,EAAE,aAAa,CAAC,iCAAiC,CAAC,CAAC;IAC/D;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACvC,CAAC;AAEF;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC7B,oEAAoE;IACpE,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,oEAAoE;IACpE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,oBAAqB,YAAW,gBAAgB;IACzD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAU;IACjC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmD;IAC5E,OAAO,CAAC,QAAQ,CAAC,yBAAyB,CAAU;IACpD,+FAA+F;IAC/F,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAiE;gBAE5F,EACR,YAAY,EACZ,MAAM,EACN,IAAI,EACJ,YAAY,EACZ,yBAAiC,GACpC,EAAE,2BAA2B;IAQ9B,2DAA2D;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,OAAO,CAAC,qBAAqB;IAc7B,oFAAoF;IACpF,OAAO,CAAC,kBAAkB;IAsC1B;;;;;OAKG;YACW,kBAAkB;IAgDhC;;;;;OAKG;YACW,aAAa;IAyC3B,8EAA8E;YAChE,qBAAqB;IA0FnC;;;;;OAKG;IACU,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IA8E/E,qGAAqG;YACvF,oBAAoB;IA0ClC;;;;;;;OAOG;YACW,4BAA4B;IAkC1C;;;;;OAKG;IACH,OAAO,CAAC,wCAAwC;CAwBnD"}
|
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
import { isInitializeRequest } from "@modelcontextprotocol/server";
|
|
2
|
+
import { NodeStreamableHTTPServerTransport } from "@modelcontextprotocol/node";
|
|
3
|
+
import { LogId, getRandomUUID, requestIdAttr } from "@mongodb-js/mcp-core";
|
|
4
|
+
import { SessionLimitExceededError } from "@mongodb-js/mcp-core";
|
|
5
|
+
import { sleep } from "./utils.js";
|
|
6
|
+
/**
|
|
7
|
+
* Session error codes as they were served to 2025-era clients on main; the
|
|
8
|
+
* shared constants were removed from mcp-core with the session surface, so
|
|
9
|
+
* the legacy path keeps its wire contract locally.
|
|
10
|
+
*/
|
|
11
|
+
const JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED = -32001;
|
|
12
|
+
const JSON_RPC_ERROR_CODE_SESSION_ID_INVALID = -32002;
|
|
13
|
+
const JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND = -32003;
|
|
14
|
+
const JSON_RPC_ERROR_CODE_INVALID_REQUEST = -32004;
|
|
15
|
+
const JSON_RPC_ERROR_CODE_DISALLOWED_EXTERNAL_SESSION = -32005;
|
|
16
|
+
const JSON_RPC_ERROR_CODE_SESSION_LIMIT_EXCEEDED = -32006;
|
|
17
|
+
/** Guards against oversized `mcp-session-id` headers (e.g. an abusive client). */
|
|
18
|
+
const MAX_SESSION_ID_LENGTH = 512;
|
|
19
|
+
/**
|
|
20
|
+
* Serves 2025-era (legacy) MCP requests over streamable HTTP **sessionfully**.
|
|
21
|
+
*
|
|
22
|
+
* The 2026-07-28 protocol is stateless and carries client identity per request
|
|
23
|
+
* in the `_meta` envelope. The 2025 protocol is not: it declares the client's
|
|
24
|
+
* negotiated capabilities (incl. `elicitation`) in the `initialize` handshake
|
|
25
|
+
* and delivers elicitation as real server→client requests. Both need a live,
|
|
26
|
+
* correlated transport, which is exactly what the SDK's legacy elicitation
|
|
27
|
+
* shim relies on. The SDK's `legacy: 'stateless'` mode cannot do that (each
|
|
28
|
+
* request gets a fresh instance: no capabilities, no return channel).
|
|
29
|
+
*
|
|
30
|
+
* This handler owns that sessionful lifecycle: an `initialize` POST creates a
|
|
31
|
+
* session (a connected transport/server pair), later requests and the SSE idle
|
|
32
|
+
* stream reuse the session's transport (the shim's return channel), and a
|
|
33
|
+
* DELETE closes it. Sessions are bounded by the injected `sessionStore`.
|
|
34
|
+
* Keeping it isolated from {@link MCPHttpServer} leaves the modern stateless
|
|
35
|
+
* path clean.
|
|
36
|
+
*/
|
|
37
|
+
export class LegacyMcpHttpHandler {
|
|
38
|
+
constructor({ createServer, logger, http, sessionStore, externallyManagedSessions = false, }) {
|
|
39
|
+
/** Serializes concurrent initialization of the same session id (see {@link createSession}). */
|
|
40
|
+
this.pendingInitializations = new Map();
|
|
41
|
+
this.createServer = createServer;
|
|
42
|
+
this.logger = logger;
|
|
43
|
+
this.http = http;
|
|
44
|
+
this.sessions = sessionStore;
|
|
45
|
+
this.externallyManagedSessions = externallyManagedSessions;
|
|
46
|
+
}
|
|
47
|
+
/** Closes every live session (e.g. on server shutdown). */
|
|
48
|
+
async close() {
|
|
49
|
+
await this.sessions.closeAllSessions();
|
|
50
|
+
}
|
|
51
|
+
buildTransportContext(req) {
|
|
52
|
+
return {
|
|
53
|
+
headers: req.headers,
|
|
54
|
+
query: req.query,
|
|
55
|
+
// The explicit auth state of this request. Legacy serving does not
|
|
56
|
+
// normalize authInfo itself; hosts supply it via `req.auth` when
|
|
57
|
+
// using authenticated mode.
|
|
58
|
+
authInfo: req.auth
|
|
59
|
+
? { mode: "authenticated", state: req.auth }
|
|
60
|
+
: { mode: "unauthenticated" },
|
|
61
|
+
protocol: "legacy",
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Reports a session error the way main did: same codes, messages, and statuses. */
|
|
65
|
+
reportSessionError(res, errorCode) {
|
|
66
|
+
let message;
|
|
67
|
+
let statusCode = 400;
|
|
68
|
+
switch (errorCode) {
|
|
69
|
+
case JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED:
|
|
70
|
+
message = "session id is required";
|
|
71
|
+
break;
|
|
72
|
+
case JSON_RPC_ERROR_CODE_SESSION_ID_INVALID:
|
|
73
|
+
message = "session id is invalid";
|
|
74
|
+
break;
|
|
75
|
+
case JSON_RPC_ERROR_CODE_INVALID_REQUEST:
|
|
76
|
+
message = "invalid request";
|
|
77
|
+
break;
|
|
78
|
+
case JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND:
|
|
79
|
+
message = "session not found";
|
|
80
|
+
statusCode = 404;
|
|
81
|
+
break;
|
|
82
|
+
case JSON_RPC_ERROR_CODE_DISALLOWED_EXTERNAL_SESSION:
|
|
83
|
+
message = "cannot provide sessionId when externally managed sessions are disabled";
|
|
84
|
+
break;
|
|
85
|
+
case JSON_RPC_ERROR_CODE_SESSION_LIMIT_EXCEEDED:
|
|
86
|
+
message = "server has reached the maximum number of concurrent sessions, try again later";
|
|
87
|
+
statusCode = 503;
|
|
88
|
+
break;
|
|
89
|
+
default:
|
|
90
|
+
message = "unknown error";
|
|
91
|
+
statusCode = 500;
|
|
92
|
+
}
|
|
93
|
+
res.status(statusCode).json({
|
|
94
|
+
jsonrpc: "2.0",
|
|
95
|
+
error: {
|
|
96
|
+
code: errorCode,
|
|
97
|
+
message,
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Keeps the session's SSE idle stream alive through proxies and reaps dead
|
|
103
|
+
* clients: pings every 30s and closes the transport after 3 consecutive
|
|
104
|
+
* failures. Not started in JSON response mode, where connections are
|
|
105
|
+
* short-lived and pings aren't needed.
|
|
106
|
+
*/
|
|
107
|
+
async startKeepAliveLoop({ transport, signal, }) {
|
|
108
|
+
if (this.http.responseType === "json") {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
let failedPings = 0;
|
|
112
|
+
while (!signal.aborted) {
|
|
113
|
+
try {
|
|
114
|
+
this.logger.debug({
|
|
115
|
+
id: LogId.streamableHttpTransportKeepAlive,
|
|
116
|
+
context: "streamableHttpTransport",
|
|
117
|
+
message: "Sending ping",
|
|
118
|
+
});
|
|
119
|
+
await transport.send({
|
|
120
|
+
jsonrpc: "2.0",
|
|
121
|
+
method: "ping",
|
|
122
|
+
});
|
|
123
|
+
failedPings = 0;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
try {
|
|
127
|
+
failedPings++;
|
|
128
|
+
this.logger.warning({
|
|
129
|
+
id: LogId.streamableHttpTransportKeepAliveFailure,
|
|
130
|
+
context: "streamableHttpTransport",
|
|
131
|
+
message: `Error sending ping (attempt #${failedPings}): ${err instanceof Error ? err.message : String(err)}`,
|
|
132
|
+
});
|
|
133
|
+
if (failedPings > 3) {
|
|
134
|
+
await transport.close();
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
// Ignore the error of the transport close
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
await sleep(30000, { signal });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Creates a fresh session (or returns the existing / in-flight one for an
|
|
147
|
+
* externally managed id). Concurrent initializations of the same session id
|
|
148
|
+
* are serialized so they don't race on `addSession` (see
|
|
149
|
+
* {@link createSessionInstance}).
|
|
150
|
+
*/
|
|
151
|
+
async createSession(req, providedSessionId, isImplicitInitialization = false) {
|
|
152
|
+
const sessionId = providedSessionId ?? getRandomUUID();
|
|
153
|
+
// An externally managed session may already exist in the store (e.g. a
|
|
154
|
+
// client re-sends `initialize` for the same id). Reuse it rather than
|
|
155
|
+
// creating a duplicate, which would throw "Session already exists".
|
|
156
|
+
const existingTransport = await this.sessions.getSession(sessionId, req.headers);
|
|
157
|
+
if (existingTransport) {
|
|
158
|
+
return existingTransport;
|
|
159
|
+
}
|
|
160
|
+
// Another request is already initializing this id; wait for it and reuse
|
|
161
|
+
// its transport instead of racing it.
|
|
162
|
+
const pendingInit = this.pendingInitializations.get(sessionId);
|
|
163
|
+
if (pendingInit) {
|
|
164
|
+
this.logger.debug({
|
|
165
|
+
id: LogId.streamableHttpTransportSessionNotFound,
|
|
166
|
+
context: "streamableHttpTransport",
|
|
167
|
+
message: `Session with ID ${sessionId} is already being initialized, waiting`,
|
|
168
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
169
|
+
});
|
|
170
|
+
try {
|
|
171
|
+
return await pendingInit;
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// The initializer handles its own error; fall through to retry.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const initPromise = this.createSessionInstance(req, sessionId, isImplicitInitialization);
|
|
178
|
+
this.pendingInitializations.set(sessionId, initPromise);
|
|
179
|
+
try {
|
|
180
|
+
return await initPromise;
|
|
181
|
+
}
|
|
182
|
+
finally {
|
|
183
|
+
this.pendingInitializations.delete(sessionId);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/** Builds the server/transport pair and registers it in the session store. */
|
|
187
|
+
async createSessionInstance(req, sessionId, isImplicitInitialization) {
|
|
188
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
189
|
+
sessionIdGenerator: () => sessionId,
|
|
190
|
+
enableJsonResponse: this.http.responseType === "json",
|
|
191
|
+
});
|
|
192
|
+
// HACK: When we're implicitly re-initializing an externally managed
|
|
193
|
+
// session, mark the transport as already initialized so the SDK does
|
|
194
|
+
// not reject follow-up requests with "transport not initialized".
|
|
195
|
+
// Guard the internal field: if the SDK's transport shape changes, the
|
|
196
|
+
// session degrades gracefully rather than throwing before the request
|
|
197
|
+
// can be answered.
|
|
198
|
+
if (isImplicitInitialization) {
|
|
199
|
+
const internalTransport = transport._webStandardTransport;
|
|
200
|
+
if (internalTransport) {
|
|
201
|
+
internalTransport._initialized = true;
|
|
202
|
+
internalTransport.sessionId = sessionId;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
const server = (await this.createServer(this.buildTransportContext(req)));
|
|
206
|
+
// Admit (check cap, evict LRU idle victim). A rejection admits nothing.
|
|
207
|
+
try {
|
|
208
|
+
await this.sessions.addSession({ sessionId, transport, logger: this.logger, headers: req.headers });
|
|
209
|
+
}
|
|
210
|
+
catch (error) {
|
|
211
|
+
// Dispose the un-connected server we built for a rejected session.
|
|
212
|
+
await server.close().catch(() => undefined);
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
const keepAliveController = new AbortController();
|
|
216
|
+
void this.startKeepAliveLoop({ transport, signal: keepAliveController.signal });
|
|
217
|
+
// When the transport closes (client disconnect or store-initiated
|
|
218
|
+
// eviction/timeout), tear down the server and remove the session.
|
|
219
|
+
transport.onclose = () => {
|
|
220
|
+
keepAliveController.abort();
|
|
221
|
+
void server
|
|
222
|
+
.close()
|
|
223
|
+
.catch((error) => {
|
|
224
|
+
this.logger.error({
|
|
225
|
+
id: LogId.streamableHttpTransportCloseFailure,
|
|
226
|
+
context: "streamableHttpTransport",
|
|
227
|
+
message: `Error closing legacy session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
228
|
+
});
|
|
229
|
+
})
|
|
230
|
+
.finally(() => {
|
|
231
|
+
// The store removes the session before it closes the
|
|
232
|
+
// transport, so on store-initiated closes (eviction, idle
|
|
233
|
+
// timeout) the session is already gone — nothing to do.
|
|
234
|
+
if (!this.sessions.hasSession(sessionId)) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
this.sessions.closeSession({ sessionId, reason: "transport_closed" }).catch((error) => {
|
|
238
|
+
this.logger.error({
|
|
239
|
+
id: LogId.streamableHttpTransportCloseFailure,
|
|
240
|
+
context: "streamableHttpTransport",
|
|
241
|
+
message: `Error removing legacy session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
};
|
|
246
|
+
try {
|
|
247
|
+
await server.connect(transport);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
// Don't leave a never-connected session registered; closing the
|
|
251
|
+
// transport triggers the onclose teardown above.
|
|
252
|
+
await this.sessions.closeSession({ sessionId, reason: "unknown" }).catch(() => undefined);
|
|
253
|
+
await server.close().catch(() => undefined);
|
|
254
|
+
throw error;
|
|
255
|
+
}
|
|
256
|
+
if (isImplicitInitialization) {
|
|
257
|
+
await this.restoreNegotiatedClientState(server, sessionId, req.headers);
|
|
258
|
+
}
|
|
259
|
+
else {
|
|
260
|
+
this.captureNegotiatedClientStateOnInitialize(server, sessionId, req.headers);
|
|
261
|
+
}
|
|
262
|
+
return transport;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Serves a 2025-era request. POSTs that carry no session begin a new session
|
|
266
|
+
* (the `initialize` handshake); POSTs/GETs/DELETEs that carry a session id
|
|
267
|
+
* are routed to the stored transport, preserving the client's negotiated
|
|
268
|
+
* state and providing the SSE return channel for server→client requests.
|
|
269
|
+
*/
|
|
270
|
+
async handle(req, res) {
|
|
271
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
272
|
+
if (req.method === "GET") {
|
|
273
|
+
// The SSE idle stream only exists in SSE response mode.
|
|
274
|
+
if (this.http.responseType === "json") {
|
|
275
|
+
res.status(405).set("Allow", ["POST", "DELETE"]).send("Method Not Allowed");
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
await this.handleSessionRequest(req, res, sessionId);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (req.method === "DELETE") {
|
|
282
|
+
await this.handleSessionRequest(req, res, sessionId);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
// POST /mcp.
|
|
286
|
+
if (sessionId && typeof sessionId !== "string") {
|
|
287
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_INVALID);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (typeof sessionId === "string" && sessionId.length > MAX_SESSION_ID_LENGTH) {
|
|
291
|
+
this.logger.debug({
|
|
292
|
+
id: LogId.streamableHttpTransportSessionNotFound,
|
|
293
|
+
context: "streamableHttpTransport",
|
|
294
|
+
message: `Session ID exceeds maximum length ${MAX_SESSION_ID_LENGTH}`,
|
|
295
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
296
|
+
});
|
|
297
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_INVALID);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (isInitializeRequest(req.body)) {
|
|
301
|
+
const resolvedSessionId = typeof sessionId === "string" && this.externallyManagedSessions ? sessionId : undefined;
|
|
302
|
+
if (sessionId && !resolvedSessionId) {
|
|
303
|
+
// A client-supplied session id is only honored when externally
|
|
304
|
+
// managed sessions are enabled; otherwise it is rejected.
|
|
305
|
+
this.logger.debug({
|
|
306
|
+
id: LogId.streamableHttpTransportDisallowedExternalSessionError,
|
|
307
|
+
context: "streamableHttpTransport",
|
|
308
|
+
message: `Client provided session ID ${sessionId}, but externallyManagedSessions is disabled`,
|
|
309
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
310
|
+
});
|
|
311
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_DISALLOWED_EXTERNAL_SESSION);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
let transport;
|
|
315
|
+
try {
|
|
316
|
+
transport = await this.createSession(req, resolvedSessionId);
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
if (error instanceof SessionLimitExceededError) {
|
|
320
|
+
this.logger.warning({
|
|
321
|
+
id: LogId.streamableHttpTransportRequestFailure,
|
|
322
|
+
context: "streamableHttpTransport",
|
|
323
|
+
message: `Rejecting legacy session startup: ${error.message}`,
|
|
324
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
325
|
+
});
|
|
326
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_LIMIT_EXCEEDED);
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
await transport.handleRequest(req, res, req.body);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
if (!sessionId) {
|
|
335
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_INVALID_REQUEST);
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
await this.handleSessionRequest(req, res, sessionId);
|
|
339
|
+
}
|
|
340
|
+
/** Routes a session-carrying request to its live transport; reports a session error when missing. */
|
|
341
|
+
async handleSessionRequest(req, res, sessionId) {
|
|
342
|
+
if (!sessionId) {
|
|
343
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED);
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (typeof sessionId !== "string") {
|
|
347
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_INVALID);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
let transport = await this.sessions.getSession(sessionId, req.headers);
|
|
351
|
+
if (!transport) {
|
|
352
|
+
if (!this.externallyManagedSessions) {
|
|
353
|
+
this.logger.debug({
|
|
354
|
+
id: LogId.streamableHttpTransportSessionNotFound,
|
|
355
|
+
context: "streamableHttpTransport",
|
|
356
|
+
message: `Session with ID ${sessionId} not found`,
|
|
357
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
358
|
+
});
|
|
359
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
// Externally managed sessions may be implicitly re-initialized: a
|
|
363
|
+
// session held externally can arrive at this pod without being in
|
|
364
|
+
// the store. Build a fresh session under the supplied id and
|
|
365
|
+
// restore the client's previously negotiated capabilities (if the
|
|
366
|
+
// store persists them) instead of rejecting the request.
|
|
367
|
+
this.logger.debug({
|
|
368
|
+
id: LogId.streamableHttpTransportSessionNotFound,
|
|
369
|
+
context: "streamableHttpTransport",
|
|
370
|
+
message: `Session with ID ${sessionId} not found, implicitly re-initializing`,
|
|
371
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
372
|
+
});
|
|
373
|
+
transport = await this.createSession(req, sessionId, true);
|
|
374
|
+
}
|
|
375
|
+
await transport.handleRequest(req, res, req.body);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Restores the client state negotiated during the session's original
|
|
379
|
+
* `initialize` onto an implicitly re-initialized server. The session's
|
|
380
|
+
* server on this pod never saw the client's `initialize` request, so
|
|
381
|
+
* without this it would treat the client as capability-less — e.g.
|
|
382
|
+
* skipping confirmation elicitation for destructive tools. No-op when the
|
|
383
|
+
* session store does not persist negotiated client state.
|
|
384
|
+
*/
|
|
385
|
+
async restoreNegotiatedClientState(server, sessionId, headers) {
|
|
386
|
+
let state;
|
|
387
|
+
try {
|
|
388
|
+
state = await this.sessions.loadNegotiatedClientState(sessionId, headers);
|
|
389
|
+
}
|
|
390
|
+
catch (error) {
|
|
391
|
+
// The restored session stays usable; it just behaves as if the
|
|
392
|
+
// client had no optional capabilities.
|
|
393
|
+
this.logger.warning({
|
|
394
|
+
id: LogId.streamableHttpTransportClientStateRestoreFailure,
|
|
395
|
+
context: "streamableHttpTransport",
|
|
396
|
+
message: `Failed to restore negotiated client state for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
397
|
+
attributes: { ...requestIdAttr(headers) },
|
|
398
|
+
});
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
if (!state) {
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
// HACK: the SDK offers no supported way to seed a Server with a
|
|
405
|
+
// previously negotiated initialization, so we write the private fields
|
|
406
|
+
// the initialize handler would have populated.
|
|
407
|
+
const protocolServer = server.mcpServer.server;
|
|
408
|
+
protocolServer._clientCapabilities = state.clientCapabilities;
|
|
409
|
+
protocolServer._clientVersion = state.clientInfo;
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Arranges for the client state negotiated by a real `initialize` exchange
|
|
413
|
+
* to be persisted through the session store, so implicit re-initializations
|
|
414
|
+
* of this session can restore it. Wraps the `oninitialized` callback
|
|
415
|
+
* installed by `server.connect`, hence must run after it.
|
|
416
|
+
*/
|
|
417
|
+
captureNegotiatedClientStateOnInitialize(server, sessionId, headers) {
|
|
418
|
+
const protocolServer = server.mcpServer.server;
|
|
419
|
+
const originalOnInitialized = protocolServer.oninitialized;
|
|
420
|
+
protocolServer.oninitialized = () => {
|
|
421
|
+
originalOnInitialized?.();
|
|
422
|
+
const state = {
|
|
423
|
+
clientCapabilities: protocolServer.getClientCapabilities(),
|
|
424
|
+
clientInfo: protocolServer.getClientVersion(),
|
|
425
|
+
};
|
|
426
|
+
this.sessions.saveNegotiatedClientState(sessionId, state, headers).catch((error) => {
|
|
427
|
+
this.logger.warning({
|
|
428
|
+
id: LogId.streamableHttpTransportClientStateSaveFailure,
|
|
429
|
+
context: "streamableHttpTransport",
|
|
430
|
+
message: `Failed to save negotiated client state for session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
431
|
+
attributes: { ...requestIdAttr(headers) },
|
|
432
|
+
});
|
|
433
|
+
});
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
//# sourceMappingURL=legacyMcpHttpHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacyMcpHttpHandler.js","sourceRoot":"","sources":["../src/legacyMcpHttpHandler.ts"],"names":[],"mappings":"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"}
|