@orkestrel/mcp 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -7
- package/dist/src/browser/index.d.ts +26 -12
- package/dist/src/browser/index.js +43 -17
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +1277 -137
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1113 -184
- package/dist/src/core/index.d.ts +1113 -184
- package/dist/src/core/index.js +1233 -134
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +361 -68
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +201 -33
- package/dist/src/server/index.d.ts +201 -33
- package/dist/src/server/index.js +352 -70
- package/dist/src/server/index.js.map +1 -1
- package/package.json +10 -10
|
@@ -27,9 +27,28 @@ var MCP_SESSION_HEADER = "mcp-session-id";
|
|
|
27
27
|
* requests; `createMCPRoutes` rejects a present unsupported value before dispatch.
|
|
28
28
|
*/
|
|
29
29
|
var MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
30
|
+
/** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */
|
|
31
|
+
var MCP_METHOD_HEADER = "mcp-method";
|
|
32
|
+
/** The modern Streamable-HTTP request header carrying a named method's target. */
|
|
33
|
+
var MCP_NAME_HEADER = "mcp-name";
|
|
34
|
+
/** The reverse-proxy response header controlling buffering of an SSE response. */
|
|
35
|
+
var SSE_BUFFERING_HEADER = "x-accel-buffering";
|
|
36
|
+
/** The `X-Accel-Buffering` value that disables reverse-proxy buffering. */
|
|
37
|
+
var SSE_BUFFERING_DISABLED = "no";
|
|
30
38
|
/** The default request path `createMCPRoutes` mounts the transport's `POST` route at. */
|
|
31
39
|
var DEFAULT_MCP_PATH = "/mcp";
|
|
32
40
|
/**
|
|
41
|
+
* The default interval in milliseconds between SSE keepalive comments on held-open MCP
|
|
42
|
+
* responses.
|
|
43
|
+
*
|
|
44
|
+
* @remarks
|
|
45
|
+
* Fifteen seconds is infrequent enough to avoid chatty idle connections while bounding dead
|
|
46
|
+
* client detection and staying comfortably inside common intermediary idle windows.
|
|
47
|
+
*/
|
|
48
|
+
var DEFAULT_MCP_KEEPALIVE_INTERVAL = 15e3;
|
|
49
|
+
/** The comment text written by the held-open MCP response keepalive. */
|
|
50
|
+
var SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
51
|
+
/**
|
|
33
52
|
* The WebSocket subprotocol the MCP-over-WebSocket transports negotiate — sent by the
|
|
34
53
|
* client in `Sec-WebSocket-Protocol`, echoed by the server in its `101` handshake.
|
|
35
54
|
*
|
|
@@ -67,6 +86,19 @@ var DEFAULT_MCP_SESSION_TTL = 3e5;
|
|
|
67
86
|
//#endregion
|
|
68
87
|
//#region src/server/helpers.ts
|
|
69
88
|
/**
|
|
89
|
+
* Create a readable stream from its pull and cancellation behaviours.
|
|
90
|
+
*
|
|
91
|
+
* @param pull - The behaviour that supplies the stream's next chunk
|
|
92
|
+
* @param cancel - The behaviour that releases the stream after consumer cancellation
|
|
93
|
+
* @returns A readable stream backed by the supplied behaviours
|
|
94
|
+
*/
|
|
95
|
+
function createReadableStream(pull, cancel) {
|
|
96
|
+
return new ReadableStream({
|
|
97
|
+
pull,
|
|
98
|
+
cancel
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
70
102
|
* Whether the request's `Accept` header opts into a Server-Sent-Events response.
|
|
71
103
|
*
|
|
72
104
|
* @remarks
|
|
@@ -85,6 +117,55 @@ function acceptsEventStream(request) {
|
|
|
85
117
|
return accept.toLowerCase().includes("text/event-stream");
|
|
86
118
|
}
|
|
87
119
|
/**
|
|
120
|
+
* Whether an HTTP request satisfies the endpoint's origin gate.
|
|
121
|
+
*
|
|
122
|
+
* @remarks
|
|
123
|
+
* Validation is enabled by default. A request without `Origin` is allowed. A canonical origin
|
|
124
|
+
* whose host is the `localhost` or `[::1]` literal, or belongs to the `127.0.0.0/8` literal
|
|
125
|
+
* range, is allowed without configuration; every other present origin must occur exactly in
|
|
126
|
+
* the caller-supplied list. Invalid and opaque (`null`) origins are denied. `enabled: false`
|
|
127
|
+
* delegates validation to an upstream layer and allows the request through this gate.
|
|
128
|
+
*
|
|
129
|
+
* @param request - The fetch-standard request to validate
|
|
130
|
+
* @param options - Shared origin validation and delegation options
|
|
131
|
+
* @returns `true` when the request may reach MCP dispatch
|
|
132
|
+
*/
|
|
133
|
+
function allowsOrigin(request, options) {
|
|
134
|
+
if (options?.enabled === false) return true;
|
|
135
|
+
const origin = request.headers.get("origin");
|
|
136
|
+
if (origin === null) return true;
|
|
137
|
+
let parsed;
|
|
138
|
+
try {
|
|
139
|
+
parsed = new URL(origin);
|
|
140
|
+
} catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
if (parsed.origin !== origin) return false;
|
|
144
|
+
if (parsed.hostname === "localhost" || parsed.hostname === "[::1]" || /^127(?:\.\d{1,3}){3}$/.test(parsed.hostname)) return true;
|
|
145
|
+
return options?.origins?.includes(parsed.origin) ?? false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Whether a modern HTTP request's required standard headers match its JSON-RPC body.
|
|
149
|
+
*
|
|
150
|
+
* @remarks
|
|
151
|
+
* Requires `MCP-Protocol-Version` to equal the reserved `_meta` version and `Mcp-Method`
|
|
152
|
+
* to equal `method`. `Mcp-Name` is required only for `tools/call`, where it must equal
|
|
153
|
+
* `params.name`; discovery and listing requests need no name because none is derivable.
|
|
154
|
+
* Legacy requests return `false` because this predicate models the modern contract only.
|
|
155
|
+
*
|
|
156
|
+
* @param request - The HTTP request carrying the headers
|
|
157
|
+
* @param message - The parsed JSON-RPC request body
|
|
158
|
+
* @returns `true` only when every method-applicable modern header matches
|
|
159
|
+
*/
|
|
160
|
+
function matchesModernHeaders(request, message) {
|
|
161
|
+
if (!(0, _src_core.isModernRequest)(message)) return false;
|
|
162
|
+
const version = ((0, _orkestrel_contract.isRecord)(message.params?.["_meta"]) ? message.params["_meta"] : void 0)?.[_src_core.MCP_META_VERSION];
|
|
163
|
+
if (!(0, _orkestrel_contract.isString)(version) || request.headers.get("mcp-protocol-version") !== version || request.headers.get("mcp-method") !== message.method) return false;
|
|
164
|
+
if (message.method !== "tools/call") return true;
|
|
165
|
+
const name = message.params?.["name"];
|
|
166
|
+
return (0, _orkestrel_contract.isString)(name) && request.headers.get("mcp-name") === name;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
88
169
|
* Read the request's `mcp-session-id` header — the session id a stateful transport
|
|
89
170
|
* validates, or `undefined` when absent.
|
|
90
171
|
*
|
|
@@ -125,7 +206,7 @@ function readLastEventId(request) {
|
|
|
125
206
|
* JSON-RPC error body.
|
|
126
207
|
*
|
|
127
208
|
* @remarks
|
|
128
|
-
* Returns `Response.json(
|
|
209
|
+
* Returns `Response.json(buildJSONRPCError(null, JSONRPC_INVALID_REQUEST, 'Session not found'),
|
|
129
210
|
* { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a
|
|
130
211
|
* JSON-RPC error BODY with a `null` id) but at the session-not-found status. Shared by
|
|
131
212
|
* every {@link import('./middlewares.js').createMCPSession} validation site — the
|
|
@@ -136,7 +217,7 @@ function readLastEventId(request) {
|
|
|
136
217
|
* @returns The `404` JSON-RPC error `Response`
|
|
137
218
|
*/
|
|
138
219
|
function rejectUnknownSession() {
|
|
139
|
-
return Response.json((0, _src_core.
|
|
220
|
+
return Response.json((0, _src_core.buildJSONRPCError)(null, _src_core.JSONRPC_INVALID_REQUEST, "Session not found"), { status: 404 });
|
|
140
221
|
}
|
|
141
222
|
/**
|
|
142
223
|
* Decode a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
|
|
@@ -336,60 +417,213 @@ function bridgeMessageTransport(transport) {
|
|
|
336
417
|
};
|
|
337
418
|
}
|
|
338
419
|
//#endregion
|
|
420
|
+
//#region src/server/inferers.ts
|
|
421
|
+
/**
|
|
422
|
+
* Infer the legacy revision an `initialize` request negotiates.
|
|
423
|
+
*
|
|
424
|
+
* @remarks
|
|
425
|
+
* A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
|
|
426
|
+
* request selects the newest supported legacy revision, matching the core initialize result.
|
|
427
|
+
*
|
|
428
|
+
* @param request - The legacy initialize request
|
|
429
|
+
* @returns The negotiated legacy protocol revision
|
|
430
|
+
*/
|
|
431
|
+
function inferLegacyVersion(request) {
|
|
432
|
+
const requested = request.params?.["protocolVersion"];
|
|
433
|
+
const version = (0, _src_core.inferVersion)((0, _orkestrel_contract.isString)(requested) ? [requested] : []);
|
|
434
|
+
if (version !== void 0 && (0, _src_core.inferEra)(version) === "legacy") return version;
|
|
435
|
+
return _src_core.MCP_PROTOCOL_VERSION;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Infer the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
|
|
439
|
+
*
|
|
440
|
+
* @remarks
|
|
441
|
+
* Notifications are accepted with `202`. Legacy response envelopes retain uniform `200`
|
|
442
|
+
* status semantics, including in-band errors. Modern header/capability/version/parameter
|
|
443
|
+
* failures map to `400`, method-not-found maps to `404`, and every other modern result maps
|
|
444
|
+
* to `200`.
|
|
445
|
+
*
|
|
446
|
+
* @param response - The dispatch response, or `undefined` for a notification
|
|
447
|
+
* @param era - The structurally selected request era
|
|
448
|
+
* @returns The HTTP response status
|
|
449
|
+
*/
|
|
450
|
+
function inferStatus(response, era) {
|
|
451
|
+
if (response === void 0) return 202;
|
|
452
|
+
if (era === "legacy" || response.error === void 0) return 200;
|
|
453
|
+
if (response.error.code === _src_core.JSONRPC_METHOD_NOT_FOUND) return 404;
|
|
454
|
+
if (response.error.code === _src_core.MCP_HEADER_MISMATCH || response.error.code === _src_core.MCP_MISSING_CAPABILITY || response.error.code === _src_core.MCP_UNSUPPORTED_VERSION || response.error.code === _src_core.JSONRPC_INVALID_PARAMS) return 400;
|
|
455
|
+
return 200;
|
|
456
|
+
}
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/server/transports/HTTPDisconnect.ts
|
|
459
|
+
/**
|
|
460
|
+
* The HTTP response-disconnect bridge for an MCP SSE stream.
|
|
461
|
+
*
|
|
462
|
+
* @remarks
|
|
463
|
+
* Composes the incoming request signal with a controller owned by the MCP HTTP face. The
|
|
464
|
+
* returned {@link signal} therefore observes both an incomplete request body and cancellation
|
|
465
|
+
* of the streamed response body. {@link bridge} preserves the supplied SSE response while
|
|
466
|
+
* forwarding its body through a cancellation-aware stream. While that response is held open,
|
|
467
|
+
* the bridge writes SSE comment frames at the configured keepalive interval so an idle dead
|
|
468
|
+
* client becomes observable to the HTTP writer. It never decides how an abort changes handler
|
|
469
|
+
* or session state.
|
|
470
|
+
*/
|
|
471
|
+
var HTTPDisconnect = class {
|
|
472
|
+
#abort = new AbortController();
|
|
473
|
+
#lifecycle = new AbortController();
|
|
474
|
+
#interval;
|
|
475
|
+
#signal;
|
|
476
|
+
#timer;
|
|
477
|
+
constructor(signal, options) {
|
|
478
|
+
this.#interval = options?.interval ?? 15e3;
|
|
479
|
+
this.#signal = AbortSignal.any([signal, this.#abort.signal]);
|
|
480
|
+
}
|
|
481
|
+
get signal() {
|
|
482
|
+
return this.#signal;
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Bridge cancellation of an SSE response body into this disconnect signal.
|
|
486
|
+
*
|
|
487
|
+
* @param stream - The open SSE stream whose response will be consumed by the HTTP writer
|
|
488
|
+
* @returns A response with the same status and headers whose body forwards the SSE bytes
|
|
489
|
+
*/
|
|
490
|
+
bridge(stream) {
|
|
491
|
+
const response = stream.response;
|
|
492
|
+
const body = response.body;
|
|
493
|
+
if (body === null) throw new Error("MCP SSE response has no body");
|
|
494
|
+
const reader = body.getReader();
|
|
495
|
+
this.#timer = setInterval(() => {
|
|
496
|
+
if (stream.closed) this.#stop();
|
|
497
|
+
else stream.comment(SSE_KEEPALIVE_COMMENT);
|
|
498
|
+
}, this.#interval);
|
|
499
|
+
this.#signal.addEventListener("abort", () => this.#stop(), {
|
|
500
|
+
once: true,
|
|
501
|
+
signal: this.#lifecycle.signal
|
|
502
|
+
});
|
|
503
|
+
if (this.#signal.aborted || stream.closed) this.#stop();
|
|
504
|
+
return new Response(createReadableStream(async (controller) => {
|
|
505
|
+
try {
|
|
506
|
+
const chunk = await reader.read();
|
|
507
|
+
if (chunk.done) {
|
|
508
|
+
this.#stop();
|
|
509
|
+
controller.close();
|
|
510
|
+
} else controller.enqueue(chunk.value);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
this.#stop();
|
|
513
|
+
controller.error(error);
|
|
514
|
+
}
|
|
515
|
+
}, async (reason) => {
|
|
516
|
+
this.#abort.abort();
|
|
517
|
+
this.#stop();
|
|
518
|
+
await reader.cancel(reason);
|
|
519
|
+
}), {
|
|
520
|
+
status: response.status,
|
|
521
|
+
statusText: response.statusText,
|
|
522
|
+
headers: response.headers
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
#stop() {
|
|
526
|
+
if (this.#timer !== void 0) {
|
|
527
|
+
clearInterval(this.#timer);
|
|
528
|
+
this.#timer = void 0;
|
|
529
|
+
}
|
|
530
|
+
this.#lifecycle.abort();
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
//#endregion
|
|
339
534
|
//#region src/server/handlers.ts
|
|
340
535
|
/**
|
|
341
536
|
* Create the Streamable-HTTP POST handler used by `createMCPRoutes`.
|
|
342
537
|
*
|
|
343
538
|
* @remarks
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
539
|
+
* Modern requests require matching protocol/method headers and a matching name header only
|
|
540
|
+
* for `tools/call`; mismatch returns HTTP `400` + `-32020`. Headerless `initialize` is
|
|
541
|
+
* accepted, while every other headerless request needs a live legacy session to supply its
|
|
542
|
+
* pinned version. A present origin must occur in `origin.origins` unless validation is
|
|
543
|
+
* explicitly delegated upstream. Modern dispatch errors use their protocol status map; legacy
|
|
544
|
+
* errors remain in-band at HTTP `200`. A streamed response composes the fetch-standard request
|
|
545
|
+
* signal with response-body cancellation and supplies the result to every dispatched modern
|
|
546
|
+
* handler through `MCPDispatchOptions.signal`.
|
|
347
547
|
*
|
|
348
548
|
* @param mcp - The transport-agnostic MCP server to dispatch through
|
|
349
|
-
* @param
|
|
549
|
+
* @param options - Optional streaming, origin-validation, and SSE keepalive options
|
|
350
550
|
* @returns A request handler for the stateless MCP POST route
|
|
351
551
|
*
|
|
352
552
|
* @example
|
|
353
553
|
* ```ts
|
|
354
554
|
* import { createMCPServer } from '@orkestrel/mcp'
|
|
355
555
|
* import { createMCPPostHandler } from '@orkestrel/mcp/server'
|
|
356
|
-
* import { createToolManager } from '@orkestrel/
|
|
556
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
357
557
|
*
|
|
358
|
-
* const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
|
|
359
|
-
* const handler = createMCPPostHandler(mcp, true)
|
|
558
|
+
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
559
|
+
* const handler = createMCPPostHandler(mcp, { streaming: true })
|
|
360
560
|
* await handler(new Request('http://localhost/mcp', {
|
|
361
561
|
* method: 'POST',
|
|
362
562
|
* body: '{"jsonrpc":"2.0","method":"ping","id":1}',
|
|
363
563
|
* }))
|
|
364
564
|
* ```
|
|
365
565
|
*/
|
|
366
|
-
function createMCPPostHandler(mcp,
|
|
566
|
+
function createMCPPostHandler(mcp, options) {
|
|
567
|
+
const streaming = options?.streaming ?? true;
|
|
568
|
+
const origin = options?.origin;
|
|
367
569
|
return async (request) => {
|
|
368
|
-
|
|
369
|
-
if (protocol !== null && !_src_core.SUPPORTED_PROTOCOL_VERSIONS.includes(protocol)) return Response.json((0, _src_core.jsonRPCError)(null, _src_core.JSONRPC_INVALID_REQUEST, `Unsupported MCP protocol version '${protocol}'`), { status: 400 });
|
|
570
|
+
if (!allowsOrigin(request, origin)) return new Response(null, { status: 403 });
|
|
370
571
|
let text;
|
|
371
572
|
try {
|
|
372
573
|
text = await request.text();
|
|
373
574
|
} catch {
|
|
374
|
-
return Response.json((0, _src_core.
|
|
575
|
+
return Response.json((0, _src_core.buildJSONRPCError)(null, _src_core.JSONRPC_PARSE_ERROR, "Parse error"), { status: 400 });
|
|
375
576
|
}
|
|
376
577
|
let parsed;
|
|
377
578
|
try {
|
|
378
579
|
parsed = JSON.parse(text);
|
|
379
580
|
} catch {
|
|
380
|
-
return Response.json((0, _src_core.
|
|
581
|
+
return Response.json((0, _src_core.buildJSONRPCError)(null, _src_core.JSONRPC_PARSE_ERROR, "Parse error"), { status: 400 });
|
|
381
582
|
}
|
|
382
583
|
const rpcRequest = (0, _src_core.parseJSONRPCMessage)(parsed);
|
|
383
|
-
if (rpcRequest === void 0 || !("method" in rpcRequest)) return Response.json((0, _src_core.
|
|
384
|
-
const
|
|
385
|
-
|
|
386
|
-
|
|
584
|
+
if (rpcRequest === void 0 || !("method" in rpcRequest)) return Response.json((0, _src_core.buildJSONRPCError)(null, _src_core.JSONRPC_INVALID_REQUEST, "Invalid Request"), { status: 400 });
|
|
585
|
+
const era = (0, _src_core.isModernRequest)(rpcRequest) ? "modern" : "legacy";
|
|
586
|
+
const id = rpcRequest.id ?? null;
|
|
587
|
+
const protocol = request.headers.get(MCP_PROTOCOL_VERSION_HEADER);
|
|
588
|
+
if (era === "modern") {
|
|
589
|
+
if ((0, _src_core.parseRequestContext)(rpcRequest) === void 0) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.JSONRPC_INVALID_PARAMS, "Invalid params: malformed modern request metadata"), { status: 400 });
|
|
590
|
+
if (!matchesModernHeaders(request, rpcRequest)) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.MCP_HEADER_MISMATCH, "MCP request headers do not match the request body"), { status: 400 });
|
|
591
|
+
} else {
|
|
592
|
+
if (protocol === null && !(0, _src_core.isInitializeRequest)(rpcRequest)) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.MCP_HEADER_MISMATCH, "MCP request headers do not match the request body"), { status: 400 });
|
|
593
|
+
if (protocol !== null && !(0, _src_core.isMCPVersion)(protocol)) return Response.json((0, _src_core.buildJSONRPCError)(id, _src_core.MCP_UNSUPPORTED_VERSION, `Unsupported MCP protocol version '${protocol}'`, {
|
|
594
|
+
supported: _src_core.SUPPORTED_PROTOCOL_VERSIONS,
|
|
595
|
+
requested: protocol
|
|
596
|
+
}), { status: 400 });
|
|
597
|
+
}
|
|
598
|
+
const disconnect = new HTTPDisconnect(request.signal, options?.keepalive);
|
|
599
|
+
const response = await mcp.dispatch(rpcRequest, { signal: disconnect.signal });
|
|
600
|
+
if (response !== void 0 && Symbol.asyncIterator in response) {
|
|
601
|
+
const stream = (0, _orkestrel_server.openStream)();
|
|
602
|
+
stream.response.headers.set(SSE_BUFFERING_HEADER, "no");
|
|
603
|
+
queueMicrotask(async () => {
|
|
604
|
+
try {
|
|
605
|
+
let next = await response.next();
|
|
606
|
+
while (!next.done) {
|
|
607
|
+
stream.write({ data: JSON.stringify(next.value) });
|
|
608
|
+
next = await response.next();
|
|
609
|
+
}
|
|
610
|
+
stream.write({ data: JSON.stringify(next.value) });
|
|
611
|
+
} catch {} finally {
|
|
612
|
+
stream.end();
|
|
613
|
+
}
|
|
614
|
+
});
|
|
615
|
+
return disconnect.bridge(stream);
|
|
616
|
+
}
|
|
617
|
+
const status = inferStatus(response, era);
|
|
618
|
+
if (response === void 0) return new Response(null, { status });
|
|
619
|
+
if (status === 200 && streaming && acceptsEventStream(request)) {
|
|
387
620
|
const stream = (0, _orkestrel_server.openStream)();
|
|
621
|
+
stream.response.headers.set(SSE_BUFFERING_HEADER, "no");
|
|
388
622
|
stream.write({ data: JSON.stringify(response) });
|
|
389
623
|
stream.end();
|
|
390
624
|
return stream.response;
|
|
391
625
|
}
|
|
392
|
-
return Response.json(response);
|
|
626
|
+
return Response.json(response, { status });
|
|
393
627
|
};
|
|
394
628
|
}
|
|
395
629
|
//#endregion
|
|
@@ -413,16 +647,17 @@ function createMCPPostHandler(mcp, streaming) {
|
|
|
413
647
|
* readEventStream}) — the inverse of the server's `openStream` seam, so the wire
|
|
414
648
|
* round-trips. A `202`
|
|
415
649
|
* Accepted (a notification) carries no body and emits nothing.
|
|
416
|
-
* - **Session and protocol
|
|
650
|
+
* - **Session and protocol headers.** `start()` is a no-op (a
|
|
417
651
|
* request/response transport opens no long-lived connection). The
|
|
418
652
|
* `mcp-session-id` response header, when a STATEFUL server sends one (on
|
|
419
653
|
* `initialize`), is captured into `session` and then ECHOED as the
|
|
420
654
|
* `mcp-session-id` request header on every SUBSEQUENT request — so an
|
|
421
655
|
* `MCPClient` passes a stateful server's session validation. The
|
|
422
656
|
* initialize result's `protocolVersion` is likewise captured, but only
|
|
423
|
-
* when it is a SUPPORTED value, and echoed as `mcp-protocol-version` on
|
|
424
|
-
*
|
|
425
|
-
*
|
|
657
|
+
* when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
|
|
658
|
+
* subsequent legacy requests. Modern requests instead derive protocol and method
|
|
659
|
+
* headers from the message, plus the name header only for `tools/call`.
|
|
660
|
+
* Before initialize returns, neither captured legacy header is sent.
|
|
426
661
|
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
427
662
|
* POST is headerless; the captured `session` persists across `close()`.
|
|
428
663
|
* - **Total at the boundary (§14).** Every reply is narrowed (`parseJSONRPCMessage`,
|
|
@@ -469,7 +704,7 @@ var HTTPClientTransport = class {
|
|
|
469
704
|
"content-type": "application/json",
|
|
470
705
|
accept: "application/json, text/event-stream",
|
|
471
706
|
...this.#session === void 0 ? {} : { [MCP_SESSION_HEADER]: this.#session },
|
|
472
|
-
...this.#
|
|
707
|
+
...this.#buildHeaders(message),
|
|
473
708
|
...this.#headers
|
|
474
709
|
},
|
|
475
710
|
body: JSON.stringify(message),
|
|
@@ -487,6 +722,18 @@ var HTTPClientTransport = class {
|
|
|
487
722
|
this.#protocol = void 0;
|
|
488
723
|
this.#emitter.emit("close");
|
|
489
724
|
}
|
|
725
|
+
#buildHeaders(message) {
|
|
726
|
+
if ((0, _src_core.isJSONRPCRequest)(message) && (0, _src_core.isModernRequest)(message)) {
|
|
727
|
+
const version = ((0, _orkestrel_contract.isRecord)(message.params?.["_meta"]) ? message.params["_meta"] : void 0)?.[_src_core.MCP_META_VERSION];
|
|
728
|
+
const name = message.params?.["name"];
|
|
729
|
+
return {
|
|
730
|
+
...(0, _orkestrel_contract.isString)(version) ? { [MCP_PROTOCOL_VERSION_HEADER]: version } : {},
|
|
731
|
+
[MCP_METHOD_HEADER]: message.method,
|
|
732
|
+
...message.method === "tools/call" && (0, _orkestrel_contract.isString)(name) ? { [MCP_NAME_HEADER]: name } : {}
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
return this.#protocol === void 0 ? {} : { [MCP_PROTOCOL_VERSION_HEADER]: this.#protocol };
|
|
736
|
+
}
|
|
490
737
|
async #deliver(response) {
|
|
491
738
|
if (response.status === 202) return;
|
|
492
739
|
const type = response.headers.get("content-type") ?? "";
|
|
@@ -504,7 +751,7 @@ var HTTPClientTransport = class {
|
|
|
504
751
|
}
|
|
505
752
|
}
|
|
506
753
|
#capture(message) {
|
|
507
|
-
if ((0, _src_core.isJSONRPCResponse)(message) && (0, _orkestrel_contract.isRecord)(message.result) && (0,
|
|
754
|
+
if ((0, _src_core.isJSONRPCResponse)(message) && (0, _orkestrel_contract.isRecord)(message.result) && (0, _src_core.isMCPVersion)(message.result["protocolVersion"])) this.#protocol = message.result["protocolVersion"];
|
|
508
755
|
this.#emitter.emit("message", message);
|
|
509
756
|
}
|
|
510
757
|
};
|
|
@@ -1035,12 +1282,11 @@ var StdioServerTransport = class {
|
|
|
1035
1282
|
* - A **transport** failure — a malformed JSON body, or a parsed value that is not a
|
|
1036
1283
|
* JSON-RPC REQUEST — is an HTTP `400` carrying a JSON-RPC error BODY (`-32700` Parse
|
|
1037
1284
|
* error / `-32600` Invalid Request, id `null`).
|
|
1038
|
-
* -
|
|
1039
|
-
*
|
|
1040
|
-
*
|
|
1041
|
-
* -
|
|
1042
|
-
*
|
|
1043
|
-
* envelope (the error is in-band per JSON-RPC, NOT an HTTP error).
|
|
1285
|
+
* - Modern protocol/method/name headers are validated against the body; a mismatch is
|
|
1286
|
+
* HTTP `400` + `-32020`. Headerless initialize is accepted, a live legacy session supplies
|
|
1287
|
+
* its pinned revision, and every other headerless request is rejected.
|
|
1288
|
+
* - Legacy dispatch errors stay IN-BAND at HTTP `200`; modern errors map to `400` for
|
|
1289
|
+
* `-32020` / `-32021` / `-32022` / `-32602`, `404` for `-32601`, and `200` otherwise.
|
|
1044
1290
|
* - A **notification** (a request with no `id`, which `dispatch` resolves to
|
|
1045
1291
|
* `undefined`) is a `202 Accepted` with no body.
|
|
1046
1292
|
*
|
|
@@ -1055,13 +1301,15 @@ var StdioServerTransport = class {
|
|
|
1055
1301
|
* validates the `mcp-session-id`, and serves the resumable `GET {path}` + `DELETE {path}`,
|
|
1056
1302
|
* leaving this route to dispatch the validated `POST`.
|
|
1057
1303
|
*
|
|
1058
|
-
* This is MECHANISM, not policy: compose auth /
|
|
1059
|
-
*
|
|
1304
|
+
* This is MECHANISM, not policy: compose auth / rate-limiting (and the session middleware)
|
|
1305
|
+
* IN FRONT as ordinary middleware; the optional `origin` group carries the deployment's shared
|
|
1306
|
+
* allowlist or explicitly delegates validation to an upstream layer.
|
|
1060
1307
|
*
|
|
1061
1308
|
* @typeParam TState - The consumer's opaque per-request state type
|
|
1062
1309
|
* @param mcp - The transport-agnostic {@link MCPServerInterface} to expose over HTTP
|
|
1063
1310
|
* @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}) and `streaming`
|
|
1064
|
-
* (default `true`); see
|
|
1311
|
+
* (default `true`), plus the shared `origin` validation options; see
|
|
1312
|
+
* {@link HTTPTransportOptions}
|
|
1065
1313
|
* @returns The {@link RouteInput}s to register with the router
|
|
1066
1314
|
*
|
|
1067
1315
|
* @example
|
|
@@ -1069,7 +1317,7 @@ var StdioServerTransport = class {
|
|
|
1069
1317
|
* import { createMCPServer, createToolManager } from '@src/core'
|
|
1070
1318
|
* import { createMCPRoutes } from '@src/server'
|
|
1071
1319
|
*
|
|
1072
|
-
* const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
|
|
1320
|
+
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
1073
1321
|
* const routes = createMCPRoutes(mcp) // POST /mcp dispatches JSON-RPC (JSON or SSE per Accept)
|
|
1074
1322
|
* ```
|
|
1075
1323
|
*/
|
|
@@ -1078,7 +1326,7 @@ function createMCPRoutes(mcp, options) {
|
|
|
1078
1326
|
method: "POST",
|
|
1079
1327
|
path: options?.path ?? "/mcp",
|
|
1080
1328
|
name: "mcp",
|
|
1081
|
-
handler: createMCPPostHandler(mcp, options
|
|
1329
|
+
handler: createMCPPostHandler(mcp, options)
|
|
1082
1330
|
}];
|
|
1083
1331
|
}
|
|
1084
1332
|
/**
|
|
@@ -1095,9 +1343,9 @@ function createMCPRoutes(mcp, options) {
|
|
|
1095
1343
|
* correlation. Add `options.headers` (e.g. an `Authorization` bearer) to reach a guarded
|
|
1096
1344
|
* server. `start` / `close` hold no connection; against a STATEFUL server it captures the
|
|
1097
1345
|
* `mcp-session-id` from `initialize` and echoes it on later requests. It also captures
|
|
1098
|
-
* the initialize result's `protocolVersion` and sends `mcp-protocol-version` on
|
|
1099
|
-
* subsequent request
|
|
1100
|
-
*
|
|
1346
|
+
* the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each
|
|
1347
|
+
* subsequent legacy request. Modern requests derive protocol and method headers directly
|
|
1348
|
+
* from the message, plus a name header only for `tools/call`.
|
|
1101
1349
|
*
|
|
1102
1350
|
* @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
|
|
1103
1351
|
* every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
|
|
@@ -1159,7 +1407,7 @@ function createHTTPClientTransport(options) {
|
|
|
1159
1407
|
* import { createMCPServer, createToolManager } from '@src/core'
|
|
1160
1408
|
* import { createWebSocketServer } from '@src/server'
|
|
1161
1409
|
*
|
|
1162
|
-
* const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
|
|
1410
|
+
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
1163
1411
|
* server.upgrade(createWebSocketServer(mcp)) // an MCP client now connects over ws://…/mcp
|
|
1164
1412
|
* ```
|
|
1165
1413
|
*/
|
|
@@ -1280,7 +1528,7 @@ function createStdioClientTransport(options) {
|
|
|
1280
1528
|
* import { createMCPServer, createToolManager } from '@src/core'
|
|
1281
1529
|
* import { createStdioServer } from '@src/server'
|
|
1282
1530
|
*
|
|
1283
|
-
* const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
|
|
1531
|
+
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
1284
1532
|
* createStdioServer(mcp).start() // an MCP client now connects over this process's stdio
|
|
1285
1533
|
* ```
|
|
1286
1534
|
*/
|
|
@@ -1310,12 +1558,17 @@ function createStdioServer(mcp, options) {
|
|
|
1310
1558
|
* `path` (default {@link DEFAULT_MCP_PATH}); a request to any other path passes straight
|
|
1311
1559
|
* through (`next()`).
|
|
1312
1560
|
*
|
|
1561
|
+
* A modern-shaped POST also passes straight through via `next()`, ignoring any session id.
|
|
1562
|
+
* The remaining behavior is the legacy session layer:
|
|
1563
|
+
*
|
|
1313
1564
|
* - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route
|
|
1314
1565
|
* can re-read it via a freshly-built forwarded `Request`). Resolves a session via {@link
|
|
1315
1566
|
* readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an
|
|
1316
1567
|
* ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link
|
|
1317
1568
|
* isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)
|
|
1318
|
-
* and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`).
|
|
1569
|
+
* and sets `context.state.session`; neither → {@link rejectUnknownSession} (`404`). The
|
|
1570
|
+
* minted entry pins the negotiated legacy revision, which is supplied to a later headerless
|
|
1571
|
+
* live-session request. It then
|
|
1319
1572
|
* FORWARDS a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the
|
|
1320
1573
|
* already-consumed original — so the route re-reads the same body, and stamps the response
|
|
1321
1574
|
* with {@link MCP_SESSION_HEADER}.
|
|
@@ -1323,8 +1576,8 @@ function createStdioServer(mcp, options) {
|
|
|
1323
1576
|
* an invalid / unknown id is the same `404`. A valid session opens the resumable
|
|
1324
1577
|
* server→client stream via `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:
|
|
1325
1578
|
* replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE
|
|
1326
|
-
* attaching the stream for live pushes, then attaches;
|
|
1327
|
-
* detaches it. Long-lived — never `end()`ed here.
|
|
1579
|
+
* attaching the stream for live pushes, then attaches; cancellation of the streamed response
|
|
1580
|
+
* body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.
|
|
1328
1581
|
* - **`DELETE {path}`.** Resolves the session; a valid id deletes it from the store and answers
|
|
1329
1582
|
* `204`; an invalid / unknown id is the same `404`.
|
|
1330
1583
|
*
|
|
@@ -1338,7 +1591,8 @@ function createStdioServer(mcp, options) {
|
|
|
1338
1591
|
* @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session
|
|
1339
1592
|
* sweep window, ms — omit for sessions that live until an explicit `DELETE`), `capacity`
|
|
1340
1593
|
* (the folded per-session replay-log bound), and `clock` (the deterministic epoch-ms clock;
|
|
1341
|
-
* defaults to `Date.now`); see
|
|
1594
|
+
* defaults to `Date.now`), plus the shared `origin` validation options; see
|
|
1595
|
+
* {@link MCPSessionOptions}
|
|
1342
1596
|
* @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable
|
|
1343
1597
|
* `GET` / `DELETE`
|
|
1344
1598
|
*
|
|
@@ -1347,7 +1601,7 @@ function createStdioServer(mcp, options) {
|
|
|
1347
1601
|
* import { createMCPServer, createToolManager } from '@src/core'
|
|
1348
1602
|
* import { createMCPRoutes, createMCPSession } from '@src/server'
|
|
1349
1603
|
*
|
|
1350
|
-
* const mcp = createMCPServer({ name: 'docs', version: '1.0.0', tools: createToolManager() })
|
|
1604
|
+
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
1351
1605
|
* router.use(createMCPSession({ ttl: 60_000 })) // stateful: mint + validate + resumable GET / DELETE
|
|
1352
1606
|
* router.add(createMCPRoutes(mcp)) // the route stays session-agnostic
|
|
1353
1607
|
* ```
|
|
@@ -1357,9 +1611,27 @@ function createMCPSession(options) {
|
|
|
1357
1611
|
const capacity = options?.capacity;
|
|
1358
1612
|
const ttl = options?.ttl;
|
|
1359
1613
|
const clock = options?.clock ?? Date.now;
|
|
1614
|
+
const origin = options?.origin;
|
|
1360
1615
|
const store = /* @__PURE__ */ new Map();
|
|
1361
1616
|
return async (request, context, next) => {
|
|
1362
1617
|
if (context.url.pathname !== path) return next();
|
|
1618
|
+
if (!allowsOrigin(request, origin)) return new Response(null, { status: 403 });
|
|
1619
|
+
let parsed;
|
|
1620
|
+
let text;
|
|
1621
|
+
if (context.method === "POST") {
|
|
1622
|
+
try {
|
|
1623
|
+
text = await request.text();
|
|
1624
|
+
parsed = (0, _src_core.parseJSONRPCMessage)(JSON.parse(text));
|
|
1625
|
+
} catch {
|
|
1626
|
+
parsed = void 0;
|
|
1627
|
+
}
|
|
1628
|
+
if (text !== void 0 && parsed !== void 0 && (0, _src_core.isModernRequest)(parsed)) return next(new Request(context.url, {
|
|
1629
|
+
method: "POST",
|
|
1630
|
+
headers: request.headers,
|
|
1631
|
+
body: text,
|
|
1632
|
+
signal: request.signal
|
|
1633
|
+
}));
|
|
1634
|
+
}
|
|
1363
1635
|
if (ttl !== void 0) {
|
|
1364
1636
|
const cutoff = clock() - ttl;
|
|
1365
1637
|
for (const [id, entry] of store) if (entry.touched <= cutoff) store.delete(id);
|
|
@@ -1376,7 +1648,8 @@ function createMCPSession(options) {
|
|
|
1376
1648
|
if (current !== void 0) {
|
|
1377
1649
|
entry = {
|
|
1378
1650
|
session: current.session,
|
|
1379
|
-
touched: clock()
|
|
1651
|
+
touched: clock(),
|
|
1652
|
+
version: current.version
|
|
1380
1653
|
};
|
|
1381
1654
|
store.set(id, entry);
|
|
1382
1655
|
}
|
|
@@ -1385,6 +1658,8 @@ function createMCPSession(options) {
|
|
|
1385
1658
|
if (entry === void 0) return rejectUnknownSession();
|
|
1386
1659
|
const session = entry.session;
|
|
1387
1660
|
const stream = (0, _orkestrel_server.openStream)();
|
|
1661
|
+
const disconnect = new HTTPDisconnect(request.signal, options?.keepalive);
|
|
1662
|
+
stream.response.headers.set(SSE_BUFFERING_HEADER, "no");
|
|
1388
1663
|
stream.comment("open");
|
|
1389
1664
|
const lastEventId = readLastEventId(request);
|
|
1390
1665
|
if (lastEventId !== void 0) for (const e of session.replay(lastEventId)) stream.write({
|
|
@@ -1392,56 +1667,71 @@ function createMCPSession(options) {
|
|
|
1392
1667
|
data: JSON.stringify(e.message)
|
|
1393
1668
|
});
|
|
1394
1669
|
session.attach(stream);
|
|
1395
|
-
if (
|
|
1396
|
-
else
|
|
1397
|
-
return stream
|
|
1670
|
+
if (disconnect.signal.aborted) session.detach(stream);
|
|
1671
|
+
else disconnect.signal.addEventListener("abort", () => session.detach(stream), { once: true });
|
|
1672
|
+
return disconnect.bridge(stream);
|
|
1398
1673
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
parsed
|
|
1674
|
+
if (context.method !== "POST" || text === void 0) return next();
|
|
1675
|
+
let created;
|
|
1676
|
+
if (entry === void 0) if (parsed !== void 0 && (0, _src_core.isInitializeRequest)(parsed)) {
|
|
1677
|
+
created = {
|
|
1678
|
+
session: new MCPSession(crypto.randomUUID(), capacity !== void 0 ? { capacity } : {}),
|
|
1679
|
+
touched: clock(),
|
|
1680
|
+
version: inferLegacyVersion(parsed)
|
|
1681
|
+
};
|
|
1682
|
+
entry = created;
|
|
1683
|
+
} else return rejectUnknownSession();
|
|
1684
|
+
if (!Reflect.set(context.state, "session", entry.session)) throw new Error("MCP session state is not writable");
|
|
1685
|
+
const headers = new Headers(request.headers);
|
|
1686
|
+
if (parsed === void 0 || !(0, _src_core.isInitializeRequest)(parsed)) {
|
|
1687
|
+
const protocol = headers.get(MCP_PROTOCOL_VERSION_HEADER);
|
|
1688
|
+
if (protocol === null) headers.set(MCP_PROTOCOL_VERSION_HEADER, entry.version);
|
|
1689
|
+
else if (protocol !== entry.version) {
|
|
1690
|
+
const requestId = parsed !== void 0 && "method" in parsed ? parsed.id ?? null : null;
|
|
1691
|
+
return Response.json((0, _src_core.buildJSONRPCError)(requestId, _src_core.MCP_HEADER_MISMATCH, "MCP protocol version does not match the active session"), { status: 400 });
|
|
1406
1692
|
}
|
|
1407
|
-
if (parsed !== void 0 && (0, _src_core.isInitializeRequest)(parsed)) {
|
|
1408
|
-
const session = new MCPSession(crypto.randomUUID(), capacity !== void 0 ? { capacity } : {});
|
|
1409
|
-
entry = {
|
|
1410
|
-
session,
|
|
1411
|
-
touched: clock()
|
|
1412
|
-
};
|
|
1413
|
-
store.set(session.id, entry);
|
|
1414
|
-
} else return rejectUnknownSession();
|
|
1415
1693
|
}
|
|
1416
|
-
if (!Reflect.set(context.state, "session", entry.session)) throw new Error("MCP session state is not writable");
|
|
1417
1694
|
const response = await next(new Request(context.url, {
|
|
1418
1695
|
method: "POST",
|
|
1419
|
-
headers
|
|
1420
|
-
body: text
|
|
1696
|
+
headers,
|
|
1697
|
+
body: text,
|
|
1698
|
+
signal: request.signal
|
|
1421
1699
|
}));
|
|
1700
|
+
if (created !== void 0) {
|
|
1701
|
+
if (!response.ok) return response;
|
|
1702
|
+
store.set(created.session.id, created);
|
|
1703
|
+
}
|
|
1422
1704
|
response.headers.set(MCP_SESSION_HEADER, entry.session.id);
|
|
1423
1705
|
return response;
|
|
1424
1706
|
};
|
|
1425
1707
|
}
|
|
1426
1708
|
//#endregion
|
|
1709
|
+
exports.DEFAULT_MCP_KEEPALIVE_INTERVAL = DEFAULT_MCP_KEEPALIVE_INTERVAL;
|
|
1427
1710
|
exports.DEFAULT_MCP_PATH = DEFAULT_MCP_PATH;
|
|
1428
1711
|
exports.DEFAULT_MCP_SESSION_CAPACITY = DEFAULT_MCP_SESSION_CAPACITY;
|
|
1429
1712
|
exports.DEFAULT_MCP_SESSION_TTL = DEFAULT_MCP_SESSION_TTL;
|
|
1430
1713
|
exports.HTTPClientTransport = HTTPClientTransport;
|
|
1431
1714
|
exports.MCPSession = MCPSession;
|
|
1715
|
+
exports.MCP_METHOD_HEADER = MCP_METHOD_HEADER;
|
|
1716
|
+
exports.MCP_NAME_HEADER = MCP_NAME_HEADER;
|
|
1432
1717
|
exports.MCP_PROTOCOL_VERSION_HEADER = MCP_PROTOCOL_VERSION_HEADER;
|
|
1433
1718
|
exports.MCP_SESSION_HEADER = MCP_SESSION_HEADER;
|
|
1434
1719
|
exports.MCP_WEBSOCKET_SUBPROTOCOL = MCP_WEBSOCKET_SUBPROTOCOL;
|
|
1720
|
+
exports.SSE_BUFFERING_DISABLED = SSE_BUFFERING_DISABLED;
|
|
1721
|
+
exports.SSE_BUFFERING_HEADER = SSE_BUFFERING_HEADER;
|
|
1722
|
+
exports.SSE_KEEPALIVE_COMMENT = SSE_KEEPALIVE_COMMENT;
|
|
1435
1723
|
exports.StdioClientTransport = StdioClientTransport;
|
|
1436
1724
|
exports.StdioServerTransport = StdioServerTransport;
|
|
1437
1725
|
exports.WebSocketClientTransport = WebSocketClientTransport;
|
|
1438
1726
|
exports.WebSocketServerTransport = WebSocketServerTransport;
|
|
1439
1727
|
exports.acceptsEventStream = acceptsEventStream;
|
|
1728
|
+
exports.allowsOrigin = allowsOrigin;
|
|
1440
1729
|
exports.bridgeMessageTransport = bridgeMessageTransport;
|
|
1441
1730
|
exports.createHTTPClientTransport = createHTTPClientTransport;
|
|
1442
1731
|
exports.createMCPPostHandler = createMCPPostHandler;
|
|
1443
1732
|
exports.createMCPRoutes = createMCPRoutes;
|
|
1444
1733
|
exports.createMCPSession = createMCPSession;
|
|
1734
|
+
exports.createReadableStream = createReadableStream;
|
|
1445
1735
|
exports.createStdioClientTransport = createStdioClientTransport;
|
|
1446
1736
|
exports.createStdioServer = createStdioServer;
|
|
1447
1737
|
exports.createWebSocketClientTransport = createWebSocketClientTransport;
|
|
@@ -1449,6 +1739,9 @@ exports.createWebSocketServer = createWebSocketServer;
|
|
|
1449
1739
|
exports.decodeEvent = decodeEvent;
|
|
1450
1740
|
exports.dispatchLines = dispatchLines;
|
|
1451
1741
|
exports.extractLines = extractLines;
|
|
1742
|
+
exports.inferLegacyVersion = inferLegacyVersion;
|
|
1743
|
+
exports.inferStatus = inferStatus;
|
|
1744
|
+
exports.matchesModernHeaders = matchesModernHeaders;
|
|
1452
1745
|
exports.readEventStream = readEventStream;
|
|
1453
1746
|
exports.readLastEventId = readLastEventId;
|
|
1454
1747
|
exports.readSessionHeader = readSessionHeader;
|