toolcraft 0.0.232 → 0.0.234
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/applied-default.d.ts +3 -0
- package/dist/applied-default.js +16 -0
- package/dist/cli.js +508 -204
- package/dist/composition.json +3 -3
- package/dist/discriminator.d.ts +9 -0
- package/dist/discriminator.js +32 -0
- package/dist/error-report.d.ts +2 -18
- package/dist/error-report.js +12 -14
- package/dist/http.js +8 -1
- package/dist/human-in-loop/gate.js +6 -0
- package/dist/index.compile-check.js +22 -5
- package/dist/index.d.ts +12 -5
- package/dist/index.js +50 -0
- package/dist/json-schema-converter.d.ts +14 -6
- package/dist/json-schema-converter.js +150 -20
- package/dist/mcp-proxy-metadata.d.ts +2 -0
- package/dist/mcp-proxy-metadata.js +13 -0
- package/dist/mcp-proxy.d.ts +1 -0
- package/dist/mcp-proxy.js +65 -47
- package/dist/mcp-result.d.ts +3 -0
- package/dist/mcp-result.js +10 -0
- package/dist/mcp.js +211 -153
- package/dist/redaction.js +26 -17
- package/dist/renderer.js +40 -29
- package/dist/schema-member-names.d.ts +2 -0
- package/dist/schema-member-names.js +39 -0
- package/dist/schema-scope.d.ts +1 -0
- package/dist/schema-scope.js +29 -16
- package/dist/schema.d.ts +1 -1
- package/dist/sdk.d.ts +2 -1
- package/dist/sdk.js +164 -50
- package/dist/stream.js +24 -23
- package/dist/testing/diagnostic-value.d.ts +1 -0
- package/dist/testing/diagnostic-value.js +57 -0
- package/dist/testing/fakes.js +2 -1
- package/dist/testing/harness.js +1 -0
- package/dist/testing/memory-fs.js +42 -1
- package/dist/testing/parity.js +50 -6
- package/dist/union-validation.d.ts +5 -0
- package/dist/union-validation.js +36 -0
- package/node_modules/@poe-code/process-runner/dist/types.d.ts +2 -0
- package/node_modules/mcp-oauth-server/dist/index.js +47 -6
- package/node_modules/tiny-http-mcp-server/dist/cli.js +14 -2
- package/node_modules/tiny-http-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-http-mcp-server/dist/http-server.d.ts +7 -5
- package/node_modules/tiny-http-mcp-server/dist/http-server.js +3 -11
- package/node_modules/tiny-http-mcp-server/dist/http-transport.d.ts +6 -1
- package/node_modules/tiny-http-mcp-server/dist/http-transport.js +196 -15
- package/node_modules/tiny-http-mcp-server/dist/index.d.ts +2 -2
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.d.ts +3 -0
- package/node_modules/tiny-http-mcp-server/dist/modern-headers.js +35 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/dist/parse-body.js +9 -5
- package/node_modules/tiny-http-mcp-server/dist/test-support.js +17 -0
- package/node_modules/tiny-http-mcp-server/dist/testing.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/dist/testing.js +3 -2
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/default-oauth-client-provider.js +21 -4
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts +1 -1
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js +14 -6
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-fetch.js +8 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.d.ts +1 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/http-response.js +50 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.d.ts +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/index.js +2 -0
- package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/server/jwks-token-verifier.js +21 -3
- package/node_modules/tiny-http-mcp-server/package.json +1 -1
- package/node_modules/tiny-mcp-client/README.md +1 -0
- package/node_modules/tiny-mcp-client/dist/index.d.ts +178 -101
- package/node_modules/tiny-mcp-client/dist/index.js +4696 -291
- package/node_modules/tiny-mcp-client/package.json +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/base64.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/base64.js +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/composition.json +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/audio.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.d.ts +5 -1
- package/node_modules/tiny-stdio-mcp-server/dist/content/convert.js +41 -6
- package/node_modules/tiny-stdio-mcp-server/dist/content/file.d.ts +5 -0
- package/node_modules/tiny-stdio-mcp-server/dist/content/image.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.d.ts +10 -0
- package/node_modules/tiny-stdio-mcp-server/dist/headers.js +180 -0
- package/node_modules/tiny-stdio-mcp-server/dist/index.d.ts +5 -5
- package/node_modules/tiny-stdio-mcp-server/dist/index.js +1 -1
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.d.ts +220 -0
- package/node_modules/tiny-stdio-mcp-server/dist/input-request-schema.js +1995 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/jsonrpc.js +38 -25
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.d.ts +2 -0
- package/node_modules/tiny-stdio-mcp-server/dist/metadata.js +37 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.d.ts +4 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol-validation.js +53 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.d.ts +17 -0
- package/node_modules/tiny-stdio-mcp-server/dist/protocol.js +228 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/request-cancellation.js +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.d.ts +18 -0
- package/node_modules/tiny-stdio-mcp-server/dist/sampling-messages.js +34 -0
- package/node_modules/tiny-stdio-mcp-server/dist/schema.d.ts +4 -1
- package/node_modules/tiny-stdio-mcp-server/dist/server.d.ts +9 -4
- package/node_modules/tiny-stdio-mcp-server/dist/server.js +334 -135
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.d.ts +3 -0
- package/node_modules/tiny-stdio-mcp-server/dist/stdio-input.js +50 -28
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.d.ts +11 -0
- package/node_modules/tiny-stdio-mcp-server/dist/subscriptions.js +128 -0
- package/node_modules/tiny-stdio-mcp-server/dist/types.d.ts +58 -18
- package/node_modules/tiny-stdio-mcp-server/dist/types.js +2 -1
- package/node_modules/tiny-stdio-mcp-server/dist/uri.d.ts +1 -0
- package/node_modules/tiny-stdio-mcp-server/dist/uri.js +41 -0
- package/node_modules/tiny-stdio-mcp-server/package.json +8 -0
- package/node_modules/toolcraft-design/dist/command-registry.d.ts +11 -0
- package/node_modules/toolcraft-design/dist/command-registry.js +24 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/ansi.js +81 -27
- package/node_modules/toolcraft-design/dist/dashboard/buffer.js +39 -25
- package/node_modules/toolcraft-design/dist/dashboard/components/border.js +6 -5
- package/node_modules/toolcraft-design/dist/dashboard/components/footer.js +26 -21
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.d.ts +1 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/output-pane.js +12 -6
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.d.ts +2 -1
- package/node_modules/toolcraft-design/dist/dashboard/components/stats-pane.js +51 -10
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.d.ts +4 -0
- package/node_modules/toolcraft-design/dist/dashboard/dashboard.js +77 -4
- package/node_modules/toolcraft-design/dist/dashboard/index.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/index.js +2 -0
- package/node_modules/toolcraft-design/dist/dashboard/keymap.js +26 -6
- package/node_modules/toolcraft-design/dist/dashboard/layout.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/layout.js +19 -3
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/line-buffer.js +63 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/dashboard/output-preview.js +70 -0
- package/node_modules/toolcraft-design/dist/dashboard/snapshot.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/store.js +9 -3
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-strings.js +108 -0
- package/node_modules/toolcraft-design/dist/dashboard/terminal-width.js +3 -1
- package/node_modules/toolcraft-design/dist/dashboard/terminal.js +11 -1
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/dashboard/testing/pipeline-scenario.js +126 -0
- package/node_modules/toolcraft-design/dist/dashboard/types.d.ts +3 -1
- package/node_modules/toolcraft-design/dist/event-groups.d.ts +19 -0
- package/node_modules/toolcraft-design/dist/event-groups.js +49 -0
- package/node_modules/toolcraft-design/dist/index.d.ts +6 -1
- package/node_modules/toolcraft-design/dist/index.js +5 -1
- package/node_modules/toolcraft-design/dist/inline-notice.d.ts +14 -0
- package/node_modules/toolcraft-design/dist/inline-notice.js +28 -0
- package/node_modules/toolcraft-design/dist/metric.d.ts +8 -0
- package/node_modules/toolcraft-design/dist/metric.js +21 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.d.ts +6 -0
- package/node_modules/toolcraft-design/dist/overlay-manager.js +10 -0
- package/node_modules/toolcraft-design/dist/primitives.d.ts +12 -0
- package/node_modules/toolcraft-design/dist/primitives.js +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.d.ts +7 -0
- package/node_modules/toolcraft-design/dist/progress-group.js +10 -0
- package/node_modules/toolcraft-design/dist/render-performance.d.ts +33 -0
- package/node_modules/toolcraft-design/dist/render-performance.js +71 -0
- package/node_modules/toolcraft-design/dist/task-tree.d.ts +22 -0
- package/node_modules/toolcraft-design/dist/task-tree.js +79 -0
- package/node_modules/toolcraft-design/dist/viewport.d.ts +18 -0
- package/node_modules/toolcraft-design/dist/viewport.js +56 -0
- package/node_modules/toolcraft-schema/dist/clone-default.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/clone-default.js +26 -0
- package/node_modules/toolcraft-schema/dist/index.compile-check.js +1 -0
- package/node_modules/toolcraft-schema/dist/index.d.ts +18 -9
- package/node_modules/toolcraft-schema/dist/index.js +46 -8
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/compiler.js +6 -0
- package/node_modules/toolcraft-schema/dist/json-schema/evaluate.js +6 -3
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json-schema/normalize-nullability.js +69 -0
- package/node_modules/toolcraft-schema/dist/json-schema/types.d.ts +1 -0
- package/node_modules/toolcraft-schema/dist/json.d.ts +4 -1
- package/node_modules/toolcraft-schema/dist/json.js +57 -1
- package/node_modules/toolcraft-schema/dist/native-json-schema.d.ts +10 -0
- package/node_modules/toolcraft-schema/dist/native-json-schema.js +11 -0
- package/node_modules/toolcraft-schema/dist/validate.d.ts +5 -1
- package/node_modules/toolcraft-schema/dist/validate.js +61 -47
- package/node_modules/toolcraft-schema/package.json +1 -1
- package/package.json +6 -3
|
@@ -4,6 +4,7 @@ import { formatErrorResponse, formatSuccessResponse } from "tiny-stdio-mcp-serve
|
|
|
4
4
|
import { JsonRpcMessageError, readAndClassifyBody } from "./parse-body.js";
|
|
5
5
|
import { createSessionStore, defaultSessionIdGenerator } from "./session.js";
|
|
6
6
|
import { formatSseEvent, SSE_HEADERS } from "./sse.js";
|
|
7
|
+
import { validateModernHeaders } from "./modern-headers.js";
|
|
7
8
|
const ALLOWED_METHODS = "POST, GET, DELETE, OPTIONS";
|
|
8
9
|
const MCP_SESSION_ID_HEADER = "Mcp-Session-Id";
|
|
9
10
|
const LOCAL_HOSTS = ["localhost", "127.0.0.1", "::1"];
|
|
@@ -25,6 +26,7 @@ export class StreamableHttpTransport {
|
|
|
25
26
|
allowedOrigins;
|
|
26
27
|
allowedHosts;
|
|
27
28
|
maxRequestBytes;
|
|
29
|
+
maxResponseBytes;
|
|
28
30
|
maxBatchSize;
|
|
29
31
|
maxSessions;
|
|
30
32
|
maxSessionsPerSubject;
|
|
@@ -45,6 +47,8 @@ export class StreamableHttpTransport {
|
|
|
45
47
|
responseRequestIds = new WeakMap();
|
|
46
48
|
responseOrigins = new WeakMap();
|
|
47
49
|
responseRejectionReasons = new WeakMap();
|
|
50
|
+
modernRequests = new Map();
|
|
51
|
+
modernStreams = new Set();
|
|
48
52
|
nextNotificationEventId = 1;
|
|
49
53
|
nextRequestId = 1;
|
|
50
54
|
activeToolCalls = 0;
|
|
@@ -62,10 +66,13 @@ export class StreamableHttpTransport {
|
|
|
62
66
|
this.allowedHosts = new Set((options.allowedHosts ?? LOCAL_HOSTS).map((host) => this.normalizeHost(host)));
|
|
63
67
|
this.maxRequestBytes = validateOptionalIntegerOption("maxRequestBytes", options.maxRequestBytes, 1);
|
|
64
68
|
this.maxBatchSize = validateOptionalIntegerOption("maxBatchSize", options.maxBatchSize, 1);
|
|
69
|
+
this.maxResponseBytes = validateOptionalIntegerOption("maxResponseBytes", options.maxResponseBytes, 1) ?? 16 * 1024 * 1024;
|
|
65
70
|
this.maxSessions = validateOptionalIntegerOption("maxSessions", options.maxSessions, 1) ?? 128;
|
|
66
71
|
this.maxSessionsPerSubject =
|
|
67
|
-
validateOptionalIntegerOption("maxSessionsPerSubject", options.maxSessionsPerSubject, 1) ??
|
|
68
|
-
|
|
72
|
+
validateOptionalIntegerOption("maxSessionsPerSubject", options.maxSessionsPerSubject, 1) ??
|
|
73
|
+
16;
|
|
74
|
+
this.sessionTtlMs =
|
|
75
|
+
validateOptionalIntegerOption("sessionTtlMs", options.sessionTtlMs, 1) ?? 15 * 60_000;
|
|
69
76
|
this.maxStreamsPerSession =
|
|
70
77
|
validateOptionalIntegerOption("maxStreamsPerSession", options.maxStreamsPerSession, 1) ?? 1;
|
|
71
78
|
this.maxStreamBufferBytes =
|
|
@@ -120,6 +127,12 @@ export class StreamableHttpTransport {
|
|
|
120
127
|
this.respondWithRejection(res, 403, "origin_not_allowed", `Origin ${JSON.stringify(this.readOrigin(req) ?? "")} is not allowed; add it to allowedOrigins.`);
|
|
121
128
|
return;
|
|
122
129
|
}
|
|
130
|
+
if (req.headers["mcp-protocol-version"] === "2026-07-28" &&
|
|
131
|
+
req.method !== "POST" &&
|
|
132
|
+
req.method !== "OPTIONS") {
|
|
133
|
+
this.respondWithStatus(res, 405, undefined, { Allow: "POST, OPTIONS" });
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
123
136
|
switch (req.method) {
|
|
124
137
|
case "POST":
|
|
125
138
|
await this.handlePost(req, res);
|
|
@@ -172,6 +185,12 @@ export class StreamableHttpTransport {
|
|
|
172
185
|
}
|
|
173
186
|
async close() {
|
|
174
187
|
this.closed = true;
|
|
188
|
+
for (const [response, controller] of this.modernRequests) {
|
|
189
|
+
controller.abort();
|
|
190
|
+
response.end();
|
|
191
|
+
}
|
|
192
|
+
this.modernRequests.clear();
|
|
193
|
+
this.modernStreams.clear();
|
|
175
194
|
if (this.sessionExpiryInterval !== undefined) {
|
|
176
195
|
clearInterval(this.sessionExpiryInterval);
|
|
177
196
|
this.sessionExpiryInterval = undefined;
|
|
@@ -221,6 +240,20 @@ export class StreamableHttpTransport {
|
|
|
221
240
|
this.respondWithJsonRpcError(res, 400, code, message, id);
|
|
222
241
|
return;
|
|
223
242
|
}
|
|
243
|
+
const modern = req.headers["mcp-protocol-version"] === "2026-07-28" ||
|
|
244
|
+
classified.messages.some((message) => {
|
|
245
|
+
if (!("method" in message))
|
|
246
|
+
return false;
|
|
247
|
+
const metadata = message.params?._meta;
|
|
248
|
+
return (message.method === "server/discover" ||
|
|
249
|
+
(typeof metadata === "object" &&
|
|
250
|
+
metadata !== null &&
|
|
251
|
+
Object.prototype.hasOwnProperty.call(metadata, "io.modelcontextprotocol/protocolVersion")));
|
|
252
|
+
});
|
|
253
|
+
if (modern) {
|
|
254
|
+
await this.handleModernPost(req, res, classified);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
224
257
|
const initMessage = classified.messages.find((message) => this.isRequest(message) && message.method === "initialize");
|
|
225
258
|
let sessionId;
|
|
226
259
|
let activeSession;
|
|
@@ -237,7 +270,8 @@ export class StreamableHttpTransport {
|
|
|
237
270
|
return;
|
|
238
271
|
}
|
|
239
272
|
const authSubject = this.readAuthSubject(req);
|
|
240
|
-
if (authSubject !== undefined &&
|
|
273
|
+
if (authSubject !== undefined &&
|
|
274
|
+
this.sessionCount(authSubject) >= this.maxSessionsPerSubject) {
|
|
241
275
|
this.respondWithRejection(res, 429, "subject_session_limit_reached", "The authenticated subject has reached its session limit; close a session or retry later.");
|
|
242
276
|
return;
|
|
243
277
|
}
|
|
@@ -283,6 +317,15 @@ export class StreamableHttpTransport {
|
|
|
283
317
|
if (!("method" in message)) {
|
|
284
318
|
continue;
|
|
285
319
|
}
|
|
320
|
+
if (this.sessionIdGenerator === undefined &&
|
|
321
|
+
(message.method === "resources/subscribe" || message.method === "resources/unsubscribe")) {
|
|
322
|
+
if (this.isRequest(message))
|
|
323
|
+
responses.push(formatErrorResponse(message.id, {
|
|
324
|
+
code: JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND,
|
|
325
|
+
message: "Method not found"
|
|
326
|
+
}));
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
286
329
|
const session = activeSession;
|
|
287
330
|
if (session !== undefined &&
|
|
288
331
|
message.method !== "initialize" &&
|
|
@@ -333,7 +376,23 @@ export class StreamableHttpTransport {
|
|
|
333
376
|
this.activeToolCalls -= 1;
|
|
334
377
|
}
|
|
335
378
|
}
|
|
336
|
-
const { error
|
|
379
|
+
const { error } = handled;
|
|
380
|
+
let { result } = handled;
|
|
381
|
+
if (error === undefined &&
|
|
382
|
+
message.method === "initialize" &&
|
|
383
|
+
result !== undefined &&
|
|
384
|
+
this.sessionIdGenerator === undefined) {
|
|
385
|
+
const initialization = result;
|
|
386
|
+
const capabilities = structuredClone(initialization.capabilities);
|
|
387
|
+
for (const category of ["tools", "prompts", "resources"]) {
|
|
388
|
+
const capability = capabilities[category];
|
|
389
|
+
if (capability !== undefined)
|
|
390
|
+
delete capability.listChanged;
|
|
391
|
+
}
|
|
392
|
+
if (capabilities.resources !== undefined)
|
|
393
|
+
delete capabilities.resources.subscribe;
|
|
394
|
+
result = { ...initialization, capabilities };
|
|
395
|
+
}
|
|
337
396
|
if (isToolCall) {
|
|
338
397
|
this.emit({
|
|
339
398
|
type: "tool.end",
|
|
@@ -380,12 +439,115 @@ export class StreamableHttpTransport {
|
|
|
380
439
|
this.respondWithStatus(res, 200, sessionId, { "Content-Type": "application/json" }, body);
|
|
381
440
|
return;
|
|
382
441
|
}
|
|
442
|
+
const frames = formattedResponses.map((data) => formatSseEvent({ data }));
|
|
443
|
+
if (frames.some((frame) => Buffer.byteLength(frame, "utf8") > this.maxResponseBytes)) {
|
|
444
|
+
res.destroy();
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
383
447
|
res.writeHead(200, this.withSessionHeader(SSE_HEADERS, sessionId, res));
|
|
384
|
-
for (const
|
|
385
|
-
|
|
448
|
+
for (const frame of frames) {
|
|
449
|
+
this.writeToLiveStream(res, frame);
|
|
386
450
|
}
|
|
387
451
|
res.end();
|
|
388
452
|
}
|
|
453
|
+
async handleModernPost(req, res, body) {
|
|
454
|
+
const message = body.messages[0];
|
|
455
|
+
if (body.isBatch ||
|
|
456
|
+
body.messages.length !== 1 ||
|
|
457
|
+
message === undefined ||
|
|
458
|
+
!("method" in message)) {
|
|
459
|
+
this.respondWithJsonRpcError(res, 400, JSON_RPC_ERROR_CODES.INVALID_REQUEST, "Modern HTTP requires a single request or notification");
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const id = this.isRequest(message) ? message.id : null;
|
|
463
|
+
if (!this.acceptsResponseType(req, "application/json") ||
|
|
464
|
+
!this.acceptsResponseType(req, "text/event-stream")) {
|
|
465
|
+
this.respondWithRejection(res, 406, "response_type_not_acceptable", "Accept must allow application/json and text/event-stream.");
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
const headerError = validateModernHeaders(req.headers, message);
|
|
469
|
+
if (headerError !== undefined) {
|
|
470
|
+
this.respondWithJsonRpcError(res, 400, headerError.code, headerError.message, id);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
const controller = new AbortController();
|
|
474
|
+
const cancel = () => controller.abort();
|
|
475
|
+
res.once("close", cancel);
|
|
476
|
+
req.once("aborted", cancel);
|
|
477
|
+
this.modernRequests.set(res, controller);
|
|
478
|
+
let streaming = false;
|
|
479
|
+
const write = (data) => {
|
|
480
|
+
if (controller.signal.aborted || res.destroyed || res.writableEnded)
|
|
481
|
+
return;
|
|
482
|
+
const frame = formatSseEvent({ data });
|
|
483
|
+
if (Buffer.byteLength(frame, "utf8") > this.maxResponseBytes) {
|
|
484
|
+
controller.abort(new Error("Modern SSE response size limit exceeded"));
|
|
485
|
+
res.destroy();
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
if ((res.writableLength ?? 0) > this.maxStreamBufferBytes) {
|
|
489
|
+
controller.abort(new Error("Modern SSE output buffer limit exceeded"));
|
|
490
|
+
res.destroy();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (!streaming) {
|
|
494
|
+
res.writeHead(200, this.withSessionHeader({ ...SSE_HEADERS, "X-Accel-Buffering": "no" }, undefined, res));
|
|
495
|
+
streaming = true;
|
|
496
|
+
res.flushHeaders();
|
|
497
|
+
this.modernStreams.add(res);
|
|
498
|
+
this.startSseKeepAlive();
|
|
499
|
+
}
|
|
500
|
+
res.write(frame);
|
|
501
|
+
};
|
|
502
|
+
const session = this.server.createMessageSession((notification) => write(JSON.stringify(notification)));
|
|
503
|
+
try {
|
|
504
|
+
if (!this.isRequest(message) && !message.method.startsWith("notifications/")) {
|
|
505
|
+
this.respondWithJsonRpcError(res, 400, JSON_RPC_ERROR_CODES.INVALID_REQUEST, "A request ID is required");
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
const handled = await this.runWithRequestContext(req, () => session.handleMessage(message.method, message.params, {
|
|
509
|
+
...(this.isRequest(message) ? { requestId: message.id } : {}),
|
|
510
|
+
signal: controller.signal,
|
|
511
|
+
parameterHeaders: req.headers
|
|
512
|
+
}));
|
|
513
|
+
if (controller.signal.aborted || res.destroyed || res.writableEnded)
|
|
514
|
+
return;
|
|
515
|
+
if (!this.isRequest(message) ||
|
|
516
|
+
(handled.result === undefined && handled.error === undefined)) {
|
|
517
|
+
if (streaming)
|
|
518
|
+
res.end();
|
|
519
|
+
else
|
|
520
|
+
this.respondWithStatus(res, 202);
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
const formatted = handled.error === undefined
|
|
524
|
+
? formatSuccessResponse(message.id, handled.result)
|
|
525
|
+
: formatErrorResponse(message.id, handled.error);
|
|
526
|
+
const status = handled.error?.code === JSON_RPC_ERROR_CODES.METHOD_NOT_FOUND
|
|
527
|
+
? 404
|
|
528
|
+
: handled.error?.code === JSON_RPC_ERROR_CODES.INVALID_PARAMS ||
|
|
529
|
+
handled.error?.code === JSON_RPC_ERROR_CODES.UNSUPPORTED_PROTOCOL_VERSION ||
|
|
530
|
+
handled.error?.code === -32020 ||
|
|
531
|
+
handled.error?.code === -32021
|
|
532
|
+
? 400
|
|
533
|
+
: 200;
|
|
534
|
+
if (streaming || (!this.enableJsonResponse && status === 200)) {
|
|
535
|
+
write(formatted);
|
|
536
|
+
if (!res.destroyed)
|
|
537
|
+
res.end();
|
|
538
|
+
}
|
|
539
|
+
else
|
|
540
|
+
this.respondWithStatus(res, status, undefined, { "Content-Type": "application/json" }, formatted);
|
|
541
|
+
}
|
|
542
|
+
finally {
|
|
543
|
+
session.close();
|
|
544
|
+
res.off("close", cancel);
|
|
545
|
+
req.off("aborted", cancel);
|
|
546
|
+
this.modernRequests.delete(res);
|
|
547
|
+
this.modernStreams.delete(res);
|
|
548
|
+
this.stopSseKeepAliveIfIdle();
|
|
549
|
+
}
|
|
550
|
+
}
|
|
389
551
|
async handleGet(req, res) {
|
|
390
552
|
if (this.sessionIdGenerator === undefined) {
|
|
391
553
|
this.respondWithStatus(res, 405, undefined, {
|
|
@@ -675,10 +837,14 @@ export class StreamableHttpTransport {
|
|
|
675
837
|
return;
|
|
676
838
|
}
|
|
677
839
|
this.sseKeepAliveInterval = setInterval(() => {
|
|
840
|
+
for (const response of this.modernStreams) {
|
|
841
|
+
if (!response.writableEnded)
|
|
842
|
+
this.writeToLiveStream(response, ": keepalive\n\n");
|
|
843
|
+
}
|
|
678
844
|
for (const streams of this.sseStreams.values()) {
|
|
679
845
|
for (const response of streams) {
|
|
680
846
|
if (!response.writableEnded) {
|
|
681
|
-
this.
|
|
847
|
+
this.writeToLiveStream(response, ": keepalive\n\n");
|
|
682
848
|
}
|
|
683
849
|
}
|
|
684
850
|
}
|
|
@@ -686,7 +852,8 @@ export class StreamableHttpTransport {
|
|
|
686
852
|
this.sseKeepAliveInterval.unref();
|
|
687
853
|
}
|
|
688
854
|
stopSseKeepAliveIfIdle() {
|
|
689
|
-
if (
|
|
855
|
+
if (this.modernStreams.size > 0 ||
|
|
856
|
+
[...this.sseStreams.values()].some((streams) => streams.size > 0)) {
|
|
690
857
|
return;
|
|
691
858
|
}
|
|
692
859
|
this.stopSseKeepAlive();
|
|
@@ -704,15 +871,19 @@ export class StreamableHttpTransport {
|
|
|
704
871
|
}
|
|
705
872
|
const id = this.nextNotificationEventId++;
|
|
706
873
|
const data = JSON.stringify(notification);
|
|
707
|
-
this.recordSseEvent(sessionId, id, data);
|
|
708
874
|
const streams = this.sseStreams.get(sessionId);
|
|
709
|
-
if (streams === undefined) {
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
875
|
const event = formatSseEvent({
|
|
713
876
|
id: String(id),
|
|
714
877
|
data
|
|
715
878
|
});
|
|
879
|
+
if (Buffer.byteLength(event, "utf8") > this.maxResponseBytes) {
|
|
880
|
+
for (const response of streams ?? [])
|
|
881
|
+
response.destroy();
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
this.recordSseEvent(sessionId, id, data);
|
|
885
|
+
if (streams === undefined)
|
|
886
|
+
return;
|
|
716
887
|
let latestResponse;
|
|
717
888
|
for (const response of streams) {
|
|
718
889
|
if (!response.writableEnded) {
|
|
@@ -720,10 +891,16 @@ export class StreamableHttpTransport {
|
|
|
720
891
|
}
|
|
721
892
|
}
|
|
722
893
|
if (latestResponse !== undefined) {
|
|
723
|
-
this.
|
|
894
|
+
this.writeToLiveStream(latestResponse, event);
|
|
724
895
|
}
|
|
725
896
|
}
|
|
726
|
-
|
|
897
|
+
writeToLiveStream(response, data) {
|
|
898
|
+
if (response.destroyed || response.writableEnded)
|
|
899
|
+
return;
|
|
900
|
+
if (Buffer.byteLength(data, "utf8") > this.maxResponseBytes) {
|
|
901
|
+
response.destroy();
|
|
902
|
+
return;
|
|
903
|
+
}
|
|
727
904
|
if ((response.writableLength ?? 0) > this.maxStreamBufferBytes) {
|
|
728
905
|
response.end();
|
|
729
906
|
return;
|
|
@@ -754,7 +931,7 @@ export class StreamableHttpTransport {
|
|
|
754
931
|
const history = this.sseEventHistory.get(sessionId) ?? [];
|
|
755
932
|
for (const event of history) {
|
|
756
933
|
if (event.id > lastEventId && !res.writableEnded) {
|
|
757
|
-
this.
|
|
934
|
+
this.writeToLiveStream(res, formatSseEvent({ id: String(event.id), data: event.data }));
|
|
758
935
|
}
|
|
759
936
|
}
|
|
760
937
|
}
|
|
@@ -877,6 +1054,10 @@ export class StreamableHttpTransport {
|
|
|
877
1054
|
this.respondWithStatus(res, statusCode, undefined, { "Content-Type": "application/json" }, JSON.stringify({ error: reason, message }));
|
|
878
1055
|
}
|
|
879
1056
|
respondWithStatus(res, statusCode, sessionId, headers, body) {
|
|
1057
|
+
if (body !== undefined && Buffer.byteLength(body, "utf8") > this.maxResponseBytes) {
|
|
1058
|
+
res.destroy();
|
|
1059
|
+
return;
|
|
1060
|
+
}
|
|
880
1061
|
res.writeHead(statusCode, this.withSessionHeader(headers, sessionId, res));
|
|
881
1062
|
res.end(body);
|
|
882
1063
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export { createServer, defineSchema, Image, Audio, File, toContentBlocks, fileTypeFromBuffer, JSON_RPC_ERROR_CODES, ToolError } from "tiny-stdio-mcp-server";
|
|
2
|
-
export type { Server, TypedSchema, ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, ToolReturn, ServerOptions, ToolHandler, ToolDefinition, Tool, ToolAnnotations, ToolExecution, Icon, ContentAnnotations, ResourceLink, CallToolResult, PromptContentItem, PromptArgument, Prompt, PromptMessage, GetPromptResult, PromptHandler, PromptDefinition, Resource, ResourceTemplate, ResourceContents, ReadResourceResult, ResourceHandler, ResourceDefinition, ResourceTemplateDefinition, HandleResult, ContentItem, JSONSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult } from "tiny-stdio-mcp-server";
|
|
2
|
+
export type { Server, MessageRequestContext, MessageSession, MessageSessionContext, TypedSchema, TypedOutputSchema, ImageContent, AudioContent, EmbeddedResource, TextResourceContents, BlobResourceContents, ContentBlock, TextContent, FileTypeResult, ToolReturn, ServerOptions, ToolHandler, HandlerRequestContext, InputRequiredResult, ToolDefinition, Tool, ToolAnnotations, ToolExecution, Icon, ContentAnnotations, ResourceLink, CallToolResult, PromptContentItem, PromptArgument, Prompt, PromptMessage, GetPromptResult, PromptHandler, PromptDefinition, Resource, ResourceTemplate, ResourceContents, ReadResourceResult, ResourceHandler, ResourceDefinition, ResourceTemplateDefinition, HandleResult, ContentItem, JSONSchema, OutputSchema, JSONSchemaProperty, Transport, SDKTransport, JSONRPCRequest, JSONRPCResponse, JSONRPCError, JSONRPCMessage, JSONRPCNotification, InitializeResult, DiscoverResult } from "tiny-stdio-mcp-server";
|
|
3
3
|
export { createExpressMiddleware, createExpressOAuthHandlers, createProtectedResourceMetadataRouter } from "./express-middleware.js";
|
|
4
4
|
export type { CreateExpressOAuthHandlersOptions } from "./express-middleware.js";
|
|
5
5
|
export { createHttpServer, createProtectedResourceMetadataDocument } from "./http-server.js";
|
|
6
|
-
export type { HttpToolContext, HttpToolHandler, HttpAdditionalRequestHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
|
|
6
|
+
export type { HttpToolContext, HttpRequestContext, HttpToolHandler, HttpAdditionalRequestHandler, HttpListenOptions, HttpServer, HttpServerHandle, HttpTransportOptions, ProtectedResourceMetadataOptions, TinyHttpMcpServerOAuthOptions } from "./http-server.js";
|
|
7
7
|
export { TokenVerificationError } from "./auth.js";
|
|
8
8
|
export type { RequestAuthInfo, TokenVerifier, VerifiedAccessToken } from "./auth.js";
|
|
9
9
|
export { StreamableHttpTransport } from "./http-transport.js";
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { IncomingHttpHeaders } from "node:http";
|
|
2
|
+
import { type JSONRPCError, type JSONRPCNotification, type JSONRPCRequest } from "tiny-stdio-mcp-server";
|
|
3
|
+
export declare function validateModernHeaders(headers: IncomingHttpHeaders, request: JSONRPCRequest | JSONRPCNotification): JSONRPCError | undefined;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { JSON_RPC_ERROR_CODES } from "tiny-stdio-mcp-server";
|
|
2
|
+
import { decodeHeaderValue } from "tiny-stdio-mcp-server/headers";
|
|
3
|
+
export function validateModernHeaders(headers, request) {
|
|
4
|
+
const metadata = request.params?._meta;
|
|
5
|
+
const version = typeof metadata === "object" && metadata !== null
|
|
6
|
+
? metadata["io.modelcontextprotocol/protocolVersion"]
|
|
7
|
+
: undefined;
|
|
8
|
+
if (typeof version !== "string")
|
|
9
|
+
return {
|
|
10
|
+
code: JSON_RPC_ERROR_CODES.INVALID_PARAMS,
|
|
11
|
+
message: "Missing protocol version request metadata"
|
|
12
|
+
};
|
|
13
|
+
const expected = { "mcp-protocol-version": version };
|
|
14
|
+
// Notifications require the protocol version header but no method/name mirrors.
|
|
15
|
+
if ("id" in request) {
|
|
16
|
+
expected["mcp-method"] = request.method;
|
|
17
|
+
if (request.method === "tools/call" || request.method === "prompts/get")
|
|
18
|
+
expected["mcp-name"] = request.params?.name;
|
|
19
|
+
else if (request.method === "resources/read")
|
|
20
|
+
expected["mcp-name"] = request.params?.uri;
|
|
21
|
+
}
|
|
22
|
+
for (const [field, value] of Object.entries(expected)) {
|
|
23
|
+
const header = headers[field];
|
|
24
|
+
const actual = field === "mcp-name"
|
|
25
|
+
? decodeHeaderValue(header)
|
|
26
|
+
: typeof header === "string"
|
|
27
|
+
? header
|
|
28
|
+
: undefined;
|
|
29
|
+
if (typeof value !== "string" || actual === undefined || actual !== value)
|
|
30
|
+
return {
|
|
31
|
+
code: -32020,
|
|
32
|
+
message: `Header mismatch: ${field} must match the request body`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { IncomingMessage } from "node:http";
|
|
2
2
|
import type { JSONRPCMessage, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse } from "tiny-stdio-mcp-server";
|
|
3
3
|
export interface ClassifiedBody {
|
|
4
|
+
isBatch: boolean;
|
|
4
5
|
entries: Array<JSONRPCMessage | null>;
|
|
5
6
|
messages: JSONRPCMessage[];
|
|
6
7
|
hasRequests: boolean;
|
|
@@ -21,9 +21,7 @@ function isValidResponse(value) {
|
|
|
21
21
|
if (!hasOwn(value, "id")) {
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
-
if (value.id !== null &&
|
|
25
|
-
typeof value.id !== "string" &&
|
|
26
|
-
typeof value.id !== "number") {
|
|
24
|
+
if (value.id !== null && typeof value.id !== "string" && typeof value.id !== "number") {
|
|
27
25
|
return false;
|
|
28
26
|
}
|
|
29
27
|
const hasResult = hasOwn(value, "result");
|
|
@@ -61,7 +59,12 @@ async function readStreamBody(req, maxBytes) {
|
|
|
61
59
|
}
|
|
62
60
|
chunks.push(bytes);
|
|
63
61
|
}
|
|
64
|
-
|
|
62
|
+
try {
|
|
63
|
+
return new TextDecoder("utf-8", { fatal: true }).decode(Buffer.concat(chunks));
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw new Error("Parse error");
|
|
67
|
+
}
|
|
65
68
|
}
|
|
66
69
|
async function readRawBody(req, preParsed, options) {
|
|
67
70
|
const reqWithBody = req;
|
|
@@ -145,6 +148,7 @@ export async function readAndClassifyBody(req, preParsed, options = {}) {
|
|
|
145
148
|
entries.push(request);
|
|
146
149
|
}
|
|
147
150
|
return {
|
|
151
|
+
isBatch,
|
|
148
152
|
entries,
|
|
149
153
|
messages,
|
|
150
154
|
hasRequests: requests.length > 0,
|
|
@@ -152,6 +156,6 @@ export async function readAndClassifyBody(req, preParsed, options = {}) {
|
|
|
152
156
|
hasResponses: responses.length > 0,
|
|
153
157
|
requests,
|
|
154
158
|
notifications,
|
|
155
|
-
responses
|
|
159
|
+
responses
|
|
156
160
|
};
|
|
157
161
|
}
|
|
@@ -166,6 +166,7 @@ async function fetchInMemory(server, url, init) {
|
|
|
166
166
|
response.headerValues = new Headers();
|
|
167
167
|
response.headersSent = false;
|
|
168
168
|
response.writableEnded = false;
|
|
169
|
+
response.destroyed = false;
|
|
169
170
|
const setResponseHeader = (name, value) => {
|
|
170
171
|
response.headerValues.delete(name);
|
|
171
172
|
if (Array.isArray(value) && name.toLowerCase() === "set-cookie") {
|
|
@@ -224,6 +225,22 @@ async function fetchInMemory(server, url, init) {
|
|
|
224
225
|
}
|
|
225
226
|
return response;
|
|
226
227
|
});
|
|
228
|
+
response.destroy = ((error) => {
|
|
229
|
+
if (!response.destroyed) {
|
|
230
|
+
response.destroyed = true;
|
|
231
|
+
try {
|
|
232
|
+
if (error === undefined)
|
|
233
|
+
streamController?.close();
|
|
234
|
+
else
|
|
235
|
+
streamController?.error(error);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// The consumer may have cancelled the stream already.
|
|
239
|
+
}
|
|
240
|
+
response.emit("close");
|
|
241
|
+
}
|
|
242
|
+
return response;
|
|
243
|
+
});
|
|
227
244
|
response.flushHeaders = (() => {
|
|
228
245
|
response.headersSent = true;
|
|
229
246
|
});
|
|
@@ -56,4 +56,4 @@ export declare function createInMemoryTokenVerifier(options?: Partial<{
|
|
|
56
56
|
now: () => number;
|
|
57
57
|
}>): InMemoryTokenVerifier;
|
|
58
58
|
export declare function createHttpTestPair(server: HttpServer): Promise<HttpTestPair>;
|
|
59
|
-
export declare function createHttpTestPairWithTinyClient(server: HttpServer): Promise<TinyHttpTestPair>;
|
|
59
|
+
export declare function createHttpTestPairWithTinyClient(server: HttpServer, clientOptions?: Pick<import("tiny-mcp-client").McpClientOptions, "protocolVersion">): Promise<TinyHttpTestPair>;
|
|
@@ -135,7 +135,7 @@ export async function createHttpTestPair(server) {
|
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
|
-
export async function createHttpTestPairWithTinyClient(server) {
|
|
138
|
+
export async function createHttpTestPairWithTinyClient(server, clientOptions = {}) {
|
|
139
139
|
let tinyMcpClient;
|
|
140
140
|
try {
|
|
141
141
|
tinyMcpClient = await import("tiny-mcp-client");
|
|
@@ -147,7 +147,8 @@ export async function createHttpTestPairWithTinyClient(server) {
|
|
|
147
147
|
const handle = await server.listenHttp({ port: 0 });
|
|
148
148
|
const requests = [];
|
|
149
149
|
const client = new tinyMcpClient.McpClient({
|
|
150
|
-
clientInfo: { name: "tiny-http-test-client", version: "1.0.0" }
|
|
150
|
+
clientInfo: { name: "tiny-http-test-client", version: "1.0.0" },
|
|
151
|
+
...clientOptions
|
|
151
152
|
});
|
|
152
153
|
const transport = new tinyMcpClient.HttpTransport({
|
|
153
154
|
url: handle.url,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { isIP } from "node:net";
|
|
2
|
+
import { fetchMcpResponse } from "../http-fetch.js";
|
|
1
3
|
import { URL } from "node:url";
|
|
2
4
|
import { createAuthStoreClientStore, createAuthStoreSessionStore } from "./auth-store-session-store.js";
|
|
3
5
|
import { createLoopbackAuthorizationSession } from "./loopback-authorization.js";
|
|
@@ -62,6 +64,15 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
62
64
|
async function ensureAuthorizedSession(resource, discovery, fetch, allowInteractive, forceRefresh = false) {
|
|
63
65
|
const canonicalResource = canonicalizeResourceIndicator(resource);
|
|
64
66
|
let session = await loadSession(canonicalResource);
|
|
67
|
+
if (discovery !== undefined && getOwnString(discovery.authorizationServerMetadata, "issuer") !== discovery.authorizationServer) {
|
|
68
|
+
throw new Error("OAuth discovery authorization-server issuer mismatch");
|
|
69
|
+
}
|
|
70
|
+
if (session !== null && (canonicalizeResourceIndicator(session.resource) !== canonicalResource
|
|
71
|
+
|| getOwnString(session.discovery.authorizationServerMetadata, "issuer") !== session.authorizationServer
|
|
72
|
+
|| (discovery !== undefined && discovery.authorizationServer !== session.authorizationServer))) {
|
|
73
|
+
await clearSession(canonicalResource);
|
|
74
|
+
session = null;
|
|
75
|
+
}
|
|
65
76
|
const sessionDiscovery = resolveDiscovery(discovery, session);
|
|
66
77
|
if (session?.tokens !== undefined && !forceRefresh && !isExpired(session.tokens, now)) {
|
|
67
78
|
return session;
|
|
@@ -281,14 +292,16 @@ export function createDefaultOAuthClientProvider(options) {
|
|
|
281
292
|
}
|
|
282
293
|
}
|
|
283
294
|
const registrationBody = buildClientRegistrationBody(getClientMetadata(options.client), redirectUri);
|
|
284
|
-
const
|
|
295
|
+
const signal = AbortSignal.timeout(30_000);
|
|
296
|
+
const response = await fetchMcpResponse(fetch, registrationEndpoint, {
|
|
285
297
|
method: "POST",
|
|
286
298
|
headers: {
|
|
287
299
|
"Content-Type": "application/json"
|
|
288
300
|
},
|
|
289
|
-
body: JSON.stringify(registrationBody)
|
|
301
|
+
body: JSON.stringify(registrationBody),
|
|
302
|
+
signal
|
|
290
303
|
});
|
|
291
|
-
const payload = await readOAuthJsonObjectResponse(response);
|
|
304
|
+
const payload = await readOAuthJsonObjectResponse(response, signal);
|
|
292
305
|
const clientId = getOwnString(payload, "client_id");
|
|
293
306
|
if (clientId === undefined || clientId.trim().length === 0) {
|
|
294
307
|
throw new Error("OAuth client registration response missing client_id");
|
|
@@ -545,10 +558,14 @@ function isLoopbackHostname(hostname) {
|
|
|
545
558
|
const normalizedHostname = normalizeHostname(hostname);
|
|
546
559
|
return (normalizedHostname === "localhost" ||
|
|
547
560
|
normalizedHostname === "::1" ||
|
|
548
|
-
normalizedHostname
|
|
561
|
+
normalizedHostname === "[::1]" ||
|
|
562
|
+
(isIP(normalizedHostname) === 4 && normalizedHostname.startsWith("127.")));
|
|
549
563
|
}
|
|
550
564
|
function assertSecureUrl(value, label) {
|
|
551
565
|
const url = new URL(value);
|
|
566
|
+
if (url.username !== "" || url.password !== "" || url.hash !== "") {
|
|
567
|
+
throw new Error(`${label} must not include credentials or fragment`);
|
|
568
|
+
}
|
|
552
569
|
if (url.protocol === "https:") {
|
|
553
570
|
return;
|
|
554
571
|
}
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.d.ts
CHANGED
|
@@ -36,5 +36,5 @@ export declare function refreshAccessToken(input: {
|
|
|
36
36
|
fetch: OAuthMetadataFetch;
|
|
37
37
|
now: () => number;
|
|
38
38
|
}): Promise<StoredOAuthTokens>;
|
|
39
|
-
export declare function readOAuthJsonObjectResponse(response: Response): Promise<Record<string, unknown>>;
|
|
39
|
+
export declare function readOAuthJsonObjectResponse(response: Response, signal?: AbortSignal): Promise<Record<string, unknown>>;
|
|
40
40
|
export {};
|
package/node_modules/tiny-http-mcp-server/node_modules/mcp-oauth/dist/client/token-endpoint.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { canonicalizeResourceIndicator } from "../resource-indicator.js";
|
|
2
|
+
import { readBoundedResponseText } from "../http-response.js";
|
|
3
|
+
import { fetchMcpResponse } from "../http-fetch.js";
|
|
2
4
|
const MAX_JS_DATE_MS = 8_640_000_000_000_000;
|
|
3
5
|
export class OAuthError extends Error {
|
|
4
6
|
error;
|
|
@@ -68,14 +70,16 @@ async function requestTokens(input) {
|
|
|
68
70
|
if (input.clientSecret !== undefined) {
|
|
69
71
|
body.set("client_secret", input.clientSecret);
|
|
70
72
|
}
|
|
71
|
-
const
|
|
73
|
+
const signal = AbortSignal.timeout(30_000);
|
|
74
|
+
const response = await fetchMcpResponse(input.fetch, input.tokenEndpoint, {
|
|
72
75
|
method: "POST",
|
|
73
76
|
headers: {
|
|
74
77
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
75
78
|
},
|
|
76
|
-
body: body.toString()
|
|
79
|
+
body: body.toString(),
|
|
80
|
+
signal
|
|
77
81
|
});
|
|
78
|
-
const payload = await readOAuthJsonObjectResponse(response);
|
|
82
|
+
const payload = await readOAuthJsonObjectResponse(response, signal);
|
|
79
83
|
const accessToken = getOwnEntry(payload, "access_token");
|
|
80
84
|
if (typeof accessToken !== "string" || accessToken.trim().length === 0) {
|
|
81
85
|
throw new Error("OAuth token response missing access_token");
|
|
@@ -115,13 +119,17 @@ async function requestTokens(input) {
|
|
|
115
119
|
scope: normalizedScope === undefined ? undefined : normalizedScope
|
|
116
120
|
};
|
|
117
121
|
}
|
|
118
|
-
export async function readOAuthJsonObjectResponse(response) {
|
|
122
|
+
export async function readOAuthJsonObjectResponse(response, signal) {
|
|
119
123
|
const fallbackError = createFallbackOAuthError(response.status);
|
|
120
124
|
let payload;
|
|
121
125
|
try {
|
|
122
|
-
payload = await response
|
|
126
|
+
payload = JSON.parse(await readBoundedResponseText(response, 1024 * 1024, undefined, signal));
|
|
123
127
|
}
|
|
124
|
-
catch {
|
|
128
|
+
catch (error) {
|
|
129
|
+
if (signal?.aborted)
|
|
130
|
+
throw signal.reason;
|
|
131
|
+
if (error instanceof Error && error.message.startsWith("HTTP response exceeds "))
|
|
132
|
+
throw error;
|
|
125
133
|
if (!response.ok) {
|
|
126
134
|
throw fallbackError;
|
|
127
135
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function fetchMcpResponse(fetchImplementation: (input: string | URL, init?: RequestInit) => Promise<Response>, input: string | URL, init?: RequestInit): Promise<Response>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export async function fetchMcpResponse(fetchImplementation, input, init = {}) {
|
|
2
|
+
const response = await fetchImplementation(input, { ...init, redirect: "error" });
|
|
3
|
+
if (response.redirected || response.type === "opaqueredirect") {
|
|
4
|
+
void response.body?.cancel().catch(() => undefined);
|
|
5
|
+
throw new Error("MCP HTTP redirects are not allowed");
|
|
6
|
+
}
|
|
7
|
+
return response;
|
|
8
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function readBoundedResponseText(response: Response, maxBytes: number, readers?: Set<ReadableStreamDefaultReader<Uint8Array>>, signal?: AbortSignal): Promise<string>;
|