@orkestrel/mcp 0.0.27 → 0.0.29
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 +12 -15
- package/dist/src/browser/index.d.ts +184 -324
- package/dist/src/browser/index.js +166 -469
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +826 -352
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1265 -855
- package/dist/src/core/index.d.ts +1265 -855
- package/dist/src/core/index.js +815 -352
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +364 -680
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +395 -516
- package/dist/src/server/index.d.ts +395 -516
- package/dist/src/server/index.js +358 -665
- package/dist/src/server/index.js.map +1 -1
- package/package.json +26 -27
|
@@ -1,32 +1,30 @@
|
|
|
1
|
-
import { EmitterInterface } from '@orkestrel/emitter';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { JSONRPCMessage
|
|
6
|
-
import { JSONRPCResponse } from '@orkestrel/mcp';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import { TokenSecret } from '@orkestrel/server';
|
|
26
|
-
import { UpgradeHandler } from '@orkestrel/server';
|
|
1
|
+
import type { EmitterInterface } from '@orkestrel/emitter';
|
|
2
|
+
import type { HTTPClientTransportOptions } from '@orkestrel/mcp';
|
|
3
|
+
import type { IncomingMessage } from 'node:http';
|
|
4
|
+
import type { JSONRPCInvocation } from '@orkestrel/mcp';
|
|
5
|
+
import type { JSONRPCMessage } from '@orkestrel/mcp';
|
|
6
|
+
import type { JSONRPCResponse } from '@orkestrel/mcp';
|
|
7
|
+
import type { MCPContinuationInterface } from '@orkestrel/mcp';
|
|
8
|
+
import type { MCPDispatcherInterface } from '@orkestrel/mcp';
|
|
9
|
+
import type { MCPEra } from '@orkestrel/mcp';
|
|
10
|
+
import type { MCPHeaderParameter } from '@orkestrel/mcp';
|
|
11
|
+
import type { MCPLegacyVersion } from '@orkestrel/mcp';
|
|
12
|
+
import type { MCPMessageTransportEventMap } from '@orkestrel/mcp';
|
|
13
|
+
import type { MCPMessageTransportInterface } from '@orkestrel/mcp';
|
|
14
|
+
import type { MCPStreamControllerInterface } from '@orkestrel/mcp';
|
|
15
|
+
import type { MCPTransportInterface } from '@orkestrel/mcp';
|
|
16
|
+
import type { MCPVersion } from '@orkestrel/mcp';
|
|
17
|
+
import type { MiddlewareHandler } from '@orkestrel/server';
|
|
18
|
+
import type { NodeWebSocketInterface } from '@orkestrel/websocket';
|
|
19
|
+
import type { RouteContext } from '@orkestrel/router';
|
|
20
|
+
import type { RouteInput } from '@orkestrel/router';
|
|
21
|
+
import type { ServerEventMap } from '@orkestrel/server';
|
|
22
|
+
import type { StreamInterface } from '@orkestrel/server';
|
|
23
|
+
import type { TokenSecret } from '@orkestrel/server';
|
|
24
|
+
import type { UpgradeHandler } from '@orkestrel/server';
|
|
27
25
|
|
|
28
26
|
/**
|
|
29
|
-
*
|
|
27
|
+
* Checks whether the request's `Accept` header opts into a Server-Sent-Events response.
|
|
30
28
|
*
|
|
31
29
|
* @remarks
|
|
32
30
|
* Reads the fetch-standard `Request.headers.get('accept')` and returns `true` when it
|
|
@@ -36,12 +34,12 @@ import { UpgradeHandler } from '@orkestrel/server';
|
|
|
36
34
|
* — an absent / unmatched header returns `false`.
|
|
37
35
|
*
|
|
38
36
|
* @param request - The fetch-standard `Request`
|
|
39
|
-
* @returns
|
|
37
|
+
* @returns True if the client `Accept`s `text/event-stream`; false otherwise
|
|
40
38
|
*/
|
|
41
39
|
export declare function acceptsEventStream(request: Request): boolean;
|
|
42
40
|
|
|
43
41
|
/**
|
|
44
|
-
*
|
|
42
|
+
* Checks whether an HTTP request satisfies the endpoint's origin gate.
|
|
45
43
|
*
|
|
46
44
|
* @remarks
|
|
47
45
|
* Validation is enabled by default. A request without `Origin` is allowed. A canonical origin
|
|
@@ -52,43 +50,45 @@ export declare function acceptsEventStream(request: Request): boolean;
|
|
|
52
50
|
*
|
|
53
51
|
* @param request - The fetch-standard request to validate
|
|
54
52
|
* @param options - Shared origin validation and delegation options
|
|
55
|
-
* @returns
|
|
53
|
+
* @returns True if the request may reach MCP dispatch; false otherwise
|
|
56
54
|
*/
|
|
57
55
|
export declare function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean;
|
|
58
56
|
|
|
59
57
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* {@link
|
|
63
|
-
*
|
|
64
|
-
* import('
|
|
65
|
-
*
|
|
66
|
-
*
|
|
58
|
+
* Creates the server-side mirror of
|
|
59
|
+
* {@link import('@orkestrel/mcp').createDuplexClientTransport}: the adapter that bridges a
|
|
60
|
+
* message-channel {@link MCPMessageTransportInterface}
|
|
61
|
+
* (the shape the stdio and WebSocket server transports already implement) onto the
|
|
62
|
+
* environment-agnostic {@link import('@orkestrel/mcp').MCPTransportInterface} port — what
|
|
63
|
+
* {@link createStdioServer} and {@link createWebSocketServer} pipe through `bindServer`, so
|
|
64
|
+
* the request/reply/error pump those factories used to hand-roll identically now lives once
|
|
65
|
+
* in the core binder. {@link import('@orkestrel/mcp').createDuplexClientTransport} adapts the
|
|
66
|
+
* same contracts the other way.
|
|
67
67
|
*
|
|
68
68
|
* @remarks
|
|
69
69
|
* `send` decodes the already-serialized reply string back to a {@link JSONRPCMessage}
|
|
70
70
|
* and writes it through `transport.send` (the same `JSON.stringify` the underlying
|
|
71
71
|
* transport already performs, so the wire bytes are unchanged). `listen` filters
|
|
72
|
-
* `transport`'s `message` event to
|
|
72
|
+
* `transport`'s `message` event to invocations only — requests and notifications, never a
|
|
73
73
|
* stray response, exactly as the prior hand-rolled pumps did — and re-serializes each one
|
|
74
74
|
* back to a string for `bindServer`. `closed` bridges `transport`'s `close` event. `close`
|
|
75
75
|
* closes the underlying `transport`.
|
|
76
76
|
*
|
|
77
|
-
* @remarks A message crossing this bridge is decoded and re-encoded
|
|
78
|
-
*
|
|
77
|
+
* @remarks A message crossing this bridge is decoded and re-encoded twice, and that is
|
|
78
|
+
* accepted rather than accidental. Inbound: the carrier already parsed the frame into a
|
|
79
79
|
* {@link JSONRPCMessage}, and `listen` re-serializes it so `bindServer` can decode it again
|
|
80
80
|
* under the server's own `limit`. Outbound: `bindServer` serialized the reply, `send` parses
|
|
81
81
|
* it back, and the carrier stringifies it once more. The cost is two extra `JSON.parse` /
|
|
82
|
-
* `JSON.stringify` round trips per message, paid to keep
|
|
83
|
-
* of a hand-rolled one per carrier. It is
|
|
82
|
+
* `JSON.stringify` round trips per message, paid to keep one pump in the core binder instead
|
|
83
|
+
* of a hand-rolled one per carrier. It is bounded rather than unbounded because the binder
|
|
84
84
|
* decodes within `server.limit.message`, so an oversized frame is refused before the second
|
|
85
85
|
* decode rather than after it. Removing the cost means giving `MCPTransportInterface` a
|
|
86
86
|
* message-shaped face beside its string one, which every transport would then carry.
|
|
87
87
|
*
|
|
88
88
|
* @remarks Per {@link import('@orkestrel/mcp').MCPTransportInterface}, `listen`/`closed`
|
|
89
|
-
* each hold
|
|
89
|
+
* each hold the single current handler (a second call replaces the first, never adds).
|
|
90
90
|
* Because the underlying `transport.emitter` is ADD-based (`on` subscribes, never
|
|
91
|
-
* replaces), this bridge installs
|
|
91
|
+
* replaces), this bridge installs one stable emitter listener per event on first use
|
|
92
92
|
* and re-routes it to whichever handler is active (`undefined` while
|
|
93
93
|
* none is), so rebinding never double-dispatches.
|
|
94
94
|
*
|
|
@@ -105,47 +105,38 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
|
|
|
105
105
|
* import { bindServer } from '@orkestrel/mcp'
|
|
106
106
|
*
|
|
107
107
|
* const transport = new StdioServerTransport(process.stdin, process.stdout)
|
|
108
|
-
* bindServer(mcp,
|
|
108
|
+
* bindServer(mcp, createDuplexServerTransport(transport))
|
|
109
109
|
* ```
|
|
110
110
|
*/
|
|
111
|
-
export declare function
|
|
111
|
+
export declare function createDuplexServerTransport(transport: MCPMessageTransportInterface): MCPTransportInterface;
|
|
112
112
|
|
|
113
113
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* @param response - The response whose status is reported
|
|
117
|
-
* @param type - The response's content type, or an empty string when absent
|
|
118
|
-
* @returns An error naming the HTTP status and unsupported response shape
|
|
119
|
-
*
|
|
120
|
-
* @example
|
|
121
|
-
* ```ts
|
|
122
|
-
* const error = buildResponseError(new Response('', { status: 500 }), '')
|
|
123
|
-
* ```
|
|
124
|
-
*/
|
|
125
|
-
export declare function buildResponseError(response: Response, type: string): Error;
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
129
|
-
* — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
|
|
114
|
+
* Creates the HTTP client transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
115
|
+
* — a {@link MCPMessageTransportInterface} that drives a remote Streamable-HTTP MCP server
|
|
130
116
|
* over `fetch`. The egress mirror of {@link createMCPRoutes}.
|
|
131
117
|
*
|
|
132
118
|
* @remarks
|
|
119
|
+
* It returns the core {@link import('@orkestrel/mcp').HTTPClientTransport}, the same class the
|
|
120
|
+
* browser face's `createHTTPClientTransport` returns, because the class touches `fetch`,
|
|
121
|
+
* `Response`, `AbortController`, `AbortSignal`, and `WeakMap` alone.
|
|
122
|
+
*
|
|
123
|
+
* @remarks
|
|
133
124
|
* Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client sends is
|
|
134
125
|
* `POST`ed to `options.url` with `content-type: application/json` and an `Accept` of
|
|
135
|
-
* both `application/json` and `text/event-stream` (the server answers with
|
|
126
|
+
* both `application/json` and `text/event-stream` (the server answers with either — a
|
|
136
127
|
* plain JSON envelope or a Streamable-HTTP SSE `data:` event, decoded with `@orkestrel/sse`),
|
|
137
128
|
* and the reply is surfaced on the transport's `message` event for the client's id
|
|
138
129
|
* correlation. Add `options.headers` (for example, an `Authorization` bearer) to reach a guarded
|
|
139
|
-
* server. `start` / `close` hold no connection; against a
|
|
130
|
+
* server. `start` / `close` hold no connection; against a stateful server it captures the
|
|
140
131
|
* `mcp-session-id` from `initialize` and echoes it on later requests. It also captures
|
|
141
132
|
* the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each
|
|
142
133
|
* subsequent legacy request. Modern requests derive protocol and method headers directly
|
|
143
134
|
* from the message, plus a name header only for `tools/call`.
|
|
144
135
|
*
|
|
145
|
-
* @param options - `url` (the remote endpoint;
|
|
136
|
+
* @param options - `url` (the remote endpoint; required), optional `headers` merged onto
|
|
146
137
|
* every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
|
|
147
138
|
* (ms, applied with `AbortSignal.timeout`); see {@link HTTPClientTransportOptions}
|
|
148
|
-
* @returns A working {@link
|
|
139
|
+
* @returns A working {@link MCPMessageTransportInterface} over `fetch`
|
|
149
140
|
*
|
|
150
141
|
* @example
|
|
151
142
|
* ```ts
|
|
@@ -159,7 +150,7 @@ export declare function buildResponseError(response: Response, type: string): Er
|
|
|
159
150
|
* const tools = await client.tools()
|
|
160
151
|
* ```
|
|
161
152
|
*/
|
|
162
|
-
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions):
|
|
153
|
+
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPMessageTransportInterface;
|
|
163
154
|
|
|
164
155
|
/**
|
|
165
156
|
* Adapts the installed server token primitives to the host-neutral MCP continuation port.
|
|
@@ -177,7 +168,7 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
|
|
|
177
168
|
* method carrying a named target — `tools/call` and `prompts/get` against `params.name`,
|
|
178
169
|
* `resources/read` against `params.uri` — with a Base64-sentinel value decoded before the
|
|
179
170
|
* comparison; a missing, mismatched, or invalidly encoded value returns HTTP `400` + `-32020`.
|
|
180
|
-
* A protocol header naming a
|
|
171
|
+
* A protocol header naming a modern revision holds the request to that revision whatever shape
|
|
181
172
|
* its body arrived in, so a body with no parsable modern `_meta` returns HTTP `400` + `-32602`.
|
|
182
173
|
* Headerless `initialize` is accepted, while every other headerless request needs a live legacy
|
|
183
174
|
* session to supply its pinned version. A legacy-shaped request carrying a protocol header is
|
|
@@ -219,14 +210,14 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
|
|
|
219
210
|
* hand to `router.add(...)`.
|
|
220
211
|
*
|
|
221
212
|
* @remarks
|
|
222
|
-
* A
|
|
213
|
+
* A single `POST {path}` route — `createMCPRoutes` is stateless. The handler reads its own
|
|
223
214
|
* request body (its own JSON parse try/catch), so it works with or without a session
|
|
224
215
|
* middleware mounted in front. It draws a sharp line between TRANSPORT-level and
|
|
225
216
|
* DISPATCH-level outcomes:
|
|
226
217
|
*
|
|
227
218
|
* - A **transport** failure — a malformed JSON body, or a parsed value that is not a
|
|
228
|
-
* JSON-RPC
|
|
229
|
-
* error / `-32600` Invalid Request), with the `id` it could not read
|
|
219
|
+
* JSON-RPC invocation — is an HTTP `400` carrying a JSON-RPC error body (`-32700` Parse
|
|
220
|
+
* error / `-32600` Invalid Request), with the `id` it could not read omitted.
|
|
230
221
|
* - Modern protocol/method/name headers are validated against the body; a mismatch is
|
|
231
222
|
* HTTP `400` + `-32020`. Headerless initialize is accepted, a live legacy session supplies
|
|
232
223
|
* its pinned revision, and every other headerless request is rejected.
|
|
@@ -238,16 +229,16 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
|
|
|
238
229
|
* When `streaming` is enabled (the default) and the client `Accept`s `text/event-stream`,
|
|
239
230
|
* the `200` reply is framed as a Streamable-HTTP SSE response (one `data:` event carrying
|
|
240
231
|
* the JSON-RPC envelope, then the stream ends) through `@orkestrel/server`'s generic
|
|
241
|
-
* {@link import('@orkestrel/server').
|
|
232
|
+
* {@link import('@orkestrel/server').createStream} seam; otherwise it is a plain JSON body.
|
|
242
233
|
*
|
|
243
|
-
* **Sessions are a
|
|
244
|
-
* session id. To make the transport
|
|
245
|
-
* import('./middlewares.js').createMCPSession}
|
|
234
|
+
* **Sessions are a separate, plug-and-play middleware.** `createMCPRoutes` mints / reads no
|
|
235
|
+
* session id. To make the transport stateful, mount {@link
|
|
236
|
+
* import('./middlewares.js').createMCPSession} in front — it owns the same `path`, mints +
|
|
246
237
|
* validates the `mcp-session-id`, and serves the resumable `GET {path}` + `DELETE {path}`,
|
|
247
238
|
* leaving this route to dispatch the validated `POST`.
|
|
248
239
|
*
|
|
249
|
-
* This is
|
|
250
|
-
*
|
|
240
|
+
* This is mechanism, not policy: compose auth / rate-limiting (and the session middleware)
|
|
241
|
+
* in front as ordinary middleware; the optional `origin` group carries the deployment's shared
|
|
251
242
|
* allowlist or explicitly delegates validation to an upstream layer.
|
|
252
243
|
*
|
|
253
244
|
* @typeParam TState - The consumer's opaque per-request state type
|
|
@@ -273,7 +264,7 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
|
|
|
273
264
|
* Creates the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer
|
|
274
265
|
* that fronts a session-agnostic {@link import('./factories.js').createMCPRoutes}. Compose it
|
|
275
266
|
* with `router.use(createMCPSession())` (or the equivalent middleware seam), mirroring any
|
|
276
|
-
* other closure-scoped stateful middleware. Has
|
|
267
|
+
* other closure-scoped stateful middleware. Has no dependency on `@orkestrel/middleware` — the
|
|
277
268
|
* session store, mint-on-`initialize`, and resumable stream are all native to this package.
|
|
278
269
|
*
|
|
279
270
|
* @remarks
|
|
@@ -286,39 +277,41 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
|
|
|
286
277
|
*
|
|
287
278
|
* - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route
|
|
288
279
|
* can re-read it from a freshly-built forwarded `Request`). Resolves a session through {@link
|
|
289
|
-
* readSessionHeader}: a
|
|
290
|
-
*
|
|
291
|
-
* isInitializeRequest})
|
|
292
|
-
* and sets `context.state.session`; neither → {@link rejectUnknownSession}
|
|
280
|
+
* readSessionHeader}: a valid id touches the entry and sets `context.state.session`; an
|
|
281
|
+
* absent / unknown id whose (guarded) body parses to an `initialize` request ({@link
|
|
282
|
+
* isInitializeRequest}) mints a fresh {@link MCPSession} (`crypto.randomUUID()`, the `session`
|
|
283
|
+
* options group) and sets `context.state.session`; neither → {@link rejectUnknownSession}
|
|
284
|
+
* (`404`). The
|
|
293
285
|
* minted entry pins the negotiated legacy revision, which is supplied to a later headerless
|
|
294
286
|
* live-session request. It then
|
|
295
|
-
*
|
|
287
|
+
* forwards a fresh `Request` carrying the buffered `text` (`next(forwarded)`) — never the
|
|
296
288
|
* already-consumed original — so the route re-reads the same body, and stamps the response
|
|
297
|
-
* with {@link MCP_SESSION_HEADER}. The entry's `touched` instant is read
|
|
298
|
-
* downstream response, because it means the
|
|
289
|
+
* with {@link MCP_SESSION_HEADER}. The entry's `touched` instant is read after that
|
|
290
|
+
* downstream response, because it means the last access: a request slower than `ttl` would
|
|
299
291
|
* otherwise store a session that is already expired, and the write-back RE-ASKS the store, so
|
|
300
292
|
* a `DELETE` arriving while the request was suspended is not undone.
|
|
301
293
|
* - **`GET {path}`.** Resolves the session the same way (no mint — only `initialize` mints);
|
|
302
294
|
* an invalid / unknown id is the same `404`. A valid session opens the resumable
|
|
303
|
-
* server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').
|
|
304
|
-
* replays every event after the client's `Last-Event-ID` ({@link readLastEventId})
|
|
295
|
+
* server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').createStream}:
|
|
296
|
+
* replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) before
|
|
305
297
|
* attaching the stream for live pushes, then attaches; cancellation of the streamed response
|
|
306
298
|
* body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.
|
|
307
299
|
* - **`DELETE {path}`.** Resolves the session; a valid id deletes it from the store and answers
|
|
308
300
|
* `204`; an invalid / unknown id is the same `404`.
|
|
309
301
|
*
|
|
310
|
-
* It is
|
|
302
|
+
* It is mechanism, not policy, and additive: omit it entirely for the stateless default
|
|
311
303
|
* ({@link import('./factories.js').createMCPRoutes}'s only behavior). The `path` MUST match the
|
|
312
304
|
* `createMCPRoutes` `path` it fronts. The WebSocket transport is inherently one session per
|
|
313
|
-
* connection (the socket
|
|
305
|
+
* connection (the socket is the session), so this middleware does not apply to it.
|
|
314
306
|
*
|
|
315
307
|
* @typeParam TState - The consumer's `TState`, which MUST extend {@link MCPSessionState} so
|
|
316
308
|
* the resolved session can be threaded through `context.state.session`
|
|
317
309
|
* @param options - Optional `path` (default {@link DEFAULT_MCP_PATH}), `ttl` (idle-session
|
|
318
|
-
* sweep window, ms — omit for sessions that live until an explicit `DELETE`), `
|
|
319
|
-
* (the
|
|
320
|
-
*
|
|
321
|
-
*
|
|
310
|
+
* sweep window, ms — omit for sessions that live until an explicit `DELETE`), `session`
|
|
311
|
+
* (the knobs each minted {@link MCPSession} takes — `capacity`, the log's own `ttl`, and its
|
|
312
|
+
* `clock`), and `clock` (the deterministic epoch-ms clock this middleware keeps its own
|
|
313
|
+
* bookkeeping on and hands down to a session that names none; defaults to `Date.now`), plus
|
|
314
|
+
* the shared `origin` validation options; see {@link MCPSessionMiddlewareOptions}
|
|
322
315
|
* @returns A {@link MiddlewareHandler} that mints / validates sessions + serves the resumable
|
|
323
316
|
* `GET` / `DELETE`
|
|
324
317
|
*
|
|
@@ -334,20 +327,11 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
|
|
|
334
327
|
* router.add(createMCPRoutes(createMCPLegacy(mcp))) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
335
328
|
* ```
|
|
336
329
|
*/
|
|
337
|
-
export declare function createMCPSession<TState extends MCPSessionState>(options?:
|
|
338
|
-
|
|
339
|
-
/**
|
|
340
|
-
* Creates a readable stream from its pull and cancellation behaviours.
|
|
341
|
-
*
|
|
342
|
-
* @param pull - The behaviour that supplies the stream's next chunk
|
|
343
|
-
* @param cancel - The behaviour that releases the stream after consumer cancellation
|
|
344
|
-
* @returns A readable stream backed by the supplied behaviours
|
|
345
|
-
*/
|
|
346
|
-
export declare function createReadableStream<T>(pull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>, cancel: (reason?: unknown) => void | PromiseLike<void>): ReadableStream<T>;
|
|
330
|
+
export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionMiddlewareOptions): MiddlewareHandler<TState>;
|
|
347
331
|
|
|
348
332
|
/**
|
|
349
|
-
* Creates the stdio
|
|
350
|
-
* — a {@link StdioClientTransportInterface} that spawns and drives a
|
|
333
|
+
* Creates the stdio client transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
334
|
+
* — a {@link StdioClientTransportInterface} that spawns and drives a child process MCP server
|
|
351
335
|
* over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
352
336
|
* createHTTPClientTransport} and {@link createWebSocketClientTransport}.
|
|
353
337
|
*
|
|
@@ -364,7 +348,7 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
364
348
|
* waits before the `send` rejects. An omitted `delivery` selects {@link
|
|
365
349
|
* import('./constants.js').DEFAULT_MCP_DELIVERY}; an explicit `0` removes the bound.
|
|
366
350
|
*
|
|
367
|
-
* @param options - `command` (the executable to spawn;
|
|
351
|
+
* @param options - `command` (the executable to spawn; required), optional `args`,
|
|
368
352
|
* optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
|
|
369
353
|
* `stdin` write; see {@link StdioClientTransportOptions}
|
|
370
354
|
* @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
|
|
@@ -385,7 +369,7 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
385
369
|
export declare function createStdioClientTransport(options: StdioClientTransportOptions): StdioClientTransportInterface;
|
|
386
370
|
|
|
387
371
|
/**
|
|
388
|
-
* Creates the MCP stdio transport
|
|
372
|
+
* Creates the MCP stdio transport ingress — pumps a transport-agnostic {@link
|
|
389
373
|
* MCPDispatcherInterface} over newline-delimited JSON-RPC on `stdin`/`stdout` (or an
|
|
390
374
|
* injected stream pair), the stdio mirror of {@link createWebSocketServer}.
|
|
391
375
|
*
|
|
@@ -393,9 +377,9 @@ export declare function createStdioClientTransport(options: StdioClientTransport
|
|
|
393
377
|
* Wraps `options.input` (default `process.stdin`) / `options.output` (default
|
|
394
378
|
* `process.stdout`) in a {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
|
|
395
379
|
* and pipes it through the core {@link import('@orkestrel/mcp').MCPTransportInterface} port
|
|
396
|
-
* through {@link
|
|
397
|
-
* import('@orkestrel/mcp').bindServer}: each inbound
|
|
398
|
-
* a defined response is written back as a newline-terminated line — a
|
|
380
|
+
* through {@link createDuplexServerTransport} + {@link
|
|
381
|
+
* import('@orkestrel/mcp').bindServer}: each inbound request runs through `mcp.dispatch`, and
|
|
382
|
+
* a defined response is written back as a newline-terminated line — a notification
|
|
399
383
|
* writes nothing, and a non-request message is ignored. A `dispatch` / `send` fault
|
|
400
384
|
* surfaces on `mcp.emitter`'s `error` event rather than escaping the (async) message
|
|
401
385
|
* pump.
|
|
@@ -420,8 +404,8 @@ export declare function createStdioClientTransport(options: StdioClientTransport
|
|
|
420
404
|
export declare function createStdioServer(mcp: MCPDispatcherInterface, options?: StdioServerOptions): StdioServerInterface;
|
|
421
405
|
|
|
422
406
|
/**
|
|
423
|
-
* Creates the WebSocket
|
|
424
|
-
* — a {@link
|
|
407
|
+
* Creates the WebSocket client transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
408
|
+
* — a {@link MCPMessageTransportInterface} that drives a remote MCP server over a WebSocket. The
|
|
425
409
|
* egress mirror of {@link createWebSocketServer} and the WebSocket sibling of {@link
|
|
426
410
|
* createHTTPClientTransport}.
|
|
427
411
|
*
|
|
@@ -435,9 +419,9 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
|
|
|
435
419
|
* surfaced on the transport's `message` event for the client's id correlation. Add
|
|
436
420
|
* `options.headers` (for example, an `Authorization` bearer) to reach a guarded server.
|
|
437
421
|
*
|
|
438
|
-
* @param options - `url` (the remote WebSocket endpoint;
|
|
422
|
+
* @param options - `url` (the remote WebSocket endpoint; required) and optional `headers`
|
|
439
423
|
* merged onto the upgrade request; see {@link WebSocketClientTransportOptions}
|
|
440
|
-
* @returns A working {@link
|
|
424
|
+
* @returns A working {@link MCPMessageTransportInterface} over a WebSocket
|
|
441
425
|
*
|
|
442
426
|
* @example
|
|
443
427
|
* ```ts
|
|
@@ -451,10 +435,10 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
|
|
|
451
435
|
* const tools = await client.tools()
|
|
452
436
|
* ```
|
|
453
437
|
*/
|
|
454
|
-
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions):
|
|
438
|
+
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPMessageTransportInterface;
|
|
455
439
|
|
|
456
440
|
/**
|
|
457
|
-
* Creates the MCP WebSocket transport
|
|
441
|
+
* Creates the MCP WebSocket transport ingress — an {@link UpgradeHandler} that exposes a
|
|
458
442
|
* transport-agnostic {@link MCPDispatcherInterface} over a WebSocket, the WebSocket mirror of
|
|
459
443
|
* {@link createMCPRoutes}. Register it on the spine's upgrade seam.
|
|
460
444
|
*
|
|
@@ -466,16 +450,16 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
466
450
|
* socket to the next handler (or destroys an unclaimed one): the `Upgrade` header is not
|
|
467
451
|
* `websocket`, the request path is not `options.path` (default {@link DEFAULT_MCP_PATH},
|
|
468
452
|
* `'/mcp'`), the `Sec-WebSocket-Key` is absent, or the `Sec-WebSocket-Version` is not `13`.
|
|
469
|
-
* A decline
|
|
453
|
+
* A decline never writes to the socket (it is not yet ours) — the spine owns the unclaimed
|
|
470
454
|
* outcome.
|
|
471
455
|
* - **Claims (returns `true`)** otherwise: it builds `createNodeWebSocket({ socket, key, head,
|
|
472
456
|
* protocol })` (SERVER mode → writes the `101` handshake, selects the configured subprotocol
|
|
473
|
-
* only when the client's offer contains it, and sends
|
|
457
|
+
* only when the client's offer contains it, and sends unmasked frames), wraps it in a
|
|
474
458
|
* {@link WebSocketServerTransport}, and pipes it through the core {@link
|
|
475
459
|
* import('@orkestrel/mcp').MCPTransportInterface} port through {@link
|
|
476
|
-
*
|
|
477
|
-
* each inbound
|
|
478
|
-
* as a frame — a
|
|
460
|
+
* createDuplexServerTransport} + {@link import('@orkestrel/mcp').bindServer}:
|
|
461
|
+
* each inbound request runs through `mcp.dispatch`, and a defined response is written back
|
|
462
|
+
* as a frame — a notification sends nothing, and a non-request message (a stray response) is
|
|
479
463
|
* ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
|
|
480
464
|
* escaping the (async) message pump.
|
|
481
465
|
* - **Closes on the spine's `stop`.** It holds every socket it claimed and, on `options.emitter`'s
|
|
@@ -486,12 +470,12 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
486
470
|
* then have the connection cut mid-protocol. A socket the peer already dropped is gone from
|
|
487
471
|
* the set (its transport's `close` removes it), and closing a dead one is a no-op either way.
|
|
488
472
|
*
|
|
489
|
-
* It is
|
|
490
|
-
* handler
|
|
473
|
+
* It is mechanism, not policy: compose an auth guard in front by registering an upgrade
|
|
474
|
+
* handler before this one — that handler can claim (decline + destroy) an unauthenticated
|
|
491
475
|
* upgrade so it never reaches this pump.
|
|
492
476
|
*
|
|
493
477
|
* @param mcp - The transport-agnostic {@link MCPDispatcherInterface} to expose over WebSocket
|
|
494
|
-
* @param options - The spine's `emitter` (
|
|
478
|
+
* @param options - The spine's `emitter` (required — the `stop` event this ingress closes its
|
|
495
479
|
* sockets on), plus optional `path` (default {@link DEFAULT_MCP_PATH}) and `subprotocol`
|
|
496
480
|
* (default {@link MCP_WEBSOCKET_SUBPROTOCOL}); see {@link WebSocketServerOptions}
|
|
497
481
|
* @returns An {@link UpgradeHandler} to register with the spine's `upgrade` seam
|
|
@@ -510,26 +494,12 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
510
494
|
export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options: WebSocketServerOptions): UpgradeHandler;
|
|
511
495
|
|
|
512
496
|
/**
|
|
513
|
-
*
|
|
514
|
-
* when it is not one — the per-event step {@link readEventStream} folds over.
|
|
515
|
-
*
|
|
516
|
-
* @remarks
|
|
517
|
-
* `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the event's
|
|
518
|
-
* `data`) inside a try/catch and narrows the parsed value with `parseJSONRPCMessage`.
|
|
519
|
-
* Total: malformed JSON or a non-message value yields `undefined`, never throws.
|
|
520
|
-
*
|
|
521
|
-
* @param data - One SSE event's `data` payload
|
|
522
|
-
* @returns The decoded {@link JSONRPCMessage}, or `undefined`
|
|
523
|
-
*/
|
|
524
|
-
export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* The default bound in milliseconds on one unconfirmed write to a stdio client transport's
|
|
497
|
+
* Sets the default bound in milliseconds on one unconfirmed write to a stdio client transport's
|
|
528
498
|
* child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.
|
|
529
499
|
*
|
|
530
500
|
* @remarks
|
|
531
501
|
* Ten seconds. The load-bearing property is the ordering, not the magnitude: this bound stays
|
|
532
|
-
*
|
|
502
|
+
* below {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never
|
|
533
503
|
* reads fails as an undeliverable message while the request that carried it is still open,
|
|
534
504
|
* rather than being masked by that request's own deadline expiring first. Override per
|
|
535
505
|
* transport with `delivery`; an explicit `0` there removes the bound.
|
|
@@ -537,7 +507,7 @@ export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
|
|
|
537
507
|
export declare const DEFAULT_MCP_DELIVERY = 10000;
|
|
538
508
|
|
|
539
509
|
/**
|
|
540
|
-
*
|
|
510
|
+
* Sets the default interval in milliseconds between SSE keepalive comments on held-open MCP
|
|
541
511
|
* responses.
|
|
542
512
|
*
|
|
543
513
|
* @remarks
|
|
@@ -546,24 +516,24 @@ export declare const DEFAULT_MCP_DELIVERY = 10000;
|
|
|
546
516
|
*/
|
|
547
517
|
export declare const DEFAULT_MCP_KEEPALIVE_INTERVAL = 15000;
|
|
548
518
|
|
|
549
|
-
/**
|
|
519
|
+
/** Names the default request path `createMCPRoutes` mounts the transport's `POST` route at. */
|
|
550
520
|
export declare const DEFAULT_MCP_PATH = "/mcp";
|
|
551
521
|
|
|
552
522
|
/**
|
|
553
|
-
*
|
|
523
|
+
* Sets the default capacity of a session's folded resumable event log (the per-{@link
|
|
554
524
|
* import('./MCPSession.js').MCPSession} replay log) — the maximum number of pushed
|
|
555
|
-
* server→client messages retained for replay before the
|
|
525
|
+
* server→client messages retained for replay before the oldest is evicted.
|
|
556
526
|
*
|
|
557
527
|
* @remarks
|
|
558
528
|
* Bounds the replay log's memory: only the most-recent {@link DEFAULT_MCP_SESSION_CAPACITY}
|
|
559
529
|
* pushes are retained, so a client reconnecting with a `Last-Event-ID` older than that window
|
|
560
|
-
* replays nothing (its cursor fell off the back). Override
|
|
561
|
-
* for a deeper / shallower window.
|
|
530
|
+
* replays nothing (its cursor fell off the back). Override through the `session` group of
|
|
531
|
+
* `createMCPSession`'s options (`session.capacity`) for a deeper / shallower window.
|
|
562
532
|
*/
|
|
563
533
|
export declare const DEFAULT_MCP_SESSION_CAPACITY = 1024;
|
|
564
534
|
|
|
565
535
|
/**
|
|
566
|
-
*
|
|
536
|
+
* Sets the default per-event idle lifetime (ms) of a session's folded resumable event log — an
|
|
567
537
|
* entry older than this is lazily evicted on the next access (no background timer), bounding
|
|
568
538
|
* how far back a reconnecting client may replay.
|
|
569
539
|
*
|
|
@@ -576,41 +546,22 @@ export declare const DEFAULT_MCP_SESSION_TTL = 300000;
|
|
|
576
546
|
|
|
577
547
|
/**
|
|
578
548
|
* Decodes and delivers each complete newline-framed line onto a {@link
|
|
579
|
-
*
|
|
549
|
+
* MCPMessageTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
|
|
580
550
|
* transports run their framed lines through: the server transport frames with {@link
|
|
581
551
|
* extractLines}, the client transport takes its lines from the process supervisor.
|
|
582
552
|
*
|
|
583
553
|
* @remarks
|
|
584
|
-
* A blank line is skipped (a stray trailing newline). Every other line
|
|
585
|
-
*
|
|
586
|
-
* well-formed {@link JSONRPCMessage} emits `message`,
|
|
587
|
-
*
|
|
588
|
-
*
|
|
554
|
+
* A blank line is skipped (a stray trailing newline). Every other line runs through the
|
|
555
|
+
* shared {@link import('@orkestrel/mcp').deliverMessage} fold, the one inbound decode every
|
|
556
|
+
* transport in this package shares: a well-formed {@link JSONRPCMessage} emits `message`,
|
|
557
|
+
* unparsable text emits the caught parse error, and a well-formed non-message line emits
|
|
558
|
+
* `error` naming a non-JSON-RPC stdio line (total, never throws). Pure w.r.t. its own state
|
|
559
|
+
* — the emit is the caller-owned side effect.
|
|
589
560
|
*
|
|
590
561
|
* @param emitter - The transport's {@link EmitterInterface} to emit `message` / `error` onto
|
|
591
562
|
* @param lines - The complete lines to decode and deliver
|
|
592
563
|
*/
|
|
593
|
-
export declare function dispatchLines(emitter: EmitterInterface<
|
|
594
|
-
|
|
595
|
-
/**
|
|
596
|
-
* One entry of an {@link MCPSessionInterface}'s folded replay log — a single pushed {@link
|
|
597
|
-
* JSONRPCMessage} tagged with the monotone event `id` the session assigned and the `timestamp`
|
|
598
|
-
* it was appended at (for the lazy-TTL replay window).
|
|
599
|
-
*
|
|
600
|
-
* @remarks
|
|
601
|
-
* - `id` — the session-assigned, monotonically-increasing event id (a base36 string), the
|
|
602
|
-
* value a resumable client echoes back as its `Last-Event-ID` to replay from here.
|
|
603
|
-
* - `message` — the server→client {@link JSONRPCMessage} that was pushed.
|
|
604
|
-
* - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
|
|
605
|
-
*
|
|
606
|
-
* A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
|
|
607
|
-
* returns.
|
|
608
|
-
*/
|
|
609
|
-
export declare interface EventStoreEntry {
|
|
610
|
-
readonly id: string;
|
|
611
|
-
readonly message: JSONRPCMessage;
|
|
612
|
-
readonly timestamp: number;
|
|
613
|
-
}
|
|
564
|
+
export declare function dispatchLines(emitter: EmitterInterface<MCPMessageTransportEventMap>, lines: readonly string[]): void;
|
|
614
565
|
|
|
615
566
|
/**
|
|
616
567
|
* Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared
|
|
@@ -619,7 +570,7 @@ export declare interface EventStoreEntry {
|
|
|
619
570
|
*
|
|
620
571
|
* @remarks
|
|
621
572
|
* Concatenates `buffer` (the carried-forward partial line from the previous call)
|
|
622
|
-
* with `chunk`, splits on `'\n'`, and returns every
|
|
573
|
+
* with `chunk`, splits on `'\n'`, and returns every complete line (a `'\r'` trailing
|
|
623
574
|
* a line, from a CRLF-framed peer, is trimmed) plus the final, possibly-empty
|
|
624
575
|
* fragment as the new `remainder` — the caller threads it back in as the next call's
|
|
625
576
|
* `buffer`. A chunk containing no `'\n'` yields no lines and the whole (buffer +
|
|
@@ -631,118 +582,26 @@ export declare interface EventStoreEntry {
|
|
|
631
582
|
*/
|
|
632
583
|
export declare function extractLines(buffer: string, chunk: string): LineExtraction;
|
|
633
584
|
|
|
634
|
-
/**
|
|
635
|
-
* The HTTP CLIENT transport for the Model Context Protocol — a
|
|
636
|
-
* {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server over
|
|
637
|
-
* `fetch`, the egress mirror of the server's `createMCPRoutes`.
|
|
638
|
-
*
|
|
639
|
-
* @remarks
|
|
640
|
-
* - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
|
|
641
|
-
* message to `options.url` with `content-type: application/json` and an
|
|
642
|
-
* `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
|
|
643
|
-
* answer with either framing) — plus any `options.headers` (for example, an `Authorization`
|
|
644
|
-
* bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
|
|
645
|
-
* the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
|
|
646
|
-
* to.
|
|
647
|
-
* - **Both reply framings.** A `200` with an `application/json` body is parsed with
|
|
648
|
-
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
|
|
649
|
-
* `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
|
|
650
|
-
* readEventStream}) — the inverse of the server's `openStream` seam, so the wire
|
|
651
|
-
* round-trips. A `202`
|
|
652
|
-
* Accepted (a notification) carries no body and emits nothing.
|
|
653
|
-
* - **Session and protocol headers.** `start()` is a no-op (a
|
|
654
|
-
* request/response transport opens no long-lived connection). The
|
|
655
|
-
* `mcp-session-id` response header, when a STATEFUL server sends one (on
|
|
656
|
-
* `initialize`), is captured into `session` and then ECHOED as the
|
|
657
|
-
* `mcp-session-id` request header on every SUBSEQUENT request — so an
|
|
658
|
-
* `MCPClient` passes a stateful server's session validation. The
|
|
659
|
-
* initialize result's `protocolVersion` is likewise captured, but only
|
|
660
|
-
* when it is a SUPPORTED value, and echoed as `mcp-protocol-version` alone on
|
|
661
|
-
* subsequent legacy requests. Modern requests instead derive protocol and method
|
|
662
|
-
* headers from the message, plus the name header only for `tools/call` — carried in the
|
|
663
|
-
* protocol's Base64 sentinel form whenever the tool name cannot ride as plain ASCII.
|
|
664
|
-
* Before initialize returns, neither captured legacy header is sent.
|
|
665
|
-
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
666
|
-
* POST is headerless; the captured `session` persists across `close()`.
|
|
667
|
-
* - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
|
|
668
|
-
* ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
|
|
669
|
-
* never ends would otherwise outlive the transport, with nothing left able to reach it. The
|
|
670
|
-
* aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
|
|
671
|
-
* idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
|
|
672
|
-
* - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
|
|
673
|
-
* the SSE decoder). A non-message success reply is dropped, never asserted. A non-success
|
|
674
|
-
* reply that carries no valid JSON-RPC message rejects `send` with its HTTP status and body
|
|
675
|
-
* shape. A valid JSON-RPC error body is emitted at any HTTP status. A `fetch` / decode failure
|
|
676
|
-
* on a success response surfaces on the `error` event rather than escaping `send`.
|
|
677
|
-
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
|
|
678
|
-
* `message` per decoded reply, `error` on a fault, and `close` on `close()`.
|
|
679
|
-
*
|
|
680
|
-
* @example
|
|
681
|
-
* ```ts
|
|
682
|
-
* const transport = new HTTPClientTransport({ url: 'http://localhost:3000/mcp' })
|
|
683
|
-
* const client = new MCPClient({ transport })
|
|
684
|
-
* await client.connect()
|
|
685
|
-
* ```
|
|
686
|
-
*/
|
|
687
|
-
export declare class HTTPClientTransport implements MCPClientTransportInterface_2 {
|
|
688
|
-
#private;
|
|
689
|
-
constructor(options: HTTPClientTransportOptions);
|
|
690
|
-
get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
|
|
691
|
-
get session(): string | undefined;
|
|
692
|
-
get duplex(): boolean;
|
|
693
|
-
start(): Promise<void>;
|
|
694
|
-
send(message: JSONRPCMessage_2): Promise<void>;
|
|
695
|
-
close(): Promise<void>;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
/**
|
|
699
|
-
* Options for `createHTTPClientTransport` — the remote MCP server's URL and any extra
|
|
700
|
-
* request headers.
|
|
701
|
-
*
|
|
702
|
-
* @remarks
|
|
703
|
-
* - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
|
|
704
|
-
* `POST` target every JSON-RPC message is written to, for example,
|
|
705
|
-
* `http://localhost:3000/mcp`). REQUIRED.
|
|
706
|
-
* - `headers` — extra request headers merged onto every `POST` (for example, an
|
|
707
|
-
* `Authorization` bearer for a guarded server). The transport always sets
|
|
708
|
-
* `content-type: application/json` and an `Accept` of both `application/json` and
|
|
709
|
-
* `text/event-stream` (so the server may answer with either framing); a key supplied
|
|
710
|
-
* here is merged on top.
|
|
711
|
-
* - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
|
|
712
|
-
* `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
|
|
713
|
-
* - `timeout` — an optional per-request timeout in milliseconds; when set, each
|
|
714
|
-
* `fetch` call composes that deadline with the transport's own close through
|
|
715
|
-
* `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
|
|
716
|
-
* ends the request. Omit for no transport-level deadline; the close signal is passed
|
|
717
|
-
* either way.
|
|
718
|
-
*/
|
|
719
|
-
export declare interface HTTPClientTransportOptions {
|
|
720
|
-
readonly url: string;
|
|
721
|
-
readonly headers?: Readonly<Record<string, string>>;
|
|
722
|
-
readonly fetch?: typeof fetch;
|
|
723
|
-
readonly timeout?: number;
|
|
724
|
-
}
|
|
725
|
-
|
|
726
585
|
/**
|
|
727
586
|
* Composes one incoming HTTP request lifetime with one MCP-owned SSE response lifetime.
|
|
728
587
|
*
|
|
729
588
|
* @remarks
|
|
730
|
-
* The composed {@link signal} observes request abort and
|
|
589
|
+
* The composed {@link signal} observes request abort and every way this response can end
|
|
731
590
|
* without one: consumer cancellation of the bridged body, a forwarding failure mid-pump, and a
|
|
732
591
|
* keepalive tick that finds the SSE stream already closed. That last pair is the whole point of
|
|
733
592
|
* the composition — a client that vanishes mid-stream aborts nothing by itself, so unless this
|
|
734
593
|
* object raises the signal on its own failure paths, the handler, the controlled stream, and
|
|
735
594
|
* the producer behind them all keep running for a response that can no longer be written.
|
|
736
|
-
* Graceful upstream completion is the one terminal that does
|
|
595
|
+
* Graceful upstream completion is the one terminal that does not abort: the body closes,
|
|
737
596
|
* because the exchange finished rather than ended.
|
|
738
597
|
*
|
|
739
598
|
* {@link bridge} preserves the source response status and headers, forwards its body bytes, and
|
|
740
599
|
* owns keepalive comments plus listener/timer cleanup until upstream completion, request abort,
|
|
741
600
|
* or consumer cancellation. This is a single-response lifecycle object, not a reusable bridge:
|
|
742
|
-
* a second {@link bridge} call
|
|
601
|
+
* a second {@link bridge} call throws rather than arming a second keepalive over one lifecycle.
|
|
743
602
|
* It supplies no handler or session policy.
|
|
744
603
|
*
|
|
745
|
-
* The keepalive interval is a
|
|
604
|
+
* The keepalive interval is a budget, sanitized like every other numeric knob in this package:
|
|
746
605
|
* anything that is not a positive integer — `0`, a negative, a fractional value, `NaN`,
|
|
747
606
|
* `Infinity` — falls back to {@link DEFAULT_MCP_KEEPALIVE_INTERVAL}, and a larger value clamps
|
|
748
607
|
* to Node's `2_147_483_647` ms timer maximum. None may reach the platform's timer floor, where
|
|
@@ -751,10 +610,10 @@ export declare interface HTTPClientTransportOptions {
|
|
|
751
610
|
* @example
|
|
752
611
|
* ```ts
|
|
753
612
|
* import { HTTPDisconnect } from '@orkestrel/mcp/server'
|
|
754
|
-
* import {
|
|
613
|
+
* import { createStream } from '@orkestrel/server'
|
|
755
614
|
*
|
|
756
615
|
* const disconnect = new HTTPDisconnect(request.signal, { interval: 15_000 })
|
|
757
|
-
* const stream =
|
|
616
|
+
* const stream = createStream()
|
|
758
617
|
* const response = disconnect.bridge(stream)
|
|
759
618
|
* ```
|
|
760
619
|
*/
|
|
@@ -769,8 +628,8 @@ export declare class HTTPDisconnect {
|
|
|
769
628
|
*/
|
|
770
629
|
constructor(signal: AbortSignal, options?: MCPKeepaliveOptions);
|
|
771
630
|
/**
|
|
772
|
-
*
|
|
773
|
-
* its graceful completion.
|
|
631
|
+
* Returns the signal aborted by the incoming request, or by any end of this response that
|
|
632
|
+
* is not its graceful completion.
|
|
774
633
|
*
|
|
775
634
|
* @returns The composed lifecycle signal
|
|
776
635
|
*/
|
|
@@ -795,11 +654,11 @@ export declare class HTTPDisconnect {
|
|
|
795
654
|
* Options shared by the MCP Streamable-HTTP POST handler and route factory.
|
|
796
655
|
*
|
|
797
656
|
* @remarks
|
|
798
|
-
* - `streaming` — when `true` (the
|
|
657
|
+
* - `streaming` — when `true` (the default) the transport MAY answer with a
|
|
799
658
|
* Server-Sent-Events response (one `data:` event carrying the JSON-RPC reply, then
|
|
800
659
|
* the stream ends) whenever the client's `Accept` header includes
|
|
801
660
|
* `text/event-stream`; when `false` it always answers with a plain JSON body. Either
|
|
802
|
-
* mode carries the
|
|
661
|
+
* mode carries the same JSON-RPC response envelope — the choice is purely the wire
|
|
803
662
|
* framing the Streamable-HTTP spec lets the client negotiate.
|
|
804
663
|
* - `origin` — the shared origin-validation options passed to both the route and session
|
|
805
664
|
* enforcement sites. Validation is enabled by default: requests without `Origin` pass,
|
|
@@ -816,7 +675,7 @@ export declare class HTTPDisconnect {
|
|
|
816
675
|
*/
|
|
817
676
|
export declare interface HTTPHandlerOptions<TState = unknown> {
|
|
818
677
|
readonly streaming?: boolean;
|
|
819
|
-
/**
|
|
678
|
+
/** Requires the session layer's value; `origins` is ignored when `enabled` is `false`. */
|
|
820
679
|
readonly origin?: MCPOriginOptions;
|
|
821
680
|
readonly keepalive?: MCPKeepaliveOptions;
|
|
822
681
|
readonly caller?: MCPCallerHandler<TState>;
|
|
@@ -824,7 +683,7 @@ export declare interface HTTPHandlerOptions<TState = unknown> {
|
|
|
824
683
|
|
|
825
684
|
/**
|
|
826
685
|
* Options for `createMCPRoutes` — the mount path plus the shared POST-handler options.
|
|
827
|
-
* `createMCPRoutes` is
|
|
686
|
+
* `createMCPRoutes` is stateless; sessions are a separate middleware ({@link
|
|
828
687
|
* import('./middlewares.js').createMCPSession}), composed with `server.use`.
|
|
829
688
|
*
|
|
830
689
|
* @remarks
|
|
@@ -839,7 +698,7 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
|
|
|
839
698
|
}
|
|
840
699
|
|
|
841
700
|
/**
|
|
842
|
-
* Infers the first required MCP HTTP header
|
|
701
|
+
* Infers the first required MCP HTTP header a request's own body contradicts.
|
|
843
702
|
*
|
|
844
703
|
* @remarks
|
|
845
704
|
* A modern request derives its protocol, method, and name expectations from the JSON-RPC body,
|
|
@@ -848,12 +707,14 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
|
|
|
848
707
|
* {@link import('@orkestrel/mcp').decodeSentinel} before the comparison, so a peer that had
|
|
849
708
|
* to encode its value still matches; a sentinel whose payload is invalid decodes to nothing
|
|
850
709
|
* and therefore mismatches, which is how an invalid header value is refused. A legacy request
|
|
851
|
-
* body requires a protocol header after initialization
|
|
852
|
-
*
|
|
853
|
-
*
|
|
710
|
+
* body requires a protocol header after initialization. Messages name the expected value but
|
|
711
|
+
* never echo the client-supplied one.
|
|
712
|
+
*
|
|
713
|
+
* The expectation a live session supplies is a different rule over a different input, so it
|
|
714
|
+
* is {@link inferSessionHeaderIssue} rather than a second arm of this one.
|
|
854
715
|
*
|
|
855
716
|
* @param request - The HTTP request carrying the headers
|
|
856
|
-
* @param
|
|
717
|
+
* @param invocation - The parsed invocation body the expectations are derived from
|
|
857
718
|
* @returns The first header issue, or `undefined` when the applicable headers agree
|
|
858
719
|
*
|
|
859
720
|
* @example
|
|
@@ -862,7 +723,7 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
|
|
|
862
723
|
* issue?.header // 'Mcp-Method' when that field is absent or mismatched
|
|
863
724
|
* ```
|
|
864
725
|
*/
|
|
865
|
-
export declare function inferHeaderIssue(request: Request,
|
|
726
|
+
export declare function inferHeaderIssue(request: Request, invocation: JSONRPCInvocation): MCPHeaderIssue | undefined;
|
|
866
727
|
|
|
867
728
|
/**
|
|
868
729
|
* Infers the target one modern request's `Mcp-Name` header must carry.
|
|
@@ -894,7 +755,7 @@ export declare function inferHeaderTarget(request: JSONRPCInvocation): string |
|
|
|
894
755
|
*
|
|
895
756
|
* @remarks
|
|
896
757
|
* A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
|
|
897
|
-
* request selects the newest supported legacy revision. The read is deliberately the
|
|
758
|
+
* request selects the newest supported legacy revision. The read is deliberately the same one
|
|
898
759
|
* {@link import('@orkestrel/mcp').buildInitializeResult} performs — `isMCPLegacyVersion` over
|
|
899
760
|
* the requested revision — because the session version this pins and the version that result
|
|
900
761
|
* echoes must be the one value. Routing through `inferVersion` cannot do it: that inferer is
|
|
@@ -912,8 +773,8 @@ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegac
|
|
|
912
773
|
*
|
|
913
774
|
* @remarks
|
|
914
775
|
* The custom-header half of the standard-header seam {@link inferHeaderIssue} owns, and it
|
|
915
|
-
* takes the
|
|
916
|
-
* rule to the `Mcp-Param-*` names the server's
|
|
776
|
+
* takes the served definition's projections rather than a header issue: SEP-2243 scopes the
|
|
777
|
+
* rule to the `Mcp-Param-*` names the server's own tool definitions annotate, so a name no
|
|
917
778
|
* parameter claims is another party's header and travels through untouched.
|
|
918
779
|
*
|
|
919
780
|
* For each recognized parameter the body's value at the parameter's own property path fixes
|
|
@@ -942,6 +803,29 @@ export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPLegac
|
|
|
942
803
|
*/
|
|
943
804
|
export declare function inferParameterRefusal(request: Request, parameters: readonly MCPHeaderParameter[], values: unknown): string | undefined;
|
|
944
805
|
|
|
806
|
+
/**
|
|
807
|
+
* Infers the protocol header issue an active legacy session's pinned revision diagnoses.
|
|
808
|
+
*
|
|
809
|
+
* @remarks
|
|
810
|
+
* The session layer's rule, distinct from the body-derived one {@link inferHeaderIssue} owns:
|
|
811
|
+
* a live legacy session pinned its revision at `initialize`, so every later request on that
|
|
812
|
+
* session must name the same one. An absent header reads as `missing`, which the session
|
|
813
|
+
* middleware answers by supplying the pinned revision rather than refusing; a present header
|
|
814
|
+
* naming another revision reads as `mismatched` and is refused. The message names the session's
|
|
815
|
+
* revision and never echoes the client-supplied value.
|
|
816
|
+
*
|
|
817
|
+
* @param request - The HTTP request carrying the headers
|
|
818
|
+
* @param version - The legacy revision the active session pinned at `initialize`
|
|
819
|
+
* @returns The protocol header issue, or `undefined` when the header agrees
|
|
820
|
+
*
|
|
821
|
+
* @example
|
|
822
|
+
* ```ts
|
|
823
|
+
* const issue = inferSessionHeaderIssue(request, '2025-06-18')
|
|
824
|
+
* issue?.reason // 'missing' when the request carries no protocol header
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
export declare function inferSessionHeaderIssue(request: Request, version: MCPVersion): MCPHeaderIssue | undefined;
|
|
828
|
+
|
|
945
829
|
/**
|
|
946
830
|
* Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
|
|
947
831
|
*
|
|
@@ -958,8 +842,8 @@ export declare function inferParameterRefusal(request: Request, parameters: read
|
|
|
958
842
|
export declare function inferStatus(response: JSONRPCResponse | undefined, era: MCPEra): number;
|
|
959
843
|
|
|
960
844
|
/**
|
|
961
|
-
*
|
|
962
|
-
* buffer — every
|
|
845
|
+
* Represents the result of folding one more chunk of raw stdio bytes into a newline-framed
|
|
846
|
+
* buffer — every complete line extracted (newline-terminated in the wire bytes) plus
|
|
963
847
|
* the trailing partial line carried forward as the new `remainder`.
|
|
964
848
|
*
|
|
965
849
|
* @remarks
|
|
@@ -971,44 +855,6 @@ export declare interface LineExtraction {
|
|
|
971
855
|
readonly remainder: string;
|
|
972
856
|
}
|
|
973
857
|
|
|
974
|
-
/** The modern Streamable-HTTP request header carrying the JSON-RPC method name. */
|
|
975
|
-
export declare const MCP_METHOD_HEADER = "mcp-method";
|
|
976
|
-
|
|
977
|
-
/** The modern Streamable-HTTP request header carrying a named method's target. */
|
|
978
|
-
export declare const MCP_NAME_HEADER = "mcp-name";
|
|
979
|
-
|
|
980
|
-
/**
|
|
981
|
-
* The Streamable-HTTP transport header carrying the negotiated MCP protocol version
|
|
982
|
-
* on every post-initialize client request.
|
|
983
|
-
*
|
|
984
|
-
* @remarks
|
|
985
|
-
* Required by MCP 2025-06-18 after initialization. Both HTTP client transports
|
|
986
|
-
* capture the initialize result's `protocolVersion` and send it on subsequent
|
|
987
|
-
* requests; `createMCPRoutes` rejects a present unsupported value before dispatch.
|
|
988
|
-
*/
|
|
989
|
-
export declare const MCP_PROTOCOL_VERSION_HEADER = "mcp-protocol-version";
|
|
990
|
-
|
|
991
|
-
/**
|
|
992
|
-
* The Streamable-HTTP transport header that carries the MCP session id. When a {@link
|
|
993
|
-
* import('./middlewares.js').createMCPSession} middleware is mounted, it SETS this header on
|
|
994
|
-
* the `initialize` response (the minted id) and READS it on every subsequent request
|
|
995
|
-
* (validating the session); the stateless `createMCPRoutes` default neither sets nor reads it.
|
|
996
|
-
*/
|
|
997
|
-
export declare const MCP_SESSION_HEADER = "mcp-session-id";
|
|
998
|
-
|
|
999
|
-
/**
|
|
1000
|
-
* The WebSocket subprotocol the MCP-over-WebSocket transports negotiate — sent by the
|
|
1001
|
-
* client in `Sec-WebSocket-Protocol`, echoed by the server in its `101` handshake.
|
|
1002
|
-
*
|
|
1003
|
-
* @remarks
|
|
1004
|
-
* `createWebSocketServer` echoes it in the upgrade response and `createWebSocketClientTransport`
|
|
1005
|
-
* requests it, so an MCP WebSocket endpoint is distinguishable from any other WebSocket on the
|
|
1006
|
-
* same path. The default WebSocket upgrade path is {@link DEFAULT_MCP_PATH} (the same `'/mcp'`
|
|
1007
|
-
* the HTTP transport mounts at) — the upgrade is selected by the `Upgrade: websocket` header,
|
|
1008
|
-
* not a separate path.
|
|
1009
|
-
*/
|
|
1010
|
-
export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
1011
|
-
|
|
1012
858
|
/**
|
|
1013
859
|
* Extracts consumer-asserted caller context synchronously from an HTTP request after the
|
|
1014
860
|
* transport has validated it for dispatch.
|
|
@@ -1027,7 +873,8 @@ export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
|
1027
873
|
export declare type MCPCallerHandler<TState = unknown> = (request: Request, context: RouteContext<string, TState> | undefined) => unknown;
|
|
1028
874
|
|
|
1029
875
|
/**
|
|
1030
|
-
*
|
|
876
|
+
* Reports one required MCP HTTP header that is absent or disagrees with its server-derived
|
|
877
|
+
* value.
|
|
1031
878
|
*
|
|
1032
879
|
* @remarks
|
|
1033
880
|
* - `header` — the canonical HTTP field name safe to show to an integrator.
|
|
@@ -1042,7 +889,7 @@ export declare interface MCPHeaderIssue {
|
|
|
1042
889
|
}
|
|
1043
890
|
|
|
1044
891
|
/**
|
|
1045
|
-
*
|
|
892
|
+
* Configures the shared SSE keepalive for held-open HTTP responses.
|
|
1046
893
|
*
|
|
1047
894
|
* @remarks
|
|
1048
895
|
* - `interval` — milliseconds between SSE comment frames. Defaults to {@link
|
|
@@ -1055,7 +902,7 @@ export declare interface MCPKeepaliveOptions {
|
|
|
1055
902
|
}
|
|
1056
903
|
|
|
1057
904
|
/**
|
|
1058
|
-
*
|
|
905
|
+
* Configures the protocol-required HTTP `Origin` validation shared by the route and session
|
|
1059
906
|
* enforcement sites.
|
|
1060
907
|
*
|
|
1061
908
|
* @remarks
|
|
@@ -1071,41 +918,41 @@ export declare interface MCPOriginOptions {
|
|
|
1071
918
|
}
|
|
1072
919
|
|
|
1073
920
|
/**
|
|
1074
|
-
*
|
|
921
|
+
* Represents one MCP transport session — the per-session entity a {@link
|
|
1075
922
|
* import('./middlewares.js').createMCPSession} middleware owns, keyed by its `id`, carrying the
|
|
1076
|
-
* resumable server→client push channel with its bounded replay log
|
|
923
|
+
* resumable server→client push channel with its bounded replay log folded in.
|
|
1077
924
|
*
|
|
1078
925
|
* @remarks
|
|
1079
|
-
*
|
|
1080
|
-
* session `id`, its
|
|
926
|
+
* One entity carries the whole session: it holds the
|
|
927
|
+
* session `id`, its own bounded, replayable log of pushed server→client messages (the
|
|
1081
928
|
* resumable GET-SSE channel — a private `#events` `Map` + a monotone `#counter`, with
|
|
1082
929
|
* `capacity` / `ttl` eviction, not a separate store), and the set of open
|
|
1083
930
|
* server→client SSE streams (a resumable `GET {path}` registers through `attach`, unregisters through
|
|
1084
931
|
* `detach` on disconnect). Still a small entity (not a record), built minimal + extensible.
|
|
1085
932
|
*
|
|
1086
|
-
* - **`push` is the server-initiated primitive.** It
|
|
1087
|
-
* a monotone base36 event id) and
|
|
1088
|
-
* SSE event (`stream.write({ id, data })`). A push with
|
|
1089
|
-
* so a client that connects (or reconnects with a `Last-Event-ID`)
|
|
933
|
+
* - **`push` is the server-initiated primitive.** It appends the message to the log (assigning
|
|
934
|
+
* a monotone base36 event id) and fans it out to every attached stream as one `id:`-tagged
|
|
935
|
+
* SSE event (`stream.write({ id, data })`). A push with no attached stream is still logged,
|
|
936
|
+
* so a client that connects (or reconnects with a `Last-Event-ID`) later replays it from the
|
|
1090
937
|
* log. A `write` to a closed stream is a safe no-op (the {@link
|
|
1091
|
-
* `@orkestrel/server`'s `
|
|
938
|
+
* `@orkestrel/server`'s `createStream` contract), so a just-disconnected stream that
|
|
1092
939
|
* has not yet been `detach`ed never throws. A replayed event and the live one carry the
|
|
1093
|
-
*
|
|
940
|
+
* identical id (the log assigns it once).
|
|
1094
941
|
*
|
|
1095
942
|
* - **`replay(afterId)` is strictly-after.** It returns every retained log entry whose id sorts
|
|
1096
|
-
*
|
|
1097
|
-
* before attaching the stream for live pushes. The decision for an
|
|
943
|
+
* after `afterId` in append order — the missed-events list the `GET {path}` handler writes
|
|
944
|
+
* before attaching the stream for live pushes. The decision for an unknown / already-evicted
|
|
1098
945
|
* `afterId` (the client's cursor fell off the back of the capacity window, or never existed):
|
|
1099
|
-
* replay
|
|
1100
|
-
* lost (its cursor is
|
|
946
|
+
* replay nothing. Replaying the whole retained log would re-deliver events the client never
|
|
947
|
+
* lost (its cursor is older than everything retained); returning `[]` lets the handler then
|
|
1101
948
|
* stream only the fresh pushes that follow `attach` — the spec-sane resume.
|
|
1102
949
|
*
|
|
1103
|
-
* - **Bounded, append-ordered, plain `Map`.** The log lives in
|
|
1104
|
-
* `Map<id, entry>` — insertion order
|
|
1105
|
-
* eviction both walk the map directly.
|
|
950
|
+
* - **Bounded, append-ordered, plain `Map`.** The log lives in one insertion-ordered
|
|
951
|
+
* `Map<id, entry>` — insertion order is append order is id order, so `replay` and capacity
|
|
952
|
+
* eviction both walk the map directly. No database mirror — the log is process-local
|
|
1106
953
|
* transport mechanics, not durable state. `push` first drops every entry older than `ttl`
|
|
1107
954
|
* (lazy TTL — no background timer, the middleware's lazy-window idiom), appends, then evicts
|
|
1108
|
-
* the
|
|
955
|
+
* the oldest entries until at most `capacity` remain; `replay` also runs the lazy TTL sweep
|
|
1109
956
|
* first, so a stale entry is never replayed.
|
|
1110
957
|
*
|
|
1111
958
|
* - **No transport coupling beyond the SSE seam.** It holds session state + the generic {@link
|
|
@@ -1113,9 +960,10 @@ export declare interface MCPOriginOptions {
|
|
|
1113
960
|
* The middleware opens the stream (the spine seam) and registers it here; this class only
|
|
1114
961
|
* serializes a message onto the already-open streams.
|
|
1115
962
|
*
|
|
1116
|
-
* - **Injected clock.**
|
|
1117
|
-
* `Date.now
|
|
1118
|
-
* timer
|
|
963
|
+
* - **Injected clock.** {@link import('./types.js').MCPSessionOptions.clock} supplies the
|
|
964
|
+
* epoch-ms clock the lazy TTL sweep reads, defaulting to `Date.now` — so a test drives TTL
|
|
965
|
+
* eviction with an elapsed clock rather than a real timer, and the middleware that mints a
|
|
966
|
+
* session hands its own clock down instead of leaving the log on wall-clock time.
|
|
1119
967
|
*
|
|
1120
968
|
* @example
|
|
1121
969
|
* ```ts
|
|
@@ -1131,15 +979,15 @@ export declare class MCPSession implements MCPSessionInterface {
|
|
|
1131
979
|
get id(): string;
|
|
1132
980
|
attach(stream: StreamInterface): void;
|
|
1133
981
|
detach(stream: StreamInterface): void;
|
|
1134
|
-
push(message: JSONRPCMessage
|
|
1135
|
-
replay(afterId: string
|
|
982
|
+
push(message: JSONRPCMessage): string;
|
|
983
|
+
replay(afterId: string): readonly MCPSessionEvent[];
|
|
1136
984
|
}
|
|
1137
985
|
|
|
1138
986
|
/**
|
|
1139
|
-
*
|
|
1140
|
-
* keeps per minted session — the live {@link MCPSession} entity plus the epoch-ms
|
|
1141
|
-
* was last touched (the lazy-TTL sweep's idle clock, independent of the session's
|
|
1142
|
-
* replay-log TTL).
|
|
987
|
+
* Represents the closure store entry a {@link import('./middlewares.js').createMCPSession}
|
|
988
|
+
* middleware keeps per minted session — the live {@link MCPSession} entity plus the epoch-ms
|
|
989
|
+
* instant it was last touched (the lazy-TTL sweep's idle clock, independent of the session's
|
|
990
|
+
* own replay-log TTL).
|
|
1143
991
|
*
|
|
1144
992
|
* @remarks
|
|
1145
993
|
* - `session` — the live {@link MCPSession} entity the store keys by session id.
|
|
@@ -1151,37 +999,63 @@ export declare class MCPSession implements MCPSessionInterface {
|
|
|
1151
999
|
export declare interface MCPSessionEntry {
|
|
1152
1000
|
readonly session: MCPSession;
|
|
1153
1001
|
readonly touched: number;
|
|
1154
|
-
/**
|
|
1002
|
+
/** Holds the legacy revision negotiated when this session was minted. */
|
|
1155
1003
|
readonly version: MCPVersion;
|
|
1156
1004
|
}
|
|
1157
1005
|
|
|
1158
1006
|
/**
|
|
1159
|
-
*
|
|
1007
|
+
* Represents one entry of an {@link MCPSessionInterface}'s folded replay log — a single pushed
|
|
1008
|
+
* {@link JSONRPCMessage} tagged with the monotone event `id` the session assigned and the
|
|
1009
|
+
* `timestamp` it was appended at (for the lazy-TTL replay window).
|
|
1010
|
+
*
|
|
1011
|
+
* @remarks
|
|
1012
|
+
* - `id` — the session-assigned, monotonically-increasing event id (a base36 string), the
|
|
1013
|
+
* value a resumable client echoes back as its `Last-Event-ID` to replay from here.
|
|
1014
|
+
* - `message` — the server→client {@link JSONRPCMessage} that was pushed.
|
|
1015
|
+
* - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
|
|
1016
|
+
*
|
|
1017
|
+
* A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
|
|
1018
|
+
* returns.
|
|
1019
|
+
*/
|
|
1020
|
+
export declare interface MCPSessionEvent {
|
|
1021
|
+
readonly id: string;
|
|
1022
|
+
readonly message: JSONRPCMessage;
|
|
1023
|
+
readonly timestamp: number;
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
/**
|
|
1027
|
+
* Represents one MCP transport session — the per-session entity a {@link
|
|
1160
1028
|
* import('./middlewares.js').createMCPSession} middleware owns (the {@link
|
|
1161
1029
|
* import('./MCPSession.js').MCPSession} entity), carrying the resumable server→client push
|
|
1162
|
-
* channel with its bounded replay log
|
|
1030
|
+
* channel with its bounded replay log folded in.
|
|
1163
1031
|
*
|
|
1164
1032
|
* @remarks
|
|
1165
|
-
*
|
|
1166
|
-
*
|
|
1167
|
-
*
|
|
1168
|
-
*
|
|
1169
|
-
*
|
|
1170
|
-
* it when the client disconnects).
|
|
1171
|
-
* - `push(message)` — APPEND `message` to the session's folded replay log (assigning a
|
|
1172
|
-
* monotone event id, RETURNED) and FAN it out to every attached stream as one `id:`-tagged
|
|
1173
|
-
* SSE event — the server-initiated push primitive an in-request handler calls. A push with
|
|
1174
|
-
* no attached stream is still logged, so a later-connecting / reconnecting client replays it.
|
|
1175
|
-
* - `replay(afterId)` — the missed-events list (every retained log entry STRICTLY AFTER
|
|
1176
|
-
* `afterId`, in append order) the resumable `GET {path}` handler writes ahead of live pushes;
|
|
1177
|
-
* an unknown / evicted cursor replays NOTHING (the spec-sane resume).
|
|
1033
|
+
* The application addresses a session through `context.state.session`, the {@link
|
|
1034
|
+
* MCPSessionState} slice {@link import('./middlewares.js').createMCPSession} sets. A pushed
|
|
1035
|
+
* message with no attached stream is still logged, so a client that connects or reconnects
|
|
1036
|
+
* later replays it, and the resumable `GET {path}` handler writes a replay ahead of live
|
|
1037
|
+
* pushes.
|
|
1178
1038
|
*/
|
|
1179
1039
|
export declare interface MCPSessionInterface {
|
|
1040
|
+
/** Holds the opaque session id, a `crypto.randomUUID()` value echoed in the `mcp-session-id` header. */
|
|
1180
1041
|
readonly id: string;
|
|
1042
|
+
/**
|
|
1043
|
+
* Registers an open server→client SSE stream, a resumable `GET {path}`, so a later pushed
|
|
1044
|
+
* message reaches it.
|
|
1045
|
+
*/
|
|
1181
1046
|
attach(stream: StreamInterface): void;
|
|
1047
|
+
/** Unregisters a stream — the middleware calls it when the client disconnects. */
|
|
1182
1048
|
detach(stream: StreamInterface): void;
|
|
1049
|
+
/**
|
|
1050
|
+
* Appends a message to the folded replay log under a fresh monotone event id, returns that id,
|
|
1051
|
+
* and fans the message out to every attached stream as one `id:`-tagged SSE event.
|
|
1052
|
+
*/
|
|
1183
1053
|
push(message: JSONRPCMessage): string;
|
|
1184
|
-
|
|
1054
|
+
/**
|
|
1055
|
+
* Returns every retained log entry strictly after a cursor, in append order; an unknown or
|
|
1056
|
+
* evicted cursor replays nothing.
|
|
1057
|
+
*/
|
|
1058
|
+
replay(afterId: string): readonly MCPSessionEvent[];
|
|
1185
1059
|
}
|
|
1186
1060
|
|
|
1187
1061
|
/**
|
|
@@ -1189,19 +1063,18 @@ export declare interface MCPSessionInterface {
|
|
|
1189
1063
|
* time-to-live, and the per-session resumable event-log bound.
|
|
1190
1064
|
*
|
|
1191
1065
|
* @remarks
|
|
1192
|
-
* - `path` — the request path the session middleware
|
|
1066
|
+
* - `path` — the request path the session middleware owns (must match the `createMCPRoutes`
|
|
1193
1067
|
* `path` it fronts); a request to any other path passes straight through. Defaults to
|
|
1194
1068
|
* {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`).
|
|
1195
1069
|
* - `ttl` — the session idle lifetime in milliseconds: a session not accessed within `ttl`
|
|
1196
|
-
* is treated as
|
|
1070
|
+
* is treated as absent and lazily evicted on the next access (no background timer — the
|
|
1197
1071
|
* `createRateLimiter` lazy-window idiom). Omit it for sessions that live until an explicit
|
|
1198
1072
|
* `DELETE`.
|
|
1199
|
-
* - `
|
|
1200
|
-
*
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
* the session; this `capacity` bounds its replay log — independent knobs.)
|
|
1073
|
+
* - `session` — the knobs forwarded to each minted {@link MCPSession}: `capacity` bounds its
|
|
1074
|
+
* replay log and `ttl` is that log's per-event lifetime. This type's own `ttl` bounds the
|
|
1075
|
+
* session instead. An omitted leaf takes its {@link MCPSessionOptions} default, and an
|
|
1076
|
+
* omitted `session.clock` inherits this type's own `clock`, so one injected clock governs
|
|
1077
|
+
* both the store sweep and the log sweep unless a caller names a different one.
|
|
1205
1078
|
* - `clock` — the `() => number` epoch-ms clock {@link import('./middlewares.js').createMCPSession}
|
|
1206
1079
|
* uses directly for its own session-touch / TTL-sweep bookkeeping; defaults to `Date.now`. The
|
|
1207
1080
|
* deterministic clock a TTL test advances explicitly instead of racing a real idle window
|
|
@@ -1214,18 +1087,44 @@ export declare interface MCPSessionInterface {
|
|
|
1214
1087
|
* - `keepalive` — the SSE liveness options for the held-open resumable response. `interval`
|
|
1215
1088
|
* defaults to {@link import('./constants.js').DEFAULT_MCP_KEEPALIVE_INTERVAL}.
|
|
1216
1089
|
*/
|
|
1217
|
-
export declare interface
|
|
1090
|
+
export declare interface MCPSessionMiddlewareOptions {
|
|
1218
1091
|
readonly path?: string;
|
|
1219
1092
|
readonly ttl?: number;
|
|
1220
|
-
readonly
|
|
1093
|
+
readonly session?: MCPSessionOptions;
|
|
1221
1094
|
readonly clock?: () => number;
|
|
1222
|
-
/**
|
|
1095
|
+
/** Requires the route layer's value; `origins` is ignored when `enabled` is `false`. */
|
|
1223
1096
|
readonly origin?: MCPOriginOptions;
|
|
1224
1097
|
readonly keepalive?: MCPKeepaliveOptions;
|
|
1225
1098
|
}
|
|
1226
1099
|
|
|
1227
1100
|
/**
|
|
1228
|
-
*
|
|
1101
|
+
* Options for the {@link MCPSession} entity — its folded replay log's capacity and per-event
|
|
1102
|
+
* lifetime.
|
|
1103
|
+
*
|
|
1104
|
+
* @remarks
|
|
1105
|
+
* - `capacity` — the maximum number of pushed server→client messages retained for replay
|
|
1106
|
+
* before the oldest is evicted. Omit it for the {@link
|
|
1107
|
+
* import('./constants.js').DEFAULT_MCP_SESSION_CAPACITY} default.
|
|
1108
|
+
* - `ttl` — the PER-EVENT idle lifetime in milliseconds: a log entry older than `ttl` is
|
|
1109
|
+
* dropped by the lazy sweep `push` and `replay` run, which bounds how far a reconnecting
|
|
1110
|
+
* client may replay. Omit it for the {@link
|
|
1111
|
+
* import('./constants.js').DEFAULT_MCP_SESSION_TTL} default; a non-positive value means no
|
|
1112
|
+
* entry ever ages out by time.
|
|
1113
|
+
* - `clock` — the `() => number` epoch-ms clock the log's lazy TTL sweep reads; defaults to
|
|
1114
|
+
* `Date.now`.
|
|
1115
|
+
*
|
|
1116
|
+
* The middleware's own knobs — the owned path, the idle-SESSION sweep window, origin
|
|
1117
|
+
* validation, and keepalive — live on {@link MCPSessionMiddlewareOptions}. This type's `ttl`
|
|
1118
|
+
* and that one's measure different things, which is why they sit on different types.
|
|
1119
|
+
*/
|
|
1120
|
+
export declare interface MCPSessionOptions {
|
|
1121
|
+
readonly capacity?: number;
|
|
1122
|
+
readonly ttl?: number;
|
|
1123
|
+
readonly clock?: () => number;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/**
|
|
1127
|
+
* Declares the `context.state` slice a {@link import('./middlewares.js').createMCPSession}
|
|
1229
1128
|
* middleware sets on a validated / minted request — a consumer's `TState` extends
|
|
1230
1129
|
* this so the downstream route handler can read `context.state.session` to `push`
|
|
1231
1130
|
* a server-initiated message onto the session's resumable stream.
|
|
@@ -1240,27 +1139,6 @@ export declare interface MCPSessionState {
|
|
|
1240
1139
|
readonly session?: MCPSessionInterface;
|
|
1241
1140
|
}
|
|
1242
1141
|
|
|
1243
|
-
/**
|
|
1244
|
-
* Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
|
|
1245
|
-
* carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
|
|
1246
|
-
*
|
|
1247
|
-
* @remarks
|
|
1248
|
-
* Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({
|
|
1249
|
-
* stream: true })` (handling a multi-byte char split across reads) and `@orkestrel/sse`'s
|
|
1250
|
-
* {@link SSEParserInterface} (handling a partial line / in-progress event split across
|
|
1251
|
-
* reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} with
|
|
1252
|
-
* `parseJSONRPCMessage` (so a non-message / non-JSON `data:` event is DROPPED, never
|
|
1253
|
-
* thrown — total). It reuses the SAME `SSEParser` the server's `openStream` seam
|
|
1254
|
-
* serializes against, so the wire round-trips. A `null` body (no stream) yields no
|
|
1255
|
-
* messages; the {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
|
|
1256
|
-
* reads a request/response SSE reply (the server sends one `data:` event then ends), so
|
|
1257
|
-
* this drains to completion.
|
|
1258
|
-
*
|
|
1259
|
-
* @param response - The SSE `fetch` Response to decode (its `body` is read to completion)
|
|
1260
|
-
* @returns Every {@link JSONRPCMessage} the stream carried, in order
|
|
1261
|
-
*/
|
|
1262
|
-
export declare function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]>;
|
|
1263
|
-
|
|
1264
1142
|
/**
|
|
1265
1143
|
* Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it
|
|
1266
1144
|
* reconnects to the resumable `GET {path}` stream, or `undefined` when absent.
|
|
@@ -1300,7 +1178,7 @@ export declare function readSessionHeader(request: Request): string | undefined;
|
|
|
1300
1178
|
* @remarks
|
|
1301
1179
|
* Returns `Response.json(buildJSONRPCError(undefined, JSONRPC_INVALID_REQUEST, 'Session not
|
|
1302
1180
|
* found'), { status: 404 })`, mirroring `createMCPRoutes`'s `400` transport-failure shape (a
|
|
1303
|
-
* JSON-RPC error
|
|
1181
|
+
* JSON-RPC error body with no id) but at the session-not-found status. Shared by
|
|
1304
1182
|
* every {@link import('./middlewares.js').createMCPSession} validation site — the
|
|
1305
1183
|
* non-`initialize` `POST` path, the resumable `GET {path}` open, and the `DELETE {path}`
|
|
1306
1184
|
* session-end (each a missing / unknown / TTL-evicted id) — so the single `404` envelope
|
|
@@ -1312,16 +1190,16 @@ export declare function rejectUnknownSession(): Response;
|
|
|
1312
1190
|
|
|
1313
1191
|
/**
|
|
1314
1192
|
* Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per
|
|
1315
|
-
* notification in order, then the terminating response — and
|
|
1193
|
+
* notification in order, then the terminating response — and end the exchange however the
|
|
1316
1194
|
* pump leaves.
|
|
1317
1195
|
*
|
|
1318
1196
|
* @remarks
|
|
1319
1197
|
* The Streamable-HTTP twin of {@link import('@orkestrel/mcp').sendStream}, and it owns exactly what
|
|
1320
|
-
* that owns. The `finally` releases the exchange on
|
|
1198
|
+
* that owns. The `finally` releases the exchange on every exit — the normal terminal, a
|
|
1321
1199
|
* producer that threw, a `write` that threw, and an abort alike — because nothing else will:
|
|
1322
1200
|
* a request whose client vanished cancels nothing by itself, so an exchange this pump walks
|
|
1323
1201
|
* away from keeps its producer, its request lifetime, and its live subscription slot forever.
|
|
1324
|
-
* The exchange is released
|
|
1202
|
+
* The exchange is released before the body ends, so the slot is already back when the response
|
|
1325
1203
|
* completes.
|
|
1326
1204
|
*
|
|
1327
1205
|
* Total — never throws and never rejects. A held-open SSE response has already sent its
|
|
@@ -1338,27 +1216,26 @@ export declare function rejectUnknownSession(): Response;
|
|
|
1338
1216
|
* ```ts
|
|
1339
1217
|
* const answer = await mcp.dispatch(invocation, { signal: disconnect.signal })
|
|
1340
1218
|
* if (answer !== undefined && Symbol.asyncIterator in answer) {
|
|
1341
|
-
* const sse =
|
|
1219
|
+
* const sse = createStream()
|
|
1342
1220
|
* queueMicrotask(() => void sendEventStream(answer, sse))
|
|
1343
1221
|
* }
|
|
1344
1222
|
* ```
|
|
1345
1223
|
*/
|
|
1346
1224
|
export declare function sendEventStream(stream: MCPStreamControllerInterface, sse: StreamInterface): Promise<void>;
|
|
1347
1225
|
|
|
1348
|
-
/**
|
|
1226
|
+
/** Names the `X-Accel-Buffering` value that disables reverse-proxy buffering. */
|
|
1349
1227
|
export declare const SSE_BUFFERING_DISABLED = "no";
|
|
1350
1228
|
|
|
1351
|
-
/**
|
|
1229
|
+
/** Names the reverse-proxy response header controlling buffering of an SSE response. */
|
|
1352
1230
|
export declare const SSE_BUFFERING_HEADER = "x-accel-buffering";
|
|
1353
1231
|
|
|
1354
|
-
/**
|
|
1232
|
+
/** Names the comment text written by the held-open MCP response keepalive. */
|
|
1355
1233
|
export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
1356
1234
|
|
|
1357
1235
|
/**
|
|
1358
|
-
*
|
|
1359
|
-
* {@link StdioClientTransportInterface}
|
|
1360
|
-
*
|
|
1361
|
-
* import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
|
|
1236
|
+
* Drives a child process MCP server over newline-delimited JSON-RPC on `stdin`/`stdout` —
|
|
1237
|
+
* a {@link StdioClientTransportInterface}, the stdio sibling of {@link
|
|
1238
|
+
* import('@orkestrel/mcp').HTTPClientTransport} and {@link
|
|
1362
1239
|
* import('./WebSocketClientTransport.js').WebSocketClientTransport}.
|
|
1363
1240
|
*
|
|
1364
1241
|
* @remarks
|
|
@@ -1372,14 +1249,14 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1372
1249
|
* line is decoded and delivered through the shared {@link dispatchLines} helper — a well-formed
|
|
1373
1250
|
* {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (never throws).
|
|
1374
1251
|
* - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
|
|
1375
|
-
* through the supervisor's `send` and
|
|
1252
|
+
* through the supervisor's `send` and awaits its answer, so this promise settles only after the
|
|
1376
1253
|
* host reports the line handled rather than the moment the write is queued. The supervisor never
|
|
1377
1254
|
* rejects — it answers `false` for a channel that was closed, destroyed, or ended, for a write
|
|
1378
1255
|
* that failed, or for one that remained unconfirmed through `delivery`. A call made without a
|
|
1379
1256
|
* live child rejects as not connected; a `false` answer from a live child rejects as unable to
|
|
1380
1257
|
* deliver. The supervisor does not disclose which cause produced that answer.
|
|
1381
1258
|
* - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
|
|
1382
|
-
* (idempotent). That teardown reaches the child's
|
|
1259
|
+
* (idempotent). That teardown reaches the child's terminal moment, where the supervisor freezes
|
|
1383
1260
|
* `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
|
|
1384
1261
|
* its own to get its line pump back: the stream ends under the pump rather than throwing at it.
|
|
1385
1262
|
* A line the supervisor had already framed behind the one being delivered is dropped rather than
|
|
@@ -1392,15 +1269,15 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1392
1269
|
* child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
|
|
1393
1270
|
* route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the
|
|
1394
1271
|
* tree with `taskkill /F /T`, which nothing in the child can intercept.
|
|
1395
|
-
* - **Evidence.** `evidence` reports that retained stderr tail off the
|
|
1272
|
+
* - **Evidence.** `evidence` reports that retained stderr tail off the held child — its live tail
|
|
1396
1273
|
* while the child runs, and the value the supervisor froze at that child's terminal moment
|
|
1397
1274
|
* afterwards. The reference is held past that moment and replaced only by the next `start()`,
|
|
1398
1275
|
* which is what keeps a post-`close()` read stable without a private copy: the frozen value
|
|
1399
1276
|
* never moves again, so a detached descendant writing to the inherited stderr after the cutoff
|
|
1400
1277
|
* cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
|
|
1401
1278
|
* byte bound.
|
|
1402
|
-
* - **Observable.** Owns the `emitter` ({@link
|
|
1403
|
-
* emitter isolates a listener throw; `error` is a
|
|
1279
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the
|
|
1280
|
+
* emitter isolates a listener throw; `error` is a domain event (a transport-level
|
|
1404
1281
|
* fault, including the child spawn cause the supervisor surfaces and the notice that this
|
|
1405
1282
|
* lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
|
|
1406
1283
|
* listener-error channel.
|
|
@@ -1415,7 +1292,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1415
1292
|
export declare class StdioClientTransport implements StdioClientTransportInterface {
|
|
1416
1293
|
#private;
|
|
1417
1294
|
constructor(options: StdioClientTransportOptions);
|
|
1418
|
-
get emitter(): EmitterInterface<
|
|
1295
|
+
get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
|
|
1419
1296
|
get session(): string | undefined;
|
|
1420
1297
|
get duplex(): boolean;
|
|
1421
1298
|
get evidence(): string | undefined;
|
|
@@ -1430,28 +1307,28 @@ export declare class StdioClientTransport implements StdioClientTransportInterfa
|
|
|
1430
1307
|
* @throws Thrown with `stdio transport could not deliver the message` when a live child's write
|
|
1431
1308
|
* resolves `false`
|
|
1432
1309
|
*/
|
|
1433
|
-
send(message:
|
|
1310
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
1434
1311
|
close(): Promise<void>;
|
|
1435
1312
|
}
|
|
1436
1313
|
|
|
1437
1314
|
/**
|
|
1438
|
-
*
|
|
1439
|
-
* that also reports the supervised child's stderr tail, the
|
|
1440
|
-
* startup leaves behind.
|
|
1315
|
+
* Declares the contract `createStdioClientTransport` returns — a
|
|
1316
|
+
* {@link MCPMessageTransportInterface} that also reports the supervised child's stderr tail, the
|
|
1317
|
+
* diagnostic a child that dies at startup leaves behind.
|
|
1441
1318
|
*
|
|
1442
1319
|
* @remarks
|
|
1443
1320
|
* This contract adds `evidence` and changes nothing else: `emitter`, `session`, `duplex`,
|
|
1444
1321
|
* `start`, `send`, and `close` are the shared client-transport surface, unchanged. It sits here
|
|
1445
|
-
* rather than on {@link
|
|
1322
|
+
* rather than on {@link MCPMessageTransportInterface} because a transport that supervises no child —
|
|
1446
1323
|
* Streamable HTTP, WebSocket, a `MessagePort` pair — has no such tail, and a member every one
|
|
1447
1324
|
* of them answers `undefined` to forever is a stdio detail rather than a shared contract. A
|
|
1448
|
-
* consumer that widens this value back to {@link
|
|
1325
|
+
* consumer that widens this value back to {@link MCPMessageTransportInterface}, including by
|
|
1449
1326
|
* reading `client.transport`, loses the reader and must keep the original reference.
|
|
1450
1327
|
*/
|
|
1451
|
-
export declare interface StdioClientTransportInterface extends
|
|
1328
|
+
export declare interface StdioClientTransportInterface extends MCPMessageTransportInterface {
|
|
1452
1329
|
/**
|
|
1453
|
-
*
|
|
1454
|
-
* captured at that child's end afterwards.
|
|
1330
|
+
* Reports the supervised child's decoded stderr tail — live while a child is held, and the
|
|
1331
|
+
* value captured at that child's end afterwards.
|
|
1455
1332
|
*
|
|
1456
1333
|
* @remarks
|
|
1457
1334
|
* - **Readings.** `undefined` while no child has run and none has been captured — before the
|
|
@@ -1460,7 +1337,7 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
|
|
|
1460
1337
|
* it exited on its own or `close()` terminated it, and `''` there for a child that ran and
|
|
1461
1338
|
* wrote nothing — an empty tail is a real reading of a silent child, distinct from the
|
|
1462
1339
|
* absent one.
|
|
1463
|
-
* - **Lifetime.** The tail follows the child that produced it. The supervisor
|
|
1340
|
+
* - **Lifetime.** The tail follows the child that produced it. The supervisor freezes it at
|
|
1464
1341
|
* that child's terminal moment — the moment `close()`'s teardown resolves past, and the
|
|
1465
1342
|
* moment the exit that fires this transport's `close` settles at — and this transport keeps
|
|
1466
1343
|
* reading that same child afterwards. The frozen value never moves again, which is what
|
|
@@ -1475,18 +1352,18 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
|
|
|
1475
1352
|
* its teardown barrier while those listeners run, so a `start()` one of them calls parks
|
|
1476
1353
|
* behind it and every later listener reads the ended child's frozen tail. A natural exit
|
|
1477
1354
|
* holds that barrier only across the `error` it reports at that end, so a restart begun
|
|
1478
|
-
*
|
|
1355
|
+
* there parks until `close` has been delivered, while a `close` listener that calls
|
|
1479
1356
|
* `start()` opens the next lifetime itself and replaces the value every listener after it
|
|
1480
1357
|
* would have read.
|
|
1481
1358
|
* - **What the close path carries.** The frozen value is what the supervisor had received by
|
|
1482
1359
|
* that terminal moment, not the child's complete output.
|
|
1483
1360
|
* Windows ends the tree with `taskkill /F /T`, which nothing in the child can intercept: a
|
|
1484
1361
|
* `SIGTERM` handler never runs there, so the bytes it would have written never exist. A
|
|
1485
|
-
* child that ends on its own closes its stderr first, and
|
|
1362
|
+
* child that ends on its own closes its stderr first, and that tail is complete.
|
|
1486
1363
|
* Where that moment arrived at the supervisor's `drain` bound rather than at the child's
|
|
1487
1364
|
* own stream close, the tail stops at the cutoff and later diagnostics may have existed;
|
|
1488
1365
|
* the transport emits an `error` naming that lifetime, so a partial tail reads as partial.
|
|
1489
|
-
* - **Bound.** The supervisor keeps the
|
|
1366
|
+
* - **Bound.** The supervisor keeps the end of the child's raw stderr bytes, at most
|
|
1490
1367
|
* `@orkestrel/process`'s {@link import('@orkestrel/process').PROCESS_EVIDENCE} (2048
|
|
1491
1368
|
* bytes under 0.0.6). A child that writes more than the bound loses its earliest output
|
|
1492
1369
|
* and keeps its last, which is the half that names why it died. The bound counts raw
|
|
@@ -1507,13 +1384,13 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
|
|
|
1507
1384
|
* stdio-framed MCP server (newline-delimited JSON-RPC over `stdin`/`stdout`).
|
|
1508
1385
|
*
|
|
1509
1386
|
* @remarks
|
|
1510
|
-
* - `command` — the executable to spawn (for example, `'node'`, `'./my-mcp-server'`).
|
|
1387
|
+
* - `command` — the executable to spawn (for example, `'node'`, `'./my-mcp-server'`). Required.
|
|
1511
1388
|
* - `args` — the command-line arguments passed to `command`; defaults to none.
|
|
1512
|
-
* - `env` — environment variable overrides
|
|
1389
|
+
* - `env` — environment variable overrides merged over the parent `process.env` for the
|
|
1513
1390
|
* spawned child (the composed `@orkestrel/process` supervisor's merge semantics): when
|
|
1514
|
-
*
|
|
1391
|
+
* omitted the child inherits the full `process.env`, when provided each named key overrides
|
|
1515
1392
|
* the inherited value while every unlisted key is still inherited. This transport cannot
|
|
1516
|
-
*
|
|
1393
|
+
* replace the inherited environment entirely — the supervisor always merges over the parent.
|
|
1517
1394
|
* - `delivery` — the bound in milliseconds on one unconfirmed write to the child's `stdin`;
|
|
1518
1395
|
* an explicit `0` opts out. Defaults to {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
|
|
1519
1396
|
*/
|
|
@@ -1532,10 +1409,10 @@ export declare interface StdioClientTransportOptions {
|
|
|
1532
1409
|
* that will never read it. Default: {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
|
|
1533
1410
|
*
|
|
1534
1411
|
* An explicit `0` opts out: the bound is off, and an unconfirmed write stays pending until
|
|
1535
|
-
* the channel faults or teardown settles it. Omission does
|
|
1412
|
+
* the channel faults or teardown settles it. Omission does not opt out here, which is where
|
|
1536
1413
|
* this option diverges from the supervisor's own `delivery` on {@link
|
|
1537
|
-
* import('@orkestrel/process').ProcessOptions} — omitted
|
|
1538
|
-
*
|
|
1414
|
+
* import('@orkestrel/process').ProcessOptions} — omitted there disables the bound, omitted
|
|
1415
|
+
* here selects the default.
|
|
1539
1416
|
*
|
|
1540
1417
|
* An out-of-range value surfaces at `start()` rather than at construction. This transport
|
|
1541
1418
|
* forwards the value verbatim and adds no validator of its own, so the supervisor's own timer
|
|
@@ -1545,26 +1422,29 @@ export declare interface StdioClientTransportOptions {
|
|
|
1545
1422
|
}
|
|
1546
1423
|
|
|
1547
1424
|
/**
|
|
1548
|
-
*
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1425
|
+
* Arms and tears down the newline-delimited JSON-RPC pump over the {@link StdioServerOptions}
|
|
1426
|
+
* stream pair — the stdio ingress handle {@link import('./factories.js').createStdioServer}
|
|
1427
|
+
* returns.
|
|
1551
1428
|
*
|
|
1552
1429
|
* @remarks
|
|
1553
|
-
*
|
|
1554
|
-
*
|
|
1555
|
-
*
|
|
1556
|
-
*
|
|
1557
|
-
* request still draws exactly one reply.
|
|
1558
|
-
* - `stop()` — unbind the pump and close the transport: the listeners `start()` put on
|
|
1559
|
-
* `input` / `output` are removed, every pending `send` rejects, and `input` is released so
|
|
1560
|
-
* the process can exit. The release is complete by the time the call returns, and a
|
|
1561
|
-
* repeated `stop()` does nothing.
|
|
1562
|
-
* - **One lifetime per handle.** `stop()` ends it permanently: a `start()` issued afterwards
|
|
1563
|
-
* arms nothing, and serving again takes a fresh
|
|
1564
|
-
* {@link import('./factories.js').createStdioServer} over a live stream pair.
|
|
1430
|
+
* The subscriptions are attached, and the release complete, by the time each call returns. One
|
|
1431
|
+
* lifetime per handle: the `stop` method ends it permanently, a `start` call issued afterwards
|
|
1432
|
+
* arms nothing, and serving again takes a fresh
|
|
1433
|
+
* {@link import('./factories.js').createStdioServer} over a live stream pair.
|
|
1565
1434
|
*/
|
|
1566
1435
|
export declare interface StdioServerInterface {
|
|
1436
|
+
/**
|
|
1437
|
+
* Arms the pump: subscribes to `input` and dispatches every complete line through the bound
|
|
1438
|
+
* {@link import('@orkestrel/mcp').MCPDispatcherInterface}, writing each defined response back to
|
|
1439
|
+
* `output`. The pump arms once, so a repeated call attaches nothing further and an inbound
|
|
1440
|
+
* request still draws exactly one reply.
|
|
1441
|
+
*/
|
|
1567
1442
|
start(): void;
|
|
1443
|
+
/**
|
|
1444
|
+
* Unbinds the pump and closes the transport: removes the listeners the `start` method put on
|
|
1445
|
+
* `input` and `output`, rejects every pending write, and releases `input` so the process can
|
|
1446
|
+
* exit. A repeated call does nothing.
|
|
1447
|
+
*/
|
|
1568
1448
|
stop(): void;
|
|
1569
1449
|
}
|
|
1570
1450
|
|
|
@@ -1584,15 +1464,14 @@ export declare interface StdioServerOptions {
|
|
|
1584
1464
|
}
|
|
1585
1465
|
|
|
1586
1466
|
/**
|
|
1587
|
-
*
|
|
1588
|
-
*
|
|
1589
|
-
*
|
|
1590
|
-
*
|
|
1591
|
-
* `mcp.dispatch` over, the stdio mirror of {@link
|
|
1467
|
+
* Wraps an injectable readable/writable stream pair (`process.stdin`/`process.stdout` in
|
|
1468
|
+
* production, a test double in tests) as a {@link MCPMessageTransportInterface} — the
|
|
1469
|
+
* newline-delimited JSON-RPC channel {@link import('../factories.js').createStdioServer}
|
|
1470
|
+
* pumps `mcp.dispatch` over, the stdio mirror of {@link
|
|
1592
1471
|
* import('./WebSocketServerTransport.js').WebSocketServerTransport}.
|
|
1593
1472
|
*
|
|
1594
1473
|
* @remarks
|
|
1595
|
-
* - **Reuses `
|
|
1474
|
+
* - **Reuses `MCPMessageTransportInterface`.** The same generic carrier the HTTP
|
|
1596
1475
|
* and WebSocket server transports implement — `emitter` (`message` / `close` /
|
|
1597
1476
|
* `error`), `start`, `send`, `close`. `session` is `undefined` (the stateless v1).
|
|
1598
1477
|
* - **Inbound (`message`).** `start()` subscribes to `input`'s `data` event; each
|
|
@@ -1608,7 +1487,7 @@ export declare interface StdioServerOptions {
|
|
|
1608
1487
|
* - **`close()`** removes this transport's input and output subscriptions, rejects every
|
|
1609
1488
|
* pending send, and fires its `close`
|
|
1610
1489
|
* event (idempotent). It pauses the input only when the caller was not already reading
|
|
1611
|
-
* it at `start` (`readableFlowing !== true`)
|
|
1490
|
+
* it at `start` (`readableFlowing !== true`) and no `data` listener remains once this
|
|
1612
1491
|
* transport's own is removed — so a process holding `process.stdin` can exit, and a
|
|
1613
1492
|
* caller's own flow is never stopped underneath it. The transport preserves flowing versus
|
|
1614
1493
|
* non-flowing state and restores every caller-owned listener. A Node stream that had never been
|
|
@@ -1618,14 +1497,14 @@ export declare interface StdioServerOptions {
|
|
|
1618
1497
|
* listener receives data. The injected streams are owned by the caller (typically
|
|
1619
1498
|
* `process.stdin`/`process.stdout`), so the transport never destroys, ends, or blanket-clears
|
|
1620
1499
|
* them.
|
|
1621
|
-
* - **Observable.** Owns the `emitter` ({@link
|
|
1622
|
-
* emitter isolates a listener throw; `error` is a
|
|
1500
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the
|
|
1501
|
+
* emitter isolates a listener throw; `error` is a domain event (a transport-level
|
|
1623
1502
|
* fault), distinct from the emitter's own listener-error channel.
|
|
1624
1503
|
*/
|
|
1625
|
-
export declare class StdioServerTransport implements
|
|
1504
|
+
export declare class StdioServerTransport implements MCPMessageTransportInterface {
|
|
1626
1505
|
#private;
|
|
1627
1506
|
constructor(input: NodeJS.ReadableStream, output: NodeJS.WritableStream);
|
|
1628
|
-
get emitter(): EmitterInterface<
|
|
1507
|
+
get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
|
|
1629
1508
|
get session(): string | undefined;
|
|
1630
1509
|
get duplex(): boolean;
|
|
1631
1510
|
start(): Promise<void>;
|
|
@@ -1642,7 +1521,7 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
|
|
|
1642
1521
|
* @throws Thrown with `stdio transport is not connected` after the transport closes
|
|
1643
1522
|
* @throws Thrown with the output callback error or synchronous write failure
|
|
1644
1523
|
*/
|
|
1645
|
-
send(message:
|
|
1524
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
1646
1525
|
close(): Promise<void>;
|
|
1647
1526
|
}
|
|
1648
1527
|
|
|
@@ -1651,7 +1530,7 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
|
|
|
1651
1530
|
* the `createWebSocketServer` upgrade-path match.
|
|
1652
1531
|
*
|
|
1653
1532
|
* @remarks
|
|
1654
|
-
* A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request
|
|
1533
|
+
* A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request target
|
|
1655
1534
|
* (`'/mcp?x=1'`), narrowed with `isString` (never `as`) and defaulting to `'/'` for an
|
|
1656
1535
|
* absent target; it is parsed against a placeholder base (only the pathname matters for the upgrade
|
|
1657
1536
|
* decision) and the `pathname` returned. The upgrade handler compares this against its
|
|
@@ -1664,49 +1543,49 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
|
|
|
1664
1543
|
export declare function upgradeRequestPath(request: IncomingMessage): string;
|
|
1665
1544
|
|
|
1666
1545
|
/**
|
|
1667
|
-
*
|
|
1668
|
-
* {@link
|
|
1546
|
+
* Drives a remote MCP server over a WebSocket — a client
|
|
1547
|
+
* {@link MCPMessageTransportInterface} for the Model Context Protocol, the
|
|
1669
1548
|
* egress mirror of {@link import('./factories.js').createWebSocketServer} and the WebSocket
|
|
1670
|
-
* sibling of {@link import('
|
|
1549
|
+
* sibling of {@link import('@orkestrel/mcp').HTTPClientTransport}.
|
|
1671
1550
|
*
|
|
1672
1551
|
* @remarks
|
|
1673
1552
|
* - **Persistent bidirectional channel (unlike the HTTP transport).** `start()` performs the
|
|
1674
1553
|
* RFC 6455 client handshake: it opens a `node:http`(`s`) `GET` carrying `Connection: Upgrade`
|
|
1675
1554
|
* / `Upgrade: websocket` / a random `Sec-WebSocket-Key` / `Sec-WebSocket-Version: 13` /
|
|
1676
1555
|
* `Sec-WebSocket-Protocol: mcp` (plus any `options.headers`), awaits the client `'upgrade'`
|
|
1677
|
-
* event, and
|
|
1678
|
-
* — a mismatch (or a non-`101` response, or a request error)
|
|
1556
|
+
* event, and validates `Sec-WebSocket-Accept === computeWebSocketAccept(key)` (the D2 helper)
|
|
1557
|
+
* — a mismatch (or a non-`101` response, or a request error) rejects `start()` and the socket
|
|
1679
1558
|
* is destroyed. On success it wraps the raw upgraded socket in `createNodeWebSocket({ socket,
|
|
1680
|
-
* head })` (
|
|
1559
|
+
* head })` (client mode — no key → frames are masked per RFC 6455 §5.3) and bridges its
|
|
1681
1560
|
* `message`.
|
|
1682
1561
|
* - **The arriving socket is RE-ASKED for, never assumed.** `start()` suspends across that
|
|
1683
1562
|
* connect and upgrade, so it re-checks the transport's state before installing anything: a
|
|
1684
1563
|
* concurrent `start()` that already installed a socket, or a {@link close} that ended the
|
|
1685
|
-
* transport while the handshake was on the wire, both
|
|
1686
|
-
*
|
|
1564
|
+
* transport while the handshake was on the wire, both win — the socket that arrives late is
|
|
1565
|
+
* destroyed and never bound, so no orphan is left re-emitting frames at nobody. Both
|
|
1687
1566
|
* `start()` calls still resolve; exactly one socket is ever bound.
|
|
1688
|
-
* - **Inbound (`message`).** Each decoded text frame
|
|
1689
|
-
*
|
|
1567
|
+
* - **Inbound (`message`).** Each decoded text frame runs through the shared `deliverMessage`
|
|
1568
|
+
* fold (parse, then narrow) — a {@link JSONRPCMessage} re-emits on this transport's `message`
|
|
1690
1569
|
* event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
|
|
1691
1570
|
* non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
|
|
1692
1571
|
* / `error` bridge to this transport's events.
|
|
1693
1572
|
* - **Outbound (`send`).** `send(message)` writes one masked text frame. A socket write is not
|
|
1694
|
-
* confirmed, so this transport answers a closed channel from its own state
|
|
1573
|
+
* confirmed, so this transport answers a closed channel from its own state and the socket's
|
|
1695
1574
|
* `readyState`: a `send` with no bound socket — before `start()`, after `close()`, or after the
|
|
1696
|
-
* peer ended the socket — and a `send` on a bound socket that is not `OPEN` both
|
|
1575
|
+
* peer ended the socket — and a `send` on a bound socket that is not `OPEN` both reject with
|
|
1697
1576
|
* `WebSocket transport is not connected`. It neither drops the message nor queues it for a
|
|
1698
1577
|
* connection this transport is not holding — the browser face queues a pre-open send, and this
|
|
1699
1578
|
* one, holding no connection to flush it onto, rejects that too.
|
|
1700
1579
|
* - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
|
|
1701
|
-
* upgrade still on the wire is
|
|
1580
|
+
* upgrade still on the wire is destroyed, so a `close()` during the handshake ends the
|
|
1702
1581
|
* transport at once instead of waiting for a peer that may never answer — the suspended
|
|
1703
1582
|
* `start()` resolves, because the close is the outcome its caller asked for.
|
|
1704
1583
|
* - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`
|
|
1705
1584
|
* one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`
|
|
1706
1585
|
* → TLS through `node:https`). Either reaches the same endpoint.
|
|
1707
|
-
* - **Observable.** Owns the `emitter` ({@link
|
|
1586
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); every emit
|
|
1708
1587
|
* the emitter isolates a listener throw (a buggy observer never corrupts the transport);
|
|
1709
|
-
* `error` is a
|
|
1588
|
+
* `error` is a domain event (a transport-level fault).
|
|
1710
1589
|
*
|
|
1711
1590
|
* @example
|
|
1712
1591
|
* ```ts
|
|
@@ -1715,14 +1594,14 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
|
|
|
1715
1594
|
* await client.connect() // start() handshakes, then the MCP initialize runs over WS frames
|
|
1716
1595
|
* ```
|
|
1717
1596
|
*/
|
|
1718
|
-
export declare class WebSocketClientTransport implements
|
|
1597
|
+
export declare class WebSocketClientTransport implements MCPMessageTransportInterface {
|
|
1719
1598
|
#private;
|
|
1720
1599
|
constructor(options: WebSocketClientTransportOptions);
|
|
1721
|
-
get emitter(): EmitterInterface<
|
|
1600
|
+
get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
|
|
1722
1601
|
get session(): string | undefined;
|
|
1723
1602
|
get duplex(): boolean;
|
|
1724
1603
|
start(): Promise<void>;
|
|
1725
|
-
send(message:
|
|
1604
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
1726
1605
|
close(): Promise<void>;
|
|
1727
1606
|
}
|
|
1728
1607
|
|
|
@@ -1732,15 +1611,15 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
1732
1611
|
*
|
|
1733
1612
|
* @remarks
|
|
1734
1613
|
* - `url` — the absolute URL of the remote server's WebSocket endpoint. Accepts a `ws://` /
|
|
1735
|
-
* `wss://` URL
|
|
1614
|
+
* `wss://` URL or an `http://` / `https://` one (a `ws(s)` scheme is converted to `http(s)`
|
|
1736
1615
|
* for the underlying `node:http(s)` upgrade request; either reaches the same endpoint).
|
|
1737
|
-
*
|
|
1616
|
+
* Required.
|
|
1738
1617
|
* - `headers` — extra request headers merged onto the upgrade `GET` (for example, an `Authorization`
|
|
1739
1618
|
* bearer for a guarded server). The transport always sets `Connection: Upgrade`,
|
|
1740
1619
|
* `Upgrade: websocket`, a random `Sec-WebSocket-Key`, `Sec-WebSocket-Version: 13`, and
|
|
1741
1620
|
* `Sec-WebSocket-Protocol: mcp`; a header supplied here is merged on top.
|
|
1742
1621
|
*
|
|
1743
|
-
* **`headers` exists
|
|
1622
|
+
* **`headers` exists here and not on the browser face's `{ url, protocols }`, and that
|
|
1744
1623
|
* divergence is deliberate rather than a lag: the host performs the WebSocket handshake.**
|
|
1745
1624
|
* This face owns its own `node:http(s)` upgrade request, so it can set any header on it. A
|
|
1746
1625
|
* page cannot — the native `WebSocket` constructor takes a URL and subprotocols and nothing
|
|
@@ -1758,21 +1637,21 @@ export declare interface WebSocketClientTransportOptions {
|
|
|
1758
1637
|
*
|
|
1759
1638
|
* @remarks
|
|
1760
1639
|
* - `emitter` — the emitter of the `@orkestrel/server` spine this handler is registered on
|
|
1761
|
-
* (`server.emitter`).
|
|
1640
|
+
* (`server.emitter`). Required: on its `stop` event the handler closes every socket it
|
|
1762
1641
|
* still owns with the RFC 6455 close handshake, so the spine's drain settles at once. An
|
|
1763
1642
|
* upgraded socket is detached from the connection set the spine's own close walks, so
|
|
1764
1643
|
* nothing but the claimant can end it — leave it open and `stop()` spends its whole
|
|
1765
1644
|
* `drain` budget and then cuts the connection mid-protocol.
|
|
1766
|
-
* - `path` — the request path the upgrade handler
|
|
1645
|
+
* - `path` — the request path the upgrade handler claims; defaults to
|
|
1767
1646
|
* {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`, the same path the HTTP
|
|
1768
|
-
* transport mounts at). A protocol-upgrade request to any
|
|
1647
|
+
* transport mounts at). A protocol-upgrade request to any other path is declined
|
|
1769
1648
|
* (the handler returns `false`, so the spine fans it to the next handler or destroys it).
|
|
1770
1649
|
* - `subprotocol` — the WebSocket subprotocol selected in the `101` handshake's
|
|
1771
|
-
* `Sec-WebSocket-Protocol`; defaults to {@link import('
|
|
1650
|
+
* `Sec-WebSocket-Protocol`; defaults to {@link import('@orkestrel/mcp').MCP_WEBSOCKET_SUBPROTOCOL}
|
|
1772
1651
|
* (`'mcp'`). It is sent only when the client's offer contains that token.
|
|
1773
1652
|
*
|
|
1774
|
-
* Auth / origin policy is deliberately
|
|
1775
|
-
* transport is
|
|
1653
|
+
* Auth / origin policy is deliberately absent: like the HTTP transport, the WebSocket
|
|
1654
|
+
* transport is mechanism — compose a guard in front (a `Server.upgrade` handler registered
|
|
1776
1655
|
* before this one can decline an unauthenticated upgrade).
|
|
1777
1656
|
*/
|
|
1778
1657
|
export declare interface WebSocketServerOptions {
|
|
@@ -1782,30 +1661,30 @@ export declare interface WebSocketServerOptions {
|
|
|
1782
1661
|
}
|
|
1783
1662
|
|
|
1784
1663
|
/**
|
|
1785
|
-
*
|
|
1786
|
-
* {@link
|
|
1787
|
-
*
|
|
1664
|
+
* Wraps a {@link NodeWebSocketInterface} (the RFC 6455 wire wrapper) as a
|
|
1665
|
+
* {@link MCPMessageTransportInterface} — the per-connection JSON-RPC-over-WebSocket server
|
|
1666
|
+
* bridge, the bidirectional JSON-RPC message channel
|
|
1788
1667
|
* `createWebSocketServer` pumps `mcp.dispatch` over and the egress mirror's
|
|
1789
1668
|
* {@link import('./WebSocketClientTransport.js').WebSocketClientTransport} reuses.
|
|
1790
1669
|
*
|
|
1791
1670
|
* @remarks
|
|
1792
|
-
* - **Reuses `
|
|
1671
|
+
* - **Reuses `MCPMessageTransportInterface`.** It is the same generic carrier the HTTP
|
|
1793
1672
|
* client transport implements — `emitter` (`message` / `close` / `error`), `start`,
|
|
1794
|
-
* `send`, `close` — so the WebSocket server and client both speak
|
|
1673
|
+
* `send`, `close` — so the WebSocket server and client both speak one transport contract,
|
|
1795
1674
|
* no near-duplicate sibling interface. `session` is `undefined` (the stateless v1; a
|
|
1796
1675
|
* session id is the deferred sessions tier). The name keeps the role explicit even though
|
|
1797
1676
|
* the shape is shared.
|
|
1798
1677
|
* - **Inbound (`message`).** `start()` subscribes to the socket's `message` event; each text
|
|
1799
|
-
* frame
|
|
1678
|
+
* frame runs through the shared `deliverMessage` fold (parse, then narrow) — a
|
|
1800
1679
|
* well-formed {@link JSONRPCMessage} is re-emitted on this transport's `message` event (the
|
|
1801
1680
|
* parsed envelope the {@link import('@orkestrel/mcp').MCPServerInterface} pump dispatches), while
|
|
1802
|
-
* a non-JSON or non-message frame is surfaced on `error` and
|
|
1681
|
+
* a non-JSON or non-message frame is surfaced on `error` and dropped, never thrown. It
|
|
1803
1682
|
* also bridges the socket's `close` → this transport's `close`, and the socket's `error`.
|
|
1804
1683
|
* - **Outbound (`send`).** `send(message)` writes one text frame
|
|
1805
1684
|
* (`nodeWs.send(JSON.stringify(message))`). The underlying wrapper no-ops a write on a
|
|
1806
1685
|
* non-open socket and confirms nothing, so this bridge answers a closed channel from its own
|
|
1807
1686
|
* state and the socket's `readyState`: a `send` after `close()`, after the peer's close, or on
|
|
1808
|
-
* a socket that is not `OPEN`
|
|
1687
|
+
* a socket that is not `OPEN` rejects with `WebSocket transport is not connected` rather than
|
|
1809
1688
|
* resolving on a frame nobody wrote. `bindServer` catches that rejection and routes it to the
|
|
1810
1689
|
* dispatcher's `error` event, and it aborts every in-flight request the moment this transport's
|
|
1811
1690
|
* `close` fires — so a peer that disconnects mid-request is answered by no write at all.
|
|
@@ -1814,18 +1693,18 @@ export declare interface WebSocketServerOptions {
|
|
|
1814
1693
|
* (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives
|
|
1815
1694
|
* between that release and the peer's close echo reaches nothing: the socket-driven close path
|
|
1816
1695
|
* releases the same way, so a closed transport is never subscribed to a live socket.
|
|
1817
|
-
* - **Observable.** Owns the `emitter` ({@link
|
|
1696
|
+
* - **Observable.** Owns the `emitter` ({@link MCPMessageTransportEventMap}); the emitter
|
|
1818
1697
|
* isolates a listener throw (a buggy observer never corrupts the bridge). `error` is a
|
|
1819
|
-
*
|
|
1698
|
+
* domain event (a transport-level fault), distinct from the emitter's listener-error channel.
|
|
1820
1699
|
*/
|
|
1821
|
-
export declare class WebSocketServerTransport implements
|
|
1700
|
+
export declare class WebSocketServerTransport implements MCPMessageTransportInterface {
|
|
1822
1701
|
#private;
|
|
1823
1702
|
constructor(socket: NodeWebSocketInterface);
|
|
1824
|
-
get emitter(): EmitterInterface<
|
|
1703
|
+
get emitter(): EmitterInterface<MCPMessageTransportEventMap>;
|
|
1825
1704
|
get session(): string | undefined;
|
|
1826
1705
|
get duplex(): boolean;
|
|
1827
1706
|
start(): Promise<void>;
|
|
1828
|
-
send(message:
|
|
1707
|
+
send(message: JSONRPCMessage): Promise<void>;
|
|
1829
1708
|
close(): Promise<void>;
|
|
1830
1709
|
}
|
|
1831
1710
|
|
|
@@ -1836,12 +1715,12 @@ export declare class WebSocketServerTransport implements MCPClientTransportInter
|
|
|
1836
1715
|
* The completion callback is the writable channel's backpressure boundary. A callback error and
|
|
1837
1716
|
* a synchronous `write` throw reject the returned promise with the original value.
|
|
1838
1717
|
*
|
|
1839
|
-
* That callback is the
|
|
1718
|
+
* That callback is the only thing that settles the promise: this helper holds no timer and no
|
|
1840
1719
|
* abort, so an output that neither confirms nor fails the write parks the promise for as long as
|
|
1841
1720
|
* the caller-owned stream holds the callback. A caller wanting a bound races this promise against
|
|
1842
1721
|
* one it owns — {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
|
|
1843
1722
|
* registers such a bound per send and rejects it on `close()`, so closing the transport settles
|
|
1844
|
-
* the
|
|
1723
|
+
* the caller's `send` while the abandoned write stays with the stream that still holds its
|
|
1845
1724
|
* callback, reachable from nothing the transport retains.
|
|
1846
1725
|
*
|
|
1847
1726
|
* @param output - The writable stream that receives the line
|