toolcraft 0.0.198 → 0.0.200
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 +2 -2
- package/node_modules/tiny-http-mcp-server/dist/cli.js +7 -1
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- 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 +85 -21
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-output.d.ts +24 -0
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-output.js +131 -0
- 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 +8 -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.14",
|
|
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.200",
|
|
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.200",
|
|
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.14",
|
|
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.200",
|
|
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.200",
|
|
127
127
|
"license": "MIT"
|
|
128
128
|
},
|
|
129
129
|
{
|
|
@@ -436,7 +436,7 @@ Returned `HttpServer` instances support:
|
|
|
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. |
|
|
@@ -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. |
|
|
@@ -44,7 +44,9 @@ const HELP_TEXT = [
|
|
|
44
44
|
" --sse-keep-alive-ms <ms>",
|
|
45
45
|
" GET SSE keepalive interval (default: 30000; 0 disables)",
|
|
46
46
|
" --max-concurrent-tool-calls <count>",
|
|
47
|
-
" 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)",
|
|
48
50
|
" --trusted-proxy Trust X-Forwarded-Proto and X-Forwarded-Host",
|
|
49
51
|
" --request-timeout-ms <ms>",
|
|
50
52
|
" Node HTTP request timeout",
|
|
@@ -201,6 +203,7 @@ function parseCliOptions(args) {
|
|
|
201
203
|
"max-sse-event-history": { type: "string" },
|
|
202
204
|
"sse-keep-alive-ms": { type: "string" },
|
|
203
205
|
"max-concurrent-tool-calls": { type: "string" },
|
|
206
|
+
"max-queued-tool-calls": { type: "string" },
|
|
204
207
|
"trusted-proxy": { type: "boolean" },
|
|
205
208
|
"request-timeout-ms": { type: "string" },
|
|
206
209
|
"headers-timeout-ms": { type: "string" },
|
|
@@ -227,6 +230,7 @@ function parseCliOptions(args) {
|
|
|
227
230
|
const maxSseEventHistory = parseOptionalInteger(values["max-sse-event-history"], "--max-sse-event-history", 0);
|
|
228
231
|
const sseKeepAliveMs = parseOptionalInteger(values["sse-keep-alive-ms"], "--sse-keep-alive-ms", 0);
|
|
229
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);
|
|
230
234
|
const requestTimeoutMs = parseOptionalInteger(values["request-timeout-ms"], "--request-timeout-ms", 0);
|
|
231
235
|
const headersTimeoutMs = parseOptionalInteger(values["headers-timeout-ms"], "--headers-timeout-ms", 0);
|
|
232
236
|
const keepAliveTimeoutMs = parseOptionalInteger(values["keep-alive-timeout-ms"], "--keep-alive-timeout-ms", 0);
|
|
@@ -257,6 +261,7 @@ function parseCliOptions(args) {
|
|
|
257
261
|
...(maxSseEventHistory === undefined ? {} : { maxSseEventHistory }),
|
|
258
262
|
...(sseKeepAliveMs === undefined ? {} : { sseKeepAliveMs }),
|
|
259
263
|
...(maxConcurrentToolCalls === undefined ? {} : { maxConcurrentToolCalls }),
|
|
264
|
+
...(maxQueuedToolCalls === undefined ? {} : { maxQueuedToolCalls }),
|
|
260
265
|
trustedProxy: values["trusted-proxy"] ?? false,
|
|
261
266
|
...(requestTimeoutMs === undefined ? {} : { requestTimeoutMs }),
|
|
262
267
|
...(headersTimeoutMs === undefined ? {} : { headersTimeoutMs }),
|
|
@@ -412,6 +417,7 @@ export async function runCli(args = process.argv.slice(2), dependencies = {}) {
|
|
|
412
417
|
...(options.maxConcurrentToolCalls === undefined
|
|
413
418
|
? {}
|
|
414
419
|
: { maxConcurrentToolCalls: options.maxConcurrentToolCalls }),
|
|
420
|
+
...(options.maxQueuedToolCalls === undefined ? {} : { maxQueuedToolCalls: options.maxQueuedToolCalls }),
|
|
415
421
|
...(options.trustedProxy ? { trustedProxy: true } : {}),
|
|
416
422
|
...(oauth === undefined ? {} : { oauth })
|
|
417
423
|
});
|
|
@@ -4,6 +4,8 @@ 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";
|
|
8
|
+
import { StdioOutput } from "./stdio-output.js";
|
|
7
9
|
const PROTOCOL_VERSION = "2025-11-25";
|
|
8
10
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2025-03-26", "2025-06-18", PROTOCOL_VERSION]);
|
|
9
11
|
function compileToolSchema(schema) {
|
|
@@ -20,6 +22,21 @@ export function createServer(options) {
|
|
|
20
22
|
(!Number.isInteger(options.toolCallTimeoutMs) || options.toolCallTimeoutMs <= 0)) {
|
|
21
23
|
throw new Error("toolCallTimeoutMs must be a positive integer.");
|
|
22
24
|
}
|
|
25
|
+
const maxConcurrentToolCalls = options.maxConcurrentToolCalls ?? 4;
|
|
26
|
+
const maxQueuedToolCalls = options.maxQueuedToolCalls ?? 64;
|
|
27
|
+
const maxStdioOutputBytes = options.maxStdioOutputBytes ?? 1024 * 1024;
|
|
28
|
+
const maxPendingStdioMessages = options.maxPendingStdioMessages ?? 128;
|
|
29
|
+
for (const [name, value, minimum] of [
|
|
30
|
+
["maxConcurrentToolCalls", maxConcurrentToolCalls, 1],
|
|
31
|
+
["maxQueuedToolCalls", maxQueuedToolCalls, 0],
|
|
32
|
+
["maxStdioOutputBytes", maxStdioOutputBytes, 1],
|
|
33
|
+
["maxPendingStdioMessages", maxPendingStdioMessages, 1]
|
|
34
|
+
]) {
|
|
35
|
+
if (!Number.isSafeInteger(value) || value < minimum) {
|
|
36
|
+
throw new Error(`${name} must be a safe integer greater than or equal to ${minimum}.`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const toolAdmission = new ToolCallAdmission(maxConcurrentToolCalls, maxQueuedToolCalls);
|
|
23
40
|
const supportNotifications = options.supportNotifications !== false;
|
|
24
41
|
const supportResourceSubscriptions = options.supportResourceSubscriptions !== false;
|
|
25
42
|
const tools = new Map();
|
|
@@ -142,17 +159,33 @@ export function createServer(options) {
|
|
|
142
159
|
}
|
|
143
160
|
try {
|
|
144
161
|
let handlerResult;
|
|
162
|
+
const admissionTimeout = options.toolCallTimeoutMs === undefined ? undefined : new AbortController();
|
|
163
|
+
const admissionSignal = AbortSignal.any([
|
|
164
|
+
lifecycle.abortController.signal,
|
|
165
|
+
...(admissionTimeout === undefined ? [] : [admissionTimeout.signal])
|
|
166
|
+
]);
|
|
167
|
+
const handlerPromise = (async () => {
|
|
168
|
+
const release = await toolAdmission.acquire(admissionSignal);
|
|
169
|
+
try {
|
|
170
|
+
admissionSignal.throwIfAborted();
|
|
171
|
+
return await tool.handler(toolArgs);
|
|
172
|
+
}
|
|
173
|
+
finally {
|
|
174
|
+
release();
|
|
175
|
+
}
|
|
176
|
+
})();
|
|
145
177
|
if (options.toolCallTimeoutMs === undefined) {
|
|
146
|
-
handlerResult = await
|
|
178
|
+
handlerResult = await handlerPromise;
|
|
147
179
|
}
|
|
148
180
|
else {
|
|
149
181
|
let timeout;
|
|
150
|
-
const handlerPromise = Promise.resolve().then(() => tool.handler(toolArgs));
|
|
151
182
|
handlerResult = await Promise.race([
|
|
152
183
|
handlerPromise,
|
|
153
184
|
new Promise((_resolve, reject) => {
|
|
154
185
|
timeout = setTimeout(() => {
|
|
155
|
-
|
|
186
|
+
const error = new ToolError(JSON_RPC_ERROR_CODES.INTERNAL_ERROR, `Tool call timed out: ${toolName}`);
|
|
187
|
+
admissionTimeout.abort(error);
|
|
188
|
+
reject(error);
|
|
156
189
|
}, options.toolCallTimeoutMs);
|
|
157
190
|
})
|
|
158
191
|
]).finally(() => {
|
|
@@ -333,7 +366,7 @@ export function createServer(options) {
|
|
|
333
366
|
const processLine = async (line, write, messageHandler) => {
|
|
334
367
|
const parsed = parseMessage(line);
|
|
335
368
|
if (!parsed.success) {
|
|
336
|
-
write(formatErrorResponse(parsed.id, parsed.error) + "\n");
|
|
369
|
+
await write(formatErrorResponse(parsed.id, parsed.error) + "\n");
|
|
337
370
|
return;
|
|
338
371
|
}
|
|
339
372
|
const { request, isNotification } = parsed;
|
|
@@ -342,7 +375,7 @@ export function createServer(options) {
|
|
|
342
375
|
}
|
|
343
376
|
if (!isNotification && request.method === "notifications/initialized") {
|
|
344
377
|
const requestWithId = request;
|
|
345
|
-
write(formatErrorResponse(requestWithId.id, {
|
|
378
|
+
await write(formatErrorResponse(requestWithId.id, {
|
|
346
379
|
code: JSON_RPC_ERROR_CODES.INVALID_REQUEST,
|
|
347
380
|
message: "Invalid Request"
|
|
348
381
|
}) + "\n");
|
|
@@ -355,7 +388,7 @@ export function createServer(options) {
|
|
|
355
388
|
catch {
|
|
356
389
|
if (!isNotification) {
|
|
357
390
|
const requestWithId = request;
|
|
358
|
-
write(formatErrorResponse(requestWithId.id, {
|
|
391
|
+
await write(formatErrorResponse(requestWithId.id, {
|
|
359
392
|
code: JSON_RPC_ERROR_CODES.INTERNAL_ERROR,
|
|
360
393
|
message: "Internal error"
|
|
361
394
|
}) + "\n");
|
|
@@ -368,10 +401,10 @@ export function createServer(options) {
|
|
|
368
401
|
}
|
|
369
402
|
const requestWithId = request;
|
|
370
403
|
if (error) {
|
|
371
|
-
write(formatErrorResponse(requestWithId.id, error) + "\n");
|
|
404
|
+
await write(formatErrorResponse(requestWithId.id, error) + "\n");
|
|
372
405
|
}
|
|
373
406
|
else if (result !== undefined) {
|
|
374
|
-
write(formatSuccessResponse(requestWithId.id, result) + "\n");
|
|
407
|
+
await write(formatSuccessResponse(requestWithId.id, result) + "\n");
|
|
375
408
|
}
|
|
376
409
|
};
|
|
377
410
|
const broadcastNotification = async (method, params, canSend = () => true) => {
|
|
@@ -513,27 +546,58 @@ export function createServer(options) {
|
|
|
513
546
|
});
|
|
514
547
|
},
|
|
515
548
|
async connect(transport) {
|
|
516
|
-
return new Promise((resolve) => {
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
549
|
+
return new Promise((resolve, reject) => {
|
|
550
|
+
let inputClosed = false;
|
|
551
|
+
let settled = false;
|
|
552
|
+
const pendingMessages = new Set();
|
|
553
|
+
const output = new StdioOutput(transport.writable, maxStdioOutputBytes, fail, finish);
|
|
554
|
+
const listener = (notification) => output.write(`${JSON.stringify(notification)}\n`);
|
|
520
555
|
const session = server.createMessageSession(listener);
|
|
521
556
|
const rl = readline.createInterface({
|
|
522
557
|
input: transport.readable,
|
|
523
558
|
crlfDelay: Infinity
|
|
524
559
|
});
|
|
525
|
-
|
|
560
|
+
function fail(error) {
|
|
561
|
+
if (settled)
|
|
562
|
+
return;
|
|
563
|
+
settled = true;
|
|
564
|
+
session.close();
|
|
565
|
+
rl.close();
|
|
566
|
+
transport.readable.pause();
|
|
567
|
+
transport.readable.off("error", fail);
|
|
568
|
+
output.abort(error);
|
|
569
|
+
pendingMessages.clear();
|
|
570
|
+
reject(error);
|
|
571
|
+
}
|
|
572
|
+
function finish() {
|
|
573
|
+
if (settled || !inputClosed || pendingMessages.size > 0 || output.pending > 0)
|
|
574
|
+
return;
|
|
575
|
+
settled = true;
|
|
576
|
+
session.close();
|
|
577
|
+
transport.readable.off("error", fail);
|
|
578
|
+
output.close();
|
|
579
|
+
resolve();
|
|
580
|
+
}
|
|
581
|
+
transport.readable.on("error", fail);
|
|
582
|
+
rl.on("error", fail);
|
|
526
583
|
rl.on("line", (line) => {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
584
|
+
if (settled)
|
|
585
|
+
return;
|
|
586
|
+
if (pendingMessages.size >= maxPendingStdioMessages) {
|
|
587
|
+
fail(new Error("Stdio pending message limit exceeded"));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const message = processLine(line, data => output.write(data), session.handleMessage);
|
|
591
|
+
if (!settled)
|
|
592
|
+
pendingMessages.add(message);
|
|
593
|
+
void message.then(() => {
|
|
530
594
|
pendingMessages.delete(message);
|
|
531
|
-
|
|
595
|
+
finish();
|
|
596
|
+
}, fail);
|
|
532
597
|
});
|
|
533
|
-
rl.on("close",
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
resolve();
|
|
598
|
+
rl.on("close", () => {
|
|
599
|
+
inputClosed = true;
|
|
600
|
+
finish();
|
|
537
601
|
});
|
|
538
602
|
});
|
|
539
603
|
},
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** One connection's admitted frames, including its submitted, unsettled write. */
|
|
2
|
+
export declare class StdioOutput {
|
|
3
|
+
private readonly writable;
|
|
4
|
+
private readonly maxBytes;
|
|
5
|
+
private readonly onFailure;
|
|
6
|
+
private readonly onIdle;
|
|
7
|
+
private readonly queue;
|
|
8
|
+
private active;
|
|
9
|
+
private bytes;
|
|
10
|
+
private stopped;
|
|
11
|
+
private failure;
|
|
12
|
+
private awaitingError;
|
|
13
|
+
constructor(writable: NodeJS.WritableStream, maxBytes: number, onFailure: (error: unknown) => void, onIdle: () => void);
|
|
14
|
+
get pending(): number;
|
|
15
|
+
write(data: string): Promise<void>;
|
|
16
|
+
abort(error: unknown): void;
|
|
17
|
+
close(): void;
|
|
18
|
+
private pump;
|
|
19
|
+
private advance;
|
|
20
|
+
private readonly onDrain;
|
|
21
|
+
private readonly onError;
|
|
22
|
+
private readonly onClose;
|
|
23
|
+
private detach;
|
|
24
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/** One connection's admitted frames, including its submitted, unsettled write. */
|
|
2
|
+
export class StdioOutput {
|
|
3
|
+
writable;
|
|
4
|
+
maxBytes;
|
|
5
|
+
onFailure;
|
|
6
|
+
onIdle;
|
|
7
|
+
queue = [];
|
|
8
|
+
active;
|
|
9
|
+
bytes = 0;
|
|
10
|
+
stopped = false;
|
|
11
|
+
failure;
|
|
12
|
+
awaitingError = false;
|
|
13
|
+
constructor(writable, maxBytes, onFailure, onIdle) {
|
|
14
|
+
this.writable = writable;
|
|
15
|
+
this.maxBytes = maxBytes;
|
|
16
|
+
this.onFailure = onFailure;
|
|
17
|
+
this.onIdle = onIdle;
|
|
18
|
+
writable.on("drain", this.onDrain);
|
|
19
|
+
writable.on("error", this.onError);
|
|
20
|
+
writable.on("close", this.onClose);
|
|
21
|
+
}
|
|
22
|
+
get pending() { return this.queue.length + (this.active === undefined ? 0 : 1); }
|
|
23
|
+
write(data) {
|
|
24
|
+
if (this.stopped)
|
|
25
|
+
return Promise.reject(this.failure);
|
|
26
|
+
const bytes = Buffer.byteLength(data);
|
|
27
|
+
if (bytes > this.maxBytes - this.bytes) {
|
|
28
|
+
const error = new Error("Stdio output byte limit exceeded");
|
|
29
|
+
this.abort(error);
|
|
30
|
+
return Promise.reject(error);
|
|
31
|
+
}
|
|
32
|
+
this.bytes += bytes;
|
|
33
|
+
const promise = new Promise((resolve, reject) => {
|
|
34
|
+
this.queue.push({ data, bytes, resolve, reject });
|
|
35
|
+
});
|
|
36
|
+
this.pump();
|
|
37
|
+
return promise;
|
|
38
|
+
}
|
|
39
|
+
abort(error) {
|
|
40
|
+
if (!this.stopped) {
|
|
41
|
+
this.stopped = true;
|
|
42
|
+
this.failure = error;
|
|
43
|
+
this.active?.frame.reject(error);
|
|
44
|
+
if (this.active?.completed)
|
|
45
|
+
this.active = undefined;
|
|
46
|
+
for (const frame of this.queue.splice(0))
|
|
47
|
+
frame.reject(error);
|
|
48
|
+
this.bytes = 0;
|
|
49
|
+
this.onFailure(error);
|
|
50
|
+
}
|
|
51
|
+
// A submitted write can still fail after connection settlement. Keep its
|
|
52
|
+
// observation until callback/error/close; never destroy a caller's stream.
|
|
53
|
+
if (this.active === undefined && !this.awaitingError)
|
|
54
|
+
this.detach();
|
|
55
|
+
}
|
|
56
|
+
close() {
|
|
57
|
+
this.stopped = true;
|
|
58
|
+
this.failure = new Error("Stdio output is closed");
|
|
59
|
+
this.detach();
|
|
60
|
+
}
|
|
61
|
+
pump() {
|
|
62
|
+
if (this.stopped || this.active !== undefined)
|
|
63
|
+
return;
|
|
64
|
+
const frame = this.queue.shift();
|
|
65
|
+
if (frame === undefined) {
|
|
66
|
+
this.onIdle();
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
const active = { frame, completed: false, returned: false, drained: false, needsDrain: false };
|
|
70
|
+
this.active = active;
|
|
71
|
+
try {
|
|
72
|
+
const accepted = this.writable.write(frame.data, "utf8", (error) => {
|
|
73
|
+
if (error != null) {
|
|
74
|
+
this.awaitingError = true;
|
|
75
|
+
this.active = undefined;
|
|
76
|
+
frame.reject(error);
|
|
77
|
+
this.abort(error);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
active.completed = true;
|
|
81
|
+
if (this.stopped) {
|
|
82
|
+
this.active = undefined;
|
|
83
|
+
this.detach();
|
|
84
|
+
}
|
|
85
|
+
else {
|
|
86
|
+
this.advance();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
active.returned = true;
|
|
90
|
+
active.needsDrain = !accepted;
|
|
91
|
+
this.advance();
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
this.active = undefined;
|
|
95
|
+
frame.reject(error);
|
|
96
|
+
this.abort(error);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
advance() {
|
|
100
|
+
const active = this.active;
|
|
101
|
+
if (this.stopped || active === undefined || !active.returned || !active.completed ||
|
|
102
|
+
(active.needsDrain && !active.drained))
|
|
103
|
+
return;
|
|
104
|
+
this.active = undefined;
|
|
105
|
+
this.bytes -= active.frame.bytes;
|
|
106
|
+
active.frame.resolve();
|
|
107
|
+
this.pump();
|
|
108
|
+
}
|
|
109
|
+
onDrain = () => {
|
|
110
|
+
if (this.active !== undefined)
|
|
111
|
+
this.active.drained = true;
|
|
112
|
+
this.advance();
|
|
113
|
+
};
|
|
114
|
+
onError = (error) => {
|
|
115
|
+
this.awaitingError = false;
|
|
116
|
+
this.abort(error);
|
|
117
|
+
this.active = undefined;
|
|
118
|
+
this.detach();
|
|
119
|
+
};
|
|
120
|
+
onClose = () => {
|
|
121
|
+
this.awaitingError = false;
|
|
122
|
+
this.abort(new Error("Stdio output closed before connection completion"));
|
|
123
|
+
this.active = undefined;
|
|
124
|
+
this.detach();
|
|
125
|
+
};
|
|
126
|
+
detach() {
|
|
127
|
+
this.writable.off("drain", this.onDrain);
|
|
128
|
+
this.writable.off("error", this.onError);
|
|
129
|
+
this.writable.off("close", this.onClose);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
@@ -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,14 @@ 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;
|
|
216
|
+
/** Per-stdio-connection UTF-8 bytes queued or submitted but unsettled; defaults to 1 MiB. */
|
|
217
|
+
maxStdioOutputBytes?: number;
|
|
218
|
+
/** Per-stdio-connection messages awaiting handler/output settlement; defaults to 128. */
|
|
219
|
+
maxPendingStdioMessages?: number;
|
|
212
220
|
validateToolArguments?: boolean;
|
|
213
221
|
supportNotifications?: boolean;
|
|
214
222
|
supportResourceSubscriptions?: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.200",
|
|
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.200",
|
|
162
162
|
"toolcraft-design": "*",
|
|
163
163
|
"@poe-code/frontmatter": "*",
|
|
164
164
|
"@poe-code/agent-mcp-config": "*",
|