@modelcontextprotocol/sdk 1.24.2 → 1.25.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/cjs/client/auth-extensions.js +5 -9
- package/dist/cjs/client/auth-extensions.js.map +1 -1
- package/dist/cjs/client/auth.js +30 -37
- package/dist/cjs/client/auth.js.map +1 -1
- package/dist/cjs/client/index.d.ts +61 -14
- package/dist/cjs/client/index.d.ts.map +1 -1
- package/dist/cjs/client/index.js +117 -36
- package/dist/cjs/client/index.js.map +1 -1
- package/dist/cjs/client/middleware.js +3 -3
- package/dist/cjs/client/middleware.js.map +1 -1
- package/dist/cjs/client/sse.d.ts.map +1 -1
- package/dist/cjs/client/sse.js +20 -26
- package/dist/cjs/client/sse.js.map +1 -1
- package/dist/cjs/client/stdio.js +18 -28
- package/dist/cjs/client/stdio.js.map +1 -1
- package/dist/cjs/client/streamableHttp.d.ts.map +1 -1
- package/dist/cjs/client/streamableHttp.js +42 -47
- package/dist/cjs/client/streamableHttp.js.map +1 -1
- package/dist/cjs/client/websocket.js +6 -11
- package/dist/cjs/client/websocket.js.map +1 -1
- package/dist/cjs/examples/client/elicitationUrlExample.js +4 -5
- package/dist/cjs/examples/client/elicitationUrlExample.js.map +1 -1
- package/dist/cjs/examples/client/simpleOAuthClient.js +3 -3
- package/dist/cjs/examples/client/simpleOAuthClient.js.map +1 -1
- package/dist/cjs/examples/client/simpleStreamableHttp.js +7 -9
- package/dist/cjs/examples/client/simpleStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/client/simpleTaskInteractiveClient.js +1 -2
- package/dist/cjs/examples/client/simpleTaskInteractiveClient.js.map +1 -1
- package/dist/cjs/examples/server/honoWebStandardStreamableHttp.d.ts +10 -0
- package/dist/cjs/examples/server/honoWebStandardStreamableHttp.d.ts.map +1 -0
- package/dist/cjs/examples/server/honoWebStandardStreamableHttp.js +81 -0
- package/dist/cjs/examples/server/honoWebStandardStreamableHttp.js.map +1 -0
- package/dist/cjs/examples/server/jsonResponseStreamableHttp.js +10 -4
- package/dist/cjs/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/simpleSseServer.js +6 -3
- package/dist/cjs/examples/server/simpleSseServer.js.map +1 -1
- package/dist/cjs/examples/server/simpleStatelessStreamableHttp.js +12 -6
- package/dist/cjs/examples/server/simpleStatelessStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/simpleStreamableHttp.js +21 -11
- package/dist/cjs/examples/server/simpleStreamableHttp.js.map +1 -1
- package/dist/cjs/examples/server/simpleTaskInteractive.js +7 -11
- package/dist/cjs/examples/server/simpleTaskInteractive.js.map +1 -1
- package/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js +10 -7
- package/dist/cjs/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js +1 -1
- package/dist/cjs/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/dist/cjs/experimental/tasks/client.js +1 -2
- package/dist/cjs/experimental/tasks/client.js.map +1 -1
- package/dist/cjs/experimental/tasks/helpers.js +3 -5
- package/dist/cjs/experimental/tasks/helpers.js.map +1 -1
- package/dist/cjs/experimental/tasks/interfaces.d.ts +3 -3
- package/dist/cjs/experimental/tasks/interfaces.d.ts.map +1 -1
- package/dist/cjs/experimental/tasks/stores/in-memory.d.ts +1 -1
- package/dist/cjs/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/dist/cjs/experimental/tasks/stores/in-memory.js +3 -5
- package/dist/cjs/experimental/tasks/stores/in-memory.js.map +1 -1
- package/dist/cjs/inMemory.js +5 -7
- package/dist/cjs/inMemory.js.map +1 -1
- package/dist/cjs/server/auth/handlers/token.js +1 -1
- package/dist/cjs/server/auth/handlers/token.js.map +1 -1
- package/dist/cjs/server/auth/providers/proxyProvider.js +11 -17
- package/dist/cjs/server/auth/providers/proxyProvider.js.map +1 -1
- package/dist/cjs/server/auth/router.js +3 -6
- package/dist/cjs/server/auth/router.js.map +1 -1
- package/dist/cjs/server/completable.js +1 -1
- package/dist/cjs/server/completable.js.map +1 -1
- package/dist/cjs/server/index.d.ts +3 -3
- package/dist/cjs/server/index.d.ts.map +1 -1
- package/dist/cjs/server/index.js +20 -30
- package/dist/cjs/server/index.js.map +1 -1
- package/dist/cjs/server/mcp.d.ts.map +1 -1
- package/dist/cjs/server/mcp.js +25 -11
- package/dist/cjs/server/mcp.js.map +1 -1
- package/dist/cjs/server/middleware/hostHeaderValidation.js +1 -1
- package/dist/cjs/server/middleware/hostHeaderValidation.js.map +1 -1
- package/dist/cjs/server/sse.js +14 -17
- package/dist/cjs/server/sse.js.map +1 -1
- package/dist/cjs/server/stdio.js +4 -7
- package/dist/cjs/server/stdio.js.map +1 -1
- package/dist/cjs/server/streamableHttp.d.ts +52 -153
- package/dist/cjs/server/streamableHttp.d.ts.map +1 -1
- package/dist/cjs/server/streamableHttp.js +78 -636
- package/dist/cjs/server/streamableHttp.js.map +1 -1
- package/dist/cjs/server/webStandardStreamableHttp.d.ts +267 -0
- package/dist/cjs/server/webStandardStreamableHttp.d.ts.map +1 -0
- package/dist/cjs/server/webStandardStreamableHttp.js +729 -0
- package/dist/cjs/server/webStandardStreamableHttp.js.map +1 -0
- package/dist/cjs/server/zod-compat.d.ts +3 -1
- package/dist/cjs/server/zod-compat.d.ts.map +1 -1
- package/dist/cjs/server/zod-compat.js +11 -19
- package/dist/cjs/server/zod-compat.js.map +1 -1
- package/dist/cjs/server/zod-json-schema-compat.js +5 -6
- package/dist/cjs/server/zod-json-schema-compat.js.map +1 -1
- package/dist/cjs/shared/metadataUtils.js +1 -2
- package/dist/cjs/shared/metadataUtils.js.map +1 -1
- package/dist/cjs/shared/protocol.d.ts +1 -1
- package/dist/cjs/shared/protocol.d.ts.map +1 -1
- package/dist/cjs/shared/protocol.js +76 -88
- package/dist/cjs/shared/protocol.js.map +1 -1
- package/dist/cjs/shared/transport.js +1 -1
- package/dist/cjs/shared/transport.js.map +1 -1
- package/dist/cjs/spec.types.d.ts +315 -26
- package/dist/cjs/spec.types.d.ts.map +1 -1
- package/dist/cjs/spec.types.js +2 -2
- package/dist/cjs/spec.types.js.map +1 -1
- package/dist/cjs/types.d.ts +1392 -1248
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/types.js +201 -136
- package/dist/cjs/types.js.map +1 -1
- package/dist/cjs/validation/ajv-provider.d.ts +1 -1
- package/dist/cjs/validation/ajv-provider.d.ts.map +1 -1
- package/dist/cjs/validation/ajv-provider.js +4 -5
- package/dist/cjs/validation/ajv-provider.js.map +1 -1
- package/dist/cjs/validation/cfworker-provider.js +4 -5
- package/dist/cjs/validation/cfworker-provider.js.map +1 -1
- package/dist/cjs/validation/types.d.ts +12 -2
- package/dist/cjs/validation/types.d.ts.map +1 -1
- package/dist/esm/client/auth-extensions.js +5 -9
- package/dist/esm/client/auth-extensions.js.map +1 -1
- package/dist/esm/client/auth.js +30 -37
- package/dist/esm/client/auth.js.map +1 -1
- package/dist/esm/client/index.d.ts +61 -14
- package/dist/esm/client/index.d.ts.map +1 -1
- package/dist/esm/client/index.js +118 -37
- package/dist/esm/client/index.js.map +1 -1
- package/dist/esm/client/middleware.js +3 -3
- package/dist/esm/client/middleware.js.map +1 -1
- package/dist/esm/client/sse.d.ts.map +1 -1
- package/dist/esm/client/sse.js +20 -26
- package/dist/esm/client/sse.js.map +1 -1
- package/dist/esm/client/stdio.js +18 -28
- package/dist/esm/client/stdio.js.map +1 -1
- package/dist/esm/client/streamableHttp.d.ts.map +1 -1
- package/dist/esm/client/streamableHttp.js +43 -48
- package/dist/esm/client/streamableHttp.js.map +1 -1
- package/dist/esm/client/websocket.js +6 -11
- package/dist/esm/client/websocket.js.map +1 -1
- package/dist/esm/examples/client/elicitationUrlExample.js +4 -5
- package/dist/esm/examples/client/elicitationUrlExample.js.map +1 -1
- package/dist/esm/examples/client/simpleOAuthClient.js +3 -3
- package/dist/esm/examples/client/simpleOAuthClient.js.map +1 -1
- package/dist/esm/examples/client/simpleStreamableHttp.js +7 -9
- package/dist/esm/examples/client/simpleStreamableHttp.js.map +1 -1
- package/dist/esm/examples/client/simpleTaskInteractiveClient.js +1 -2
- package/dist/esm/examples/client/simpleTaskInteractiveClient.js.map +1 -1
- package/dist/esm/examples/server/honoWebStandardStreamableHttp.d.ts +10 -0
- package/dist/esm/examples/server/honoWebStandardStreamableHttp.d.ts.map +1 -0
- package/dist/esm/examples/server/honoWebStandardStreamableHttp.js +56 -0
- package/dist/esm/examples/server/honoWebStandardStreamableHttp.js.map +1 -0
- package/dist/esm/examples/server/jsonResponseStreamableHttp.js +10 -4
- package/dist/esm/examples/server/jsonResponseStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/simpleSseServer.js +6 -3
- package/dist/esm/examples/server/simpleSseServer.js.map +1 -1
- package/dist/esm/examples/server/simpleStatelessStreamableHttp.js +12 -6
- package/dist/esm/examples/server/simpleStatelessStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/simpleStreamableHttp.js +21 -11
- package/dist/esm/examples/server/simpleStreamableHttp.js.map +1 -1
- package/dist/esm/examples/server/simpleTaskInteractive.js +7 -11
- package/dist/esm/examples/server/simpleTaskInteractive.js.map +1 -1
- package/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js +10 -7
- package/dist/esm/examples/server/sseAndStreamableHttpCompatibleServer.js.map +1 -1
- package/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js +1 -1
- package/dist/esm/examples/server/standaloneSseWithGetStreamableHttp.js.map +1 -1
- package/dist/esm/experimental/tasks/client.js +1 -2
- package/dist/esm/experimental/tasks/client.js.map +1 -1
- package/dist/esm/experimental/tasks/helpers.js +3 -5
- package/dist/esm/experimental/tasks/helpers.js.map +1 -1
- package/dist/esm/experimental/tasks/interfaces.d.ts +3 -3
- package/dist/esm/experimental/tasks/interfaces.d.ts.map +1 -1
- package/dist/esm/experimental/tasks/stores/in-memory.d.ts +1 -1
- package/dist/esm/experimental/tasks/stores/in-memory.d.ts.map +1 -1
- package/dist/esm/experimental/tasks/stores/in-memory.js +3 -5
- package/dist/esm/experimental/tasks/stores/in-memory.js.map +1 -1
- package/dist/esm/inMemory.js +5 -7
- package/dist/esm/inMemory.js.map +1 -1
- package/dist/esm/server/auth/handlers/token.js +1 -1
- package/dist/esm/server/auth/handlers/token.js.map +1 -1
- package/dist/esm/server/auth/providers/proxyProvider.js +11 -17
- package/dist/esm/server/auth/providers/proxyProvider.js.map +1 -1
- package/dist/esm/server/auth/router.js +3 -6
- package/dist/esm/server/auth/router.js.map +1 -1
- package/dist/esm/server/completable.js +1 -1
- package/dist/esm/server/completable.js.map +1 -1
- package/dist/esm/server/index.d.ts +3 -3
- package/dist/esm/server/index.d.ts.map +1 -1
- package/dist/esm/server/index.js +20 -30
- package/dist/esm/server/index.js.map +1 -1
- package/dist/esm/server/mcp.d.ts.map +1 -1
- package/dist/esm/server/mcp.js +25 -11
- package/dist/esm/server/mcp.js.map +1 -1
- package/dist/esm/server/middleware/hostHeaderValidation.js +1 -1
- package/dist/esm/server/middleware/hostHeaderValidation.js.map +1 -1
- package/dist/esm/server/sse.js +14 -17
- package/dist/esm/server/sse.js.map +1 -1
- package/dist/esm/server/stdio.js +4 -7
- package/dist/esm/server/stdio.js.map +1 -1
- package/dist/esm/server/streamableHttp.d.ts +52 -153
- package/dist/esm/server/streamableHttp.d.ts.map +1 -1
- package/dist/esm/server/streamableHttp.js +78 -633
- package/dist/esm/server/streamableHttp.js.map +1 -1
- package/dist/esm/server/webStandardStreamableHttp.d.ts +267 -0
- package/dist/esm/server/webStandardStreamableHttp.d.ts.map +1 -0
- package/dist/esm/server/webStandardStreamableHttp.js +725 -0
- package/dist/esm/server/webStandardStreamableHttp.js.map +1 -0
- package/dist/esm/server/zod-compat.d.ts +3 -1
- package/dist/esm/server/zod-compat.d.ts.map +1 -1
- package/dist/esm/server/zod-compat.js +11 -19
- package/dist/esm/server/zod-compat.js.map +1 -1
- package/dist/esm/server/zod-json-schema-compat.js +5 -6
- package/dist/esm/server/zod-json-schema-compat.js.map +1 -1
- package/dist/esm/shared/metadataUtils.js +1 -2
- package/dist/esm/shared/metadataUtils.js.map +1 -1
- package/dist/esm/shared/protocol.d.ts +1 -1
- package/dist/esm/shared/protocol.d.ts.map +1 -1
- package/dist/esm/shared/protocol.js +77 -89
- package/dist/esm/shared/protocol.js.map +1 -1
- package/dist/esm/shared/transport.js +1 -1
- package/dist/esm/shared/transport.js.map +1 -1
- package/dist/esm/spec.types.d.ts +315 -26
- package/dist/esm/spec.types.d.ts.map +1 -1
- package/dist/esm/spec.types.js +2 -2
- package/dist/esm/spec.types.js.map +1 -1
- package/dist/esm/types.d.ts +1392 -1248
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/types.js +194 -130
- package/dist/esm/types.js.map +1 -1
- package/dist/esm/validation/ajv-provider.d.ts +1 -1
- package/dist/esm/validation/ajv-provider.d.ts.map +1 -1
- package/dist/esm/validation/ajv-provider.js +3 -4
- package/dist/esm/validation/ajv-provider.js.map +1 -1
- package/dist/esm/validation/cfworker-provider.js +4 -5
- package/dist/esm/validation/cfworker-provider.js.map +1 -1
- package/dist/esm/validation/types.d.ts +12 -2
- package/dist/esm/validation/types.d.ts.map +1 -1
- package/package.json +4 -2
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Node.js HTTP Streamable HTTP Server Transport
|
|
3
|
+
*
|
|
4
|
+
* This is a thin wrapper around `WebStandardStreamableHTTPServerTransport` that provides
|
|
5
|
+
* compatibility with Node.js HTTP server (IncomingMessage/ServerResponse).
|
|
6
|
+
*
|
|
7
|
+
* For web-standard environments (Cloudflare Workers, Deno, Bun), use `WebStandardStreamableHTTPServerTransport` directly.
|
|
8
|
+
*/
|
|
9
|
+
import { getRequestListener } from '@hono/node-server';
|
|
10
|
+
import { WebStandardStreamableHTTPServerTransport } from './webStandardStreamableHttp.js';
|
|
6
11
|
/**
|
|
7
12
|
* Server transport for Streamable HTTP: this implements the MCP Streamable HTTP transport specification.
|
|
8
13
|
* It supports both SSE streaming and direct HTTP responses.
|
|
9
14
|
*
|
|
15
|
+
* This is a wrapper around `WebStandardStreamableHTTPServerTransport` that provides Node.js HTTP compatibility.
|
|
16
|
+
* It uses the `@hono/node-server` library to convert between Node.js HTTP and Web Standard APIs.
|
|
17
|
+
*
|
|
10
18
|
* Usage example:
|
|
11
19
|
*
|
|
12
20
|
* ```typescript
|
|
@@ -38,562 +46,97 @@ const MAXIMUM_MESSAGE_SIZE = '4mb';
|
|
|
38
46
|
* - No session validation is performed
|
|
39
47
|
*/
|
|
40
48
|
export class StreamableHTTPServerTransport {
|
|
41
|
-
constructor(options) {
|
|
42
|
-
|
|
43
|
-
this.
|
|
44
|
-
this.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
this._allowedHosts = options.allowedHosts;
|
|
56
|
-
this._allowedOrigins = options.allowedOrigins;
|
|
57
|
-
this._enableDnsRebindingProtection = (_b = options.enableDnsRebindingProtection) !== null && _b !== void 0 ? _b : false;
|
|
58
|
-
this._retryInterval = options.retryInterval;
|
|
49
|
+
constructor(options = {}) {
|
|
50
|
+
// Store auth and parsedBody per request for passing through to handleRequest
|
|
51
|
+
this._requestContext = new WeakMap();
|
|
52
|
+
this._webStandardTransport = new WebStandardStreamableHTTPServerTransport(options);
|
|
53
|
+
// Create a request listener that wraps the web standard transport
|
|
54
|
+
// getRequestListener converts Node.js HTTP to Web Standard and properly handles SSE streaming
|
|
55
|
+
this._requestListener = getRequestListener(async (webRequest) => {
|
|
56
|
+
// Get context if available (set during handleRequest)
|
|
57
|
+
const context = this._requestContext.get(webRequest);
|
|
58
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
59
|
+
authInfo: context?.authInfo,
|
|
60
|
+
parsedBody: context?.parsedBody
|
|
61
|
+
});
|
|
62
|
+
});
|
|
59
63
|
}
|
|
60
64
|
/**
|
|
61
|
-
*
|
|
62
|
-
* for the Streamable HTTP transport as connections are managed per-request.
|
|
65
|
+
* Gets the session ID for this transport instance.
|
|
63
66
|
*/
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
throw new Error('Transport already started');
|
|
67
|
-
}
|
|
68
|
-
this._started = true;
|
|
67
|
+
get sessionId() {
|
|
68
|
+
return this._webStandardTransport.sessionId;
|
|
69
69
|
}
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
* @returns Error message if validation fails, undefined if validation passes.
|
|
71
|
+
* Sets callback for when the transport is closed.
|
|
73
72
|
*/
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!this._enableDnsRebindingProtection) {
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
// Validate Host header if allowedHosts is configured
|
|
80
|
-
if (this._allowedHosts && this._allowedHosts.length > 0) {
|
|
81
|
-
const hostHeader = req.headers.host;
|
|
82
|
-
if (!hostHeader || !this._allowedHosts.includes(hostHeader)) {
|
|
83
|
-
return `Invalid Host header: ${hostHeader}`;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// Validate Origin header if allowedOrigins is configured
|
|
87
|
-
if (this._allowedOrigins && this._allowedOrigins.length > 0) {
|
|
88
|
-
const originHeader = req.headers.origin;
|
|
89
|
-
if (originHeader && !this._allowedOrigins.includes(originHeader)) {
|
|
90
|
-
return `Invalid Origin header: ${originHeader}`;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
return undefined;
|
|
73
|
+
set onclose(handler) {
|
|
74
|
+
this._webStandardTransport.onclose = handler;
|
|
94
75
|
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
*/
|
|
98
|
-
async handleRequest(req, res, parsedBody) {
|
|
99
|
-
var _a;
|
|
100
|
-
// Validate request headers for DNS rebinding protection
|
|
101
|
-
const validationError = this.validateRequestHeaders(req);
|
|
102
|
-
if (validationError) {
|
|
103
|
-
res.writeHead(403).end(JSON.stringify({
|
|
104
|
-
jsonrpc: '2.0',
|
|
105
|
-
error: {
|
|
106
|
-
code: -32000,
|
|
107
|
-
message: validationError
|
|
108
|
-
},
|
|
109
|
-
id: null
|
|
110
|
-
}));
|
|
111
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error(validationError));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
if (req.method === 'POST') {
|
|
115
|
-
await this.handlePostRequest(req, res, parsedBody);
|
|
116
|
-
}
|
|
117
|
-
else if (req.method === 'GET') {
|
|
118
|
-
await this.handleGetRequest(req, res);
|
|
119
|
-
}
|
|
120
|
-
else if (req.method === 'DELETE') {
|
|
121
|
-
await this.handleDeleteRequest(req, res);
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
await this.handleUnsupportedRequest(res);
|
|
125
|
-
}
|
|
76
|
+
get onclose() {
|
|
77
|
+
return this._webStandardTransport.onclose;
|
|
126
78
|
}
|
|
127
79
|
/**
|
|
128
|
-
*
|
|
129
|
-
* Only sends if eventStore is configured (opt-in for resumability).
|
|
80
|
+
* Sets callback for transport errors.
|
|
130
81
|
*/
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const primingEventId = await this._eventStore.storeEvent(streamId, {});
|
|
136
|
-
let primingEvent = `id: ${primingEventId}\ndata: \n\n`;
|
|
137
|
-
if (this._retryInterval !== undefined) {
|
|
138
|
-
primingEvent = `id: ${primingEventId}\nretry: ${this._retryInterval}\ndata: \n\n`;
|
|
139
|
-
}
|
|
140
|
-
res.write(primingEvent);
|
|
82
|
+
set onerror(handler) {
|
|
83
|
+
this._webStandardTransport.onerror = handler;
|
|
141
84
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
*/
|
|
145
|
-
async handleGetRequest(req, res) {
|
|
146
|
-
// The client MUST include an Accept header, listing text/event-stream as a supported content type.
|
|
147
|
-
const acceptHeader = req.headers.accept;
|
|
148
|
-
if (!(acceptHeader === null || acceptHeader === void 0 ? void 0 : acceptHeader.includes('text/event-stream'))) {
|
|
149
|
-
res.writeHead(406).end(JSON.stringify({
|
|
150
|
-
jsonrpc: '2.0',
|
|
151
|
-
error: {
|
|
152
|
-
code: -32000,
|
|
153
|
-
message: 'Not Acceptable: Client must accept text/event-stream'
|
|
154
|
-
},
|
|
155
|
-
id: null
|
|
156
|
-
}));
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
// If an Mcp-Session-Id is returned by the server during initialization,
|
|
160
|
-
// clients using the Streamable HTTP transport MUST include it
|
|
161
|
-
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
|
|
162
|
-
if (!this.validateSession(req, res)) {
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
if (!this.validateProtocolVersion(req, res)) {
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
// Handle resumability: check for Last-Event-ID header
|
|
169
|
-
if (this._eventStore) {
|
|
170
|
-
const lastEventId = req.headers['last-event-id'];
|
|
171
|
-
if (lastEventId) {
|
|
172
|
-
await this.replayEvents(lastEventId, res);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// The server MUST either return Content-Type: text/event-stream in response to this HTTP GET,
|
|
177
|
-
// or else return HTTP 405 Method Not Allowed
|
|
178
|
-
const headers = {
|
|
179
|
-
'Content-Type': 'text/event-stream',
|
|
180
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
181
|
-
Connection: 'keep-alive'
|
|
182
|
-
};
|
|
183
|
-
// After initialization, always include the session ID if we have one
|
|
184
|
-
if (this.sessionId !== undefined) {
|
|
185
|
-
headers['mcp-session-id'] = this.sessionId;
|
|
186
|
-
}
|
|
187
|
-
// Check if there's already an active standalone SSE stream for this session
|
|
188
|
-
if (this._streamMapping.get(this._standaloneSseStreamId) !== undefined) {
|
|
189
|
-
// Only one GET SSE stream is allowed per session
|
|
190
|
-
res.writeHead(409).end(JSON.stringify({
|
|
191
|
-
jsonrpc: '2.0',
|
|
192
|
-
error: {
|
|
193
|
-
code: -32000,
|
|
194
|
-
message: 'Conflict: Only one SSE stream is allowed per session'
|
|
195
|
-
},
|
|
196
|
-
id: null
|
|
197
|
-
}));
|
|
198
|
-
return;
|
|
199
|
-
}
|
|
200
|
-
// We need to send headers immediately as messages will arrive much later,
|
|
201
|
-
// otherwise the client will just wait for the first message
|
|
202
|
-
res.writeHead(200, headers).flushHeaders();
|
|
203
|
-
// Assign the response to the standalone SSE stream
|
|
204
|
-
this._streamMapping.set(this._standaloneSseStreamId, res);
|
|
205
|
-
// Set up close handler for client disconnects
|
|
206
|
-
res.on('close', () => {
|
|
207
|
-
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
208
|
-
});
|
|
209
|
-
// Add error handler for standalone SSE stream
|
|
210
|
-
res.on('error', error => {
|
|
211
|
-
var _a;
|
|
212
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
213
|
-
});
|
|
85
|
+
get onerror() {
|
|
86
|
+
return this._webStandardTransport.onerror;
|
|
214
87
|
}
|
|
215
88
|
/**
|
|
216
|
-
*
|
|
217
|
-
* Only used when resumability is enabled
|
|
89
|
+
* Sets callback for incoming messages.
|
|
218
90
|
*/
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
if (!this._eventStore) {
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
// If getStreamIdForEventId is available, use it for conflict checking
|
|
226
|
-
let streamId;
|
|
227
|
-
if (this._eventStore.getStreamIdForEventId) {
|
|
228
|
-
streamId = await this._eventStore.getStreamIdForEventId(lastEventId);
|
|
229
|
-
if (!streamId) {
|
|
230
|
-
res.writeHead(400).end(JSON.stringify({
|
|
231
|
-
jsonrpc: '2.0',
|
|
232
|
-
error: {
|
|
233
|
-
code: -32000,
|
|
234
|
-
message: 'Invalid event ID format'
|
|
235
|
-
},
|
|
236
|
-
id: null
|
|
237
|
-
}));
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
// Check conflict with the SAME streamId we'll use for mapping
|
|
241
|
-
if (this._streamMapping.get(streamId) !== undefined) {
|
|
242
|
-
res.writeHead(409).end(JSON.stringify({
|
|
243
|
-
jsonrpc: '2.0',
|
|
244
|
-
error: {
|
|
245
|
-
code: -32000,
|
|
246
|
-
message: 'Conflict: Stream already has an active connection'
|
|
247
|
-
},
|
|
248
|
-
id: null
|
|
249
|
-
}));
|
|
250
|
-
return;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
const headers = {
|
|
254
|
-
'Content-Type': 'text/event-stream',
|
|
255
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
256
|
-
Connection: 'keep-alive'
|
|
257
|
-
};
|
|
258
|
-
if (this.sessionId !== undefined) {
|
|
259
|
-
headers['mcp-session-id'] = this.sessionId;
|
|
260
|
-
}
|
|
261
|
-
res.writeHead(200, headers).flushHeaders();
|
|
262
|
-
// Replay events - returns the streamId for backwards compatibility
|
|
263
|
-
const replayedStreamId = await this._eventStore.replayEventsAfter(lastEventId, {
|
|
264
|
-
send: async (eventId, message) => {
|
|
265
|
-
var _a;
|
|
266
|
-
if (!this.writeSSEEvent(res, message, eventId)) {
|
|
267
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, new Error('Failed replay events'));
|
|
268
|
-
res.end();
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
});
|
|
272
|
-
this._streamMapping.set(replayedStreamId, res);
|
|
273
|
-
// Set up close handler for client disconnects
|
|
274
|
-
res.on('close', () => {
|
|
275
|
-
this._streamMapping.delete(replayedStreamId);
|
|
276
|
-
});
|
|
277
|
-
// Add error handler for replay stream
|
|
278
|
-
res.on('error', error => {
|
|
279
|
-
var _a;
|
|
280
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
catch (error) {
|
|
284
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
285
|
-
}
|
|
91
|
+
set onmessage(handler) {
|
|
92
|
+
this._webStandardTransport.onmessage = handler;
|
|
286
93
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
*/
|
|
290
|
-
writeSSEEvent(res, message, eventId) {
|
|
291
|
-
let eventData = `event: message\n`;
|
|
292
|
-
// Include event ID if provided - this is important for resumability
|
|
293
|
-
if (eventId) {
|
|
294
|
-
eventData += `id: ${eventId}\n`;
|
|
295
|
-
}
|
|
296
|
-
eventData += `data: ${JSON.stringify(message)}\n\n`;
|
|
297
|
-
return res.write(eventData);
|
|
94
|
+
get onmessage() {
|
|
95
|
+
return this._webStandardTransport.onmessage;
|
|
298
96
|
}
|
|
299
97
|
/**
|
|
300
|
-
*
|
|
98
|
+
* Starts the transport. This is required by the Transport interface but is a no-op
|
|
99
|
+
* for the Streamable HTTP transport as connections are managed per-request.
|
|
301
100
|
*/
|
|
302
|
-
async
|
|
303
|
-
|
|
304
|
-
Allow: 'GET, POST, DELETE'
|
|
305
|
-
}).end(JSON.stringify({
|
|
306
|
-
jsonrpc: '2.0',
|
|
307
|
-
error: {
|
|
308
|
-
code: -32000,
|
|
309
|
-
message: 'Method not allowed.'
|
|
310
|
-
},
|
|
311
|
-
id: null
|
|
312
|
-
}));
|
|
101
|
+
async start() {
|
|
102
|
+
return this._webStandardTransport.start();
|
|
313
103
|
}
|
|
314
104
|
/**
|
|
315
|
-
*
|
|
105
|
+
* Closes the transport and all active connections.
|
|
316
106
|
*/
|
|
317
|
-
async
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
// Validate the Accept header
|
|
321
|
-
const acceptHeader = req.headers.accept;
|
|
322
|
-
// The client MUST include an Accept header, listing both application/json and text/event-stream as supported content types.
|
|
323
|
-
if (!(acceptHeader === null || acceptHeader === void 0 ? void 0 : acceptHeader.includes('application/json')) || !acceptHeader.includes('text/event-stream')) {
|
|
324
|
-
res.writeHead(406).end(JSON.stringify({
|
|
325
|
-
jsonrpc: '2.0',
|
|
326
|
-
error: {
|
|
327
|
-
code: -32000,
|
|
328
|
-
message: 'Not Acceptable: Client must accept both application/json and text/event-stream'
|
|
329
|
-
},
|
|
330
|
-
id: null
|
|
331
|
-
}));
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
const ct = req.headers['content-type'];
|
|
335
|
-
if (!ct || !ct.includes('application/json')) {
|
|
336
|
-
res.writeHead(415).end(JSON.stringify({
|
|
337
|
-
jsonrpc: '2.0',
|
|
338
|
-
error: {
|
|
339
|
-
code: -32000,
|
|
340
|
-
message: 'Unsupported Media Type: Content-Type must be application/json'
|
|
341
|
-
},
|
|
342
|
-
id: null
|
|
343
|
-
}));
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
const authInfo = req.auth;
|
|
347
|
-
const requestInfo = { headers: req.headers };
|
|
348
|
-
let rawMessage;
|
|
349
|
-
if (parsedBody !== undefined) {
|
|
350
|
-
rawMessage = parsedBody;
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
const parsedCt = contentType.parse(ct);
|
|
354
|
-
const body = await getRawBody(req, {
|
|
355
|
-
limit: MAXIMUM_MESSAGE_SIZE,
|
|
356
|
-
encoding: (_a = parsedCt.parameters.charset) !== null && _a !== void 0 ? _a : 'utf-8'
|
|
357
|
-
});
|
|
358
|
-
rawMessage = JSON.parse(body.toString());
|
|
359
|
-
}
|
|
360
|
-
let messages;
|
|
361
|
-
// handle batch and single messages
|
|
362
|
-
if (Array.isArray(rawMessage)) {
|
|
363
|
-
messages = rawMessage.map(msg => JSONRPCMessageSchema.parse(msg));
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
messages = [JSONRPCMessageSchema.parse(rawMessage)];
|
|
367
|
-
}
|
|
368
|
-
// Check if this is an initialization request
|
|
369
|
-
// https://spec.modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle/
|
|
370
|
-
const isInitializationRequest = messages.some(isInitializeRequest);
|
|
371
|
-
if (isInitializationRequest) {
|
|
372
|
-
// If it's a server with session management and the session ID is already set we should reject the request
|
|
373
|
-
// to avoid re-initialization.
|
|
374
|
-
if (this._initialized && this.sessionId !== undefined) {
|
|
375
|
-
res.writeHead(400).end(JSON.stringify({
|
|
376
|
-
jsonrpc: '2.0',
|
|
377
|
-
error: {
|
|
378
|
-
code: -32600,
|
|
379
|
-
message: 'Invalid Request: Server already initialized'
|
|
380
|
-
},
|
|
381
|
-
id: null
|
|
382
|
-
}));
|
|
383
|
-
return;
|
|
384
|
-
}
|
|
385
|
-
if (messages.length > 1) {
|
|
386
|
-
res.writeHead(400).end(JSON.stringify({
|
|
387
|
-
jsonrpc: '2.0',
|
|
388
|
-
error: {
|
|
389
|
-
code: -32600,
|
|
390
|
-
message: 'Invalid Request: Only one initialization request is allowed'
|
|
391
|
-
},
|
|
392
|
-
id: null
|
|
393
|
-
}));
|
|
394
|
-
return;
|
|
395
|
-
}
|
|
396
|
-
this.sessionId = (_b = this.sessionIdGenerator) === null || _b === void 0 ? void 0 : _b.call(this);
|
|
397
|
-
this._initialized = true;
|
|
398
|
-
// If we have a session ID and an onsessioninitialized handler, call it immediately
|
|
399
|
-
// This is needed in cases where the server needs to keep track of multiple sessions
|
|
400
|
-
if (this.sessionId && this._onsessioninitialized) {
|
|
401
|
-
await Promise.resolve(this._onsessioninitialized(this.sessionId));
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (!isInitializationRequest) {
|
|
405
|
-
// If an Mcp-Session-Id is returned by the server during initialization,
|
|
406
|
-
// clients using the Streamable HTTP transport MUST include it
|
|
407
|
-
// in the Mcp-Session-Id header on all of their subsequent HTTP requests.
|
|
408
|
-
if (!this.validateSession(req, res)) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
// Mcp-Protocol-Version header is required for all requests after initialization.
|
|
412
|
-
if (!this.validateProtocolVersion(req, res)) {
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
// check if it contains requests
|
|
417
|
-
const hasRequests = messages.some(isJSONRPCRequest);
|
|
418
|
-
if (!hasRequests) {
|
|
419
|
-
// if it only contains notifications or responses, return 202
|
|
420
|
-
res.writeHead(202).end();
|
|
421
|
-
// handle each message
|
|
422
|
-
for (const message of messages) {
|
|
423
|
-
(_c = this.onmessage) === null || _c === void 0 ? void 0 : _c.call(this, message, { authInfo, requestInfo });
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
else if (hasRequests) {
|
|
427
|
-
// The default behavior is to use SSE streaming
|
|
428
|
-
// but in some cases server will return JSON responses
|
|
429
|
-
const streamId = randomUUID();
|
|
430
|
-
if (!this._enableJsonResponse) {
|
|
431
|
-
const headers = {
|
|
432
|
-
'Content-Type': 'text/event-stream',
|
|
433
|
-
'Cache-Control': 'no-cache',
|
|
434
|
-
Connection: 'keep-alive'
|
|
435
|
-
};
|
|
436
|
-
// After initialization, always include the session ID if we have one
|
|
437
|
-
if (this.sessionId !== undefined) {
|
|
438
|
-
headers['mcp-session-id'] = this.sessionId;
|
|
439
|
-
}
|
|
440
|
-
res.writeHead(200, headers);
|
|
441
|
-
await this._maybeWritePrimingEvent(res, streamId);
|
|
442
|
-
}
|
|
443
|
-
// Store the response for this request to send messages back through this connection
|
|
444
|
-
// We need to track by request ID to maintain the connection
|
|
445
|
-
for (const message of messages) {
|
|
446
|
-
if (isJSONRPCRequest(message)) {
|
|
447
|
-
this._streamMapping.set(streamId, res);
|
|
448
|
-
this._requestToStreamMapping.set(message.id, streamId);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
// Set up close handler for client disconnects
|
|
452
|
-
res.on('close', () => {
|
|
453
|
-
this._streamMapping.delete(streamId);
|
|
454
|
-
});
|
|
455
|
-
// Add error handler for stream write errors
|
|
456
|
-
res.on('error', error => {
|
|
457
|
-
var _a;
|
|
458
|
-
(_a = this.onerror) === null || _a === void 0 ? void 0 : _a.call(this, error);
|
|
459
|
-
});
|
|
460
|
-
// handle each message
|
|
461
|
-
for (const message of messages) {
|
|
462
|
-
// Build closeSSEStream callback for requests when eventStore is configured
|
|
463
|
-
let closeSSEStream;
|
|
464
|
-
let closeStandaloneSSEStream;
|
|
465
|
-
if (isJSONRPCRequest(message) && this._eventStore) {
|
|
466
|
-
closeSSEStream = () => {
|
|
467
|
-
this.closeSSEStream(message.id);
|
|
468
|
-
};
|
|
469
|
-
closeStandaloneSSEStream = () => {
|
|
470
|
-
this.closeStandaloneSSEStream();
|
|
471
|
-
};
|
|
472
|
-
}
|
|
473
|
-
(_d = this.onmessage) === null || _d === void 0 ? void 0 : _d.call(this, message, { authInfo, requestInfo, closeSSEStream, closeStandaloneSSEStream });
|
|
474
|
-
}
|
|
475
|
-
// The server SHOULD NOT close the SSE stream before sending all JSON-RPC responses
|
|
476
|
-
// This will be handled by the send() method when responses are ready
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
catch (error) {
|
|
480
|
-
// return JSON-RPC formatted error
|
|
481
|
-
res.writeHead(400).end(JSON.stringify({
|
|
482
|
-
jsonrpc: '2.0',
|
|
483
|
-
error: {
|
|
484
|
-
code: -32700,
|
|
485
|
-
message: 'Parse error',
|
|
486
|
-
data: String(error)
|
|
487
|
-
},
|
|
488
|
-
id: null
|
|
489
|
-
}));
|
|
490
|
-
(_e = this.onerror) === null || _e === void 0 ? void 0 : _e.call(this, error);
|
|
491
|
-
}
|
|
107
|
+
async close() {
|
|
108
|
+
return this._webStandardTransport.close();
|
|
492
109
|
}
|
|
493
110
|
/**
|
|
494
|
-
*
|
|
111
|
+
* Sends a JSON-RPC message through the transport.
|
|
495
112
|
*/
|
|
496
|
-
async
|
|
497
|
-
|
|
498
|
-
if (!this.validateSession(req, res)) {
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (!this.validateProtocolVersion(req, res)) {
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
await Promise.resolve((_a = this._onsessionclosed) === null || _a === void 0 ? void 0 : _a.call(this, this.sessionId));
|
|
505
|
-
await this.close();
|
|
506
|
-
res.writeHead(200).end();
|
|
113
|
+
async send(message, options) {
|
|
114
|
+
return this._webStandardTransport.send(message, options);
|
|
507
115
|
}
|
|
508
116
|
/**
|
|
509
|
-
*
|
|
510
|
-
*
|
|
117
|
+
* Handles an incoming HTTP request, whether GET or POST.
|
|
118
|
+
*
|
|
119
|
+
* This method converts Node.js HTTP objects to Web Standard Request/Response
|
|
120
|
+
* and delegates to the underlying WebStandardStreamableHTTPServerTransport.
|
|
121
|
+
*
|
|
122
|
+
* @param req - Node.js IncomingMessage, optionally with auth property from middleware
|
|
123
|
+
* @param res - Node.js ServerResponse
|
|
124
|
+
* @param parsedBody - Optional pre-parsed body from body-parser middleware
|
|
511
125
|
*/
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
error: {
|
|
523
|
-
code: -32000,
|
|
524
|
-
message: 'Bad Request: Server not initialized'
|
|
525
|
-
},
|
|
526
|
-
id: null
|
|
527
|
-
}));
|
|
528
|
-
return false;
|
|
529
|
-
}
|
|
530
|
-
const sessionId = req.headers['mcp-session-id'];
|
|
531
|
-
if (!sessionId) {
|
|
532
|
-
// Non-initialization requests without a session ID should return 400 Bad Request
|
|
533
|
-
res.writeHead(400).end(JSON.stringify({
|
|
534
|
-
jsonrpc: '2.0',
|
|
535
|
-
error: {
|
|
536
|
-
code: -32000,
|
|
537
|
-
message: 'Bad Request: Mcp-Session-Id header is required'
|
|
538
|
-
},
|
|
539
|
-
id: null
|
|
540
|
-
}));
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
else if (Array.isArray(sessionId)) {
|
|
544
|
-
res.writeHead(400).end(JSON.stringify({
|
|
545
|
-
jsonrpc: '2.0',
|
|
546
|
-
error: {
|
|
547
|
-
code: -32000,
|
|
548
|
-
message: 'Bad Request: Mcp-Session-Id header must be a single value'
|
|
549
|
-
},
|
|
550
|
-
id: null
|
|
551
|
-
}));
|
|
552
|
-
return false;
|
|
553
|
-
}
|
|
554
|
-
else if (sessionId !== this.sessionId) {
|
|
555
|
-
// Reject requests with invalid session ID with 404 Not Found
|
|
556
|
-
res.writeHead(404).end(JSON.stringify({
|
|
557
|
-
jsonrpc: '2.0',
|
|
558
|
-
error: {
|
|
559
|
-
code: -32001,
|
|
560
|
-
message: 'Session not found'
|
|
561
|
-
},
|
|
562
|
-
id: null
|
|
563
|
-
}));
|
|
564
|
-
return false;
|
|
565
|
-
}
|
|
566
|
-
return true;
|
|
567
|
-
}
|
|
568
|
-
validateProtocolVersion(req, res) {
|
|
569
|
-
var _a;
|
|
570
|
-
let protocolVersion = (_a = req.headers['mcp-protocol-version']) !== null && _a !== void 0 ? _a : DEFAULT_NEGOTIATED_PROTOCOL_VERSION;
|
|
571
|
-
if (Array.isArray(protocolVersion)) {
|
|
572
|
-
protocolVersion = protocolVersion[protocolVersion.length - 1];
|
|
573
|
-
}
|
|
574
|
-
if (!SUPPORTED_PROTOCOL_VERSIONS.includes(protocolVersion)) {
|
|
575
|
-
res.writeHead(400).end(JSON.stringify({
|
|
576
|
-
jsonrpc: '2.0',
|
|
577
|
-
error: {
|
|
578
|
-
code: -32000,
|
|
579
|
-
message: `Bad Request: Unsupported protocol version (supported versions: ${SUPPORTED_PROTOCOL_VERSIONS.join(', ')})`
|
|
580
|
-
},
|
|
581
|
-
id: null
|
|
582
|
-
}));
|
|
583
|
-
return false;
|
|
584
|
-
}
|
|
585
|
-
return true;
|
|
586
|
-
}
|
|
587
|
-
async close() {
|
|
588
|
-
var _a;
|
|
589
|
-
// Close all SSE connections
|
|
590
|
-
this._streamMapping.forEach(response => {
|
|
591
|
-
response.end();
|
|
126
|
+
async handleRequest(req, res, parsedBody) {
|
|
127
|
+
// Store context for this request to pass through auth and parsedBody
|
|
128
|
+
// We need to intercept the request creation to attach this context
|
|
129
|
+
const authInfo = req.auth;
|
|
130
|
+
// Create a custom handler that includes our context
|
|
131
|
+
const handler = getRequestListener(async (webRequest) => {
|
|
132
|
+
return this._webStandardTransport.handleRequest(webRequest, {
|
|
133
|
+
authInfo,
|
|
134
|
+
parsedBody
|
|
135
|
+
});
|
|
592
136
|
});
|
|
593
|
-
|
|
594
|
-
//
|
|
595
|
-
|
|
596
|
-
(_a = this.onclose) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
137
|
+
// Delegate to the request listener which handles all the Node.js <-> Web Standard conversion
|
|
138
|
+
// including proper SSE streaming support
|
|
139
|
+
await handler(req, res);
|
|
597
140
|
}
|
|
598
141
|
/**
|
|
599
142
|
* Close an SSE stream for a specific request, triggering client reconnection.
|
|
@@ -601,112 +144,14 @@ export class StreamableHTTPServerTransport {
|
|
|
601
144
|
* client will reconnect after the retry interval specified in the priming event.
|
|
602
145
|
*/
|
|
603
146
|
closeSSEStream(requestId) {
|
|
604
|
-
|
|
605
|
-
if (!streamId)
|
|
606
|
-
return;
|
|
607
|
-
const stream = this._streamMapping.get(streamId);
|
|
608
|
-
if (stream) {
|
|
609
|
-
stream.end();
|
|
610
|
-
this._streamMapping.delete(streamId);
|
|
611
|
-
}
|
|
147
|
+
this._webStandardTransport.closeSSEStream(requestId);
|
|
612
148
|
}
|
|
613
149
|
/**
|
|
614
150
|
* Close the standalone GET SSE stream, triggering client reconnection.
|
|
615
151
|
* Use this to implement polling behavior for server-initiated notifications.
|
|
616
152
|
*/
|
|
617
153
|
closeStandaloneSSEStream() {
|
|
618
|
-
|
|
619
|
-
if (stream) {
|
|
620
|
-
stream.end();
|
|
621
|
-
this._streamMapping.delete(this._standaloneSseStreamId);
|
|
622
|
-
}
|
|
623
|
-
}
|
|
624
|
-
async send(message, options) {
|
|
625
|
-
let requestId = options === null || options === void 0 ? void 0 : options.relatedRequestId;
|
|
626
|
-
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
|
|
627
|
-
// If the message is a response, use the request ID from the message
|
|
628
|
-
requestId = message.id;
|
|
629
|
-
}
|
|
630
|
-
// Check if this message should be sent on the standalone SSE stream (no request ID)
|
|
631
|
-
// Ignore notifications from tools (which have relatedRequestId set)
|
|
632
|
-
// Those will be sent via dedicated response SSE streams
|
|
633
|
-
if (requestId === undefined) {
|
|
634
|
-
// For standalone SSE streams, we can only send requests and notifications
|
|
635
|
-
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
|
|
636
|
-
throw new Error('Cannot send a response on a standalone SSE stream unless resuming a previous client request');
|
|
637
|
-
}
|
|
638
|
-
// Generate and store event ID if event store is provided
|
|
639
|
-
// Store even if stream is disconnected so events can be replayed on reconnect
|
|
640
|
-
let eventId;
|
|
641
|
-
if (this._eventStore) {
|
|
642
|
-
// Stores the event and gets the generated event ID
|
|
643
|
-
eventId = await this._eventStore.storeEvent(this._standaloneSseStreamId, message);
|
|
644
|
-
}
|
|
645
|
-
const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId);
|
|
646
|
-
if (standaloneSse === undefined) {
|
|
647
|
-
// Stream is disconnected - event is stored for replay, nothing more to do
|
|
648
|
-
return;
|
|
649
|
-
}
|
|
650
|
-
// Send the message to the standalone SSE stream
|
|
651
|
-
this.writeSSEEvent(standaloneSse, message, eventId);
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
// Get the response for this request
|
|
655
|
-
const streamId = this._requestToStreamMapping.get(requestId);
|
|
656
|
-
const response = this._streamMapping.get(streamId);
|
|
657
|
-
if (!streamId) {
|
|
658
|
-
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
659
|
-
}
|
|
660
|
-
if (!this._enableJsonResponse) {
|
|
661
|
-
// For SSE responses, generate event ID if event store is provided
|
|
662
|
-
let eventId;
|
|
663
|
-
if (this._eventStore) {
|
|
664
|
-
eventId = await this._eventStore.storeEvent(streamId, message);
|
|
665
|
-
}
|
|
666
|
-
if (response) {
|
|
667
|
-
// Write the event to the response stream
|
|
668
|
-
this.writeSSEEvent(response, message, eventId);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
if (isJSONRPCResponse(message) || isJSONRPCError(message)) {
|
|
672
|
-
this._requestResponseMap.set(requestId, message);
|
|
673
|
-
const relatedIds = Array.from(this._requestToStreamMapping.entries())
|
|
674
|
-
.filter(([_, streamId]) => this._streamMapping.get(streamId) === response)
|
|
675
|
-
.map(([id]) => id);
|
|
676
|
-
// Check if we have responses for all requests using this connection
|
|
677
|
-
const allResponsesReady = relatedIds.every(id => this._requestResponseMap.has(id));
|
|
678
|
-
if (allResponsesReady) {
|
|
679
|
-
if (!response) {
|
|
680
|
-
throw new Error(`No connection established for request ID: ${String(requestId)}`);
|
|
681
|
-
}
|
|
682
|
-
if (this._enableJsonResponse) {
|
|
683
|
-
// All responses ready, send as JSON
|
|
684
|
-
const headers = {
|
|
685
|
-
'Content-Type': 'application/json'
|
|
686
|
-
};
|
|
687
|
-
if (this.sessionId !== undefined) {
|
|
688
|
-
headers['mcp-session-id'] = this.sessionId;
|
|
689
|
-
}
|
|
690
|
-
const responses = relatedIds.map(id => this._requestResponseMap.get(id));
|
|
691
|
-
response.writeHead(200, headers);
|
|
692
|
-
if (responses.length === 1) {
|
|
693
|
-
response.end(JSON.stringify(responses[0]));
|
|
694
|
-
}
|
|
695
|
-
else {
|
|
696
|
-
response.end(JSON.stringify(responses));
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
else {
|
|
700
|
-
// End the SSE stream
|
|
701
|
-
response.end();
|
|
702
|
-
}
|
|
703
|
-
// Clean up
|
|
704
|
-
for (const id of relatedIds) {
|
|
705
|
-
this._requestResponseMap.delete(id);
|
|
706
|
-
this._requestToStreamMapping.delete(id);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
}
|
|
154
|
+
this._webStandardTransport.closeStandaloneSSEStream();
|
|
710
155
|
}
|
|
711
156
|
}
|
|
712
157
|
//# sourceMappingURL=streamableHttp.js.map
|