@ttoss/http-server-mcp 0.23.1 → 0.24.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/README.md +15 -3
- package/dist/index.cjs +148 -86
- package/dist/index.d.cts +33 -15
- package/dist/index.d.mts +33 -15
- package/dist/index.mjs +148 -86
- package/package.json +6 -8
package/README.md
CHANGED
|
@@ -593,10 +593,17 @@ Use this when tool definitions are shared between the MCP server and an AI SDK a
|
|
|
593
593
|
- `params.name` (`string`) — Unique tool name
|
|
594
594
|
- `params.description` (`string`, optional) — Human-readable description
|
|
595
595
|
- `params.inputSchema` (`JsonObjectSchema`, optional) — Plain JSON Schema object (defaults to `{ type: 'object', properties: {} }`)
|
|
596
|
+
- `params.validateArguments` (`boolean`, optional) — Enforce `inputSchema` on `tools/call` (default: `false`); see [Argument validation](#argument-validation)
|
|
596
597
|
- `params.handler` (`(args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>`) — Tool handler receiving the raw request arguments
|
|
597
598
|
|
|
598
599
|
**Returns:** `void`
|
|
599
600
|
|
|
601
|
+
#### Argument validation
|
|
602
|
+
|
|
603
|
+
By default, `inputSchema` is **advertised but not enforced**: it round-trips verbatim over `tools/list` so clients know what to send, while arguments reach your handler unchecked. Pass `validateArguments: true` to reject mismatched calls with an MCP error before the handler runs.
|
|
604
|
+
|
|
605
|
+
Enable it once you're confident `inputSchema` describes every value the tool genuinely accepts. Schemas derived from an OpenAPI document are a common source of _incomplete_ ones — a field a client may send as `null` to clear it, or one that accepts several shapes, is easily emitted as a bare `{ type: 'string' }`. Validating against a schema like that rejects calls the underlying API would have accepted. To describe those cases accurately, use `{ type: ['string', 'null'] }` for a nullable field and forward `oneOf`/`anyOf` verbatim rather than collapsing to one type.
|
|
606
|
+
|
|
600
607
|
## Examples
|
|
601
608
|
|
|
602
609
|
### Plain JSON Schema Tool (`registerToolFromSchema`)
|
|
@@ -787,7 +794,9 @@ app.listen(3000);
|
|
|
787
794
|
|
|
788
795
|
## Protocol Details
|
|
789
796
|
|
|
790
|
-
This package implements the [Model Context Protocol](https://spec.modelcontextprotocol.io/) over HTTP using JSON responses (no SSE streaming)
|
|
797
|
+
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.
|
|
798
|
+
|
|
799
|
+
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`) instead. Both paths are served from the same `McpServer` instance and the same registered tools, so supporting the newer revision requires no configuration.
|
|
791
800
|
|
|
792
801
|
**Supported HTTP methods:**
|
|
793
802
|
|
|
@@ -812,6 +821,8 @@ If you authenticate with `auth.verifyToken`, you do not need `sessionIdGenerator
|
|
|
812
821
|
| Stateless (default) | Bearer/API tokens, serverless, multi-instance | DB/verify hit per request |
|
|
813
822
|
| Stateful (`sessionIdGenerator`) | SSE events, context-preserving streams | Needs session affinity / shared state |
|
|
814
823
|
|
|
824
|
+
The `2026-07-28` revision has no session concept in its core, so requests speaking that revision are always served statelessly regardless of `sessionIdGenerator`.
|
|
825
|
+
|
|
815
826
|
## Testing an MCP server
|
|
816
827
|
|
|
817
828
|
MCP requests are plain JSON-RPC POSTs to the router path. The `initialize` and `tools/list` methods are public by default, so they need no auth header; `tools/call` runs through `auth.verifyToken`. The client must send `Accept: application/json, text/event-stream` — the transport rejects requests that do not accept the event-stream media type.
|
|
@@ -864,7 +875,7 @@ Use the default when token auth is handled outside the OAuth flow (Cognito, API
|
|
|
864
875
|
|
|
865
876
|
The default **stateless** mode (see [Stateless vs stateful mode](#stateless-vs-stateful-mode)) is built for serverless: a fresh transport is created per request, nothing is kept in memory between invocations, and responses are plain JSON (no SSE) — exactly the request/response shape API Gateway and Lambda Function URLs expect.
|
|
866
877
|
|
|
867
|
-
Use [`@ttoss/http-server-serverless`](https://github.com/ttoss/ttoss/tree/main/packages/http-server-serverless) as the Lambda adapter. It wraps `serverless-http` and additionally populates `req.rawHeaders` from the API Gateway event before the request reaches Koa. Without this step,
|
|
878
|
+
Use [`@ttoss/http-server-serverless`](https://github.com/ttoss/ttoss/tree/main/packages/http-server-serverless) as the Lambda adapter. It wraps `serverless-http` and additionally populates `req.rawHeaders` from the API Gateway event before the request reaches Koa. Without this step, the adapter sitting between Koa and the MCP transport can drop all headers (including `Accept`), making every `initialize` request return HTTP 406.
|
|
868
879
|
|
|
869
880
|
```mermaid
|
|
870
881
|
flowchart LR
|
|
@@ -913,7 +924,8 @@ Keep the following in mind:
|
|
|
913
924
|
|
|
914
925
|
- [@ttoss/http-server](https://ttoss.dev/docs/modules/packages/http-server) - HTTP server foundation
|
|
915
926
|
- [@ttoss/http-server-serverless](https://github.com/ttoss/ttoss/tree/main/packages/http-server-serverless) - AWS Lambda adapter (required for MCP on Lambda)
|
|
916
|
-
- [@modelcontextprotocol/
|
|
927
|
+
- [@modelcontextprotocol/server](https://github.com/modelcontextprotocol/typescript-sdk) - MCP server SDK (2026-07-28 core)
|
|
928
|
+
- [@modelcontextprotocol/node](https://github.com/modelcontextprotocol/typescript-sdk) - Node.js request/response bridge for the SDK's fetch-based handler
|
|
917
929
|
|
|
918
930
|
## Resources
|
|
919
931
|
|
package/dist/index.cjs
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, {
|
|
3
3
|
value: 'Module'
|
|
4
4
|
});
|
|
5
|
-
let _modelcontextprotocol_sdk_server_streamableHttp_js = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
|
|
6
5
|
let _ttoss_auth_core_amazon_cognito = require("@ttoss/auth-core/amazon-cognito");
|
|
7
6
|
let _ttoss_http_server = require("@ttoss/http-server");
|
|
8
7
|
let _ttoss_http_server_auth = require("@ttoss/http-server-auth");
|
|
9
8
|
let node_async_hooks = require("node:async_hooks");
|
|
9
|
+
let _modelcontextprotocol_node = require("@modelcontextprotocol/node");
|
|
10
|
+
let _modelcontextprotocol_server = require("@modelcontextprotocol/server");
|
|
10
11
|
let zod = require("zod");
|
|
11
|
-
let _modelcontextprotocol_sdk_server_mcp_js = require("@modelcontextprotocol/sdk/server/mcp.js");
|
|
12
12
|
|
|
13
13
|
//#region src/context.ts
|
|
14
14
|
var requestContextStore = new node_async_hooks.AsyncLocalStorage();
|
|
@@ -24,30 +24,145 @@ var getIdentity = () => {
|
|
|
24
24
|
return requestContextStore.getStore()?.identity;
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
//#endregion
|
|
28
|
+
//#region src/serveMcpRequest.ts
|
|
29
|
+
/**
|
|
30
|
+
* Reads a header as `string | undefined`. Koa's `ctx.get` returns `''` for a
|
|
31
|
+
* missing header, which the classifier would read as "header present but
|
|
32
|
+
* empty" rather than absent.
|
|
33
|
+
*/
|
|
34
|
+
var header = (ctx, name) => {
|
|
35
|
+
const value = ctx.get(name);
|
|
36
|
+
return value === "" ? void 0 : value;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Builds the per-request serving function used by `createMcpRouter`, routing
|
|
40
|
+
* each request to the protocol revision it actually speaks.
|
|
41
|
+
*
|
|
42
|
+
* Requests are classified once at the boundary with the SDK's own
|
|
43
|
+
* `classifyInboundRequest`:
|
|
44
|
+
*
|
|
45
|
+
* - **2025-era traffic** (everything without a `2026-07-28` per-request
|
|
46
|
+
* envelope — which is all traffic from today's MCP clients) is served over
|
|
47
|
+
* `NodeStreamableHTTPServerTransport` with `enableJsonResponse: true`, the
|
|
48
|
+
* same wiring and the same plain-JSON responses this package has always
|
|
49
|
+
* produced.
|
|
50
|
+
* - **`2026-07-28` traffic** is served by `createMcpHandler` with
|
|
51
|
+
* `legacy: 'reject'`, so the modern revision is handled by the SDK's
|
|
52
|
+
* stateless core rather than the transport above. Malformed requests are
|
|
53
|
+
* routed here too, so the SDK emits its own spec-defined rejection instead of
|
|
54
|
+
* this package inventing one.
|
|
55
|
+
*
|
|
56
|
+
* Splitting on the classifier — instead of letting `createMcpHandler` serve
|
|
57
|
+
* both eras through its built-in legacy fallback — is what keeps 2025-era
|
|
58
|
+
* responses byte-compatible. That fallback constructs its transport with only
|
|
59
|
+
* `sessionIdGenerator: undefined` and no way to pass `enableJsonResponse`, so
|
|
60
|
+
* every response it produces is SSE-framed (`text/event-stream`) regardless of
|
|
61
|
+
* the handler's `responseMode`. Owning the legacy branch keeps `application/json`.
|
|
62
|
+
*/
|
|
63
|
+
var createMcpRequestServer = ({
|
|
64
|
+
server,
|
|
65
|
+
sessionIdGenerator
|
|
66
|
+
}) => {
|
|
67
|
+
const isStateful = sessionIdGenerator !== void 0;
|
|
68
|
+
const modernHandler = (0, _modelcontextprotocol_node.toNodeHandler)((0, _modelcontextprotocol_server.createMcpHandler)(() => {
|
|
69
|
+
return server;
|
|
70
|
+
}, {
|
|
71
|
+
legacy: "reject"
|
|
72
|
+
}));
|
|
73
|
+
let sharedTransport;
|
|
74
|
+
if (isStateful) {
|
|
75
|
+
sharedTransport = new _modelcontextprotocol_node.NodeStreamableHTTPServerTransport({
|
|
76
|
+
sessionIdGenerator,
|
|
77
|
+
enableJsonResponse: true
|
|
78
|
+
});
|
|
79
|
+
server.connect(sharedTransport);
|
|
80
|
+
}
|
|
81
|
+
let statelessQueue = Promise.resolve();
|
|
82
|
+
const enqueueStateless = work => {
|
|
83
|
+
const result = statelessQueue.then(() => {
|
|
84
|
+
return work();
|
|
85
|
+
});
|
|
86
|
+
statelessQueue = result.catch(() => {});
|
|
87
|
+
return result;
|
|
88
|
+
};
|
|
89
|
+
const isLegacy = (ctx, body) => {
|
|
90
|
+
return (0, _modelcontextprotocol_server.classifyInboundRequest)({
|
|
91
|
+
httpMethod: ctx.method,
|
|
92
|
+
protocolVersionHeader: header(ctx, "mcp-protocol-version"),
|
|
93
|
+
mcpMethodHeader: header(ctx, "mcp-method"),
|
|
94
|
+
mcpNameHeader: header(ctx, "mcp-name"),
|
|
95
|
+
body
|
|
96
|
+
}).kind === "legacy";
|
|
97
|
+
};
|
|
98
|
+
return async (ctx, body) => {
|
|
99
|
+
if (!isLegacy(ctx, body)) {
|
|
100
|
+
await modernHandler(ctx.req, ctx.res, body);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
if (sharedTransport) {
|
|
104
|
+
await sharedTransport.handleRequest(ctx.req, ctx.res, body);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
await enqueueStateless(async () => {
|
|
108
|
+
const transport = new _modelcontextprotocol_node.NodeStreamableHTTPServerTransport({
|
|
109
|
+
sessionIdGenerator: void 0,
|
|
110
|
+
enableJsonResponse: true
|
|
111
|
+
});
|
|
112
|
+
try {
|
|
113
|
+
await server.connect(transport);
|
|
114
|
+
await transport.handleRequest(ctx.req, ctx.res, body);
|
|
115
|
+
} finally {
|
|
116
|
+
await transport.close();
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
27
122
|
//#endregion
|
|
28
123
|
//#region src/registerToolFromSchema.ts
|
|
29
|
-
|
|
30
|
-
|
|
124
|
+
/**
|
|
125
|
+
* Converts a plain JSON Schema into the Standard Schema `registerTool` accepts.
|
|
126
|
+
*
|
|
127
|
+
* A Standard Schema keeps advertisement and enforcement in separate fields:
|
|
128
|
+
* `~standard.jsonSchema` is what `tools/list` publishes, `~standard.validate`
|
|
129
|
+
* is what `tools/call` runs. Replacing only `validate` therefore keeps the
|
|
130
|
+
* schema fully visible to clients while leaving arguments unchecked.
|
|
131
|
+
*/
|
|
132
|
+
var toStandardSchema = ({
|
|
133
|
+
inputSchema,
|
|
134
|
+
validateArguments
|
|
135
|
+
}) => {
|
|
136
|
+
const schema = (0, _modelcontextprotocol_server.fromJsonSchema)(inputSchema);
|
|
137
|
+
if (validateArguments) return schema;
|
|
138
|
+
return {
|
|
139
|
+
"~standard": {
|
|
140
|
+
...schema["~standard"],
|
|
141
|
+
validate: value => {
|
|
142
|
+
return {
|
|
143
|
+
value
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
};
|
|
31
149
|
/**
|
|
32
150
|
* Registers a tool on an MCP server using a **plain JSON Schema** object for
|
|
33
151
|
* `inputSchema` instead of a Zod shape.
|
|
34
152
|
*
|
|
35
|
-
* This is useful when tool definitions are shared between the MCP server and
|
|
36
|
-
* AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
37
|
-
* plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
153
|
+
* This is useful when tool definitions are shared between the MCP server and
|
|
154
|
+
* an AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
155
|
+
* a plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
38
156
|
* JSON-Schema→Zod conversion that would otherwise be required.
|
|
39
157
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* your handler.
|
|
45
|
-
* 2. Stores the original JSON Schema and patches the `tools/list` response
|
|
46
|
-
* handler so clients receive the verbatim JSON Schema over the wire.
|
|
158
|
+
* Thin wrapper over `@modelcontextprotocol/server`'s `fromJsonSchema`, which
|
|
159
|
+
* converts a JSON Schema into a Standard Schema that `registerTool` accepts
|
|
160
|
+
* directly, so the schema round-trips verbatim over `tools/list`. Arguments
|
|
161
|
+
* reach the handler unchecked unless `validateArguments` is enabled.
|
|
47
162
|
*
|
|
48
163
|
* @param server - The `McpServer` instance to register the tool on.
|
|
49
164
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
50
|
-
* and handler.
|
|
165
|
+
* validateArguments, and handler.
|
|
51
166
|
*
|
|
52
167
|
* @example
|
|
53
168
|
* ```typescript
|
|
@@ -77,38 +192,18 @@ var registerToolFromSchema = (server, params) => {
|
|
|
77
192
|
type: "object",
|
|
78
193
|
properties: {}
|
|
79
194
|
},
|
|
195
|
+
validateArguments = false,
|
|
80
196
|
handler
|
|
81
197
|
} = params;
|
|
82
|
-
if (!rawSchemaRegistryMap.has(server)) rawSchemaRegistryMap.set(server, /* @__PURE__ */new Map());
|
|
83
|
-
rawSchemaRegistryMap.get(server).set(name, inputSchema);
|
|
84
198
|
server.registerTool(name, {
|
|
85
199
|
description,
|
|
86
|
-
inputSchema:
|
|
200
|
+
inputSchema: toStandardSchema({
|
|
201
|
+
inputSchema,
|
|
202
|
+
validateArguments
|
|
203
|
+
})
|
|
87
204
|
}, async args => {
|
|
88
205
|
return handler(args);
|
|
89
206
|
});
|
|
90
|
-
if (!patchedServerSet.has(server)) {
|
|
91
|
-
patchedServerSet.add(server);
|
|
92
|
-
const rawServer = server.server;
|
|
93
|
-
const origHandler = rawServer?._requestHandlers?.get("tools/list");
|
|
94
|
-
if (!origHandler && process.env.NODE_ENV !== "production") console.warn("[registerToolFromSchema] Could not patch tools/list — internal MCP SDK structure may have changed. The tool will still be callable, but tools/list may return a Zod-derived schema instead of the verbatim JSON Schema.");
|
|
95
|
-
if (origHandler) rawServer._requestHandlers.set("tools/list", async (rawRequest, extra) => {
|
|
96
|
-
const result = await origHandler(rawRequest, extra);
|
|
97
|
-
const schemas = rawSchemaRegistryMap.get(server);
|
|
98
|
-
if (!schemas) return result;
|
|
99
|
-
return {
|
|
100
|
-
...result,
|
|
101
|
-
tools: result.tools.map(tool => {
|
|
102
|
-
const raw = schemas.get(tool.name);
|
|
103
|
-
if (raw !== void 0) return {
|
|
104
|
-
...tool,
|
|
105
|
-
inputSchema: raw
|
|
106
|
-
};
|
|
107
|
-
return tool;
|
|
108
|
-
})
|
|
109
|
-
};
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
207
|
};
|
|
113
208
|
|
|
114
209
|
//#endregion
|
|
@@ -427,24 +522,11 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
427
522
|
getApiHeaders,
|
|
428
523
|
auth
|
|
429
524
|
} = options;
|
|
430
|
-
const isStateful = sessionIdGenerator !== void 0;
|
|
431
525
|
const needsContext = apiBaseUrl !== void 0 || getApiHeaders !== void 0 || auth !== void 0;
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
enableJsonResponse: true
|
|
437
|
-
});
|
|
438
|
-
server.connect(sharedTransport);
|
|
439
|
-
}
|
|
440
|
-
let statelessQueue = Promise.resolve();
|
|
441
|
-
const enqueueStateless = work => {
|
|
442
|
-
const result = statelessQueue.then(() => {
|
|
443
|
-
return work();
|
|
444
|
-
});
|
|
445
|
-
statelessQueue = result.catch(() => {});
|
|
446
|
-
return result;
|
|
447
|
-
};
|
|
526
|
+
const serveRequest = createMcpRequestServer({
|
|
527
|
+
server,
|
|
528
|
+
sessionIdGenerator
|
|
529
|
+
});
|
|
448
530
|
const router = new _ttoss_http_server.Router();
|
|
449
531
|
let authCheck;
|
|
450
532
|
if (auth) {
|
|
@@ -482,36 +564,16 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
482
564
|
const handleWithContext = async (ctx, body) => {
|
|
483
565
|
const apiHeaders = getApiHeaders ? getApiHeaders(ctx) : {};
|
|
484
566
|
const identity = ctx.state.user;
|
|
485
|
-
|
|
486
|
-
|
|
567
|
+
ctx.req.auth = identity;
|
|
568
|
+
const runRequest = async () => {
|
|
569
|
+
await serveRequest(ctx, body);
|
|
487
570
|
ctx.respond = false;
|
|
488
571
|
};
|
|
489
|
-
if (
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
}, () => {
|
|
495
|
-
return runRequest(sharedTransport);
|
|
496
|
-
});else await runRequest(sharedTransport);
|
|
497
|
-
} else await enqueueStateless(async () => {
|
|
498
|
-
const transport = new _modelcontextprotocol_sdk_server_streamableHttp_js.StreamableHTTPServerTransport({
|
|
499
|
-
sessionIdGenerator: void 0,
|
|
500
|
-
enableJsonResponse: true
|
|
501
|
-
});
|
|
502
|
-
try {
|
|
503
|
-
await server.connect(transport);
|
|
504
|
-
if (needsContext) await requestContextStore.run({
|
|
505
|
-
apiBaseUrl,
|
|
506
|
-
apiHeaders,
|
|
507
|
-
identity
|
|
508
|
-
}, () => {
|
|
509
|
-
return runRequest(transport);
|
|
510
|
-
});else await runRequest(transport);
|
|
511
|
-
} finally {
|
|
512
|
-
await transport.close();
|
|
513
|
-
}
|
|
514
|
-
});
|
|
572
|
+
if (needsContext) await requestContextStore.run({
|
|
573
|
+
apiBaseUrl,
|
|
574
|
+
apiHeaders,
|
|
575
|
+
identity
|
|
576
|
+
}, runRequest);else await runRequest();
|
|
515
577
|
};
|
|
516
578
|
const postHandler = async ctx => {
|
|
517
579
|
try {
|
|
@@ -552,7 +614,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
552
614
|
Object.defineProperty(exports, 'McpServer', {
|
|
553
615
|
enumerable: true,
|
|
554
616
|
get: function () {
|
|
555
|
-
return
|
|
617
|
+
return _modelcontextprotocol_server.McpServer;
|
|
556
618
|
}
|
|
557
619
|
});
|
|
558
620
|
exports.apiCall = apiCall;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/// <reference types="node" />
|
|
3
|
-
import { McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/
|
|
3
|
+
import { CallToolResult, McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/server";
|
|
4
4
|
import { Router } from "@ttoss/http-server";
|
|
5
5
|
import accepts from "accepts";
|
|
6
6
|
import { AsyncLocalStorage } from "async_hooks";
|
|
@@ -16,7 +16,6 @@ import compose from "koa-compose";
|
|
|
16
16
|
import { ListenOptions, Socket } from "net";
|
|
17
17
|
import { ParsedUrlQuery } from "querystring";
|
|
18
18
|
import * as url from "url";
|
|
19
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
20
19
|
import { z } from "zod";
|
|
21
20
|
|
|
22
21
|
//#region ../../node_modules/.pnpm/@types+koa@3.0.3/node_modules/@types/koa/index.d.ts
|
|
@@ -704,10 +703,28 @@ interface RegisterToolFromSchemaParams {
|
|
|
704
703
|
* loss. Defaults to `{ type: 'object', properties: {} }` when omitted.
|
|
705
704
|
*/
|
|
706
705
|
inputSchema?: JsonObjectSchema;
|
|
706
|
+
/**
|
|
707
|
+
* Whether `tools/call` arguments are validated against `inputSchema` before
|
|
708
|
+
* the handler runs, rejecting a mismatch with an MCP error.
|
|
709
|
+
*
|
|
710
|
+
* Defaults to `false`, which forwards arguments to the handler unchecked —
|
|
711
|
+
* the behavior this helper has always had. `inputSchema` is still advertised
|
|
712
|
+
* verbatim over `tools/list` either way; this only controls enforcement.
|
|
713
|
+
*
|
|
714
|
+
* Enable it once you know `inputSchema` describes every value the tool
|
|
715
|
+
* genuinely accepts. Schemas generated from an OpenAPI document are a common
|
|
716
|
+
* source of *incomplete* ones — a field a client may send as `null` to clear
|
|
717
|
+
* it, or one accepting several shapes, is easy to emit as a bare
|
|
718
|
+
* `{ type: 'string' }`. Validating against a schema like that rejects calls
|
|
719
|
+
* the underlying API would have accepted.
|
|
720
|
+
*
|
|
721
|
+
* @default false
|
|
722
|
+
*/
|
|
723
|
+
validateArguments?: boolean;
|
|
707
724
|
/**
|
|
708
725
|
* Tool handler invoked when the AI client calls the tool.
|
|
709
|
-
* Receives the
|
|
710
|
-
*
|
|
726
|
+
* Receives the request arguments, validated against `inputSchema` only when
|
|
727
|
+
* `validateArguments` is enabled.
|
|
711
728
|
*/
|
|
712
729
|
handler: (args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>;
|
|
713
730
|
}
|
|
@@ -715,22 +732,19 @@ interface RegisterToolFromSchemaParams {
|
|
|
715
732
|
* Registers a tool on an MCP server using a **plain JSON Schema** object for
|
|
716
733
|
* `inputSchema` instead of a Zod shape.
|
|
717
734
|
*
|
|
718
|
-
* This is useful when tool definitions are shared between the MCP server and
|
|
719
|
-
* AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
720
|
-
* plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
735
|
+
* This is useful when tool definitions are shared between the MCP server and
|
|
736
|
+
* an AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
737
|
+
* a plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
721
738
|
* JSON-Schema→Zod conversion that would otherwise be required.
|
|
722
739
|
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* your handler.
|
|
728
|
-
* 2. Stores the original JSON Schema and patches the `tools/list` response
|
|
729
|
-
* handler so clients receive the verbatim JSON Schema over the wire.
|
|
740
|
+
* Thin wrapper over `@modelcontextprotocol/server`'s `fromJsonSchema`, which
|
|
741
|
+
* converts a JSON Schema into a Standard Schema that `registerTool` accepts
|
|
742
|
+
* directly, so the schema round-trips verbatim over `tools/list`. Arguments
|
|
743
|
+
* reach the handler unchecked unless `validateArguments` is enabled.
|
|
730
744
|
*
|
|
731
745
|
* @param server - The `McpServer` instance to register the tool on.
|
|
732
746
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
733
|
-
* and handler.
|
|
747
|
+
* validateArguments, and handler.
|
|
734
748
|
*
|
|
735
749
|
* @example
|
|
736
750
|
* ```typescript
|
|
@@ -1024,6 +1038,10 @@ interface McpRouterOptions {
|
|
|
1024
1038
|
* When provided, a single shared transport is created and sessions are tracked.
|
|
1025
1039
|
* When undefined (default), the server operates in stateless mode where each
|
|
1026
1040
|
* HTTP request uses its own transport instance.
|
|
1041
|
+
*
|
|
1042
|
+
* Applies to 2025-era traffic. The `2026-07-28` protocol revision has no
|
|
1043
|
+
* session concept in its core, so requests speaking that revision are always
|
|
1044
|
+
* served statelessly regardless of this option.
|
|
1027
1045
|
*/
|
|
1028
1046
|
sessionIdGenerator?: () => string;
|
|
1029
1047
|
/**
|
package/dist/index.d.mts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
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 } from "@modelcontextprotocol/server";
|
|
5
6
|
import { z } from "zod";
|
|
6
|
-
import { McpServer, McpServer as McpServer$1 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
7
7
|
import accepts from "accepts";
|
|
8
8
|
import { AsyncLocalStorage as AsyncLocalStorage$1 } from "async_hooks";
|
|
9
9
|
import Cookies from "cookies";
|
|
@@ -18,7 +18,6 @@ import compose from "koa-compose";
|
|
|
18
18
|
import { ListenOptions, Socket } from "net";
|
|
19
19
|
import { ParsedUrlQuery } from "querystring";
|
|
20
20
|
import * as url from "url";
|
|
21
|
-
import { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
|
|
22
21
|
|
|
23
22
|
//#region ../../node_modules/.pnpm/@types+koa@3.0.3/node_modules/@types/koa/index.d.ts
|
|
24
23
|
declare interface ContextDelegatedRequest {
|
|
@@ -705,10 +704,28 @@ interface RegisterToolFromSchemaParams {
|
|
|
705
704
|
* loss. Defaults to `{ type: 'object', properties: {} }` when omitted.
|
|
706
705
|
*/
|
|
707
706
|
inputSchema?: JsonObjectSchema;
|
|
707
|
+
/**
|
|
708
|
+
* Whether `tools/call` arguments are validated against `inputSchema` before
|
|
709
|
+
* the handler runs, rejecting a mismatch with an MCP error.
|
|
710
|
+
*
|
|
711
|
+
* Defaults to `false`, which forwards arguments to the handler unchecked —
|
|
712
|
+
* the behavior this helper has always had. `inputSchema` is still advertised
|
|
713
|
+
* verbatim over `tools/list` either way; this only controls enforcement.
|
|
714
|
+
*
|
|
715
|
+
* Enable it once you know `inputSchema` describes every value the tool
|
|
716
|
+
* genuinely accepts. Schemas generated from an OpenAPI document are a common
|
|
717
|
+
* source of *incomplete* ones — a field a client may send as `null` to clear
|
|
718
|
+
* it, or one accepting several shapes, is easy to emit as a bare
|
|
719
|
+
* `{ type: 'string' }`. Validating against a schema like that rejects calls
|
|
720
|
+
* the underlying API would have accepted.
|
|
721
|
+
*
|
|
722
|
+
* @default false
|
|
723
|
+
*/
|
|
724
|
+
validateArguments?: boolean;
|
|
708
725
|
/**
|
|
709
726
|
* Tool handler invoked when the AI client calls the tool.
|
|
710
|
-
* Receives the
|
|
711
|
-
*
|
|
727
|
+
* Receives the request arguments, validated against `inputSchema` only when
|
|
728
|
+
* `validateArguments` is enabled.
|
|
712
729
|
*/
|
|
713
730
|
handler: (args: Record<string, unknown>) => CallToolResult | Promise<CallToolResult>;
|
|
714
731
|
}
|
|
@@ -716,22 +733,19 @@ interface RegisterToolFromSchemaParams {
|
|
|
716
733
|
* Registers a tool on an MCP server using a **plain JSON Schema** object for
|
|
717
734
|
* `inputSchema` instead of a Zod shape.
|
|
718
735
|
*
|
|
719
|
-
* This is useful when tool definitions are shared between the MCP server and
|
|
720
|
-
* AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
721
|
-
* plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
736
|
+
* This is useful when tool definitions are shared between the MCP server and
|
|
737
|
+
* an AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
738
|
+
* a plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
722
739
|
* JSON-Schema→Zod conversion that would otherwise be required.
|
|
723
740
|
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
* your handler.
|
|
729
|
-
* 2. Stores the original JSON Schema and patches the `tools/list` response
|
|
730
|
-
* handler so clients receive the verbatim JSON Schema over the wire.
|
|
741
|
+
* Thin wrapper over `@modelcontextprotocol/server`'s `fromJsonSchema`, which
|
|
742
|
+
* converts a JSON Schema into a Standard Schema that `registerTool` accepts
|
|
743
|
+
* directly, so the schema round-trips verbatim over `tools/list`. Arguments
|
|
744
|
+
* reach the handler unchecked unless `validateArguments` is enabled.
|
|
731
745
|
*
|
|
732
746
|
* @param server - The `McpServer` instance to register the tool on.
|
|
733
747
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
734
|
-
* and handler.
|
|
748
|
+
* validateArguments, and handler.
|
|
735
749
|
*
|
|
736
750
|
* @example
|
|
737
751
|
* ```typescript
|
|
@@ -1025,6 +1039,10 @@ interface McpRouterOptions {
|
|
|
1025
1039
|
* When provided, a single shared transport is created and sessions are tracked.
|
|
1026
1040
|
* When undefined (default), the server operates in stateless mode where each
|
|
1027
1041
|
* HTTP request uses its own transport instance.
|
|
1042
|
+
*
|
|
1043
|
+
* Applies to 2025-era traffic. The `2026-07-28` protocol revision has no
|
|
1044
|
+
* session concept in its core, so requests speaking that revision are always
|
|
1045
|
+
* served statelessly regardless of this option.
|
|
1028
1046
|
*/
|
|
1029
1047
|
sessionIdGenerator?: () => string;
|
|
1030
1048
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
-
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
2
|
import { CognitoJwtVerifier } from "@ttoss/auth-core/amazon-cognito";
|
|
4
3
|
import { Router } from "@ttoss/http-server";
|
|
5
4
|
import { authMiddleware } from "@ttoss/http-server-auth";
|
|
6
5
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
7
|
-
import {
|
|
8
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
6
|
+
import { NodeStreamableHTTPServerTransport, toNodeHandler } from "@modelcontextprotocol/node";
|
|
7
|
+
import { McpServer, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
8
|
+
import { z } from "zod";
|
|
9
9
|
|
|
10
10
|
//#region src/context.ts
|
|
11
11
|
var requestContextStore = new AsyncLocalStorage();
|
|
@@ -21,30 +21,145 @@ var getIdentity = () => {
|
|
|
21
21
|
return requestContextStore.getStore()?.identity;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/serveMcpRequest.ts
|
|
26
|
+
/**
|
|
27
|
+
* Reads a header as `string | undefined`. Koa's `ctx.get` returns `''` for a
|
|
28
|
+
* missing header, which the classifier would read as "header present but
|
|
29
|
+
* empty" rather than absent.
|
|
30
|
+
*/
|
|
31
|
+
var header = (ctx, name) => {
|
|
32
|
+
const value = ctx.get(name);
|
|
33
|
+
return value === "" ? void 0 : value;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Builds the per-request serving function used by `createMcpRouter`, routing
|
|
37
|
+
* each request to the protocol revision it actually speaks.
|
|
38
|
+
*
|
|
39
|
+
* Requests are classified once at the boundary with the SDK's own
|
|
40
|
+
* `classifyInboundRequest`:
|
|
41
|
+
*
|
|
42
|
+
* - **2025-era traffic** (everything without a `2026-07-28` per-request
|
|
43
|
+
* envelope — which is all traffic from today's MCP clients) is served over
|
|
44
|
+
* `NodeStreamableHTTPServerTransport` with `enableJsonResponse: true`, the
|
|
45
|
+
* same wiring and the same plain-JSON responses this package has always
|
|
46
|
+
* produced.
|
|
47
|
+
* - **`2026-07-28` traffic** is served by `createMcpHandler` with
|
|
48
|
+
* `legacy: 'reject'`, so the modern revision is handled by the SDK's
|
|
49
|
+
* stateless core rather than the transport above. Malformed requests are
|
|
50
|
+
* routed here too, so the SDK emits its own spec-defined rejection instead of
|
|
51
|
+
* this package inventing one.
|
|
52
|
+
*
|
|
53
|
+
* Splitting on the classifier — instead of letting `createMcpHandler` serve
|
|
54
|
+
* both eras through its built-in legacy fallback — is what keeps 2025-era
|
|
55
|
+
* responses byte-compatible. That fallback constructs its transport with only
|
|
56
|
+
* `sessionIdGenerator: undefined` and no way to pass `enableJsonResponse`, so
|
|
57
|
+
* every response it produces is SSE-framed (`text/event-stream`) regardless of
|
|
58
|
+
* the handler's `responseMode`. Owning the legacy branch keeps `application/json`.
|
|
59
|
+
*/
|
|
60
|
+
var createMcpRequestServer = ({
|
|
61
|
+
server,
|
|
62
|
+
sessionIdGenerator
|
|
63
|
+
}) => {
|
|
64
|
+
const isStateful = sessionIdGenerator !== void 0;
|
|
65
|
+
const modernHandler = toNodeHandler(createMcpHandler(() => {
|
|
66
|
+
return server;
|
|
67
|
+
}, {
|
|
68
|
+
legacy: "reject"
|
|
69
|
+
}));
|
|
70
|
+
let sharedTransport;
|
|
71
|
+
if (isStateful) {
|
|
72
|
+
sharedTransport = new NodeStreamableHTTPServerTransport({
|
|
73
|
+
sessionIdGenerator,
|
|
74
|
+
enableJsonResponse: true
|
|
75
|
+
});
|
|
76
|
+
server.connect(sharedTransport);
|
|
77
|
+
}
|
|
78
|
+
let statelessQueue = Promise.resolve();
|
|
79
|
+
const enqueueStateless = work => {
|
|
80
|
+
const result = statelessQueue.then(() => {
|
|
81
|
+
return work();
|
|
82
|
+
});
|
|
83
|
+
statelessQueue = result.catch(() => {});
|
|
84
|
+
return result;
|
|
85
|
+
};
|
|
86
|
+
const isLegacy = (ctx, body) => {
|
|
87
|
+
return classifyInboundRequest({
|
|
88
|
+
httpMethod: ctx.method,
|
|
89
|
+
protocolVersionHeader: header(ctx, "mcp-protocol-version"),
|
|
90
|
+
mcpMethodHeader: header(ctx, "mcp-method"),
|
|
91
|
+
mcpNameHeader: header(ctx, "mcp-name"),
|
|
92
|
+
body
|
|
93
|
+
}).kind === "legacy";
|
|
94
|
+
};
|
|
95
|
+
return async (ctx, body) => {
|
|
96
|
+
if (!isLegacy(ctx, body)) {
|
|
97
|
+
await modernHandler(ctx.req, ctx.res, body);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (sharedTransport) {
|
|
101
|
+
await sharedTransport.handleRequest(ctx.req, ctx.res, body);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
await enqueueStateless(async () => {
|
|
105
|
+
const transport = new NodeStreamableHTTPServerTransport({
|
|
106
|
+
sessionIdGenerator: void 0,
|
|
107
|
+
enableJsonResponse: true
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
await server.connect(transport);
|
|
111
|
+
await transport.handleRequest(ctx.req, ctx.res, body);
|
|
112
|
+
} finally {
|
|
113
|
+
await transport.close();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
24
119
|
//#endregion
|
|
25
120
|
//#region src/registerToolFromSchema.ts
|
|
26
|
-
|
|
27
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Converts a plain JSON Schema into the Standard Schema `registerTool` accepts.
|
|
123
|
+
*
|
|
124
|
+
* A Standard Schema keeps advertisement and enforcement in separate fields:
|
|
125
|
+
* `~standard.jsonSchema` is what `tools/list` publishes, `~standard.validate`
|
|
126
|
+
* is what `tools/call` runs. Replacing only `validate` therefore keeps the
|
|
127
|
+
* schema fully visible to clients while leaving arguments unchecked.
|
|
128
|
+
*/
|
|
129
|
+
var toStandardSchema = ({
|
|
130
|
+
inputSchema,
|
|
131
|
+
validateArguments
|
|
132
|
+
}) => {
|
|
133
|
+
const schema = fromJsonSchema(inputSchema);
|
|
134
|
+
if (validateArguments) return schema;
|
|
135
|
+
return {
|
|
136
|
+
"~standard": {
|
|
137
|
+
...schema["~standard"],
|
|
138
|
+
validate: value => {
|
|
139
|
+
return {
|
|
140
|
+
value
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
};
|
|
28
146
|
/**
|
|
29
147
|
* Registers a tool on an MCP server using a **plain JSON Schema** object for
|
|
30
148
|
* `inputSchema` instead of a Zod shape.
|
|
31
149
|
*
|
|
32
|
-
* This is useful when tool definitions are shared between the MCP server and
|
|
33
|
-
* AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
34
|
-
* plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
150
|
+
* This is useful when tool definitions are shared between the MCP server and
|
|
151
|
+
* an AI SDK agent (e.g. Vercel AI SDK's `tool()` helper), because both consume
|
|
152
|
+
* a plain JSON Schema at runtime. Using this helper eliminates the lossy
|
|
35
153
|
* JSON-Schema→Zod conversion that would otherwise be required.
|
|
36
154
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* your handler.
|
|
42
|
-
* 2. Stores the original JSON Schema and patches the `tools/list` response
|
|
43
|
-
* handler so clients receive the verbatim JSON Schema over the wire.
|
|
155
|
+
* Thin wrapper over `@modelcontextprotocol/server`'s `fromJsonSchema`, which
|
|
156
|
+
* converts a JSON Schema into a Standard Schema that `registerTool` accepts
|
|
157
|
+
* directly, so the schema round-trips verbatim over `tools/list`. Arguments
|
|
158
|
+
* reach the handler unchecked unless `validateArguments` is enabled.
|
|
44
159
|
*
|
|
45
160
|
* @param server - The `McpServer` instance to register the tool on.
|
|
46
161
|
* @param params - Tool configuration including name, description, inputSchema,
|
|
47
|
-
* and handler.
|
|
162
|
+
* validateArguments, and handler.
|
|
48
163
|
*
|
|
49
164
|
* @example
|
|
50
165
|
* ```typescript
|
|
@@ -74,38 +189,18 @@ var registerToolFromSchema = (server, params) => {
|
|
|
74
189
|
type: "object",
|
|
75
190
|
properties: {}
|
|
76
191
|
},
|
|
192
|
+
validateArguments = false,
|
|
77
193
|
handler
|
|
78
194
|
} = params;
|
|
79
|
-
if (!rawSchemaRegistryMap.has(server)) rawSchemaRegistryMap.set(server, /* @__PURE__ */new Map());
|
|
80
|
-
rawSchemaRegistryMap.get(server).set(name, inputSchema);
|
|
81
195
|
server.registerTool(name, {
|
|
82
196
|
description,
|
|
83
|
-
inputSchema:
|
|
197
|
+
inputSchema: toStandardSchema({
|
|
198
|
+
inputSchema,
|
|
199
|
+
validateArguments
|
|
200
|
+
})
|
|
84
201
|
}, async args => {
|
|
85
202
|
return handler(args);
|
|
86
203
|
});
|
|
87
|
-
if (!patchedServerSet.has(server)) {
|
|
88
|
-
patchedServerSet.add(server);
|
|
89
|
-
const rawServer = server.server;
|
|
90
|
-
const origHandler = rawServer?._requestHandlers?.get("tools/list");
|
|
91
|
-
if (!origHandler && process.env.NODE_ENV !== "production") console.warn("[registerToolFromSchema] Could not patch tools/list — internal MCP SDK structure may have changed. The tool will still be callable, but tools/list may return a Zod-derived schema instead of the verbatim JSON Schema.");
|
|
92
|
-
if (origHandler) rawServer._requestHandlers.set("tools/list", async (rawRequest, extra) => {
|
|
93
|
-
const result = await origHandler(rawRequest, extra);
|
|
94
|
-
const schemas = rawSchemaRegistryMap.get(server);
|
|
95
|
-
if (!schemas) return result;
|
|
96
|
-
return {
|
|
97
|
-
...result,
|
|
98
|
-
tools: result.tools.map(tool => {
|
|
99
|
-
const raw = schemas.get(tool.name);
|
|
100
|
-
if (raw !== void 0) return {
|
|
101
|
-
...tool,
|
|
102
|
-
inputSchema: raw
|
|
103
|
-
};
|
|
104
|
-
return tool;
|
|
105
|
-
})
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
204
|
};
|
|
110
205
|
|
|
111
206
|
//#endregion
|
|
@@ -424,24 +519,11 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
424
519
|
getApiHeaders,
|
|
425
520
|
auth
|
|
426
521
|
} = options;
|
|
427
|
-
const isStateful = sessionIdGenerator !== void 0;
|
|
428
522
|
const needsContext = apiBaseUrl !== void 0 || getApiHeaders !== void 0 || auth !== void 0;
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
enableJsonResponse: true
|
|
434
|
-
});
|
|
435
|
-
server.connect(sharedTransport);
|
|
436
|
-
}
|
|
437
|
-
let statelessQueue = Promise.resolve();
|
|
438
|
-
const enqueueStateless = work => {
|
|
439
|
-
const result = statelessQueue.then(() => {
|
|
440
|
-
return work();
|
|
441
|
-
});
|
|
442
|
-
statelessQueue = result.catch(() => {});
|
|
443
|
-
return result;
|
|
444
|
-
};
|
|
523
|
+
const serveRequest = createMcpRequestServer({
|
|
524
|
+
server,
|
|
525
|
+
sessionIdGenerator
|
|
526
|
+
});
|
|
445
527
|
const router = new Router();
|
|
446
528
|
let authCheck;
|
|
447
529
|
if (auth) {
|
|
@@ -479,36 +561,16 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
479
561
|
const handleWithContext = async (ctx, body) => {
|
|
480
562
|
const apiHeaders = getApiHeaders ? getApiHeaders(ctx) : {};
|
|
481
563
|
const identity = ctx.state.user;
|
|
482
|
-
|
|
483
|
-
|
|
564
|
+
ctx.req.auth = identity;
|
|
565
|
+
const runRequest = async () => {
|
|
566
|
+
await serveRequest(ctx, body);
|
|
484
567
|
ctx.respond = false;
|
|
485
568
|
};
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
}, () => {
|
|
492
|
-
return runRequest(sharedTransport);
|
|
493
|
-
});else await runRequest(sharedTransport);
|
|
494
|
-
} else await enqueueStateless(async () => {
|
|
495
|
-
const transport = new StreamableHTTPServerTransport({
|
|
496
|
-
sessionIdGenerator: void 0,
|
|
497
|
-
enableJsonResponse: true
|
|
498
|
-
});
|
|
499
|
-
try {
|
|
500
|
-
await server.connect(transport);
|
|
501
|
-
if (needsContext) await requestContextStore.run({
|
|
502
|
-
apiBaseUrl,
|
|
503
|
-
apiHeaders,
|
|
504
|
-
identity
|
|
505
|
-
}, () => {
|
|
506
|
-
return runRequest(transport);
|
|
507
|
-
});else await runRequest(transport);
|
|
508
|
-
} finally {
|
|
509
|
-
await transport.close();
|
|
510
|
-
}
|
|
511
|
-
});
|
|
569
|
+
if (needsContext) await requestContextStore.run({
|
|
570
|
+
apiBaseUrl,
|
|
571
|
+
apiHeaders,
|
|
572
|
+
identity
|
|
573
|
+
}, runRequest);else await runRequest();
|
|
512
574
|
};
|
|
513
575
|
const postHandler = async ctx => {
|
|
514
576
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.1",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -33,11 +33,12 @@
|
|
|
33
33
|
"dist"
|
|
34
34
|
],
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@modelcontextprotocol/
|
|
36
|
+
"@modelcontextprotocol/node": "^2.0.0",
|
|
37
|
+
"@modelcontextprotocol/server": "^2.0.0",
|
|
37
38
|
"zod": "^4.4.3",
|
|
38
|
-
"@ttoss/auth-core": "^0.
|
|
39
|
-
"@ttoss/http-server": "^0.
|
|
40
|
-
"@ttoss/http-server
|
|
39
|
+
"@ttoss/auth-core": "^0.13.0",
|
|
40
|
+
"@ttoss/http-server-auth": "^0.6.0",
|
|
41
|
+
"@ttoss/http-server": "^0.7.1"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/koa": "^3.0.3",
|
|
@@ -46,9 +47,6 @@
|
|
|
46
47
|
"tsdown": "^0.22.2",
|
|
47
48
|
"@ttoss/config": "^1.37.17"
|
|
48
49
|
},
|
|
49
|
-
"peerDependencies": {
|
|
50
|
-
"@modelcontextprotocol/sdk": "^1.29.0"
|
|
51
|
-
},
|
|
52
50
|
"publishConfig": {
|
|
53
51
|
"access": "public",
|
|
54
52
|
"provenance": true
|