toolcraft 0.0.197 → 0.0.199
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/composition.json +3 -3
- package/dist/composition.json +3 -3
- package/node_modules/tiny-http-mcp-server/README.md +6 -6
- package/node_modules/tiny-http-mcp-server/dist/cli.js +15 -3
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +4 -0
- package/node_modules/tiny-http-mcp-server/dist/http-transport.js +34 -15
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +31 -3
- package/node_modules/tiny-stdio-mcp-server/dist/tool-call-admission.d.ts +9 -0
- package/node_modules/tiny-stdio-mcp-server/dist/tool-call-admission.js +51 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +4 -0
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +2 -2
package/composition.json
CHANGED
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "tiny-http-mcp-server",
|
|
101
|
-
"version": "0.1.
|
|
101
|
+
"version": "0.1.13",
|
|
102
102
|
"license": "MIT"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.199",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.199",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
package/dist/composition.json
CHANGED
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
},
|
|
99
99
|
{
|
|
100
100
|
"name": "tiny-http-mcp-server",
|
|
101
|
-
"version": "0.1.
|
|
101
|
+
"version": "0.1.13",
|
|
102
102
|
"license": "MIT"
|
|
103
103
|
},
|
|
104
104
|
{
|
|
@@ -113,7 +113,7 @@
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "toolcraft",
|
|
116
|
-
"version": "0.0.
|
|
116
|
+
"version": "0.0.199",
|
|
117
117
|
"license": "MIT"
|
|
118
118
|
},
|
|
119
119
|
{
|
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
125
|
"name": "toolcraft-schema",
|
|
126
|
-
"version": "0.0.
|
|
126
|
+
"version": "0.0.199",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -430,13 +430,13 @@ Returned `HttpServer` instances support:
|
|
|
430
430
|
| `allowedOrigins` | `readonly string[]` | `[]` | Allowed CORS `Origin` values. Empty means no cross-origin browser clients are allowed. |
|
|
431
431
|
| `maxRequestBytes` | `number` | unlimited | Maximum JSON request body size. |
|
|
432
432
|
| `maxBatchSize` | `number` | unlimited | Maximum JSON-RPC batch member count. |
|
|
433
|
-
| `maxSessions` | `number` |
|
|
434
|
-
| `sessionTtlMs` | `number` |
|
|
433
|
+
| `maxSessions` | `number` | `128` | Maximum active sessions. |
|
|
434
|
+
| `sessionTtlMs` | `number` | `900000` | Expire idle sessions after this duration. |
|
|
435
435
|
| `maxStreamsPerSession` | `number` | `1` | Maximum concurrent GET SSE streams per session. |
|
|
436
436
|
| `maxStreamBufferBytes` | `number` | `1048576` | End a GET SSE stream before a live write when its buffered bytes exceed this limit. |
|
|
437
437
|
| `maxSseEventHistory` | `number` | `100` | Number of server-sent events retained for `Last-Event-ID` replay. |
|
|
438
438
|
| `sseKeepAliveMs` | `number` | `30000` | GET SSE keepalive interval in milliseconds. Set to `0` to disable keepalive comments. |
|
|
439
|
-
| `maxConcurrentToolCalls` | `number` |
|
|
439
|
+
| `maxConcurrentToolCalls` | `number` | `4` | Maximum concurrent tool calls across sessions. |
|
|
440
440
|
| `sessionStore` | `SessionStore` | in-memory store | Pluggable session-record storage; SSE streams and replay history remain instance-local. |
|
|
441
441
|
| `requestIdGenerator` | `() => string` | incrementing ids | Generates request ids when `X-Request-Id` is absent. |
|
|
442
442
|
| `observability` | `HttpObservabilityOptions` | none | Emits request, auth, session, stream, and tool lifecycle events. |
|
|
@@ -535,8 +535,8 @@ Returned by `listenHttp()`:
|
|
|
535
535
|
| `allowedOrigins` | `readonly string[]` | `[]` | Allowed CORS origins. |
|
|
536
536
|
| `maxRequestBytes` | `number` | unlimited | Maximum JSON request body size. |
|
|
537
537
|
| `maxBatchSize` | `number` | unlimited | Maximum JSON-RPC batch member count. |
|
|
538
|
-
| `maxSessions` | `number` |
|
|
539
|
-
| `sessionTtlMs` | `number` |
|
|
538
|
+
| `maxSessions` | `number` | `128` | Maximum active sessions. |
|
|
539
|
+
| `sessionTtlMs` | `number` | `900000` | Idle session expiration window. |
|
|
540
540
|
| `maxStreamsPerSession` | `number` | `1` | Maximum concurrent GET SSE streams per session. |
|
|
541
541
|
| `maxStreamBufferBytes` | `number` | `1048576` | End a GET SSE stream before a live write when its buffered bytes exceed this limit. |
|
|
542
542
|
| `maxSseEventHistory` | `number` | `100` | Number of SSE events retained for replay. |
|
|
@@ -622,7 +622,7 @@ tiny-http-mcp-server [options]
|
|
|
622
622
|
| `--max-stream-buffer-bytes <bytes>` | `1048576` | End a GET SSE stream before a live notification or keepalive write when buffered bytes exceed this limit. |
|
|
623
623
|
| `--max-sse-event-history <count>` | `100` | Number of SSE events retained for `Last-Event-ID` replay. |
|
|
624
624
|
| `--sse-keep-alive-ms <ms>` | `30000` | GET SSE keepalive interval in milliseconds. Set to `0` to disable keepalive comments. |
|
|
625
|
-
| `--max-concurrent-tool-calls <count>` |
|
|
625
|
+
| `--max-concurrent-tool-calls <count>` | `4` | Maximum concurrent tool calls across sessions. |
|
|
626
626
|
| `--trusted-proxy` | off | Trust `X-Forwarded-Proto` and `X-Forwarded-Host` for metadata challenge URLs. |
|
|
627
627
|
| `--request-timeout-ms <ms>` | Node default | Node HTTP request timeout. |
|
|
628
628
|
| `--headers-timeout-ms <ms>` | Node default | Node HTTP headers timeout. |
|
|
@@ -31,8 +31,10 @@ const HELP_TEXT = [
|
|
|
31
31
|
" Maximum JSON request body size",
|
|
32
32
|
" --max-batch-size <count>",
|
|
33
33
|
" Maximum JSON-RPC batch member count",
|
|
34
|
-
" --max-sessions <count> Maximum active sessions",
|
|
35
|
-
" --
|
|
34
|
+
" --max-sessions <count> Maximum active sessions (default: 128)",
|
|
35
|
+
" --max-sessions-per-subject <count>",
|
|
36
|
+
" Maximum sessions per authenticated subject (default: 16)",
|
|
37
|
+
" --session-ttl-ms <ms> Expire idle sessions (default: 900000)",
|
|
36
38
|
" --max-streams-per-session <count>",
|
|
37
39
|
" Maximum concurrent GET SSE streams per session",
|
|
38
40
|
" --max-stream-buffer-bytes <bytes>",
|
|
@@ -42,7 +44,9 @@ const HELP_TEXT = [
|
|
|
42
44
|
" --sse-keep-alive-ms <ms>",
|
|
43
45
|
" GET SSE keepalive interval (default: 30000; 0 disables)",
|
|
44
46
|
" --max-concurrent-tool-calls <count>",
|
|
45
|
-
" Maximum concurrent tool calls across sessions",
|
|
47
|
+
" Maximum concurrent tool calls across sessions (default: 4)",
|
|
48
|
+
" --max-queued-tool-calls <count>",
|
|
49
|
+
" Maximum waiting tool calls (default: 64; 0 disables waiting)",
|
|
46
50
|
" --trusted-proxy Trust X-Forwarded-Proto and X-Forwarded-Host",
|
|
47
51
|
" --request-timeout-ms <ms>",
|
|
48
52
|
" Node HTTP request timeout",
|
|
@@ -192,12 +196,14 @@ function parseCliOptions(args) {
|
|
|
192
196
|
"max-request-bytes": { type: "string" },
|
|
193
197
|
"max-batch-size": { type: "string" },
|
|
194
198
|
"max-sessions": { type: "string" },
|
|
199
|
+
"max-sessions-per-subject": { type: "string" },
|
|
195
200
|
"session-ttl-ms": { type: "string" },
|
|
196
201
|
"max-streams-per-session": { type: "string" },
|
|
197
202
|
"max-stream-buffer-bytes": { type: "string" },
|
|
198
203
|
"max-sse-event-history": { type: "string" },
|
|
199
204
|
"sse-keep-alive-ms": { type: "string" },
|
|
200
205
|
"max-concurrent-tool-calls": { type: "string" },
|
|
206
|
+
"max-queued-tool-calls": { type: "string" },
|
|
201
207
|
"trusted-proxy": { type: "boolean" },
|
|
202
208
|
"request-timeout-ms": { type: "string" },
|
|
203
209
|
"headers-timeout-ms": { type: "string" },
|
|
@@ -217,12 +223,14 @@ function parseCliOptions(args) {
|
|
|
217
223
|
const maxRequestBytes = parseOptionalInteger(values["max-request-bytes"], "--max-request-bytes", 1);
|
|
218
224
|
const maxBatchSize = parseOptionalInteger(values["max-batch-size"], "--max-batch-size", 1);
|
|
219
225
|
const maxSessions = parseOptionalInteger(values["max-sessions"], "--max-sessions", 1);
|
|
226
|
+
const maxSessionsPerSubject = parseOptionalInteger(values["max-sessions-per-subject"], "--max-sessions-per-subject", 1);
|
|
220
227
|
const sessionTtlMs = parseOptionalInteger(values["session-ttl-ms"], "--session-ttl-ms", 1);
|
|
221
228
|
const maxStreamsPerSession = parseOptionalInteger(values["max-streams-per-session"], "--max-streams-per-session", 1);
|
|
222
229
|
const maxStreamBufferBytes = parseOptionalInteger(values["max-stream-buffer-bytes"], "--max-stream-buffer-bytes", 0);
|
|
223
230
|
const maxSseEventHistory = parseOptionalInteger(values["max-sse-event-history"], "--max-sse-event-history", 0);
|
|
224
231
|
const sseKeepAliveMs = parseOptionalInteger(values["sse-keep-alive-ms"], "--sse-keep-alive-ms", 0);
|
|
225
232
|
const maxConcurrentToolCalls = parseOptionalInteger(values["max-concurrent-tool-calls"], "--max-concurrent-tool-calls", 1);
|
|
233
|
+
const maxQueuedToolCalls = parseOptionalInteger(values["max-queued-tool-calls"], "--max-queued-tool-calls", 0);
|
|
226
234
|
const requestTimeoutMs = parseOptionalInteger(values["request-timeout-ms"], "--request-timeout-ms", 0);
|
|
227
235
|
const headersTimeoutMs = parseOptionalInteger(values["headers-timeout-ms"], "--headers-timeout-ms", 0);
|
|
228
236
|
const keepAliveTimeoutMs = parseOptionalInteger(values["keep-alive-timeout-ms"], "--keep-alive-timeout-ms", 0);
|
|
@@ -246,12 +254,14 @@ function parseCliOptions(args) {
|
|
|
246
254
|
...(maxRequestBytes === undefined ? {} : { maxRequestBytes }),
|
|
247
255
|
...(maxBatchSize === undefined ? {} : { maxBatchSize }),
|
|
248
256
|
...(maxSessions === undefined ? {} : { maxSessions }),
|
|
257
|
+
...(maxSessionsPerSubject === undefined ? {} : { maxSessionsPerSubject }),
|
|
249
258
|
...(sessionTtlMs === undefined ? {} : { sessionTtlMs }),
|
|
250
259
|
...(maxStreamsPerSession === undefined ? {} : { maxStreamsPerSession }),
|
|
251
260
|
...(maxStreamBufferBytes === undefined ? {} : { maxStreamBufferBytes }),
|
|
252
261
|
...(maxSseEventHistory === undefined ? {} : { maxSseEventHistory }),
|
|
253
262
|
...(sseKeepAliveMs === undefined ? {} : { sseKeepAliveMs }),
|
|
254
263
|
...(maxConcurrentToolCalls === undefined ? {} : { maxConcurrentToolCalls }),
|
|
264
|
+
...(maxQueuedToolCalls === undefined ? {} : { maxQueuedToolCalls }),
|
|
255
265
|
trustedProxy: values["trusted-proxy"] ?? false,
|
|
256
266
|
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
257
267
|
...(headersTimeoutMs === undefined ? {} : { headersTimeoutMs }),
|
|
@@ -392,6 +402,7 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
392
402
|
: { maxRequestBytes: options.maxRequestBytes }),
|
|
393
403
|
...(options.maxBatchSize === undefined ? {} : { maxBatchSize: options.maxBatchSize }),
|
|
394
404
|
...(options.maxSessions === undefined ? {} : { maxSessions: options.maxSessions }),
|
|
405
|
+
...(options.maxSessionsPerSubject === undefined ? {} : { maxSessionsPerSubject: options.maxSessionsPerSubject }),
|
|
395
406
|
...(options.sessionTtlMs === undefined ? {} : { sessionTtlMs: options.sessionTtlMs }),
|
|
396
407
|
...(options.maxStreamsPerSession === undefined
|
|
397
408
|
? {}
|
|
@@ -406,6 +417,7 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
406
417
|
...(options.maxConcurrentToolCalls === undefined
|
|
407
418
|
? {}
|
|
408
419
|
: { maxConcurrentToolCalls: options.maxConcurrentToolCalls }),
|
|
420
|
+
...(options.maxQueuedToolCalls === undefined ? {} : { maxQueuedToolCalls: options.maxQueuedToolCalls }),
|
|
409
421
|
...(options.trustedProxy ? { trustedProxy: true } : {}),
|
|
410
422
|
...(oauth === undefined ? {} : { oauth })
|
|
411
423
|
});
|
|
@@ -66,6 +66,8 @@ export interface StreamableHttpTransportOptions {
|
|
|
66
66
|
maxRequestBytes?: number;
|
|
67
67
|
maxBatchSize?: number;
|
|
68
68
|
maxSessions?: number;
|
|
69
|
+
/** Maximum sessions per authenticated subject or client ID; defaults to 16. */
|
|
70
|
+
maxSessionsPerSubject?: number;
|
|
69
71
|
sessionTtlMs?: number;
|
|
70
72
|
maxStreamsPerSession?: number;
|
|
71
73
|
maxStreamBufferBytes?: number;
|
|
@@ -88,6 +90,7 @@ export declare class StreamableHttpTransport {
|
|
|
88
90
|
private readonly maxRequestBytes;
|
|
89
91
|
private readonly maxBatchSize;
|
|
90
92
|
private readonly maxSessions;
|
|
93
|
+
private readonly maxSessionsPerSubject;
|
|
91
94
|
private readonly sessionTtlMs;
|
|
92
95
|
private readonly maxStreamsPerSession;
|
|
93
96
|
private readonly maxStreamBufferBytes;
|
|
@@ -128,6 +131,7 @@ export declare class StreamableHttpTransport {
|
|
|
128
131
|
private touchSession;
|
|
129
132
|
private isExpired;
|
|
130
133
|
private purgeExpiredSessions;
|
|
134
|
+
private sessions;
|
|
131
135
|
private sessionCount;
|
|
132
136
|
private deleteSession;
|
|
133
137
|
private createLocalMessageSession;
|
|
@@ -27,6 +27,7 @@ export class StreamableHttpTransport {
|
|
|
27
27
|
maxRequestBytes;
|
|
28
28
|
maxBatchSize;
|
|
29
29
|
maxSessions;
|
|
30
|
+
maxSessionsPerSubject;
|
|
30
31
|
sessionTtlMs;
|
|
31
32
|
maxStreamsPerSession;
|
|
32
33
|
maxStreamBufferBytes;
|
|
@@ -61,8 +62,10 @@ export class StreamableHttpTransport {
|
|
|
61
62
|
this.allowedHosts = new Set((options.allowedHosts ?? LOCAL_HOSTS).map((host) => this.normalizeHost(host)));
|
|
62
63
|
this.maxRequestBytes = validateOptionalIntegerOption("maxRequestBytes", options.maxRequestBytes, 1);
|
|
63
64
|
this.maxBatchSize = validateOptionalIntegerOption("maxBatchSize", options.maxBatchSize, 1);
|
|
64
|
-
this.maxSessions = validateOptionalIntegerOption("maxSessions", options.maxSessions, 1);
|
|
65
|
-
this.
|
|
65
|
+
this.maxSessions = validateOptionalIntegerOption("maxSessions", options.maxSessions, 1) ?? 128;
|
|
66
|
+
this.maxSessionsPerSubject =
|
|
67
|
+
validateOptionalIntegerOption("maxSessionsPerSubject", options.maxSessionsPerSubject, 1) ?? 16;
|
|
68
|
+
this.sessionTtlMs = validateOptionalIntegerOption("sessionTtlMs", options.sessionTtlMs, 1) ?? 15 * 60_000;
|
|
66
69
|
this.maxStreamsPerSession =
|
|
67
70
|
validateOptionalIntegerOption("maxStreamsPerSession", options.maxStreamsPerSession, 1) ?? 1;
|
|
68
71
|
this.maxStreamBufferBytes =
|
|
@@ -77,7 +80,7 @@ export class StreamableHttpTransport {
|
|
|
77
80
|
this.requestIdGenerator = options.requestIdGenerator ?? (() => `req-${this.nextRequestId++}`);
|
|
78
81
|
this.observability = options.observability ?? {};
|
|
79
82
|
this.trustedProxy = options.trustedProxy ?? false;
|
|
80
|
-
if (this.
|
|
83
|
+
if (this.sessionIdGenerator !== undefined) {
|
|
81
84
|
this.sessionExpiryInterval = setInterval(() => {
|
|
82
85
|
try {
|
|
83
86
|
this.purgeExpiredSessions();
|
|
@@ -228,10 +231,16 @@ export class StreamableHttpTransport {
|
|
|
228
231
|
this.respondWithRejection(res, 400, "session_id_required", "Mcp-Session-Id is required; initialize a session first.");
|
|
229
232
|
return;
|
|
230
233
|
}
|
|
231
|
-
|
|
234
|
+
this.purgeExpiredSessions();
|
|
235
|
+
if (this.sessionCount() >= this.maxSessions) {
|
|
232
236
|
this.respondWithRejection(res, 503, "session_limit_reached", "The server has reached its session limit; close a session or retry later.");
|
|
233
237
|
return;
|
|
234
238
|
}
|
|
239
|
+
const authSubject = this.readAuthSubject(req);
|
|
240
|
+
if (authSubject !== undefined && this.sessionCount(authSubject) >= this.maxSessionsPerSubject) {
|
|
241
|
+
this.respondWithRejection(res, 429, "subject_session_limit_reached", "The authenticated subject has reached its session limit; close a session or retry later.");
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
235
244
|
const newSessionId = this.sessionIdGenerator();
|
|
236
245
|
if (!this.isValidNewSessionId(newSessionId)) {
|
|
237
246
|
this.respondWithStatus(res, 500);
|
|
@@ -239,7 +248,6 @@ export class StreamableHttpTransport {
|
|
|
239
248
|
}
|
|
240
249
|
sessionId = newSessionId;
|
|
241
250
|
activeSession = this.sessionStore.create(newSessionId);
|
|
242
|
-
const authSubject = this.readAuthSubject(req);
|
|
243
251
|
if (authSubject !== undefined) {
|
|
244
252
|
activeSession.authSubject = authSubject;
|
|
245
253
|
}
|
|
@@ -562,26 +570,37 @@ export class StreamableHttpTransport {
|
|
|
562
570
|
session.lastSeenAt = new Date();
|
|
563
571
|
}
|
|
564
572
|
isExpired(session) {
|
|
565
|
-
if (this.sessionTtlMs === undefined) {
|
|
566
|
-
return false;
|
|
567
|
-
}
|
|
568
573
|
return Date.now() - session.lastSeenAt.getTime() > this.sessionTtlMs;
|
|
569
574
|
}
|
|
570
575
|
purgeExpiredSessions() {
|
|
571
|
-
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
for (const session of [...this.sessionStore.entries()]) {
|
|
576
|
+
for (const session of [...this.sessions()]) {
|
|
575
577
|
if (this.isExpired(session)) {
|
|
576
578
|
this.deleteSession(session.id, "expired");
|
|
577
579
|
}
|
|
578
580
|
}
|
|
579
581
|
}
|
|
580
|
-
|
|
582
|
+
*sessions() {
|
|
581
583
|
if (this.sessionStore.entries !== undefined) {
|
|
582
|
-
|
|
584
|
+
yield* this.sessionStore.entries();
|
|
585
|
+
}
|
|
586
|
+
else {
|
|
587
|
+
for (const id of this.sessionMessages.keys()) {
|
|
588
|
+
const session = this.sessionStore.get(id);
|
|
589
|
+
if (session !== undefined)
|
|
590
|
+
yield session;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
sessionCount(authSubject) {
|
|
595
|
+
if (authSubject === undefined && this.sessionStore.entries === undefined) {
|
|
596
|
+
return this.sessionMessages.size;
|
|
597
|
+
}
|
|
598
|
+
let count = 0;
|
|
599
|
+
for (const session of this.sessions()) {
|
|
600
|
+
if (authSubject === undefined || session.authSubject === authSubject)
|
|
601
|
+
count++;
|
|
583
602
|
}
|
|
584
|
-
return
|
|
603
|
+
return count;
|
|
585
604
|
}
|
|
586
605
|
deleteSession(sessionId, reason) {
|
|
587
606
|
const deleted = this.sessionStore.delete(sessionId);
|
|
@@ -4,6 +4,7 @@ import { JSON_RPC_ERROR_CODES, ToolError } from "./types.js";
|
|
|
4
4
|
import { parseMessage, formatSuccessResponse, formatErrorResponse } from "./jsonrpc.js";
|
|
5
5
|
import { parseUriTemplate } from "./uri-template.js";
|
|
6
6
|
import { toContentBlocks } from "./content/convert.js";
|
|
7
|
+
import { ToolCallAdmission } from "./tool-call-admission.js";
|
|
7
8
|
const PROTOCOL_VERSION = "2025-11-25";
|
|
8
9
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2025-03-26", "2025-06-18", PROTOCOL_VERSION]);
|
|
9
10
|
function compileToolSchema(schema) {
|
|
@@ -20,6 +21,17 @@ export function createServer(options) {
|
|
|
20
21
|
(!Number.isInteger(options.toolCallTimeoutMs) || options.toolCallTimeoutMs <= 0)) {
|
|
21
22
|
throw new Error("toolCallTimeoutMs must be a positive integer.");
|
|
22
23
|
}
|
|
24
|
+
const maxConcurrentToolCalls = options.maxConcurrentToolCalls ?? 4;
|
|
25
|
+
const maxQueuedToolCalls = options.maxQueuedToolCalls ?? 64;
|
|
26
|
+
for (const [name, value, minimum] of [
|
|
27
|
+
["maxConcurrentToolCalls", maxConcurrentToolCalls, 1],
|
|
28
|
+
["maxQueuedToolCalls", maxQueuedToolCalls, 0]
|
|
29
|
+
]) {
|
|
30
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
31
|
+
throw new Error(`${name} must be a safe integer greater than or equal to ${minimum}.`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const toolAdmission = new ToolCallAdmission(maxConcurrentToolCalls, maxQueuedToolCalls);
|
|
23
35
|
const supportNotifications = options.supportNotifications !== false;
|
|
24
36
|
const supportResourceSubscriptions = options.supportResourceSubscriptions !== false;
|
|
25
37
|
const tools = new Map();
|
|
@@ -142,17 +154,33 @@ export function createServer(options) {
|
|
|
142
154
|
}
|
|
143
155
|
try {
|
|
144
156
|
let handlerResult;
|
|
157
|
+
const admissionTimeout = options.toolCallTimeoutMs === undefined ? undefined : new AbortController();
|
|
158
|
+
const admissionSignal = AbortSignal.any([
|
|
159
|
+
lifecycle.abortController.signal,
|
|
160
|
+
...(admissionTimeout === undefined ? [] : [admissionTimeout.signal])
|
|
161
|
+
]);
|
|
162
|
+
const handlerPromise = (async () => {
|
|
163
|
+
const release = await toolAdmission.acquire(admissionSignal);
|
|
164
|
+
try {
|
|
165
|
+
admissionSignal.throwIfAborted();
|
|
166
|
+
return await tool.handler(toolArgs);
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
release();
|
|
170
|
+
}
|
|
171
|
+
})();
|
|
145
172
|
if (options.toolCallTimeoutMs === undefined) {
|
|
146
|
-
handlerResult = await
|
|
173
|
+
handlerResult = await handlerPromise;
|
|
147
174
|
}
|
|
148
175
|
else {
|
|
149
176
|
let timeout;
|
|
150
|
-
const handlerPromise = Promise.resolve().then(() => tool.handler(toolArgs));
|
|
151
177
|
handlerResult = await Promise.race([
|
|
152
178
|
handlerPromise,
|
|
153
179
|
new Promise((_resolve, reject) => {
|
|
154
180
|
timeout = setTimeout(() => {
|
|
155
|
-
|
|
181
|
+
const error = new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Tool call timed out: ${toolName}`);
|
|
182
|
+
admissionTimeout.abort(error);
|
|
183
|
+
reject(error);
|
|
156
184
|
}, options.toolCallTimeoutMs);
|
|
157
185
|
})
|
|
158
186
|
]).finally(() => {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare class ToolCallAdmission {
|
|
2
|
+
private readonly maximumActive;
|
|
3
|
+
private readonly maximumQueued;
|
|
4
|
+
private active;
|
|
5
|
+
private readonly waiting;
|
|
6
|
+
constructor(maximumActive: number, maximumQueued: number);
|
|
7
|
+
acquire(signal: AbortSignal): Promise<() => void>;
|
|
8
|
+
private release;
|
|
9
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { ToolError } from "./types.js";
|
|
2
|
+
export class ToolCallAdmission {
|
|
3
|
+
maximumActive;
|
|
4
|
+
maximumQueued;
|
|
5
|
+
active = 0;
|
|
6
|
+
waiting = [];
|
|
7
|
+
constructor(maximumActive, maximumQueued) {
|
|
8
|
+
this.maximumActive = maximumActive;
|
|
9
|
+
this.maximumQueued = maximumQueued;
|
|
10
|
+
}
|
|
11
|
+
acquire(signal) {
|
|
12
|
+
signal.throwIfAborted();
|
|
13
|
+
if (this.active < this.maximumActive) {
|
|
14
|
+
this.active++;
|
|
15
|
+
return Promise.resolve(this.release());
|
|
16
|
+
}
|
|
17
|
+
if (this.waiting.length >= this.maximumQueued) {
|
|
18
|
+
throw new ToolError(-32000, "Too many queued tool calls");
|
|
19
|
+
}
|
|
20
|
+
return new Promise((resolve, reject) => {
|
|
21
|
+
const waiting = {
|
|
22
|
+
signal,
|
|
23
|
+
resolve,
|
|
24
|
+
abort: () => {
|
|
25
|
+
const index = this.waiting.indexOf(waiting);
|
|
26
|
+
if (index !== -1)
|
|
27
|
+
this.waiting.splice(index, 1);
|
|
28
|
+
reject(signal.reason);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
this.waiting.push(waiting);
|
|
32
|
+
signal.addEventListener("abort", waiting.abort, { once: true });
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
release() {
|
|
36
|
+
let released = false;
|
|
37
|
+
return () => {
|
|
38
|
+
if (released)
|
|
39
|
+
return;
|
|
40
|
+
released = true;
|
|
41
|
+
const next = this.waiting.shift();
|
|
42
|
+
if (next === undefined) {
|
|
43
|
+
this.active--;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
next.signal.removeEventListener("abort", next.abort);
|
|
47
|
+
next.resolve(this.release());
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -209,6 +209,10 @@ export interface ServerOptions {
|
|
|
209
209
|
name: string;
|
|
210
210
|
version: string;
|
|
211
211
|
toolCallTimeoutMs?: number;
|
|
212
|
+
/** Shared active tool-handler capacity across sessions; defaults to four. */
|
|
213
|
+
maxConcurrentToolCalls?: number;
|
|
214
|
+
/** Maximum tool calls waiting for capacity; defaults to 64. Zero disables waiting. */
|
|
215
|
+
maxQueuedToolCalls?: number;
|
|
212
216
|
validateToolArguments?: boolean;
|
|
213
217
|
supportNotifications?: boolean;
|
|
214
218
|
supportResourceSubscriptions?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.199",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -158,7 +158,7 @@
|
|
|
158
158
|
"yaml"
|
|
159
159
|
],
|
|
160
160
|
"optionalDependencies": {
|
|
161
|
-
"toolcraft-schema": "0.0.
|
|
161
|
+
"toolcraft-schema": "0.0.199",
|
|
162
162
|
"toolcraft-design": "*",
|
|
163
163
|
"@poe-code/frontmatter": "*",
|
|
164
164
|
"@poe-code/agent-mcp-config": "*",
|