@mongodb-js/mcp-http-runners 3.0.0-prerelease.2 → 3.0.0-prerelease.3
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 +88 -0
- package/dist/legacyMcpHttpHandler.d.ts.map +1 -0
- package/dist/legacyMcpHttpHandler.js +294 -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 +101 -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,88 @@
|
|
|
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
|
+
export type LegacyMcpHttpHandlerOptions = {
|
|
15
|
+
/** Builds a fresh request-scoped server for the legacy session. */
|
|
16
|
+
createServer: LegacyServerFactory;
|
|
17
|
+
logger: ILogger;
|
|
18
|
+
http: HttpServerOptions;
|
|
19
|
+
/** Session store backing the legacy sessionful path (auth-aware or durable). */
|
|
20
|
+
sessionStore: ISessionStore<NodeStreamableHTTPServerTransport>;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The contract {@link MCPHttpServer.createLegacyHandler} returns for 2025-era
|
|
24
|
+
* serving. It is deliberately transport-shaped so alternative implementations
|
|
25
|
+
* (a different transport, a distributed session store, ...) can be returned by
|
|
26
|
+
* overriding `createLegacyHandler`.
|
|
27
|
+
*/
|
|
28
|
+
export interface LegacyMcpHandler {
|
|
29
|
+
/** Serves one 2025-era HTTP request (POST/GET/DELETE) to `/mcp`. */
|
|
30
|
+
handle(req: express.Request, res: express.Response): Promise<void>;
|
|
31
|
+
/** Releases any held resources (e.g. live sessions) on shutdown. */
|
|
32
|
+
close(): Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Serves 2025-era (legacy) MCP requests over streamable HTTP **sessionfully**.
|
|
36
|
+
*
|
|
37
|
+
* The 2026-07-28 protocol is stateless and carries client identity per request
|
|
38
|
+
* in the `_meta` envelope. The 2025 protocol is not: it declares the client's
|
|
39
|
+
* negotiated capabilities (incl. `elicitation`) in the `initialize` handshake
|
|
40
|
+
* and delivers elicitation as real server→client requests. Both need a live,
|
|
41
|
+
* correlated transport, which is exactly what the SDK's legacy elicitation
|
|
42
|
+
* shim relies on. The SDK's `legacy: 'stateless'` mode cannot do that (each
|
|
43
|
+
* request gets a fresh instance: no capabilities, no return channel).
|
|
44
|
+
*
|
|
45
|
+
* This handler owns that sessionful lifecycle: an `initialize` POST creates a
|
|
46
|
+
* session (a connected transport/server pair), later requests and the SSE idle
|
|
47
|
+
* stream reuse the session's transport (the shim's return channel), and a
|
|
48
|
+
* DELETE closes it. Sessions are bounded by the injected `sessionStore`.
|
|
49
|
+
* Keeping it isolated from {@link MCPHttpServer} leaves the modern stateless
|
|
50
|
+
* path clean.
|
|
51
|
+
*/
|
|
52
|
+
export declare class LegacyMcpHttpHandler implements LegacyMcpHandler {
|
|
53
|
+
private readonly createServer;
|
|
54
|
+
private readonly logger;
|
|
55
|
+
private readonly http;
|
|
56
|
+
private readonly sessions;
|
|
57
|
+
constructor({ createServer, logger, http, sessionStore }: LegacyMcpHttpHandlerOptions);
|
|
58
|
+
/** Closes every live session (e.g. on server shutdown). */
|
|
59
|
+
close(): Promise<void>;
|
|
60
|
+
private buildTransportContext;
|
|
61
|
+
/** Reports a session error the way main did: same codes, messages, and statuses. */
|
|
62
|
+
private reportSessionError;
|
|
63
|
+
/**
|
|
64
|
+
* Keeps the session's SSE idle stream alive through proxies and reaps dead
|
|
65
|
+
* clients: pings every 30s and closes the transport after 3 consecutive
|
|
66
|
+
* failures. Not started in JSON response mode, where connections are
|
|
67
|
+
* short-lived and pings aren't needed.
|
|
68
|
+
*/
|
|
69
|
+
private startKeepAliveLoop;
|
|
70
|
+
/**
|
|
71
|
+
* Creates a fresh session: builds a request-scoped server, connects it to a
|
|
72
|
+
* sessionful streamable HTTP transport (so `initialize` negotiates and the
|
|
73
|
+
* SDK keeps the client's capabilities on the connected server), and stores
|
|
74
|
+
* the transport so later requests and the SSE idle stream reuse it (the
|
|
75
|
+
* return channel the legacy elicitation shim needs).
|
|
76
|
+
*/
|
|
77
|
+
private createSession;
|
|
78
|
+
/**
|
|
79
|
+
* Serves a 2025-era request. POSTs that carry no session begin a new session
|
|
80
|
+
* (the `initialize` handshake); POSTs/GETs/DELETEs that carry a session id
|
|
81
|
+
* are routed to the stored transport, preserving the client's negotiated
|
|
82
|
+
* state and providing the SSE return channel for server→client requests.
|
|
83
|
+
*/
|
|
84
|
+
handle(req: express.Request, res: express.Response): Promise<void>;
|
|
85
|
+
/** Routes a session-carrying request to its live transport; reports a session error when missing. */
|
|
86
|
+
private handleSessionRequest;
|
|
87
|
+
}
|
|
88
|
+
//# 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,EACb,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAA6B,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAwBrF,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;CAChC,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;CAClE,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;gBAEhE,EAAE,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,2BAA2B;IAOrF,2DAA2D;IAC9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAInC,OAAO,CAAC,qBAAqB;IAa7B,oFAAoF;IACpF,OAAO,CAAC,kBAAkB;IAsC1B;;;;;OAKG;YACW,kBAAkB;IAgDhC;;;;;;OAMG;YACW,aAAa;IA8D3B;;;;;OAKG;IACU,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAkE/E,qGAAqG;YACvF,oBAAoB;CA0BrC"}
|
|
@@ -0,0 +1,294 @@
|
|
|
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
|
+
/**
|
|
18
|
+
* Serves 2025-era (legacy) MCP requests over streamable HTTP **sessionfully**.
|
|
19
|
+
*
|
|
20
|
+
* The 2026-07-28 protocol is stateless and carries client identity per request
|
|
21
|
+
* in the `_meta` envelope. The 2025 protocol is not: it declares the client's
|
|
22
|
+
* negotiated capabilities (incl. `elicitation`) in the `initialize` handshake
|
|
23
|
+
* and delivers elicitation as real server→client requests. Both need a live,
|
|
24
|
+
* correlated transport, which is exactly what the SDK's legacy elicitation
|
|
25
|
+
* shim relies on. The SDK's `legacy: 'stateless'` mode cannot do that (each
|
|
26
|
+
* request gets a fresh instance: no capabilities, no return channel).
|
|
27
|
+
*
|
|
28
|
+
* This handler owns that sessionful lifecycle: an `initialize` POST creates a
|
|
29
|
+
* session (a connected transport/server pair), later requests and the SSE idle
|
|
30
|
+
* stream reuse the session's transport (the shim's return channel), and a
|
|
31
|
+
* DELETE closes it. Sessions are bounded by the injected `sessionStore`.
|
|
32
|
+
* Keeping it isolated from {@link MCPHttpServer} leaves the modern stateless
|
|
33
|
+
* path clean.
|
|
34
|
+
*/
|
|
35
|
+
export class LegacyMcpHttpHandler {
|
|
36
|
+
constructor({ createServer, logger, http, sessionStore }) {
|
|
37
|
+
this.createServer = createServer;
|
|
38
|
+
this.logger = logger;
|
|
39
|
+
this.http = http;
|
|
40
|
+
this.sessions = sessionStore;
|
|
41
|
+
}
|
|
42
|
+
/** Closes every live session (e.g. on server shutdown). */
|
|
43
|
+
async close() {
|
|
44
|
+
await this.sessions.closeAllSessions();
|
|
45
|
+
}
|
|
46
|
+
buildTransportContext(req) {
|
|
47
|
+
return {
|
|
48
|
+
headers: req.headers,
|
|
49
|
+
query: req.query,
|
|
50
|
+
// The explicit auth state of this request. Legacy serving does not
|
|
51
|
+
// normalize authInfo itself; hosts supply it via `req.auth` when
|
|
52
|
+
// using authenticated mode.
|
|
53
|
+
authInfo: req.auth
|
|
54
|
+
? { mode: "authenticated", state: req.auth }
|
|
55
|
+
: { mode: "unauthenticated" },
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/** Reports a session error the way main did: same codes, messages, and statuses. */
|
|
59
|
+
reportSessionError(res, errorCode) {
|
|
60
|
+
let message;
|
|
61
|
+
let statusCode = 400;
|
|
62
|
+
switch (errorCode) {
|
|
63
|
+
case JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED:
|
|
64
|
+
message = "session id is required";
|
|
65
|
+
break;
|
|
66
|
+
case JSON_RPC_ERROR_CODE_SESSION_ID_INVALID:
|
|
67
|
+
message = "session id is invalid";
|
|
68
|
+
break;
|
|
69
|
+
case JSON_RPC_ERROR_CODE_INVALID_REQUEST:
|
|
70
|
+
message = "invalid request";
|
|
71
|
+
break;
|
|
72
|
+
case JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND:
|
|
73
|
+
message = "session not found";
|
|
74
|
+
statusCode = 404;
|
|
75
|
+
break;
|
|
76
|
+
case JSON_RPC_ERROR_CODE_DISALLOWED_EXTERNAL_SESSION:
|
|
77
|
+
message = "cannot provide sessionId when externally managed sessions are disabled";
|
|
78
|
+
break;
|
|
79
|
+
case JSON_RPC_ERROR_CODE_SESSION_LIMIT_EXCEEDED:
|
|
80
|
+
message = "server has reached the maximum number of concurrent sessions, try again later";
|
|
81
|
+
statusCode = 503;
|
|
82
|
+
break;
|
|
83
|
+
default:
|
|
84
|
+
message = "unknown error";
|
|
85
|
+
statusCode = 500;
|
|
86
|
+
}
|
|
87
|
+
res.status(statusCode).json({
|
|
88
|
+
jsonrpc: "2.0",
|
|
89
|
+
error: {
|
|
90
|
+
code: errorCode,
|
|
91
|
+
message,
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Keeps the session's SSE idle stream alive through proxies and reaps dead
|
|
97
|
+
* clients: pings every 30s and closes the transport after 3 consecutive
|
|
98
|
+
* failures. Not started in JSON response mode, where connections are
|
|
99
|
+
* short-lived and pings aren't needed.
|
|
100
|
+
*/
|
|
101
|
+
async startKeepAliveLoop({ transport, signal, }) {
|
|
102
|
+
if (this.http.responseType === "json") {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
let failedPings = 0;
|
|
106
|
+
while (!signal.aborted) {
|
|
107
|
+
try {
|
|
108
|
+
this.logger.debug({
|
|
109
|
+
id: LogId.streamableHttpTransportKeepAlive,
|
|
110
|
+
context: "streamableHttpTransport",
|
|
111
|
+
message: "Sending ping",
|
|
112
|
+
});
|
|
113
|
+
await transport.send({
|
|
114
|
+
jsonrpc: "2.0",
|
|
115
|
+
method: "ping",
|
|
116
|
+
});
|
|
117
|
+
failedPings = 0;
|
|
118
|
+
}
|
|
119
|
+
catch (err) {
|
|
120
|
+
try {
|
|
121
|
+
failedPings++;
|
|
122
|
+
this.logger.warning({
|
|
123
|
+
id: LogId.streamableHttpTransportKeepAliveFailure,
|
|
124
|
+
context: "streamableHttpTransport",
|
|
125
|
+
message: `Error sending ping (attempt #${failedPings}): ${err instanceof Error ? err.message : String(err)}`,
|
|
126
|
+
});
|
|
127
|
+
if (failedPings > 3) {
|
|
128
|
+
await transport.close();
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Ignore the error of the transport close
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
await sleep(30000, { signal });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Creates a fresh session: builds a request-scoped server, connects it to a
|
|
141
|
+
* sessionful streamable HTTP transport (so `initialize` negotiates and the
|
|
142
|
+
* SDK keeps the client's capabilities on the connected server), and stores
|
|
143
|
+
* the transport so later requests and the SSE idle stream reuse it (the
|
|
144
|
+
* return channel the legacy elicitation shim needs).
|
|
145
|
+
*/
|
|
146
|
+
async createSession(req) {
|
|
147
|
+
const sessionId = getRandomUUID();
|
|
148
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
149
|
+
sessionIdGenerator: () => sessionId,
|
|
150
|
+
enableJsonResponse: this.http.responseType === "json",
|
|
151
|
+
});
|
|
152
|
+
const server = (await this.createServer(this.buildTransportContext(req)));
|
|
153
|
+
// Admit (check cap, evict LRU idle victim). A rejection admits nothing.
|
|
154
|
+
try {
|
|
155
|
+
await this.sessions.addSession({ sessionId, transport, logger: this.logger, headers: req.headers });
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
// Dispose the un-connected server we built for a rejected session.
|
|
159
|
+
await server.close().catch(() => undefined);
|
|
160
|
+
throw error;
|
|
161
|
+
}
|
|
162
|
+
const keepAliveController = new AbortController();
|
|
163
|
+
void this.startKeepAliveLoop({ transport, signal: keepAliveController.signal });
|
|
164
|
+
// When the transport closes (client disconnect or store-initiated
|
|
165
|
+
// eviction/timeout), tear down the server and remove the session.
|
|
166
|
+
transport.onclose = () => {
|
|
167
|
+
keepAliveController.abort();
|
|
168
|
+
void server
|
|
169
|
+
.close()
|
|
170
|
+
.catch((error) => {
|
|
171
|
+
this.logger.error({
|
|
172
|
+
id: LogId.streamableHttpTransportCloseFailure,
|
|
173
|
+
context: "streamableHttpTransport",
|
|
174
|
+
message: `Error closing legacy session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
175
|
+
});
|
|
176
|
+
})
|
|
177
|
+
.finally(() => {
|
|
178
|
+
// The store removes the session before it closes the
|
|
179
|
+
// transport, so on store-initiated closes (eviction, idle
|
|
180
|
+
// timeout) the session is already gone — nothing to do.
|
|
181
|
+
if (!this.sessions.hasSession(sessionId)) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
this.sessions.closeSession({ sessionId, reason: "transport_closed" }).catch((error) => {
|
|
185
|
+
this.logger.error({
|
|
186
|
+
id: LogId.streamableHttpTransportCloseFailure,
|
|
187
|
+
context: "streamableHttpTransport",
|
|
188
|
+
message: `Error removing legacy session ${sessionId}: ${error instanceof Error ? error.message : String(error)}`,
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
await server.connect(transport);
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
// Don't leave a never-connected session registered; closing the
|
|
198
|
+
// transport triggers the onclose teardown above.
|
|
199
|
+
await this.sessions.closeSession({ sessionId, reason: "unknown" }).catch(() => undefined);
|
|
200
|
+
await server.close().catch(() => undefined);
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
return transport;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Serves a 2025-era request. POSTs that carry no session begin a new session
|
|
207
|
+
* (the `initialize` handshake); POSTs/GETs/DELETEs that carry a session id
|
|
208
|
+
* are routed to the stored transport, preserving the client's negotiated
|
|
209
|
+
* state and providing the SSE return channel for server→client requests.
|
|
210
|
+
*/
|
|
211
|
+
async handle(req, res) {
|
|
212
|
+
const sessionId = req.headers["mcp-session-id"];
|
|
213
|
+
if (req.method === "GET") {
|
|
214
|
+
// The SSE idle stream only exists in SSE response mode.
|
|
215
|
+
if (this.http.responseType === "json") {
|
|
216
|
+
res.status(405).set("Allow", ["POST", "DELETE"]).send("Method Not Allowed");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
await this.handleSessionRequest(req, res, sessionId);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (req.method === "DELETE") {
|
|
223
|
+
await this.handleSessionRequest(req, res, sessionId);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
// POST /mcp.
|
|
227
|
+
if (sessionId && typeof sessionId !== "string") {
|
|
228
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_INVALID);
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (isInitializeRequest(req.body)) {
|
|
232
|
+
if (sessionId) {
|
|
233
|
+
// This shim has no externally managed sessions; a client-provided
|
|
234
|
+
// session id on initialize is rejected as on main.
|
|
235
|
+
this.logger.debug({
|
|
236
|
+
id: LogId.streamableHttpTransportDisallowedExternalSessionError,
|
|
237
|
+
context: "streamableHttpTransport",
|
|
238
|
+
message: `Client provided session ID ${sessionId} on initialize, but externally managed sessions are not supported`,
|
|
239
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
240
|
+
});
|
|
241
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_DISALLOWED_EXTERNAL_SESSION);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
let transport;
|
|
245
|
+
try {
|
|
246
|
+
transport = await this.createSession(req);
|
|
247
|
+
}
|
|
248
|
+
catch (error) {
|
|
249
|
+
if (error instanceof SessionLimitExceededError) {
|
|
250
|
+
this.logger.warning({
|
|
251
|
+
id: LogId.streamableHttpTransportRequestFailure,
|
|
252
|
+
context: "streamableHttpTransport",
|
|
253
|
+
message: `Rejecting legacy session startup: ${error.message}`,
|
|
254
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
255
|
+
});
|
|
256
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_LIMIT_EXCEEDED);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
throw error;
|
|
260
|
+
}
|
|
261
|
+
await transport.handleRequest(req, res, req.body);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (!sessionId) {
|
|
265
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_INVALID_REQUEST);
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
await this.handleSessionRequest(req, res, sessionId);
|
|
269
|
+
}
|
|
270
|
+
/** Routes a session-carrying request to its live transport; reports a session error when missing. */
|
|
271
|
+
async handleSessionRequest(req, res, sessionId) {
|
|
272
|
+
if (!sessionId) {
|
|
273
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_REQUIRED);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
if (typeof sessionId !== "string") {
|
|
277
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_ID_INVALID);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
const transport = await this.sessions.getSession(sessionId, req.headers);
|
|
281
|
+
if (!transport) {
|
|
282
|
+
this.logger.debug({
|
|
283
|
+
id: LogId.streamableHttpTransportSessionNotFound,
|
|
284
|
+
context: "streamableHttpTransport",
|
|
285
|
+
message: `Session with ID ${sessionId} not found`,
|
|
286
|
+
attributes: { ...requestIdAttr(req.headers) },
|
|
287
|
+
});
|
|
288
|
+
this.reportSessionError(res, JSON_RPC_ERROR_CODE_SESSION_NOT_FOUND);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
await transport.handleRequest(req, res, req.body);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=legacyMcpHttpHandler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"legacyMcpHttpHandler.js","sourceRoot":"","sources":["../src/legacyMcpHttpHandler.ts"],"names":[],"mappings":"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"}
|
package/dist/mcpHttpServer.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { ICompositeLogger, IMetrics, DefaultMetricDefinitions, TransportRequestContext,
|
|
1
|
+
import { type McpHttpHandler } from "@modelcontextprotocol/server";
|
|
2
|
+
import type { ICompositeLogger, IMetrics, DefaultMetricDefinitions, TransportRequestContext, HttpServerOptions, BaseServer } from "@mongodb-js/mcp-types";
|
|
3
3
|
import { ExpressBasedHttpServer } from "./expressBasedHttpServer.js";
|
|
4
|
+
import { type LegacyMcpHandler, type LegacySessionOptions } from "./legacyMcpHttpHandler.js";
|
|
4
5
|
/**
|
|
5
6
|
* Options for creating an MCPHttpServer instance.
|
|
6
7
|
*/
|
|
@@ -8,19 +9,32 @@ export type MCPHttpServerOptions<TMetrics extends DefaultMetricDefinitions = Def
|
|
|
8
9
|
options: {
|
|
9
10
|
/** HTTP server options */
|
|
10
11
|
http: HttpServerOptions;
|
|
11
|
-
/** Session management options */
|
|
12
|
-
session: SessionManagementOptions;
|
|
13
12
|
};
|
|
14
13
|
/** Logger for the server */
|
|
15
14
|
logger: ICompositeLogger;
|
|
16
15
|
/** Metrics instance */
|
|
17
16
|
metrics: IMetrics<TMetrics>;
|
|
18
|
-
/**
|
|
19
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Tunables for the 2025-era (legacy) session lifecycle: the `maxSessions`
|
|
19
|
+
* cap, the idle/notification timeouts, and the LRU eviction idle grace. See
|
|
20
|
+
* {@link LegacySessionOptions}.
|
|
21
|
+
*/
|
|
22
|
+
sessionOptions?: LegacySessionOptions;
|
|
20
23
|
};
|
|
21
24
|
/**
|
|
22
|
-
* HTTP server that
|
|
25
|
+
* HTTP server that serves MCP requests over HTTP.
|
|
26
|
+
*
|
|
27
|
+
* The 2026-07-28 protocol is served **statelessly** through the SDK's
|
|
28
|
+
* `createMcpHandler` (`legacy: 'reject'`): each request builds a fresh
|
|
29
|
+
* request-scoped server and carries client identity per request in the `_meta`
|
|
30
|
+
* envelope. No per-client sessions, no `mcp-session-id` routing on that path.
|
|
23
31
|
*
|
|
32
|
+
* 2025-era (legacy) requests are delegated to a sessionful
|
|
33
|
+
* {@link LegacyMcpHttpHandler} (see its docs for why): the 2025 protocol needs
|
|
34
|
+
* a live, correlated transport for its `initialize`-declared capabilities and
|
|
35
|
+
* server→client elicitation, which the SDK's `legacy: 'stateless'` mode cannot
|
|
36
|
+
* provide. The modern/legacy split is isolated from this class so each path
|
|
37
|
+
* stays clean.
|
|
24
38
|
*
|
|
25
39
|
* @example
|
|
26
40
|
* ```typescript
|
|
@@ -31,56 +45,41 @@ export type MCPHttpServerOptions<TMetrics extends DefaultMetricDefinitions = Def
|
|
|
31
45
|
* }
|
|
32
46
|
* ```
|
|
33
47
|
*/
|
|
34
|
-
export declare abstract class MCPHttpServer<TServer extends
|
|
35
|
-
|
|
36
|
-
readonly
|
|
48
|
+
export declare abstract class MCPHttpServer<TServer extends BaseServer = BaseServer, TMetrics extends DefaultMetricDefinitions = DefaultMetricDefinitions> extends ExpressBasedHttpServer {
|
|
49
|
+
/** The 2026-07-28 serving entry; modern requests are routed here. */
|
|
50
|
+
private readonly modernHandler;
|
|
51
|
+
/** Sessionful 2025-era serving (initialize/SSE/requests carried per session). */
|
|
52
|
+
private readonly legacyHandler;
|
|
37
53
|
protected readonly metrics: IMetrics<TMetrics>;
|
|
38
|
-
|
|
39
|
-
constructor({ options, logger, metrics, sessionStore }: MCPHttpServerOptions<TMetrics>);
|
|
54
|
+
constructor({ options, logger, metrics, sessionOptions }: MCPHttpServerOptions<TMetrics>);
|
|
40
55
|
stop(): Promise<void>;
|
|
56
|
+
/**
|
|
57
|
+
* Builds the handler that serves 2025-era (legacy) traffic. Subclasses may
|
|
58
|
+
* override this to plug in an alternative legacy transport/session
|
|
59
|
+
* implementation; the default returns a {@link LegacyMcpHttpHandler} built
|
|
60
|
+
* from {@link MCPHttpServer.createServerForRequest}.
|
|
61
|
+
*/
|
|
62
|
+
protected createLegacyHandler({ logger, http, sessionOptions, }: {
|
|
63
|
+
logger: ICompositeLogger;
|
|
64
|
+
http: HttpServerOptions;
|
|
65
|
+
sessionOptions?: LegacySessionOptions;
|
|
66
|
+
}): LegacyMcpHandler;
|
|
41
67
|
/**
|
|
42
68
|
* Creates a server instance for a specific request. Override this method
|
|
43
69
|
* in subclasses to customize per-request server creation.
|
|
44
70
|
*/
|
|
45
71
|
protected abstract createServerForRequest(request: TransportRequestContext): Promise<TServer>;
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Ensures the session for the given sessionId is initialized, serializing
|
|
50
|
-
* concurrent initialization attempts so only one runs at a time.
|
|
51
|
-
*
|
|
52
|
-
* If a session already exists in the store, this is a no-op.
|
|
53
|
-
* If another request is already initializing this session, this call waits
|
|
54
|
-
* for that initialization to complete.
|
|
55
|
-
* Otherwise, this call performs the initialization.
|
|
56
|
-
*
|
|
57
|
-
* After this method resolves, the caller should look up the transport from
|
|
58
|
-
* the session store via `sessionStore.getSession()`.
|
|
59
|
-
*
|
|
60
|
-
* When `isImplicitInitialization` is true, the transport is pre-configured as
|
|
61
|
-
* initialized (bypassing the MCP initialize handshake) so that it can handle
|
|
62
|
-
* non-initialize requests immediately. When false, the transport is left in
|
|
63
|
-
* its default state so it can process the initialize request normally.
|
|
64
|
-
*/
|
|
65
|
-
private ensureSessionInitialized;
|
|
66
|
-
/**
|
|
67
|
-
* Restores the client state negotiated during the session's original
|
|
68
|
-
* initialization onto an implicitly re-initialized server. The server on
|
|
69
|
-
* this pod never saw the client's `initialize` request, so without this
|
|
70
|
-
* it would treat the client as capability-less — e.g. skipping
|
|
71
|
-
* confirmation elicitation for destructive tools. No-op when the session
|
|
72
|
-
* store does not persist negotiated client state.
|
|
73
|
-
*/
|
|
74
|
-
private restoreNegotiatedClientState;
|
|
72
|
+
/** Request middleware hook for `/mcp`, called after body parse + header validation, before routing. */
|
|
73
|
+
protected registerMiddlewares(): void;
|
|
75
74
|
/**
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* `
|
|
80
|
-
*
|
|
75
|
+
* Builds the 2026-07-28 serving entry. One factory backs every modern
|
|
76
|
+
* request: it constructs a fresh request-scoped server, registers it, and
|
|
77
|
+
* hands the underlying {@link McpServer} to `createMcpHandler`.
|
|
78
|
+
* `legacy: 'reject'` means this entry serves ONLY the 2026-07-28 era;
|
|
79
|
+
* 2025-era traffic is routed to the sessionful legacy handler (see
|
|
80
|
+
* {@link LegacyMcpHttpHandler}).
|
|
81
81
|
*/
|
|
82
|
-
|
|
83
|
-
protected setupMiddlewares(): void;
|
|
82
|
+
protected createModernHandler(): McpHttpHandler;
|
|
84
83
|
protected setupRoutes(): Promise<void>;
|
|
85
84
|
private withErrorHandling;
|
|
86
85
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcpHttpServer.d.ts","sourceRoot":"","sources":["../src/mcpHttpServer.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"mcpHttpServer.d.ts","sourceRoot":"","sources":["../src/mcpHttpServer.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,cAAc,EAEtB,MAAM,8BAA8B,CAAC;AAItC,OAAO,KAAK,EACR,gBAAgB,EAChB,QAAQ,EACR,wBAAwB,EACxB,uBAAuB,EACvB,iBAAiB,EACjB,UAAU,EACb,MAAM,uBAAuB,CAAC;AAQ/B,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAwB,KAAK,gBAAgB,EAAE,KAAK,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AAEnH;;GAEG;AACH,MAAM,MAAM,oBAAoB,CAAC,QAAQ,SAAS,wBAAwB,GAAG,wBAAwB,IAAI;IACrG,OAAO,EAAE;QACL,0BAA0B;QAC1B,IAAI,EAAE,iBAAiB,CAAC;KAC3B,CAAC;IACF,4BAA4B;IAC5B,MAAM,EAAE,gBAAgB,CAAC;IACzB,uBAAuB;IACvB,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACzC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,8BAAsB,aAAa,CAC/B,OAAO,SAAS,UAAU,GAAG,UAAU,EACvC,QAAQ,SAAS,wBAAwB,GAAG,wBAAwB,CACtE,SAAQ,sBAAsB;IAC5B,qEAAqE;IACrE,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAiB;IAC/C,iFAAiF;IACjF,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAmB;IACjD,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;gBAEnC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,oBAAoB,CAAC,QAAQ,CAAC;IAa3E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAKlC;;;;;OAKG;IACH,SAAS,CAAC,mBAAmB,CAAC,EAC1B,MAAM,EACN,IAAI,EACJ,cAAc,GACjB,EAAE;QACC,MAAM,EAAE,gBAAgB,CAAC;QACzB,IAAI,EAAE,iBAAiB,CAAC;QACxB,cAAc,CAAC,EAAE,oBAAoB,CAAC;KACzC,GAAG,gBAAgB;IAkBpB;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,sBAAsB,CAAC,OAAO,EAAE,uBAAuB,GAAG,OAAO,CAAC,OAAO,CAAC;IAE7F,uGAAuG;IACvG,SAAS,CAAC,mBAAmB,IAAI,IAAI;IAErC;;;;;;;OAOG;IACH,SAAS,CAAC,mBAAmB,IAAI,cAAc;cAgDtB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAiDrD,OAAO,CAAC,iBAAiB;CA0B5B"}
|