@ttoss/http-server-mcp 0.28.0 → 0.28.1
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/MIGRATIONS.md +35 -0
- package/README.md +21 -1
- package/dist/index.cjs +88 -33
- package/dist/index.d.cts +26 -1
- package/dist/index.d.mts +26 -1
- package/dist/index.mjs +89 -34
- package/package.json +1 -1
package/MIGRATIONS.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Migrations
|
|
2
2
|
|
|
3
|
+
## The `2026-07-28` revision needs `createMcpServer`
|
|
4
|
+
|
|
5
|
+
That revision is served only when `createMcpRouter` is given a
|
|
6
|
+
`createMcpServer` factory.
|
|
7
|
+
|
|
8
|
+
```diff
|
|
9
|
+
+const buildServer = () => {
|
|
10
|
+
+ const mcpServer = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
11
|
+
+ registerEverything(mcpServer);
|
|
12
|
+
+ return mcpServer;
|
|
13
|
+
+};
|
|
14
|
+
+
|
|
15
|
+
-createMcpRouter(mcpServer, {
|
|
16
|
+
+createMcpRouter(buildServer(), {
|
|
17
|
+
+ createMcpServer: buildServer,
|
|
18
|
+
auth: { ... },
|
|
19
|
+
});
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
**Why it cannot default to the server you already pass.** The negotiated
|
|
23
|
+
revision is instance state: serving one `2026-07-28` request marks that
|
|
24
|
+
`McpServer` modern for good, and it then validates every later message against
|
|
25
|
+
that revision. One instance serving both eras is pinned by the first client to
|
|
26
|
+
speak the newer one, after which every 2025-era request is answered
|
|
27
|
+
`-32602 Request is missing the required _meta envelope for protocol revision
|
|
28
|
+
2026-07-28` at **HTTP 200** for the life of the process — a status and body a
|
|
29
|
+
client reads as "no tools" rather than as a fault. The SDK's own serving
|
|
30
|
+
entries take a factory and call it once per request for this reason.
|
|
31
|
+
|
|
32
|
+
**What you will observe if you miss this.** A client speaking `2026-07-28` gets
|
|
33
|
+
`400` with `-32022 Unsupported protocol version` and `data.supported` listing
|
|
34
|
+
the 2025-era revisions, which it can renegotiate from. Only deployments
|
|
35
|
+
actually receiving that traffic are affected; every client without the
|
|
36
|
+
per-request envelope is served exactly as before.
|
|
37
|
+
|
|
3
38
|
## `tools/list` is no longer public by default
|
|
4
39
|
|
|
5
40
|
`auth.publicMethods` now defaults to `['initialize']` instead of
|
package/README.md
CHANGED
|
@@ -906,7 +906,27 @@ app.listen(3000);
|
|
|
906
906
|
|
|
907
907
|
This package implements the [Model Context Protocol](https://spec.modelcontextprotocol.io/) over HTTP using JSON responses (no SSE streaming), and serves each request over the protocol revision it actually speaks.
|
|
908
908
|
|
|
909
|
-
Requests are classified once at the boundary. Traffic from today's MCP clients is served over `NodeStreamableHTTPServerTransport` with `enableJsonResponse: true`, adapting Koa's context-based middleware to the SDK's Node.js request/response expectations. Requests carrying the `2026-07-28` revision's per-request envelope are served by that revision's stateless core (`createMcpHandler`)
|
|
909
|
+
Requests are classified once at the boundary, and the classifier's answer is three-way. Traffic from today's MCP clients is served over `NodeStreamableHTTPServerTransport` with `enableJsonResponse: true`, adapting Koa's context-based middleware to the SDK's Node.js request/response expectations. Requests carrying the `2026-07-28` revision's per-request envelope are served by that revision's stateless core (`createMcpHandler`). Requests the classifier refuses outright are answered with its own rejection — the status, code, message and data it chose — rather than passed to either era's handler.
|
|
910
|
+
|
|
911
|
+
### Serving the `2026-07-28` revision
|
|
912
|
+
|
|
913
|
+
Set `createMcpServer` to a factory returning an `McpServer` with the same tools registered:
|
|
914
|
+
|
|
915
|
+
```typescript
|
|
916
|
+
const buildServer = () => {
|
|
917
|
+
const mcpServer = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
918
|
+
registerEverything(mcpServer);
|
|
919
|
+
return mcpServer;
|
|
920
|
+
};
|
|
921
|
+
|
|
922
|
+
const mcpRouter = createMcpRouter(buildServer(), {
|
|
923
|
+
createMcpServer: buildServer,
|
|
924
|
+
});
|
|
925
|
+
```
|
|
926
|
+
|
|
927
|
+
It has to be a factory, and cannot default to the server the router was given, because the negotiated revision is instance state: serving one `2026-07-28` request marks that `McpServer` modern for good, and it then validates every later message against that revision. One instance serving both eras is pinned by the first client to speak the newer one, after which every 2025-era request is answered `-32602 Request is missing the required _meta envelope…` at HTTP 200 for the life of the process. The SDK's own serving entries take a factory and call it once per request for the same reason.
|
|
928
|
+
|
|
929
|
+
Without `createMcpServer`, `2026-07-28` requests get the unsupported-protocol-version error listing the revisions this endpoint does serve, so that client renegotiates and no other client is affected.
|
|
910
930
|
|
|
911
931
|
**Supported HTTP methods:**
|
|
912
932
|
|
package/dist/index.cjs
CHANGED
|
@@ -36,41 +36,88 @@ var header = (ctx, name) => {
|
|
|
36
36
|
const value = ctx.get(name);
|
|
37
37
|
return value === "" ? void 0 : value;
|
|
38
38
|
};
|
|
39
|
+
/** The id to echo in an error answer, by the SDK's own rule. */
|
|
40
|
+
var echoableRequestId = body => {
|
|
41
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
|
|
42
|
+
const {
|
|
43
|
+
id,
|
|
44
|
+
method
|
|
45
|
+
} = body;
|
|
46
|
+
if (typeof method !== "string") return null;
|
|
47
|
+
return typeof id === "string" || typeof id === "number" ? id : null;
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* Writes a JSON-RPC error straight to the Node response: `createMcpRouter` sets
|
|
51
|
+
* `ctx.respond = false` after serving, so Koa never writes `ctx.body`.
|
|
52
|
+
*/
|
|
53
|
+
var emitJsonRpcError = (ctx, answer, body) => {
|
|
54
|
+
ctx.res.writeHead(answer.httpStatus, {
|
|
55
|
+
"content-type": "application/json"
|
|
56
|
+
});
|
|
57
|
+
ctx.res.end(JSON.stringify({
|
|
58
|
+
jsonrpc: "2.0",
|
|
59
|
+
error: {
|
|
60
|
+
code: answer.code,
|
|
61
|
+
message: answer.message,
|
|
62
|
+
...(answer.data !== void 0 && {
|
|
63
|
+
data: answer.data
|
|
64
|
+
})
|
|
65
|
+
},
|
|
66
|
+
id: echoableRequestId(body)
|
|
67
|
+
}));
|
|
68
|
+
};
|
|
69
|
+
/** The SDK's own ladder rejection, emitted with the status the SDK chose. */
|
|
70
|
+
var rejectionAnswer = rejection => {
|
|
71
|
+
return {
|
|
72
|
+
httpStatus: rejection.httpStatus,
|
|
73
|
+
code: rejection.code,
|
|
74
|
+
message: rejection.message,
|
|
75
|
+
...(rejection.data !== void 0 && {
|
|
76
|
+
data: rejection.data
|
|
77
|
+
})
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* The answer to `2026-07-28` traffic without `createMcpServer`. `data.supported`
|
|
82
|
+
* is what lets the client renegotiate onto a revision this endpoint serves.
|
|
83
|
+
*/
|
|
84
|
+
var REVISION_NOT_SERVED_ANSWER = {
|
|
85
|
+
httpStatus: 400,
|
|
86
|
+
code: _modelcontextprotocol_server.ProtocolErrorCode.UnsupportedProtocolVersion,
|
|
87
|
+
message: "Unsupported protocol version: this endpoint serves the 2025-era MCP revisions only. Pass createMcpServer to createMcpRouter to serve protocol revision 2026-07-28.",
|
|
88
|
+
data: {
|
|
89
|
+
supported: [..._modelcontextprotocol_server.SUPPORTED_PROTOCOL_VERSIONS]
|
|
90
|
+
}
|
|
91
|
+
};
|
|
39
92
|
/**
|
|
40
93
|
* Builds the per-request serving function used by `createMcpRouter`, routing
|
|
41
|
-
* each request
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
* both eras through its built-in legacy fallback — is what keeps 2025-era
|
|
59
|
-
* responses byte-compatible. That fallback constructs its transport with only
|
|
60
|
-
* `sessionIdGenerator: undefined` and no way to pass `enableJsonResponse`, so
|
|
61
|
-
* every response it produces is SSE-framed (`text/event-stream`) regardless of
|
|
62
|
-
* the handler's `responseMode`. Owning the legacy branch keeps `application/json`.
|
|
94
|
+
* each request by the SDK's three-way `classifyInboundRequest` outcome.
|
|
95
|
+
*
|
|
96
|
+
* **Why the modern era needs `createMcpServer` rather than the `server` above.**
|
|
97
|
+
* The negotiated revision is *instance* state: the SDK marks an instance modern
|
|
98
|
+
* when it serves one modern request, and it then validates every later message
|
|
99
|
+
* against `2026-07-28` — there is no per-request era consult. One instance
|
|
100
|
+
* serving both eras is therefore pinned by the first client to speak the newer
|
|
101
|
+
* revision, after which every 2025-era request is answered `-32602 Request is
|
|
102
|
+
* missing the required _meta envelope…` at HTTP 200 for the life of the
|
|
103
|
+
* process. The SDK's own entries take a factory and call it per request for
|
|
104
|
+
* this reason (they also `close()` the instance, which a shared one cannot
|
|
105
|
+
* survive). Without the factory, `2026-07-28` traffic gets
|
|
106
|
+
* {@link REVISION_NOT_SERVED_ANSWER} and no other client is affected.
|
|
107
|
+
*
|
|
108
|
+
* **Why not `createMcpHandler`'s built-in legacy fallback.** It constructs its
|
|
109
|
+
* transport with no way to pass `enableJsonResponse`, so every response it
|
|
110
|
+
* produces is SSE-framed. Owning the legacy branch keeps `application/json`.
|
|
63
111
|
*/
|
|
64
112
|
var createMcpRequestServer = ({
|
|
113
|
+
createMcpServer,
|
|
65
114
|
server,
|
|
66
115
|
sessionIdGenerator
|
|
67
116
|
}) => {
|
|
68
117
|
const isStateful = sessionIdGenerator !== void 0;
|
|
69
|
-
const modernHandler = (0, _modelcontextprotocol_node.toNodeHandler)((0, _modelcontextprotocol_server.createMcpHandler)(
|
|
70
|
-
return server;
|
|
71
|
-
}, {
|
|
118
|
+
const modernHandler = createMcpServer ? (0, _modelcontextprotocol_node.toNodeHandler)((0, _modelcontextprotocol_server.createMcpHandler)(createMcpServer, {
|
|
72
119
|
legacy: "reject"
|
|
73
|
-
}));
|
|
120
|
+
})) : void 0;
|
|
74
121
|
let sharedTransport;
|
|
75
122
|
if (isStateful) {
|
|
76
123
|
sharedTransport = new _modelcontextprotocol_node.NodeStreamableHTTPServerTransport({
|
|
@@ -87,17 +134,23 @@ var createMcpRequestServer = ({
|
|
|
87
134
|
statelessQueue = result.catch(() => {});
|
|
88
135
|
return result;
|
|
89
136
|
};
|
|
90
|
-
|
|
91
|
-
|
|
137
|
+
return async (ctx, body) => {
|
|
138
|
+
const outcome = (0, _modelcontextprotocol_server.classifyInboundRequest)({
|
|
92
139
|
httpMethod: ctx.method,
|
|
93
140
|
protocolVersionHeader: header(ctx, "mcp-protocol-version"),
|
|
94
141
|
mcpMethodHeader: header(ctx, "mcp-method"),
|
|
95
142
|
mcpNameHeader: header(ctx, "mcp-name"),
|
|
96
143
|
body
|
|
97
|
-
})
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
144
|
+
});
|
|
145
|
+
if (outcome.kind === "reject") {
|
|
146
|
+
emitJsonRpcError(ctx, rejectionAnswer(outcome), body);
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (outcome.kind === "modern") {
|
|
150
|
+
if (modernHandler === void 0) {
|
|
151
|
+
emitJsonRpcError(ctx, REVISION_NOT_SERVED_ANSWER, body);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
101
154
|
await modernHandler(ctx.req, ctx.res, body);
|
|
102
155
|
return;
|
|
103
156
|
}
|
|
@@ -692,6 +745,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
692
745
|
path = "/mcp",
|
|
693
746
|
aliases = [],
|
|
694
747
|
sessionIdGenerator,
|
|
748
|
+
createMcpServer,
|
|
695
749
|
apiBaseUrl,
|
|
696
750
|
getApiHeaders,
|
|
697
751
|
auth
|
|
@@ -699,7 +753,8 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
699
753
|
const needsContext = apiBaseUrl !== void 0 || getApiHeaders !== void 0 || auth !== void 0;
|
|
700
754
|
const serveRequest = createMcpRequestServer({
|
|
701
755
|
server,
|
|
702
|
-
sessionIdGenerator
|
|
756
|
+
sessionIdGenerator,
|
|
757
|
+
createMcpServer
|
|
703
758
|
});
|
|
704
759
|
const router = new _ttoss_http_server.Router();
|
|
705
760
|
let authCheck;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
import { CallToolResult, McpServer, McpServer as McpServer$1, RegisteredResource } from "@modelcontextprotocol/server";
|
|
3
|
+
import { CallToolResult, McpServer, McpServer as McpServer$1, McpServerFactory, RegisteredResource } from "@modelcontextprotocol/server";
|
|
4
4
|
import { Router } from "@ttoss/http-server";
|
|
5
5
|
import accepts from "accepts";
|
|
6
6
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -1310,6 +1310,31 @@ interface McpRouterOptions {
|
|
|
1310
1310
|
* served statelessly regardless of this option.
|
|
1311
1311
|
*/
|
|
1312
1312
|
sessionIdGenerator?: () => string;
|
|
1313
|
+
/**
|
|
1314
|
+
* Per-request factory for the `McpServer` serving one `2026-07-28` request.
|
|
1315
|
+
* Set it to serve that revision; without it, requests carrying its
|
|
1316
|
+
* per-request envelope get the unsupported-protocol-version error naming the
|
|
1317
|
+
* 2025-era revisions this endpoint does serve. Register the same tools here
|
|
1318
|
+
* as on `server`, so the two eras cannot drift apart.
|
|
1319
|
+
*
|
|
1320
|
+
* It cannot default to `server`: the negotiated revision is instance state,
|
|
1321
|
+
* so one instance serving both eras is pinned to `2026-07-28` by the first
|
|
1322
|
+
* client to speak it, and every 2025-era request after that is answered
|
|
1323
|
+
* `-32602 … missing the required _meta envelope` at HTTP 200. The SDK's own
|
|
1324
|
+
* serving entries take a factory and call it once per request.
|
|
1325
|
+
*
|
|
1326
|
+
* @example
|
|
1327
|
+
* ```typescript
|
|
1328
|
+
* const buildServer = () => {
|
|
1329
|
+
* const mcpServer = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
1330
|
+
* registerEverything(mcpServer);
|
|
1331
|
+
* return mcpServer;
|
|
1332
|
+
* };
|
|
1333
|
+
*
|
|
1334
|
+
* createMcpRouter(buildServer(), { createMcpServer: buildServer });
|
|
1335
|
+
* ```
|
|
1336
|
+
*/
|
|
1337
|
+
createMcpServer?: McpServerFactory;
|
|
1313
1338
|
/**
|
|
1314
1339
|
* Base URL prepended to relative paths passed to `apiCall` (paths starting
|
|
1315
1340
|
* with `/`). Tool handlers can then call `apiCall('GET', '/resource')` without
|
package/dist/index.d.mts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
3
|
import { Router } from "@ttoss/http-server";
|
|
4
4
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
5
|
-
import { CallToolResult, McpServer, McpServer as McpServer$1, RegisteredResource } from "@modelcontextprotocol/server";
|
|
5
|
+
import { CallToolResult, McpServer, McpServer as McpServer$1, McpServerFactory, RegisteredResource } from "@modelcontextprotocol/server";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import accepts from "accepts";
|
|
8
8
|
import { AsyncLocalStorage as AsyncLocalStorage$1 } from "async_hooks";
|
|
@@ -1311,6 +1311,31 @@ interface McpRouterOptions {
|
|
|
1311
1311
|
* served statelessly regardless of this option.
|
|
1312
1312
|
*/
|
|
1313
1313
|
sessionIdGenerator?: () => string;
|
|
1314
|
+
/**
|
|
1315
|
+
* Per-request factory for the `McpServer` serving one `2026-07-28` request.
|
|
1316
|
+
* Set it to serve that revision; without it, requests carrying its
|
|
1317
|
+
* per-request envelope get the unsupported-protocol-version error naming the
|
|
1318
|
+
* 2025-era revisions this endpoint does serve. Register the same tools here
|
|
1319
|
+
* as on `server`, so the two eras cannot drift apart.
|
|
1320
|
+
*
|
|
1321
|
+
* It cannot default to `server`: the negotiated revision is instance state,
|
|
1322
|
+
* so one instance serving both eras is pinned to `2026-07-28` by the first
|
|
1323
|
+
* client to speak it, and every 2025-era request after that is answered
|
|
1324
|
+
* `-32602 … missing the required _meta envelope` at HTTP 200. The SDK's own
|
|
1325
|
+
* serving entries take a factory and call it once per request.
|
|
1326
|
+
*
|
|
1327
|
+
* @example
|
|
1328
|
+
* ```typescript
|
|
1329
|
+
* const buildServer = () => {
|
|
1330
|
+
* const mcpServer = new McpServer({ name: 'my-server', version: '1.0.0' });
|
|
1331
|
+
* registerEverything(mcpServer);
|
|
1332
|
+
* return mcpServer;
|
|
1333
|
+
* };
|
|
1334
|
+
*
|
|
1335
|
+
* createMcpRouter(buildServer(), { createMcpServer: buildServer });
|
|
1336
|
+
* ```
|
|
1337
|
+
*/
|
|
1338
|
+
createMcpServer?: McpServerFactory;
|
|
1314
1339
|
/**
|
|
1315
1340
|
* Base URL prepended to relative paths passed to `apiCall` (paths starting
|
|
1316
1341
|
* with `/`). Tool handlers can then call `apiCall('GET', '/resource')` without
|
package/dist/index.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { Router } from "@ttoss/http-server";
|
|
|
5
5
|
import { authMiddleware } from "@ttoss/http-server-auth";
|
|
6
6
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
7
|
import { NodeStreamableHTTPServerTransport, toNodeHandler } from "@modelcontextprotocol/node";
|
|
8
|
-
import { McpServer, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
8
|
+
import { McpServer, ProtocolErrorCode, SUPPORTED_PROTOCOL_VERSIONS, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
|
|
11
11
|
//#region src/context.ts
|
|
@@ -33,41 +33,88 @@ var header = (ctx, name) => {
|
|
|
33
33
|
const value = ctx.get(name);
|
|
34
34
|
return value === "" ? void 0 : value;
|
|
35
35
|
};
|
|
36
|
+
/** The id to echo in an error answer, by the SDK's own rule. */
|
|
37
|
+
var echoableRequestId = body => {
|
|
38
|
+
if (body === null || typeof body !== "object" || Array.isArray(body)) return null;
|
|
39
|
+
const {
|
|
40
|
+
id,
|
|
41
|
+
method
|
|
42
|
+
} = body;
|
|
43
|
+
if (typeof method !== "string") return null;
|
|
44
|
+
return typeof id === "string" || typeof id === "number" ? id : null;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Writes a JSON-RPC error straight to the Node response: `createMcpRouter` sets
|
|
48
|
+
* `ctx.respond = false` after serving, so Koa never writes `ctx.body`.
|
|
49
|
+
*/
|
|
50
|
+
var emitJsonRpcError = (ctx, answer, body) => {
|
|
51
|
+
ctx.res.writeHead(answer.httpStatus, {
|
|
52
|
+
"content-type": "application/json"
|
|
53
|
+
});
|
|
54
|
+
ctx.res.end(JSON.stringify({
|
|
55
|
+
jsonrpc: "2.0",
|
|
56
|
+
error: {
|
|
57
|
+
code: answer.code,
|
|
58
|
+
message: answer.message,
|
|
59
|
+
...(answer.data !== void 0 && {
|
|
60
|
+
data: answer.data
|
|
61
|
+
})
|
|
62
|
+
},
|
|
63
|
+
id: echoableRequestId(body)
|
|
64
|
+
}));
|
|
65
|
+
};
|
|
66
|
+
/** The SDK's own ladder rejection, emitted with the status the SDK chose. */
|
|
67
|
+
var rejectionAnswer = rejection => {
|
|
68
|
+
return {
|
|
69
|
+
httpStatus: rejection.httpStatus,
|
|
70
|
+
code: rejection.code,
|
|
71
|
+
message: rejection.message,
|
|
72
|
+
...(rejection.data !== void 0 && {
|
|
73
|
+
data: rejection.data
|
|
74
|
+
})
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* The answer to `2026-07-28` traffic without `createMcpServer`. `data.supported`
|
|
79
|
+
* is what lets the client renegotiate onto a revision this endpoint serves.
|
|
80
|
+
*/
|
|
81
|
+
var REVISION_NOT_SERVED_ANSWER = {
|
|
82
|
+
httpStatus: 400,
|
|
83
|
+
code: ProtocolErrorCode.UnsupportedProtocolVersion,
|
|
84
|
+
message: "Unsupported protocol version: this endpoint serves the 2025-era MCP revisions only. Pass createMcpServer to createMcpRouter to serve protocol revision 2026-07-28.",
|
|
85
|
+
data: {
|
|
86
|
+
supported: [...SUPPORTED_PROTOCOL_VERSIONS]
|
|
87
|
+
}
|
|
88
|
+
};
|
|
36
89
|
/**
|
|
37
90
|
* Builds the per-request serving function used by `createMcpRouter`, routing
|
|
38
|
-
* each request
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
* both eras through its built-in legacy fallback — is what keeps 2025-era
|
|
56
|
-
* responses byte-compatible. That fallback constructs its transport with only
|
|
57
|
-
* `sessionIdGenerator: undefined` and no way to pass `enableJsonResponse`, so
|
|
58
|
-
* every response it produces is SSE-framed (`text/event-stream`) regardless of
|
|
59
|
-
* the handler's `responseMode`. Owning the legacy branch keeps `application/json`.
|
|
91
|
+
* each request by the SDK's three-way `classifyInboundRequest` outcome.
|
|
92
|
+
*
|
|
93
|
+
* **Why the modern era needs `createMcpServer` rather than the `server` above.**
|
|
94
|
+
* The negotiated revision is *instance* state: the SDK marks an instance modern
|
|
95
|
+
* when it serves one modern request, and it then validates every later message
|
|
96
|
+
* against `2026-07-28` — there is no per-request era consult. One instance
|
|
97
|
+
* serving both eras is therefore pinned by the first client to speak the newer
|
|
98
|
+
* revision, after which every 2025-era request is answered `-32602 Request is
|
|
99
|
+
* missing the required _meta envelope…` at HTTP 200 for the life of the
|
|
100
|
+
* process. The SDK's own entries take a factory and call it per request for
|
|
101
|
+
* this reason (they also `close()` the instance, which a shared one cannot
|
|
102
|
+
* survive). Without the factory, `2026-07-28` traffic gets
|
|
103
|
+
* {@link REVISION_NOT_SERVED_ANSWER} and no other client is affected.
|
|
104
|
+
*
|
|
105
|
+
* **Why not `createMcpHandler`'s built-in legacy fallback.** It constructs its
|
|
106
|
+
* transport with no way to pass `enableJsonResponse`, so every response it
|
|
107
|
+
* produces is SSE-framed. Owning the legacy branch keeps `application/json`.
|
|
60
108
|
*/
|
|
61
109
|
var createMcpRequestServer = ({
|
|
110
|
+
createMcpServer,
|
|
62
111
|
server,
|
|
63
112
|
sessionIdGenerator
|
|
64
113
|
}) => {
|
|
65
114
|
const isStateful = sessionIdGenerator !== void 0;
|
|
66
|
-
const modernHandler = toNodeHandler(createMcpHandler(
|
|
67
|
-
return server;
|
|
68
|
-
}, {
|
|
115
|
+
const modernHandler = createMcpServer ? toNodeHandler(createMcpHandler(createMcpServer, {
|
|
69
116
|
legacy: "reject"
|
|
70
|
-
}));
|
|
117
|
+
})) : void 0;
|
|
71
118
|
let sharedTransport;
|
|
72
119
|
if (isStateful) {
|
|
73
120
|
sharedTransport = new NodeStreamableHTTPServerTransport({
|
|
@@ -84,17 +131,23 @@ var createMcpRequestServer = ({
|
|
|
84
131
|
statelessQueue = result.catch(() => {});
|
|
85
132
|
return result;
|
|
86
133
|
};
|
|
87
|
-
|
|
88
|
-
|
|
134
|
+
return async (ctx, body) => {
|
|
135
|
+
const outcome = classifyInboundRequest({
|
|
89
136
|
httpMethod: ctx.method,
|
|
90
137
|
protocolVersionHeader: header(ctx, "mcp-protocol-version"),
|
|
91
138
|
mcpMethodHeader: header(ctx, "mcp-method"),
|
|
92
139
|
mcpNameHeader: header(ctx, "mcp-name"),
|
|
93
140
|
body
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
141
|
+
});
|
|
142
|
+
if (outcome.kind === "reject") {
|
|
143
|
+
emitJsonRpcError(ctx, rejectionAnswer(outcome), body);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
if (outcome.kind === "modern") {
|
|
147
|
+
if (modernHandler === void 0) {
|
|
148
|
+
emitJsonRpcError(ctx, REVISION_NOT_SERVED_ANSWER, body);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
98
151
|
await modernHandler(ctx.req, ctx.res, body);
|
|
99
152
|
return;
|
|
100
153
|
}
|
|
@@ -689,6 +742,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
689
742
|
path = "/mcp",
|
|
690
743
|
aliases = [],
|
|
691
744
|
sessionIdGenerator,
|
|
745
|
+
createMcpServer,
|
|
692
746
|
apiBaseUrl,
|
|
693
747
|
getApiHeaders,
|
|
694
748
|
auth
|
|
@@ -696,7 +750,8 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
696
750
|
const needsContext = apiBaseUrl !== void 0 || getApiHeaders !== void 0 || auth !== void 0;
|
|
697
751
|
const serveRequest = createMcpRequestServer({
|
|
698
752
|
server,
|
|
699
|
-
sessionIdGenerator
|
|
753
|
+
sessionIdGenerator,
|
|
754
|
+
createMcpServer
|
|
700
755
|
});
|
|
701
756
|
const router = new Router();
|
|
702
757
|
let authCheck;
|