@ttoss/http-server-mcp 0.40.0 → 0.42.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/README.md +16 -1
- package/dist/index.cjs +62 -10
- package/dist/index.d.cts +26 -0
- package/dist/index.d.mts +26 -0
- package/dist/index.mjs +63 -11
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -484,13 +484,14 @@ registerTools({ server: fullServer, tools });
|
|
|
484
484
|
registerTools({ server: deferredServer, tools, defer: true });
|
|
485
485
|
```
|
|
486
486
|
|
|
487
|
-
A schema enters the model's context only for the tools it is about to use. `call` enforces the tool's schema as a direct call would, and its errors help the model recover: an unknown name answers suggestions (close typos first, then near-misses and tools sharing the name's words, by how much they share), and invalid arguments
|
|
487
|
+
A schema enters the model's context only for the tools it is about to use. `call` enforces the tool's schema as a direct call would, and its errors help the model recover: an unknown name answers suggestions (close typos first, then near-misses and tools sharing the name's words, by how much they share), and invalid arguments carry the input schema, so a `call` made without `describe` corrects itself in one retry.
|
|
488
488
|
|
|
489
489
|
Pass options instead of `true` to tune it:
|
|
490
490
|
|
|
491
491
|
- `except` — tools kept standalone, not deferred; they stay reachable through `search` too. Defaults to those linked to an [MCP Apps](#mcp-apps-interactive-uis) view, since a host finds a view through the tool's own `tools/list` entry.
|
|
492
492
|
- `visible` — runs on every `search`, `describe` and `call`; a tool it rejects is absent from all three. Standalone tools stay listed.
|
|
493
493
|
- `search` — replaces the default ranking, `rankTools`, e.g. with embeddings.
|
|
494
|
+
- `schemaOnError` (default `'invalidArguments'`) — which failed calls carry the input schema. By default only arguments that fail it do: a tool's own error result or a thrown error is usually a refusal of the operation (a 404, a 409), and a large schema on every refusal costs more context than deferring saved. `'always'` attaches it to those too, for tools that do not validate their arguments and whose errors may be argument errors only the API detects.
|
|
494
495
|
- `searchLimit` (default `10`), `searchMaxLimit` — the largest `limit` accepted (default `50`, or `searchLimit` when higher) — and `names` (default `search` / `describe` / `call`).
|
|
495
496
|
|
|
496
497
|
A gate (below) sees each call with the tool's own name and arguments, so authorization stays per tool when deferred.
|
|
@@ -676,6 +677,7 @@ Creates a Koa router configured to handle MCP protocol requests.
|
|
|
676
677
|
- `createMcpServer` (`McpServerFactory`) — Per-request factory serving the `2026-07-28` revision; see [Serving the `2026-07-28` revision](#serving-the-2026-07-28-revision)
|
|
677
678
|
- `bus` (`ServerEventBus`) — Change-event bus `subscriptions/listen` streams subscribe to (default: in-process); see [Change notifications and shutdown](#change-notifications-and-shutdown)
|
|
678
679
|
- `keepAliveMs` (`number`) — SSE keepalive interval for `2026-07-28` streams (default: `15000`)
|
|
680
|
+
- `streaming` (`boolean`) — `false` on a host that buffers the response (AWS Lambda behind API Gateway): no change notifications are advertised, so `subscriptions/listen` completes at once (default: `true`); see [Hosting without streaming](#hosting-without-streaming-aws-lambda)
|
|
679
681
|
- `apiBaseUrl` (`string`) — Base URL prepended to relative paths in `apiCall`
|
|
680
682
|
- `getApiHeaders` (`(ctx: Context) => Record<string, string>`) — Return headers to inject into every `apiCall` for this request
|
|
681
683
|
- `auth` (`McpAuthOptions`) — OAuth/JWT authentication; see [Authentication](#authentication)
|
|
@@ -1052,6 +1054,19 @@ process.on('SIGTERM', async () => {
|
|
|
1052
1054
|
|
|
1053
1055
|
The default bus is in-process, so `notify` reaches the streams this process holds. Behind several replicas, pass a `bus` implementing `ServerEventBus` over a shared pub/sub. Keep `keepAliveMs` below the idle timeout of anything in front of the server, or a quiet stream is cut there. 2025-era traffic is served statelessly, so it holds no stream to notify.
|
|
1054
1056
|
|
|
1057
|
+
### Hosting without streaming (AWS Lambda)
|
|
1058
|
+
|
|
1059
|
+
A host that buffers the whole response — AWS Lambda behind API Gateway, or any runtime without response streaming — never delivers a stream that stays open: the invocation dies at its timeout, and the client reopens the stream at once. Pass `streaming: false` there:
|
|
1060
|
+
|
|
1061
|
+
```typescript
|
|
1062
|
+
const mcpRouter = createMcpRouter(buildServer(), {
|
|
1063
|
+
createMcpServer: buildServer,
|
|
1064
|
+
streaming: false,
|
|
1065
|
+
});
|
|
1066
|
+
```
|
|
1067
|
+
|
|
1068
|
+
The router then stops every server instance it serves from advertising change notifications: `listChanged` for tools, prompts and resources, and `resources.subscribe`. `McpServer` turns them on by default for each kind you register, which is what makes the SDK hold a `subscriptions/listen` open; with nothing advertised, a listen is acknowledged and completed in the same response. A kind you register nothing for stays unadvertised. Nothing is lost on such a host: no process outlives the request to send a notification, so `notify` and `close()` reach no client.
|
|
1069
|
+
|
|
1055
1070
|
**Supported HTTP methods:**
|
|
1056
1071
|
|
|
1057
1072
|
- `POST /mcp` - Send JSON-RPC requests/notifications
|
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,49 @@ var NO_NOTIFIER = {
|
|
|
55
55
|
resourceUpdated: () => {}
|
|
56
56
|
};
|
|
57
57
|
/**
|
|
58
|
+
* Stops the server advertising change notifications for the tools, prompts
|
|
59
|
+
* and resources it serves, keeping every other capability as registered.
|
|
60
|
+
*
|
|
61
|
+
* `McpServer` advertises `listChanged: true` for each kind the moment one is
|
|
62
|
+
* registered, and the SDK then honours a `2026-07-28` `subscriptions/listen`
|
|
63
|
+
* with an SSE stream it never ends. A host that buffers the response until the
|
|
64
|
+
* handler returns — AWS Lambda behind API Gateway — delivers nothing and kills
|
|
65
|
+
* the invocation at its timeout, and the client reopens the stream at once, so
|
|
66
|
+
* each open session keeps one invocation busy for good (measured on a
|
|
67
|
+
* production Lambda in 2026-10: one 30 s timeout per session every ~30 s).
|
|
68
|
+
* With nothing advertised the SDK acknowledges the
|
|
69
|
+
* listen and completes it in the same response. A kind with nothing
|
|
70
|
+
* registered stays unadvertised, since advertising it would answer its `list`
|
|
71
|
+
* call with "method not found".
|
|
72
|
+
*/
|
|
73
|
+
var withoutChangeNotifications = product => {
|
|
74
|
+
const server = product instanceof _modelcontextprotocol_server.McpServer ? product.server : product;
|
|
75
|
+
const {
|
|
76
|
+
tools,
|
|
77
|
+
prompts,
|
|
78
|
+
resources
|
|
79
|
+
} = server.getCapabilities();
|
|
80
|
+
const off = {};
|
|
81
|
+
if (tools) off.tools = {
|
|
82
|
+
listChanged: false
|
|
83
|
+
};
|
|
84
|
+
if (prompts) off.prompts = {
|
|
85
|
+
listChanged: false
|
|
86
|
+
};
|
|
87
|
+
if (resources) off.resources = {
|
|
88
|
+
listChanged: false,
|
|
89
|
+
subscribe: false
|
|
90
|
+
};
|
|
91
|
+
server.registerCapabilities(off);
|
|
92
|
+
return product;
|
|
93
|
+
};
|
|
94
|
+
/** `factory`, with each instance it builds passed through {@link withoutChangeNotifications}. */
|
|
95
|
+
var servingNoChangeNotifications = factory => {
|
|
96
|
+
return factory && (async ctx => {
|
|
97
|
+
return withoutChangeNotifications(await factory(ctx));
|
|
98
|
+
});
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
58
101
|
* Reads a header as `string | undefined`. Koa's `ctx.get` returns `''` for a
|
|
59
102
|
* missing header, which the classifier would read as "header present but
|
|
60
103
|
* empty" rather than absent.
|
|
@@ -141,10 +184,13 @@ var createMcpRequestServer = ({
|
|
|
141
184
|
server,
|
|
142
185
|
sessionIdGenerator,
|
|
143
186
|
bus,
|
|
144
|
-
keepAliveMs
|
|
187
|
+
keepAliveMs,
|
|
188
|
+
streaming = true
|
|
145
189
|
}) => {
|
|
146
190
|
const isStateful = sessionIdGenerator !== void 0;
|
|
147
|
-
|
|
191
|
+
if (!streaming) withoutChangeNotifications(server);
|
|
192
|
+
const factory = streaming ? createMcpServer : servingNoChangeNotifications(createMcpServer);
|
|
193
|
+
const modern = factory ? (0, _modelcontextprotocol_server.createMcpHandler)(factory, {
|
|
148
194
|
legacy: "reject",
|
|
149
195
|
bus,
|
|
150
196
|
keepAliveMs
|
|
@@ -951,6 +997,7 @@ var deferredToolsOf = ({
|
|
|
951
997
|
tools,
|
|
952
998
|
searchLimit,
|
|
953
999
|
searchMaxLimit: options.searchMaxLimit ?? Math.max(DEFAULT_SEARCH_MAX_LIMIT, searchLimit),
|
|
1000
|
+
schemaOnError: options.schemaOnError ?? "invalidArguments",
|
|
954
1001
|
searchTools: options.search ?? rankTools,
|
|
955
1002
|
visibleTools: () => {
|
|
956
1003
|
return tools.filter(isVisible);
|
|
@@ -1094,7 +1141,8 @@ var describeToolOf = ({
|
|
|
1094
1141
|
/** Runs one tool the way a direct call would, with errors that help a retry. */
|
|
1095
1142
|
var runTool = async ({
|
|
1096
1143
|
tool,
|
|
1097
|
-
args
|
|
1144
|
+
args,
|
|
1145
|
+
schemaOnError
|
|
1098
1146
|
}) => {
|
|
1099
1147
|
const schemas = resolveToolSchemas(tool);
|
|
1100
1148
|
const validated = await schemas.input["~standard"].validate(args);
|
|
@@ -1106,24 +1154,24 @@ var runTool = async ({
|
|
|
1106
1154
|
},
|
|
1107
1155
|
isError: true
|
|
1108
1156
|
});
|
|
1109
|
-
const schemaHint = {
|
|
1157
|
+
const schemaHint = schemaOnError === "always" ? [{
|
|
1110
1158
|
type: "text",
|
|
1111
1159
|
text: JSON.stringify({
|
|
1112
1160
|
inputSchema: schemas.inputJson
|
|
1113
1161
|
})
|
|
1114
|
-
};
|
|
1162
|
+
}] : [];
|
|
1115
1163
|
try {
|
|
1116
1164
|
const result = await tool.handler(validated.value);
|
|
1117
1165
|
return result.isError ? {
|
|
1118
1166
|
...result,
|
|
1119
|
-
content: [...result.content, schemaHint]
|
|
1167
|
+
content: [...result.content, ...schemaHint]
|
|
1120
1168
|
} : result;
|
|
1121
1169
|
} catch (error) {
|
|
1122
1170
|
return {
|
|
1123
1171
|
content: [{
|
|
1124
1172
|
type: "text",
|
|
1125
1173
|
text: errorMessageOf(error)
|
|
1126
|
-
}, schemaHint],
|
|
1174
|
+
}, ...schemaHint],
|
|
1127
1175
|
isError: true
|
|
1128
1176
|
};
|
|
1129
1177
|
}
|
|
@@ -1131,7 +1179,8 @@ var runTool = async ({
|
|
|
1131
1179
|
var callToolOf = ({
|
|
1132
1180
|
names,
|
|
1133
1181
|
find,
|
|
1134
|
-
visibleTools
|
|
1182
|
+
visibleTools,
|
|
1183
|
+
schemaOnError
|
|
1135
1184
|
}) => {
|
|
1136
1185
|
return {
|
|
1137
1186
|
name: names.call,
|
|
@@ -1166,7 +1215,8 @@ var callToolOf = ({
|
|
|
1166
1215
|
});
|
|
1167
1216
|
return runTool({
|
|
1168
1217
|
tool,
|
|
1169
|
-
args: args.arguments ?? {}
|
|
1218
|
+
args: args.arguments ?? {},
|
|
1219
|
+
schemaOnError
|
|
1170
1220
|
});
|
|
1171
1221
|
}
|
|
1172
1222
|
};
|
|
@@ -1644,6 +1694,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
1644
1694
|
createMcpServer,
|
|
1645
1695
|
bus,
|
|
1646
1696
|
keepAliveMs,
|
|
1697
|
+
streaming,
|
|
1647
1698
|
apiBaseUrl,
|
|
1648
1699
|
getApiHeaders,
|
|
1649
1700
|
auth
|
|
@@ -1654,7 +1705,8 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
1654
1705
|
sessionIdGenerator,
|
|
1655
1706
|
createMcpServer,
|
|
1656
1707
|
bus,
|
|
1657
|
-
keepAliveMs
|
|
1708
|
+
keepAliveMs,
|
|
1709
|
+
streaming
|
|
1658
1710
|
});
|
|
1659
1711
|
const router = new _ttoss_http_server.Router();
|
|
1660
1712
|
let authCheck;
|
package/dist/index.d.cts
CHANGED
|
@@ -861,6 +861,20 @@ interface DeferToolsOptions {
|
|
|
861
861
|
* @default 50, or `searchLimit` when that is higher
|
|
862
862
|
*/
|
|
863
863
|
searchMaxLimit?: number;
|
|
864
|
+
/**
|
|
865
|
+
* Which failed `call`s carry the tool's input schema.
|
|
866
|
+
*
|
|
867
|
+
* - `'invalidArguments'` — only arguments that fail the tool's schema, so a
|
|
868
|
+
* `call` made without `describe` corrects itself in one retry. A tool's
|
|
869
|
+
* own error result or a thrown error is usually a refusal of the
|
|
870
|
+
* operation (a 404, a 409), and a large schema on every refusal costs
|
|
871
|
+
* more context than deferring saved.
|
|
872
|
+
* - `'always'` — those carry it too. For tools that do not validate their
|
|
873
|
+
* arguments, whose errors may be argument errors only the API detects.
|
|
874
|
+
*
|
|
875
|
+
* @default 'invalidArguments'
|
|
876
|
+
*/
|
|
877
|
+
schemaOnError?: 'always' | 'invalidArguments';
|
|
864
878
|
/**
|
|
865
879
|
* Runs on every `search`, `describe` and `call`. A tool it rejects is
|
|
866
880
|
* absent from all three; `call` answers it as an unknown name. Tools
|
|
@@ -1707,6 +1721,18 @@ export interface McpRouterOptions {
|
|
|
1707
1721
|
* @default 15000
|
|
1708
1722
|
*/
|
|
1709
1723
|
keepAliveMs?: number;
|
|
1724
|
+
/**
|
|
1725
|
+
* Whether the host delivers a response while the handler is still writing
|
|
1726
|
+
* it. Set `false` on a host that buffers the whole response — AWS Lambda
|
|
1727
|
+
* behind API Gateway, or any serverless runtime without response streaming.
|
|
1728
|
+
* The router then stops every instance it serves from advertising change
|
|
1729
|
+
* notifications (`listChanged` for tools, prompts and resources, and
|
|
1730
|
+
* `resources.subscribe`), so a `2026-07-28` `subscriptions/listen` is
|
|
1731
|
+
* acknowledged and completed at once instead of being held until the
|
|
1732
|
+
* platform's timeout. `notify` then reaches no client.
|
|
1733
|
+
* @default true
|
|
1734
|
+
*/
|
|
1735
|
+
streaming?: boolean;
|
|
1710
1736
|
/**
|
|
1711
1737
|
* Base URL prepended to relative paths passed to `apiCall` (paths starting
|
|
1712
1738
|
* with `/`). Tool handlers can then call `apiCall('GET', '/resource')` without
|
package/dist/index.d.mts
CHANGED
|
@@ -862,6 +862,20 @@ interface DeferToolsOptions {
|
|
|
862
862
|
* @default 50, or `searchLimit` when that is higher
|
|
863
863
|
*/
|
|
864
864
|
searchMaxLimit?: number;
|
|
865
|
+
/**
|
|
866
|
+
* Which failed `call`s carry the tool's input schema.
|
|
867
|
+
*
|
|
868
|
+
* - `'invalidArguments'` — only arguments that fail the tool's schema, so a
|
|
869
|
+
* `call` made without `describe` corrects itself in one retry. A tool's
|
|
870
|
+
* own error result or a thrown error is usually a refusal of the
|
|
871
|
+
* operation (a 404, a 409), and a large schema on every refusal costs
|
|
872
|
+
* more context than deferring saved.
|
|
873
|
+
* - `'always'` — those carry it too. For tools that do not validate their
|
|
874
|
+
* arguments, whose errors may be argument errors only the API detects.
|
|
875
|
+
*
|
|
876
|
+
* @default 'invalidArguments'
|
|
877
|
+
*/
|
|
878
|
+
schemaOnError?: 'always' | 'invalidArguments';
|
|
865
879
|
/**
|
|
866
880
|
* Runs on every `search`, `describe` and `call`. A tool it rejects is
|
|
867
881
|
* absent from all three; `call` answers it as an unknown name. Tools
|
|
@@ -1708,6 +1722,18 @@ export interface McpRouterOptions {
|
|
|
1708
1722
|
* @default 15000
|
|
1709
1723
|
*/
|
|
1710
1724
|
keepAliveMs?: number;
|
|
1725
|
+
/**
|
|
1726
|
+
* Whether the host delivers a response while the handler is still writing
|
|
1727
|
+
* it. Set `false` on a host that buffers the whole response — AWS Lambda
|
|
1728
|
+
* behind API Gateway, or any serverless runtime without response streaming.
|
|
1729
|
+
* The router then stops every instance it serves from advertising change
|
|
1730
|
+
* notifications (`listChanged` for tools, prompts and resources, and
|
|
1731
|
+
* `resources.subscribe`), so a `2026-07-28` `subscriptions/listen` is
|
|
1732
|
+
* acknowledged and completed at once instead of being held until the
|
|
1733
|
+
* platform's timeout. `notify` then reaches no client.
|
|
1734
|
+
* @default true
|
|
1735
|
+
*/
|
|
1736
|
+
streaming?: boolean;
|
|
1711
1737
|
/**
|
|
1712
1738
|
* Base URL prepended to relative paths passed to `apiCall` (paths starting
|
|
1713
1739
|
* 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 { InMemoryServerEventBus, McpServer, ProtocolErrorCode, SUPPORTED_PROTOCOL_VERSIONS, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
8
|
+
import { InMemoryServerEventBus, McpServer, McpServer as McpServer$1, ProtocolErrorCode, SUPPORTED_PROTOCOL_VERSIONS, classifyInboundRequest, createMcpHandler, fromJsonSchema } from "@modelcontextprotocol/server";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
|
|
11
11
|
//#region src/context.ts
|
|
@@ -52,6 +52,49 @@ var NO_NOTIFIER = {
|
|
|
52
52
|
resourceUpdated: () => {}
|
|
53
53
|
};
|
|
54
54
|
/**
|
|
55
|
+
* Stops the server advertising change notifications for the tools, prompts
|
|
56
|
+
* and resources it serves, keeping every other capability as registered.
|
|
57
|
+
*
|
|
58
|
+
* `McpServer` advertises `listChanged: true` for each kind the moment one is
|
|
59
|
+
* registered, and the SDK then honours a `2026-07-28` `subscriptions/listen`
|
|
60
|
+
* with an SSE stream it never ends. A host that buffers the response until the
|
|
61
|
+
* handler returns — AWS Lambda behind API Gateway — delivers nothing and kills
|
|
62
|
+
* the invocation at its timeout, and the client reopens the stream at once, so
|
|
63
|
+
* each open session keeps one invocation busy for good (measured on a
|
|
64
|
+
* production Lambda in 2026-10: one 30 s timeout per session every ~30 s).
|
|
65
|
+
* With nothing advertised the SDK acknowledges the
|
|
66
|
+
* listen and completes it in the same response. A kind with nothing
|
|
67
|
+
* registered stays unadvertised, since advertising it would answer its `list`
|
|
68
|
+
* call with "method not found".
|
|
69
|
+
*/
|
|
70
|
+
var withoutChangeNotifications = product => {
|
|
71
|
+
const server = product instanceof McpServer$1 ? product.server : product;
|
|
72
|
+
const {
|
|
73
|
+
tools,
|
|
74
|
+
prompts,
|
|
75
|
+
resources
|
|
76
|
+
} = server.getCapabilities();
|
|
77
|
+
const off = {};
|
|
78
|
+
if (tools) off.tools = {
|
|
79
|
+
listChanged: false
|
|
80
|
+
};
|
|
81
|
+
if (prompts) off.prompts = {
|
|
82
|
+
listChanged: false
|
|
83
|
+
};
|
|
84
|
+
if (resources) off.resources = {
|
|
85
|
+
listChanged: false,
|
|
86
|
+
subscribe: false
|
|
87
|
+
};
|
|
88
|
+
server.registerCapabilities(off);
|
|
89
|
+
return product;
|
|
90
|
+
};
|
|
91
|
+
/** `factory`, with each instance it builds passed through {@link withoutChangeNotifications}. */
|
|
92
|
+
var servingNoChangeNotifications = factory => {
|
|
93
|
+
return factory && (async ctx => {
|
|
94
|
+
return withoutChangeNotifications(await factory(ctx));
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
55
98
|
* Reads a header as `string | undefined`. Koa's `ctx.get` returns `''` for a
|
|
56
99
|
* missing header, which the classifier would read as "header present but
|
|
57
100
|
* empty" rather than absent.
|
|
@@ -138,10 +181,13 @@ var createMcpRequestServer = ({
|
|
|
138
181
|
server,
|
|
139
182
|
sessionIdGenerator,
|
|
140
183
|
bus,
|
|
141
|
-
keepAliveMs
|
|
184
|
+
keepAliveMs,
|
|
185
|
+
streaming = true
|
|
142
186
|
}) => {
|
|
143
187
|
const isStateful = sessionIdGenerator !== void 0;
|
|
144
|
-
|
|
188
|
+
if (!streaming) withoutChangeNotifications(server);
|
|
189
|
+
const factory = streaming ? createMcpServer : servingNoChangeNotifications(createMcpServer);
|
|
190
|
+
const modern = factory ? createMcpHandler(factory, {
|
|
145
191
|
legacy: "reject",
|
|
146
192
|
bus,
|
|
147
193
|
keepAliveMs
|
|
@@ -948,6 +994,7 @@ var deferredToolsOf = ({
|
|
|
948
994
|
tools,
|
|
949
995
|
searchLimit,
|
|
950
996
|
searchMaxLimit: options.searchMaxLimit ?? Math.max(DEFAULT_SEARCH_MAX_LIMIT, searchLimit),
|
|
997
|
+
schemaOnError: options.schemaOnError ?? "invalidArguments",
|
|
951
998
|
searchTools: options.search ?? rankTools,
|
|
952
999
|
visibleTools: () => {
|
|
953
1000
|
return tools.filter(isVisible);
|
|
@@ -1091,7 +1138,8 @@ var describeToolOf = ({
|
|
|
1091
1138
|
/** Runs one tool the way a direct call would, with errors that help a retry. */
|
|
1092
1139
|
var runTool = async ({
|
|
1093
1140
|
tool,
|
|
1094
|
-
args
|
|
1141
|
+
args,
|
|
1142
|
+
schemaOnError
|
|
1095
1143
|
}) => {
|
|
1096
1144
|
const schemas = resolveToolSchemas(tool);
|
|
1097
1145
|
const validated = await schemas.input["~standard"].validate(args);
|
|
@@ -1103,24 +1151,24 @@ var runTool = async ({
|
|
|
1103
1151
|
},
|
|
1104
1152
|
isError: true
|
|
1105
1153
|
});
|
|
1106
|
-
const schemaHint = {
|
|
1154
|
+
const schemaHint = schemaOnError === "always" ? [{
|
|
1107
1155
|
type: "text",
|
|
1108
1156
|
text: JSON.stringify({
|
|
1109
1157
|
inputSchema: schemas.inputJson
|
|
1110
1158
|
})
|
|
1111
|
-
};
|
|
1159
|
+
}] : [];
|
|
1112
1160
|
try {
|
|
1113
1161
|
const result = await tool.handler(validated.value);
|
|
1114
1162
|
return result.isError ? {
|
|
1115
1163
|
...result,
|
|
1116
|
-
content: [...result.content, schemaHint]
|
|
1164
|
+
content: [...result.content, ...schemaHint]
|
|
1117
1165
|
} : result;
|
|
1118
1166
|
} catch (error) {
|
|
1119
1167
|
return {
|
|
1120
1168
|
content: [{
|
|
1121
1169
|
type: "text",
|
|
1122
1170
|
text: errorMessageOf(error)
|
|
1123
|
-
}, schemaHint],
|
|
1171
|
+
}, ...schemaHint],
|
|
1124
1172
|
isError: true
|
|
1125
1173
|
};
|
|
1126
1174
|
}
|
|
@@ -1128,7 +1176,8 @@ var runTool = async ({
|
|
|
1128
1176
|
var callToolOf = ({
|
|
1129
1177
|
names,
|
|
1130
1178
|
find,
|
|
1131
|
-
visibleTools
|
|
1179
|
+
visibleTools,
|
|
1180
|
+
schemaOnError
|
|
1132
1181
|
}) => {
|
|
1133
1182
|
return {
|
|
1134
1183
|
name: names.call,
|
|
@@ -1163,7 +1212,8 @@ var callToolOf = ({
|
|
|
1163
1212
|
});
|
|
1164
1213
|
return runTool({
|
|
1165
1214
|
tool,
|
|
1166
|
-
args: args.arguments ?? {}
|
|
1215
|
+
args: args.arguments ?? {},
|
|
1216
|
+
schemaOnError
|
|
1167
1217
|
});
|
|
1168
1218
|
}
|
|
1169
1219
|
};
|
|
@@ -1641,6 +1691,7 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
1641
1691
|
createMcpServer,
|
|
1642
1692
|
bus,
|
|
1643
1693
|
keepAliveMs,
|
|
1694
|
+
streaming,
|
|
1644
1695
|
apiBaseUrl,
|
|
1645
1696
|
getApiHeaders,
|
|
1646
1697
|
auth
|
|
@@ -1651,7 +1702,8 @@ var createMcpRouter = (server, options = {}) => {
|
|
|
1651
1702
|
sessionIdGenerator,
|
|
1652
1703
|
createMcpServer,
|
|
1653
1704
|
bus,
|
|
1654
|
-
keepAliveMs
|
|
1705
|
+
keepAliveMs,
|
|
1706
|
+
streaming
|
|
1655
1707
|
});
|
|
1656
1708
|
const router = new Router();
|
|
1657
1709
|
let authCheck;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ttoss/http-server-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.42.0",
|
|
4
4
|
"description": "Model Context Protocol (MCP) server integration for @ttoss/http-server",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"@modelcontextprotocol/server": "^2.3.1",
|
|
39
39
|
"zod": "^4.6.7",
|
|
40
40
|
"@ttoss/auth-core": "^0.20.1",
|
|
41
|
-
"@ttoss/http-server": "^0.14.2",
|
|
42
41
|
"@ttoss/http-server-auth": "^0.10.2",
|
|
43
|
-
"@ttoss/i18n-core": "^0.9.2"
|
|
42
|
+
"@ttoss/i18n-core": "^0.9.2",
|
|
43
|
+
"@ttoss/http-server": "^0.14.2"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@modelcontextprotocol/client": "^2.3.1",
|