@orkestrel/mcp 0.0.19 → 0.0.21
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 +23 -22
- package/dist/src/browser/index.d.ts +92 -70
- package/dist/src/browser/index.js +164 -81
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/core/index.cjs +269 -257
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +351 -358
- package/dist/src/core/index.d.ts +351 -358
- package/dist/src/core/index.js +270 -257
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +331 -173
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +266 -146
- package/dist/src/server/index.d.ts +266 -146
- package/dist/src/server/index.js +331 -173
- package/dist/src/server/index.js.map +1 -1
- package/package.json +13 -10
|
@@ -55,17 +55,17 @@ export declare function acceptsEventStream(request: Request): boolean;
|
|
|
55
55
|
export declare function allowsOrigin(request: Request, options?: MCPOriginOptions): boolean;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
58
|
+
* Bridges a message-channel {@link MCPClientTransportInterface} (the shape the stdio and
|
|
59
59
|
* WebSocket SERVER transports already implement) into the environment-agnostic
|
|
60
|
-
* {@link import('@
|
|
60
|
+
* {@link import('@orkestrel/mcp').MCPTransportInterface} port — the adapter
|
|
61
61
|
* {@link import('./factories.js').createStdioServer} and {@link
|
|
62
62
|
* import('./factories.js').createWebSocketServer} pipe through `bindServer`, so the
|
|
63
|
-
* request/reply/error pump those
|
|
63
|
+
* request/reply/error pump those factories used to hand-roll identically now
|
|
64
64
|
* lives ONCE in the core binder.
|
|
65
65
|
*
|
|
66
66
|
* @remarks
|
|
67
67
|
* `send` decodes the already-serialized reply string back to a {@link JSONRPCMessage}
|
|
68
|
-
* and writes it
|
|
68
|
+
* and writes it through `transport.send` (the same `JSON.stringify` the underlying
|
|
69
69
|
* transport already performs, so the wire bytes are unchanged). `listen` filters
|
|
70
70
|
* `transport`'s `message` event to INVOCATIONS ONLY — requests and notifications, never a
|
|
71
71
|
* stray response, exactly as the prior hand-rolled pumps did — and re-serializes each one
|
|
@@ -83,24 +83,24 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
|
|
|
83
83
|
* decode rather than after it. Removing the cost means giving `MCPTransportInterface` a
|
|
84
84
|
* message-shaped face beside its string one, which every transport would then carry.
|
|
85
85
|
*
|
|
86
|
-
* @remarks Per {@link import('@
|
|
86
|
+
* @remarks Per {@link import('@orkestrel/mcp').MCPTransportInterface}, `listen`/`closed`
|
|
87
87
|
* each hold THE SINGLE current handler (a second call REPLACES the first, never adds).
|
|
88
|
-
*
|
|
88
|
+
* Because the underlying `transport.emitter` is ADD-based (`on` subscribes, never
|
|
89
89
|
* replaces), this bridge installs ONE stable emitter listener per event on first use
|
|
90
|
-
* and re-routes it to whichever handler is
|
|
90
|
+
* and re-routes it to whichever handler is active (`undefined` while
|
|
91
91
|
* none is), so rebinding never double-dispatches.
|
|
92
92
|
*
|
|
93
|
-
* @remarks A response whose `result` serializes away (
|
|
93
|
+
* @remarks A response whose `result` serializes away (for example, `undefined`) is dropped by
|
|
94
94
|
* the message validators on the wire's decode side — an asymmetry the stdio/WS carrier
|
|
95
|
-
* shares with the streamable-HTTP face,
|
|
95
|
+
* shares with the streamable-HTTP face, because both round-trip through `JSON.stringify`
|
|
96
96
|
* / `JSON.parse` before re-validation.
|
|
97
97
|
*
|
|
98
98
|
* @param transport - The message-channel transport to bridge (stdio or WebSocket)
|
|
99
|
-
* @returns An {@link import('@
|
|
99
|
+
* @returns An {@link import('@orkestrel/mcp').MCPTransportInterface} `bindServer` can drive
|
|
100
100
|
*
|
|
101
101
|
* @example
|
|
102
102
|
* ```ts
|
|
103
|
-
* import { bindServer } from '@
|
|
103
|
+
* import { bindServer } from '@orkestrel/mcp'
|
|
104
104
|
*
|
|
105
105
|
* const transport = new StdioServerTransport(process.stdin, process.stdout)
|
|
106
106
|
* bindServer(mcp, bridgeMessageTransport(transport))
|
|
@@ -109,7 +109,7 @@ export declare function allowsOrigin(request: Request, options?: MCPOriginOption
|
|
|
109
109
|
export declare function bridgeMessageTransport(transport: MCPClientTransportInterface): MCPTransportInterface;
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
|
-
*
|
|
112
|
+
* Creates the HTTP CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
113
113
|
* — a {@link MCPClientTransportInterface} that drives a REMOTE Streamable-HTTP MCP server
|
|
114
114
|
* over `fetch`. The egress mirror of {@link createMCPRoutes}.
|
|
115
115
|
*
|
|
@@ -117,9 +117,9 @@ export declare function bridgeMessageTransport(transport: MCPClientTransportInte
|
|
|
117
117
|
* Hand it to `createMCPClient({ transport })`: each JSON-RPC message the client sends is
|
|
118
118
|
* `POST`ed to `options.url` with `content-type: application/json` and an `Accept` of
|
|
119
119
|
* both `application/json` and `text/event-stream` (the server answers with EITHER — a
|
|
120
|
-
* plain JSON envelope or a Streamable-HTTP SSE `data:` event, decoded
|
|
120
|
+
* plain JSON envelope or a Streamable-HTTP SSE `data:` event, decoded with `@orkestrel/sse`),
|
|
121
121
|
* and the reply is surfaced on the transport's `message` event for the client's id
|
|
122
|
-
* correlation. Add `options.headers` (
|
|
122
|
+
* correlation. Add `options.headers` (for example, an `Authorization` bearer) to reach a guarded
|
|
123
123
|
* server. `start` / `close` hold no connection; against a STATEFUL server it captures the
|
|
124
124
|
* `mcp-session-id` from `initialize` and echoes it on later requests. It also captures
|
|
125
125
|
* the initialize result's `protocolVersion` and sends `mcp-protocol-version` alone on each
|
|
@@ -128,13 +128,13 @@ export declare function bridgeMessageTransport(transport: MCPClientTransportInte
|
|
|
128
128
|
*
|
|
129
129
|
* @param options - `url` (the remote endpoint; REQUIRED), optional `headers` merged onto
|
|
130
130
|
* every request, optional `fetch` (default `globalThis.fetch`), and optional `timeout`
|
|
131
|
-
* (ms, applied
|
|
131
|
+
* (ms, applied with `AbortSignal.timeout`); see {@link HTTPClientTransportOptions}
|
|
132
132
|
* @returns A working {@link MCPClientTransportInterface} over `fetch`
|
|
133
133
|
*
|
|
134
134
|
* @example
|
|
135
135
|
* ```ts
|
|
136
|
-
* import { createMCPClient } from '@
|
|
137
|
-
* import { createHTTPClientTransport } from '@
|
|
136
|
+
* import { createMCPClient } from '@orkestrel/mcp'
|
|
137
|
+
* import { createHTTPClientTransport } from '@orkestrel/mcp/server'
|
|
138
138
|
*
|
|
139
139
|
* const client = createMCPClient({
|
|
140
140
|
* transport: createHTTPClientTransport({ url: 'http://localhost:3000/mcp' }),
|
|
@@ -146,7 +146,7 @@ export declare function bridgeMessageTransport(transport: MCPClientTransportInte
|
|
|
146
146
|
export declare function createHTTPClientTransport(options: HTTPClientTransportOptions): MCPClientTransportInterface;
|
|
147
147
|
|
|
148
148
|
/**
|
|
149
|
-
*
|
|
149
|
+
* Adapts the installed server token primitives to the host-neutral MCP continuation port.
|
|
150
150
|
*
|
|
151
151
|
* @param secret - Current signing secret or `[current, ...older]` rotation list
|
|
152
152
|
* @returns A continuation port that seals and opens opaque canonical state strings
|
|
@@ -154,7 +154,7 @@ export declare function createHTTPClientTransport(options: HTTPClientTransportOp
|
|
|
154
154
|
export declare function createMCPContinuation(secret: TokenSecret): MCPContinuationInterface;
|
|
155
155
|
|
|
156
156
|
/**
|
|
157
|
-
*
|
|
157
|
+
* Creates the Streamable-HTTP POST handler used by `createMCPRoutes`.
|
|
158
158
|
*
|
|
159
159
|
* @remarks
|
|
160
160
|
* Modern requests require matching protocol/method headers and a matching name header only
|
|
@@ -180,7 +180,7 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
|
|
|
180
180
|
* import { createToolManager } from '@orkestrel/tool'
|
|
181
181
|
*
|
|
182
182
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
183
|
-
* const handler = createMCPPostHandler(createMCPLegacy(mcp), { streaming: true })
|
|
183
|
+
* const handler = createMCPPostHandler(createMCPLegacy(mcp), { streaming: true }) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
184
184
|
* await handler(new Request('http://localhost/mcp', {
|
|
185
185
|
* method: 'POST',
|
|
186
186
|
* body: '{"jsonrpc":"2.0","method":"ping","id":1}',
|
|
@@ -190,8 +190,8 @@ export declare function createMCPContinuation(secret: TokenSecret): MCPContinuat
|
|
|
190
190
|
export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatcherInterface, options?: HTTPHandlerOptions<TState>): (request: Request, context?: RouteContext<string, TState>) => Promise<Response>;
|
|
191
191
|
|
|
192
192
|
/**
|
|
193
|
-
*
|
|
194
|
-
* {@link MCPDispatcherInterface} (the `@
|
|
193
|
+
* Creates the MCP Streamable-HTTP transport routes — mounts a transport-agnostic
|
|
194
|
+
* {@link MCPDispatcherInterface} (the `@orkestrel/mcp` dispatch boundary) on the fetch-standard router
|
|
195
195
|
* spine, pumping each `POST` body through `mcp.dispatch`. Returns the {@link RouteInput}s to
|
|
196
196
|
* hand to `router.add(...)`.
|
|
197
197
|
*
|
|
@@ -214,7 +214,7 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
|
|
|
214
214
|
*
|
|
215
215
|
* When `streaming` is enabled (the default) and the client `Accept`s `text/event-stream`,
|
|
216
216
|
* the `200` reply is framed as a Streamable-HTTP SSE response (one `data:` event carrying
|
|
217
|
-
* the JSON-RPC envelope, then the stream ends)
|
|
217
|
+
* the JSON-RPC envelope, then the stream ends) through `@orkestrel/server`'s generic
|
|
218
218
|
* {@link import('@orkestrel/server').openStream} seam; otherwise it is a plain JSON body.
|
|
219
219
|
*
|
|
220
220
|
* **Sessions are a SEPARATE, plug-and-play middleware.** `createMCPRoutes` mints / reads no
|
|
@@ -236,19 +236,20 @@ export declare function createMCPPostHandler<TState = unknown>(mcp: MCPDispatche
|
|
|
236
236
|
*
|
|
237
237
|
* @example
|
|
238
238
|
* ```ts
|
|
239
|
-
* import { createMCPLegacy, createMCPServer
|
|
240
|
-
* import { createMCPRoutes } from '@
|
|
239
|
+
* import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
|
|
240
|
+
* import { createMCPRoutes } from '@orkestrel/mcp/server'
|
|
241
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
241
242
|
*
|
|
242
243
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
243
|
-
* const routes = createMCPRoutes(createMCPLegacy(mcp)) //
|
|
244
|
+
* const routes = createMCPRoutes(createMCPLegacy(mcp)) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
244
245
|
* ```
|
|
245
246
|
*/
|
|
246
247
|
export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInterface, options?: HTTPTransportOptions<TState>): ReadonlyArray<RouteInput<string, TState>>;
|
|
247
248
|
|
|
248
249
|
/**
|
|
249
|
-
*
|
|
250
|
+
* Creates the native MCP session {@link MiddlewareHandler} — the plug-and-play stateful layer
|
|
250
251
|
* that fronts a session-agnostic {@link import('./factories.js').createMCPRoutes}. Compose it
|
|
251
|
-
*
|
|
252
|
+
* with `router.use(createMCPSession())` (or the equivalent middleware seam), mirroring any
|
|
252
253
|
* other closure-scoped stateful middleware. Has NO dependency on `@orkestrel/middleware` — the
|
|
253
254
|
* session store, mint-on-`initialize`, and resumable stream are all native to this package.
|
|
254
255
|
*
|
|
@@ -257,11 +258,11 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
|
|
|
257
258
|
* `path` (default {@link DEFAULT_MCP_PATH}); a request to any other path passes straight
|
|
258
259
|
* through (`next()`).
|
|
259
260
|
*
|
|
260
|
-
* A modern-shaped POST also passes straight through
|
|
261
|
+
* A modern-shaped POST also passes straight through with `next()`, ignoring any session id.
|
|
261
262
|
* The remaining behavior is the legacy session layer:
|
|
262
263
|
*
|
|
263
264
|
* - **`POST {path}`.** Buffers `const text = await request.text()` (so the downstream route
|
|
264
|
-
* can re-read it
|
|
265
|
+
* can re-read it from a freshly-built forwarded `Request`). Resolves a session through {@link
|
|
265
266
|
* readSessionHeader}: a VALID id touches the entry and sets `context.state.session`; an
|
|
266
267
|
* ABSENT / unknown id whose (guarded) body parses to an `initialize` request ({@link
|
|
267
268
|
* isInitializeRequest}) MINTS a fresh {@link MCPSession} (`crypto.randomUUID()`, `capacity`)
|
|
@@ -276,7 +277,7 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
|
|
|
276
277
|
* a `DELETE` arriving while the request was suspended is not undone.
|
|
277
278
|
* - **`GET {path}`.** Resolves the session the same way (no mint — only `initialize` mints);
|
|
278
279
|
* an invalid / unknown id is the same `404`. A valid session opens the resumable
|
|
279
|
-
* server→client stream
|
|
280
|
+
* server→client stream through `@orkestrel/server`'s {@link import('@orkestrel/server').openStream}:
|
|
280
281
|
* replays every event after the client's `Last-Event-ID` ({@link readLastEventId}) BEFORE
|
|
281
282
|
* attaching the stream for live pushes, then attaches; cancellation of the streamed response
|
|
282
283
|
* body composes with `request.signal` and detaches it. Long-lived — never `end()`ed here.
|
|
@@ -300,18 +301,20 @@ export declare function createMCPRoutes<TState = unknown>(mcp: MCPDispatcherInte
|
|
|
300
301
|
*
|
|
301
302
|
* @example
|
|
302
303
|
* ```ts
|
|
303
|
-
* import {
|
|
304
|
-
* import { createMCPRoutes, createMCPSession } from '@
|
|
304
|
+
* import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
|
|
305
|
+
* import { createMCPRoutes, createMCPSession } from '@orkestrel/mcp/server'
|
|
306
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
305
307
|
*
|
|
306
308
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
307
309
|
* router.use(createMCPSession({ ttl: 60_000 })) // stateful: mint + validate + resumable GET / DELETE
|
|
308
|
-
*
|
|
310
|
+
* // The route stays session-agnostic:
|
|
311
|
+
* router.add(createMCPRoutes(createMCPLegacy(mcp))) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
309
312
|
* ```
|
|
310
313
|
*/
|
|
311
314
|
export declare function createMCPSession<TState extends MCPSessionState>(options?: MCPSessionOptions): MiddlewareHandler<TState>;
|
|
312
315
|
|
|
313
316
|
/**
|
|
314
|
-
*
|
|
317
|
+
* Creates a readable stream from its pull and cancellation behaviours.
|
|
315
318
|
*
|
|
316
319
|
* @param pull - The behaviour that supplies the stream's next chunk
|
|
317
320
|
* @param cancel - The behaviour that releases the stream after consumer cancellation
|
|
@@ -320,28 +323,30 @@ export declare function createMCPSession<TState extends MCPSessionState>(options
|
|
|
320
323
|
export declare function createReadableStream<T>(pull: (controller: ReadableStreamDefaultController<T>) => void | PromiseLike<void>, cancel: (reason?: unknown) => void | PromiseLike<void>): ReadableStream<T>;
|
|
321
324
|
|
|
322
325
|
/**
|
|
323
|
-
*
|
|
324
|
-
* — a {@link
|
|
326
|
+
* Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
327
|
+
* — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
|
|
325
328
|
* over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
326
329
|
* createHTTPClientTransport} and {@link createWebSocketClientTransport}.
|
|
327
330
|
*
|
|
328
331
|
* @remarks
|
|
329
332
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
|
|
330
333
|
* spawns `options.command` with `options.args` and `options.env`, piping its
|
|
331
|
-
* `stdin`/`stdout` for the JSON-RPC channel
|
|
332
|
-
*
|
|
334
|
+
* `stdin`/`stdout` for the JSON-RPC channel. The child's `stderr` is piped too, and
|
|
335
|
+
* retained as a bounded tail this transport reports as `evidence` — the parent never
|
|
336
|
+
* inherits it. Each JSON-RPC message the client `send`s is written as one
|
|
333
337
|
* newline-terminated line to the child's `stdin`; each decoded reply line from the
|
|
334
338
|
* child's `stdout` is surfaced on the transport's `message` event for the client's
|
|
335
339
|
* id correlation.
|
|
336
340
|
*
|
|
337
341
|
* @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
|
|
338
342
|
* and optional `env`; see {@link StdioClientTransportOptions}
|
|
339
|
-
* @returns A working {@link
|
|
343
|
+
* @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
|
|
344
|
+
* whose `evidence` carries the supervised child's bounded stderr tail
|
|
340
345
|
*
|
|
341
346
|
* @example
|
|
342
347
|
* ```ts
|
|
343
|
-
* import { createMCPClient } from '@
|
|
344
|
-
* import { createStdioClientTransport } from '@
|
|
348
|
+
* import { createMCPClient } from '@orkestrel/mcp'
|
|
349
|
+
* import { createStdioClientTransport } from '@orkestrel/mcp/server'
|
|
345
350
|
*
|
|
346
351
|
* const client = createMCPClient({
|
|
347
352
|
* transport: createStdioClientTransport({ command: 'node', args: ['./server.js'] }),
|
|
@@ -350,19 +355,19 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
350
355
|
* const tools = await client.tools()
|
|
351
356
|
* ```
|
|
352
357
|
*/
|
|
353
|
-
export declare function createStdioClientTransport(options: StdioClientTransportOptions):
|
|
358
|
+
export declare function createStdioClientTransport(options: StdioClientTransportOptions): StdioClientTransportInterface;
|
|
354
359
|
|
|
355
360
|
/**
|
|
356
|
-
*
|
|
361
|
+
* Creates the MCP stdio transport INGRESS — pumps a transport-agnostic {@link
|
|
357
362
|
* MCPDispatcherInterface} over newline-delimited JSON-RPC on `stdin`/`stdout` (or an
|
|
358
363
|
* injected stream pair), the stdio mirror of {@link createWebSocketServer}.
|
|
359
364
|
*
|
|
360
365
|
* @remarks
|
|
361
366
|
* Wraps `options.input` (default `process.stdin`) / `options.output` (default
|
|
362
367
|
* `process.stdout`) in a {@link import('./transports/StdioServerTransport.js').StdioServerTransport}
|
|
363
|
-
* and pipes it through the core {@link import('@
|
|
364
|
-
*
|
|
365
|
-
* import('@
|
|
368
|
+
* and pipes it through the core {@link import('@orkestrel/mcp').MCPTransportInterface} port
|
|
369
|
+
* through {@link import('./helpers.js').bridgeMessageTransport} + {@link
|
|
370
|
+
* import('@orkestrel/mcp').bindServer}: each inbound REQUEST runs through `mcp.dispatch`, and
|
|
366
371
|
* a defined response is written back as a newline-terminated line — a NOTIFICATION
|
|
367
372
|
* writes nothing, and a non-request message is ignored. A `dispatch` / `send` fault
|
|
368
373
|
* surfaces on `mcp.emitter`'s `error` event rather than escaping the (async) message
|
|
@@ -375,11 +380,13 @@ export declare function createStdioClientTransport(options: StdioClientTransport
|
|
|
375
380
|
*
|
|
376
381
|
* @example
|
|
377
382
|
* ```ts
|
|
378
|
-
* import {
|
|
379
|
-
* import { createStdioServer } from '@
|
|
383
|
+
* import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
|
|
384
|
+
* import { createStdioServer } from '@orkestrel/mcp/server'
|
|
385
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
380
386
|
*
|
|
381
387
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
382
|
-
*
|
|
388
|
+
* // An MCP client now connects over this process's stdio:
|
|
389
|
+
* createStdioServer(createMCPLegacy(mcp)).start() // answers `initialize` too; pass `mcp` alone for modern-only
|
|
383
390
|
* ```
|
|
384
391
|
*/
|
|
385
392
|
export declare function createStdioServer(mcp: MCPDispatcherInterface, options?: StdioServerOptions): {
|
|
@@ -388,7 +395,7 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
|
|
|
388
395
|
};
|
|
389
396
|
|
|
390
397
|
/**
|
|
391
|
-
*
|
|
398
|
+
* Creates the WebSocket CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
392
399
|
* — a {@link MCPClientTransportInterface} that drives a REMOTE MCP server over a WebSocket. The
|
|
393
400
|
* egress mirror of {@link createWebSocketServer} and the WebSocket sibling of {@link
|
|
394
401
|
* createHTTPClientTransport}.
|
|
@@ -397,11 +404,11 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
|
|
|
397
404
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`) performs
|
|
398
405
|
* the RFC 6455 client handshake against `options.url` (accepting a `ws://` / `wss://` or an
|
|
399
406
|
* `http://` / `https://` URL — a `ws(s)` scheme is converted to `http(s)` for the underlying
|
|
400
|
-
* upgrade request), validates the `Sec-WebSocket-Accept` (
|
|
407
|
+
* upgrade request), validates the `Sec-WebSocket-Accept` (with `@orkestrel/websocket`'s
|
|
401
408
|
* `computeWebSocketAccept`), and opens a persistent bidirectional frame channel; each JSON-RPC
|
|
402
409
|
* message the client `send`s is written as one masked text frame, and each decoded reply is
|
|
403
410
|
* surfaced on the transport's `message` event for the client's id correlation. Add
|
|
404
|
-
* `options.headers` (
|
|
411
|
+
* `options.headers` (for example, an `Authorization` bearer) to reach a guarded server.
|
|
405
412
|
*
|
|
406
413
|
* @param options - `url` (the remote WebSocket endpoint; REQUIRED) and optional `headers`
|
|
407
414
|
* merged onto the upgrade request; see {@link WebSocketClientTransportOptions}
|
|
@@ -409,8 +416,8 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
|
|
|
409
416
|
*
|
|
410
417
|
* @example
|
|
411
418
|
* ```ts
|
|
412
|
-
* import { createMCPClient } from '@
|
|
413
|
-
* import { createWebSocketClientTransport } from '@
|
|
419
|
+
* import { createMCPClient } from '@orkestrel/mcp'
|
|
420
|
+
* import { createWebSocketClientTransport } from '@orkestrel/mcp/server'
|
|
414
421
|
*
|
|
415
422
|
* const client = createMCPClient({
|
|
416
423
|
* transport: createWebSocketClientTransport({ url: 'ws://localhost:3000/mcp' }),
|
|
@@ -422,7 +429,7 @@ export declare function createStdioServer(mcp: MCPDispatcherInterface, options?:
|
|
|
422
429
|
export declare function createWebSocketClientTransport(options: WebSocketClientTransportOptions): MCPClientTransportInterface;
|
|
423
430
|
|
|
424
431
|
/**
|
|
425
|
-
*
|
|
432
|
+
* Creates the MCP WebSocket transport INGRESS — an {@link UpgradeHandler} that exposes a
|
|
426
433
|
* transport-agnostic {@link MCPDispatcherInterface} over a WebSocket, the WebSocket mirror of
|
|
427
434
|
* {@link createMCPRoutes}. Register it on the spine's upgrade seam.
|
|
428
435
|
*
|
|
@@ -437,11 +444,11 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
437
444
|
* A decline NEVER writes to the socket (it is not yet ours) — the spine owns the unclaimed
|
|
438
445
|
* outcome.
|
|
439
446
|
* - **Claims (returns `true`)** otherwise: it builds `createNodeWebSocket({ socket, key, head,
|
|
440
|
-
* protocol })` (SERVER mode → writes the `101` handshake,
|
|
441
|
-
*
|
|
447
|
+
* protocol })` (SERVER mode → writes the `101` handshake, selects the configured subprotocol
|
|
448
|
+
* only when the client's offer contains it, and sends UNMASKED frames), wraps it in a
|
|
442
449
|
* {@link WebSocketServerTransport}, and pipes it through the core {@link
|
|
443
|
-
* import('@
|
|
444
|
-
* import('./helpers.js').bridgeMessageTransport} + {@link import('@
|
|
450
|
+
* import('@orkestrel/mcp').MCPTransportInterface} port through {@link
|
|
451
|
+
* import('./helpers.js').bridgeMessageTransport} + {@link import('@orkestrel/mcp').bindServer}:
|
|
445
452
|
* each inbound REQUEST runs through `mcp.dispatch`, and a defined response is written back
|
|
446
453
|
* as a frame — a NOTIFICATION sends nothing, and a non-request message (a stray response) is
|
|
447
454
|
* ignored. A `dispatch` / `send` fault surfaces on `mcp.emitter`'s `error` event rather than
|
|
@@ -466,23 +473,25 @@ export declare function createWebSocketClientTransport(options: WebSocketClientT
|
|
|
466
473
|
*
|
|
467
474
|
* @example
|
|
468
475
|
* ```ts
|
|
469
|
-
* import {
|
|
470
|
-
* import { createWebSocketServer } from '@
|
|
476
|
+
* import { createMCPLegacy, createMCPServer } from '@orkestrel/mcp'
|
|
477
|
+
* import { createWebSocketServer } from '@orkestrel/mcp/server'
|
|
478
|
+
* import { createToolManager } from '@orkestrel/tool'
|
|
471
479
|
*
|
|
472
480
|
* const mcp = createMCPServer({ identity: { name: 'docs', version: '1.0.0' }, tools: createToolManager() })
|
|
473
|
-
*
|
|
481
|
+
* // Claims the MCP upgrade at ws://…/mcp:
|
|
482
|
+
* server.upgrade(createWebSocketServer(createMCPLegacy(mcp), { emitter: server.emitter })) // answers `initialize` too; pass `mcp` alone for modern-only
|
|
474
483
|
* ```
|
|
475
484
|
*/
|
|
476
485
|
export declare function createWebSocketServer(mcp: MCPDispatcherInterface, options: WebSocketServerOptions): UpgradeHandler;
|
|
477
486
|
|
|
478
487
|
/**
|
|
479
|
-
*
|
|
488
|
+
* Decodes one SSE event's `data` string into a {@link JSONRPCMessage}, or `undefined`
|
|
480
489
|
* when it is not one — the per-event step {@link readEventStream} folds over.
|
|
481
490
|
*
|
|
482
491
|
* @remarks
|
|
483
492
|
* `JSON.parse`s the `data` (the server serializes the JSON-RPC envelope as the event's
|
|
484
493
|
* `data`) inside a try/catch and narrows the parsed value with `parseJSONRPCMessage`.
|
|
485
|
-
* Total
|
|
494
|
+
* Total: malformed JSON or a non-message value yields `undefined`, never throws.
|
|
486
495
|
*
|
|
487
496
|
* @param data - One SSE event's `data` payload
|
|
488
497
|
* @returns The decoded {@link JSONRPCMessage}, or `undefined`
|
|
@@ -528,19 +537,20 @@ export declare const DEFAULT_MCP_SESSION_CAPACITY = 1024;
|
|
|
528
537
|
export declare const DEFAULT_MCP_SESSION_TTL = 300000;
|
|
529
538
|
|
|
530
539
|
/**
|
|
531
|
-
*
|
|
540
|
+
* Decodes and delivers each complete newline-framed line onto a {@link
|
|
532
541
|
* MCPClientTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
|
|
533
|
-
* transports
|
|
542
|
+
* transports run their framed lines through: the server transport frames with {@link
|
|
543
|
+
* extractLines}, the client transport takes its lines from the process supervisor.
|
|
534
544
|
*
|
|
535
545
|
* @remarks
|
|
536
546
|
* A blank line is skipped (a stray trailing newline). Every other line is decoded
|
|
537
547
|
* with {@link decodeEvent} (`JSON.parse` + `parseJSONRPCMessage`, guarded); a
|
|
538
548
|
* well-formed {@link JSONRPCMessage} emits `message`, a malformed / non-message line
|
|
539
|
-
* emits `error` (
|
|
549
|
+
* emits `error` (total, never throws). Pure w.r.t. its own state — the emit is
|
|
540
550
|
* the caller-owned side effect.
|
|
541
551
|
*
|
|
542
552
|
* @param emitter - The transport's {@link EmitterInterface} to emit `message` / `error` onto
|
|
543
|
-
* @param lines - The complete lines
|
|
553
|
+
* @param lines - The complete lines to decode and deliver
|
|
544
554
|
*/
|
|
545
555
|
export declare function dispatchLines(emitter: EmitterInterface<MCPClientTransportEventMap>, lines: readonly string[]): void;
|
|
546
556
|
|
|
@@ -555,7 +565,7 @@ export declare function dispatchLines(emitter: EmitterInterface<MCPClientTranspo
|
|
|
555
565
|
* - `message` — the server→client {@link JSONRPCMessage} that was pushed.
|
|
556
566
|
* - `timestamp` — the epoch-ms instant the entry was appended, read by the TTL eviction.
|
|
557
567
|
*
|
|
558
|
-
* A plain value record (no behavior
|
|
568
|
+
* A plain value record (no behavior) — the unit {@link MCPSessionInterface.replay}
|
|
559
569
|
* returns.
|
|
560
570
|
*/
|
|
561
571
|
export declare interface EventStoreEntry {
|
|
@@ -565,7 +575,7 @@ export declare interface EventStoreEntry {
|
|
|
565
575
|
}
|
|
566
576
|
|
|
567
577
|
/**
|
|
568
|
-
*
|
|
578
|
+
* Folds one more chunk of raw stdio bytes into a newline-framed buffer — the shared
|
|
569
579
|
* line-framing step both stdio transports (client and server) read their inbound
|
|
570
580
|
* newline-delimited JSON-RPC messages through.
|
|
571
581
|
*
|
|
@@ -592,12 +602,12 @@ export declare function extractLines(buffer: string, chunk: string): LineExtract
|
|
|
592
602
|
* - **Request/response over `fetch`.** `send(message)` POSTs the JSON-serialized
|
|
593
603
|
* message to `options.url` with `content-type: application/json` and an
|
|
594
604
|
* `Accept` of BOTH `application/json` and `text/event-stream` (so the server may
|
|
595
|
-
* answer with either framing) — plus any `options.headers` (
|
|
605
|
+
* answer with either framing) — plus any `options.headers` (for example, an `Authorization`
|
|
596
606
|
* bearer). It then decodes the reply and emits each decoded {@link JSONRPCMessage} on
|
|
597
|
-
* the `message` event the {@link import('@
|
|
607
|
+
* the `message` event the {@link import('@orkestrel/mcp').MCPClientInterface} subscribes
|
|
598
608
|
* to.
|
|
599
609
|
* - **Both reply framings.** A `200` with an `application/json` body is parsed with
|
|
600
|
-
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded
|
|
610
|
+
* `parseJSONRPCMessage`; a `200` with a `text/event-stream` body is decoded with the
|
|
601
611
|
* `@orkestrel/sse` {@link import('@orkestrel/sse').SSEParserInterface} ({@link
|
|
602
612
|
* readEventStream}) — the inverse of the server's `openStream` seam, so the wire
|
|
603
613
|
* round-trips. A `202`
|
|
@@ -615,10 +625,15 @@ export declare function extractLines(buffer: string, chunk: string): LineExtract
|
|
|
615
625
|
* Before initialize returns, neither captured legacy header is sent.
|
|
616
626
|
* `close()` clears the captured protocol so a reconnect's `initialize`
|
|
617
627
|
* POST is headerless; the captured `session` persists across `close()`.
|
|
618
|
-
* -
|
|
628
|
+
* - **`close()` releases what is in flight.** Every `fetch` this transport still has open is
|
|
629
|
+
* ABORTED, which cancels the response body a `send` is reading — an SSE reply the server
|
|
630
|
+
* never ends would otherwise outlive the transport, with nothing left able to reach it. The
|
|
631
|
+
* aborted read surfaces on `error` and the `send` reporting it resolves. `close()` is
|
|
632
|
+
* idempotent (one `close` event per connected lifetime), and `start()` opens the next one.
|
|
633
|
+
* - **Total at the boundary.** Every reply is narrowed (`parseJSONRPCMessage`,
|
|
619
634
|
* the SSE decoder) — a non-message reply is dropped, never asserted; a `fetch` /
|
|
620
635
|
* decode failure surfaces on the `error` event rather than escaping `send`.
|
|
621
|
-
* - **Observable
|
|
636
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); fires
|
|
622
637
|
* `message` per decoded reply, `error` on a fault, and `close` on `close()`.
|
|
623
638
|
*
|
|
624
639
|
* @example
|
|
@@ -645,9 +660,9 @@ export declare class HTTPClientTransport implements MCPClientTransportInterface_
|
|
|
645
660
|
*
|
|
646
661
|
* @remarks
|
|
647
662
|
* - `url` — the absolute URL of the remote server's Streamable-HTTP endpoint (the
|
|
648
|
-
* `POST` target every JSON-RPC message is written to,
|
|
663
|
+
* `POST` target every JSON-RPC message is written to, for example,
|
|
649
664
|
* `http://localhost:3000/mcp`). REQUIRED.
|
|
650
|
-
* - `headers` — extra request headers merged onto every `POST` (
|
|
665
|
+
* - `headers` — extra request headers merged onto every `POST` (for example, an
|
|
651
666
|
* `Authorization` bearer for a guarded server). The transport always sets
|
|
652
667
|
* `content-type: application/json` and an `Accept` of both `application/json` and
|
|
653
668
|
* `text/event-stream` (so the server may answer with either framing); a key supplied
|
|
@@ -655,8 +670,10 @@ export declare class HTTPClientTransport implements MCPClientTransportInterface_
|
|
|
655
670
|
* - `fetch` — the `fetch` implementation to issue each `POST` with; defaults to
|
|
656
671
|
* `globalThis.fetch`. Injectable for a test double or a non-global `fetch`.
|
|
657
672
|
* - `timeout` — an optional per-request timeout in milliseconds; when set, each
|
|
658
|
-
* `fetch` call
|
|
659
|
-
*
|
|
673
|
+
* `fetch` call composes that deadline with the transport's own close through
|
|
674
|
+
* `AbortSignal.any([close, AbortSignal.timeout(timeout)])`, so whichever fires first
|
|
675
|
+
* ends the request. Omit for no transport-level deadline; the close signal is passed
|
|
676
|
+
* either way.
|
|
660
677
|
*/
|
|
661
678
|
export declare interface HTTPClientTransportOptions {
|
|
662
679
|
readonly url: string;
|
|
@@ -666,7 +683,7 @@ export declare interface HTTPClientTransportOptions {
|
|
|
666
683
|
}
|
|
667
684
|
|
|
668
685
|
/**
|
|
669
|
-
*
|
|
686
|
+
* Composes one incoming HTTP request lifetime with one MCP-owned SSE response lifetime.
|
|
670
687
|
*
|
|
671
688
|
* @remarks
|
|
672
689
|
* The composed {@link signal} observes request abort and EVERY way this response can end
|
|
@@ -680,7 +697,8 @@ export declare interface HTTPClientTransportOptions {
|
|
|
680
697
|
*
|
|
681
698
|
* {@link bridge} preserves the source response status and headers, forwards its body bytes, and
|
|
682
699
|
* owns keepalive comments plus listener/timer cleanup until upstream completion, request abort,
|
|
683
|
-
* or consumer cancellation. This is a single-response lifecycle object, not a reusable bridge
|
|
700
|
+
* or consumer cancellation. This is a single-response lifecycle object, not a reusable bridge:
|
|
701
|
+
* a second {@link bridge} call THROWS rather than arming a second keepalive over one lifecycle.
|
|
684
702
|
* It supplies no handler or session policy.
|
|
685
703
|
*
|
|
686
704
|
* The keepalive interval is a BUDGET, sanitized like every other numeric knob in this package:
|
|
@@ -702,7 +720,7 @@ export declare interface HTTPClientTransportOptions {
|
|
|
702
720
|
export declare class HTTPDisconnect {
|
|
703
721
|
#private;
|
|
704
722
|
/**
|
|
705
|
-
*
|
|
723
|
+
* Creates the lifecycle composition for one request and its future SSE response.
|
|
706
724
|
*
|
|
707
725
|
* @param signal - The incoming request signal
|
|
708
726
|
* @param options - Optional keepalive `interval` in milliseconds; an invalid value falls back
|
|
@@ -717,7 +735,7 @@ export declare class HTTPDisconnect {
|
|
|
717
735
|
*/
|
|
718
736
|
get signal(): AbortSignal;
|
|
719
737
|
/**
|
|
720
|
-
*
|
|
738
|
+
* Bridges one open SSE response through cancellation-aware byte forwarding and keepalives.
|
|
721
739
|
*
|
|
722
740
|
* Consumer cancellation, a read failure while forwarding, and a keepalive tick that finds the
|
|
723
741
|
* SSE stream already closed each abort {@link signal}; consumer cancellation also cancels the
|
|
@@ -726,7 +744,8 @@ export declare class HTTPDisconnect {
|
|
|
726
744
|
*
|
|
727
745
|
* @param stream - The open SSE stream whose response will be consumed by the HTTP writer
|
|
728
746
|
* @returns A one-use response preserving status, status text, headers, and SSE body bytes
|
|
729
|
-
* @throws When the supplied SSE response
|
|
747
|
+
* @throws When this disconnect has already bridged a stream, or the supplied SSE response
|
|
748
|
+
* has no body
|
|
730
749
|
*/
|
|
731
750
|
bridge(stream: StreamInterface): Response;
|
|
732
751
|
}
|
|
@@ -765,7 +784,7 @@ export declare interface HTTPHandlerOptions<TState = unknown> {
|
|
|
765
784
|
/**
|
|
766
785
|
* Options for `createMCPRoutes` — the mount path plus the shared POST-handler options.
|
|
767
786
|
* `createMCPRoutes` is STATELESS; sessions are a separate middleware ({@link
|
|
768
|
-
* import('./middlewares.js').createMCPSession}), composed
|
|
787
|
+
* import('./middlewares.js').createMCPSession}), composed with `server.use`.
|
|
769
788
|
*
|
|
770
789
|
* @remarks
|
|
771
790
|
* `path` is the request path the single `POST` route answers; it defaults to {@link
|
|
@@ -779,7 +798,7 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
|
|
|
779
798
|
}
|
|
780
799
|
|
|
781
800
|
/**
|
|
782
|
-
*
|
|
801
|
+
* Infers the first required MCP HTTP header that is missing or mismatched.
|
|
783
802
|
*
|
|
784
803
|
* @remarks
|
|
785
804
|
* A modern request derives its protocol, method, and tools/call-only name expectations from
|
|
@@ -800,7 +819,7 @@ export declare interface HTTPTransportOptions<TState = unknown> extends HTTPHand
|
|
|
800
819
|
export declare function inferHeaderIssue(request: Request, reference: JSONRPCInvocation | MCPVersion): MCPHeaderIssue | undefined;
|
|
801
820
|
|
|
802
821
|
/**
|
|
803
|
-
*
|
|
822
|
+
* Infers the legacy revision an `initialize` request negotiates.
|
|
804
823
|
*
|
|
805
824
|
* @remarks
|
|
806
825
|
* A supported legacy request is pinned exactly. A modern, malformed, absent, or unsupported
|
|
@@ -812,7 +831,7 @@ export declare function inferHeaderIssue(request: Request, reference: JSONRPCInv
|
|
|
812
831
|
export declare function inferLegacyVersion(request: JSONRPCInvocation): MCPVersion;
|
|
813
832
|
|
|
814
833
|
/**
|
|
815
|
-
*
|
|
834
|
+
* Infers the HTTP status for one MCP dispatch outcome without changing its JSON-RPC body.
|
|
816
835
|
*
|
|
817
836
|
* @remarks
|
|
818
837
|
* Notifications are accepted with `202`. Legacy response envelopes retain uniform `200`
|
|
@@ -832,7 +851,7 @@ export declare function inferStatus(response: JSONRPCResponse | undefined, era:
|
|
|
832
851
|
* the trailing partial line carried forward as the new `remainder`.
|
|
833
852
|
*
|
|
834
853
|
* @remarks
|
|
835
|
-
* A plain value record (no behavior
|
|
854
|
+
* A plain value record (no behavior) {@link import('./helpers.js').extractLines}
|
|
836
855
|
* returns; the caller threads `remainder` back in as the next call's `buffer`.
|
|
837
856
|
*/
|
|
838
857
|
export declare interface LineExtraction {
|
|
@@ -879,7 +898,7 @@ export declare const MCP_SESSION_HEADER = "mcp-session-id";
|
|
|
879
898
|
export declare const MCP_WEBSOCKET_SUBPROTOCOL = "mcp";
|
|
880
899
|
|
|
881
900
|
/**
|
|
882
|
-
*
|
|
901
|
+
* Extracts consumer-asserted caller context synchronously from an HTTP request after the
|
|
883
902
|
* transport has validated it for dispatch.
|
|
884
903
|
*
|
|
885
904
|
* @remarks
|
|
@@ -948,8 +967,8 @@ export declare interface MCPOriginOptions {
|
|
|
948
967
|
* The single session entity (the old `SessionState` + `EventStore` merged): it holds the
|
|
949
968
|
* session `id`, its OWN bounded, replayable log of pushed server→client messages (the
|
|
950
969
|
* resumable GET-SSE channel — a private `#events` `Map` + a monotone `#counter`, with
|
|
951
|
-
* `capacity` / `ttl` eviction,
|
|
952
|
-
* server→client SSE streams (a resumable `GET {path}` registers
|
|
970
|
+
* `capacity` / `ttl` eviction, not a separate store), and the set of open
|
|
971
|
+
* server→client SSE streams (a resumable `GET {path}` registers through `attach`, unregisters through
|
|
953
972
|
* `detach` on disconnect). Still a small entity (not a record), built minimal + extensible.
|
|
954
973
|
*
|
|
955
974
|
* - **`push` is the server-initiated primitive.** It APPENDS the message to the log (assigning
|
|
@@ -969,7 +988,7 @@ export declare interface MCPOriginOptions {
|
|
|
969
988
|
* lost (its cursor is OLDER than everything retained); returning `[]` lets the handler then
|
|
970
989
|
* stream only the fresh pushes that follow `attach` — the spec-sane resume.
|
|
971
990
|
*
|
|
972
|
-
* - **Bounded, append-ordered, plain `Map
|
|
991
|
+
* - **Bounded, append-ordered, plain `Map`.** The log lives in ONE insertion-ordered
|
|
973
992
|
* `Map<id, entry>` — insertion order IS append order IS id order, so `replay` and capacity
|
|
974
993
|
* eviction both walk the map directly. NO database mirror — the log is process-local
|
|
975
994
|
* transport mechanics, not durable state. `push` first drops every entry older than `ttl`
|
|
@@ -983,8 +1002,8 @@ export declare interface MCPOriginOptions {
|
|
|
983
1002
|
* serializes a message onto the already-open streams.
|
|
984
1003
|
*
|
|
985
1004
|
* - **Injected clock.** `push` / `replay` accept an optional `now` (epoch ms), defaulting to
|
|
986
|
-
* `Date.now()` — so a test drives TTL eviction with an elapsed clock rather than a real
|
|
987
|
-
*
|
|
1005
|
+
* `Date.now()` — so a test drives TTL eviction with an elapsed clock rather than a real
|
|
1006
|
+
* timer.
|
|
988
1007
|
*
|
|
989
1008
|
* @example
|
|
990
1009
|
* ```ts
|
|
@@ -1074,7 +1093,7 @@ export declare interface MCPSessionInterface {
|
|
|
1074
1093
|
* - `clock` — the `() => number` epoch-ms clock {@link import('./middlewares.js').createMCPSession}
|
|
1075
1094
|
* uses directly for its own session-touch / TTL-sweep bookkeeping; defaults to `Date.now`. The
|
|
1076
1095
|
* deterministic clock a TTL test advances explicitly instead of racing a real idle window
|
|
1077
|
-
* against wall-clock
|
|
1096
|
+
* against wall-clock. Production never sets it.
|
|
1078
1097
|
* - `origin` — the same shared origin-validation options supplied to `createMCPRoutes`.
|
|
1079
1098
|
* Validation is enabled by default, accepts canonical loopback-literal origins, and rejects
|
|
1080
1099
|
* every other present origin outside its exact list before any session is minted. Set
|
|
@@ -1110,16 +1129,16 @@ export declare interface MCPSessionState {
|
|
|
1110
1129
|
}
|
|
1111
1130
|
|
|
1112
1131
|
/**
|
|
1113
|
-
*
|
|
1132
|
+
* Decodes a `fetch` Response's Server-Sent-Events body into the JSON-RPC messages it
|
|
1114
1133
|
* carried — the CLIENT-side inverse of the server's Streamable-HTTP SSE response.
|
|
1115
1134
|
*
|
|
1116
1135
|
* @remarks
|
|
1117
1136
|
* Reads the whole `response.body` stream chunk-by-chunk through a `TextDecoder({
|
|
1118
1137
|
* stream: true })` (handling a multi-byte char split across reads) and `@orkestrel/sse`'s
|
|
1119
1138
|
* {@link SSEParserInterface} (handling a partial line / in-progress event split across
|
|
1120
|
-
* reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage}
|
|
1139
|
+
* reads), then narrows each dispatched event's `data` to a {@link JSONRPCMessage} with
|
|
1121
1140
|
* `parseJSONRPCMessage` (so a non-message / non-JSON `data:` event is DROPPED, never
|
|
1122
|
-
* thrown — total
|
|
1141
|
+
* thrown — total). It reuses the SAME `SSEParser` the server's `openStream` seam
|
|
1123
1142
|
* serializes against, so the wire round-trips. A `null` body (no stream) yields no
|
|
1124
1143
|
* messages; the {@link import('./transports/HTTPClientTransport.js').HTTPClientTransport}
|
|
1125
1144
|
* reads a request/response SSE reply (the server sends one `data:` event then ends), so
|
|
@@ -1131,7 +1150,7 @@ export declare interface MCPSessionState {
|
|
|
1131
1150
|
export declare function readEventStream(response: Response): Promise<readonly JSONRPCMessage[]>;
|
|
1132
1151
|
|
|
1133
1152
|
/**
|
|
1134
|
-
*
|
|
1153
|
+
* Reads the request's `Last-Event-ID` header — the SSE resume cursor a client sends when it
|
|
1135
1154
|
* reconnects to the resumable `GET {path}` stream, or `undefined` when absent.
|
|
1136
1155
|
*
|
|
1137
1156
|
* @remarks
|
|
@@ -1147,7 +1166,7 @@ export declare function readEventStream(response: Response): Promise<readonly JS
|
|
|
1147
1166
|
export declare function readLastEventId(request: Request): string | undefined;
|
|
1148
1167
|
|
|
1149
1168
|
/**
|
|
1150
|
-
*
|
|
1169
|
+
* Reads the request's `mcp-session-id` header — the session id a stateful transport
|
|
1151
1170
|
* validates, or `undefined` when absent.
|
|
1152
1171
|
*
|
|
1153
1172
|
* @remarks
|
|
@@ -1163,7 +1182,7 @@ export declare function readLastEventId(request: Request): string | undefined;
|
|
|
1163
1182
|
export declare function readSessionHeader(request: Request): string | undefined;
|
|
1164
1183
|
|
|
1165
1184
|
/**
|
|
1166
|
-
*
|
|
1185
|
+
* Builds the stateful transport's "unknown session" rejection — an HTTP `404` carrying a
|
|
1167
1186
|
* JSON-RPC error body.
|
|
1168
1187
|
*
|
|
1169
1188
|
* @remarks
|
|
@@ -1180,12 +1199,12 @@ export declare function readSessionHeader(request: Request): string | undefined;
|
|
|
1180
1199
|
export declare function rejectUnknownSession(): Response;
|
|
1181
1200
|
|
|
1182
1201
|
/**
|
|
1183
|
-
*
|
|
1202
|
+
* Pumps a controlled held-open exchange onto an open SSE stream — one `data:` event per
|
|
1184
1203
|
* notification in order, then the terminating response — and END the exchange however the
|
|
1185
1204
|
* pump leaves.
|
|
1186
1205
|
*
|
|
1187
1206
|
* @remarks
|
|
1188
|
-
* The Streamable-HTTP twin of {@link import('@
|
|
1207
|
+
* The Streamable-HTTP twin of {@link import('@orkestrel/mcp').sendStream}, and it owns exactly what
|
|
1189
1208
|
* that owns. The `finally` releases the exchange on EVERY exit — the normal terminal, a
|
|
1190
1209
|
* producer that threw, a `write` that threw, and an abort alike — because nothing else will:
|
|
1191
1210
|
* a request whose client vanished cancels nothing by itself, so an exchange this pump walks
|
|
@@ -1193,7 +1212,7 @@ export declare function rejectUnknownSession(): Response;
|
|
|
1193
1212
|
* The exchange is released BEFORE the body ends, so the slot is already back when the response
|
|
1194
1213
|
* completes.
|
|
1195
1214
|
*
|
|
1196
|
-
* Total
|
|
1215
|
+
* Total — never throws and never rejects. A held-open SSE response has already sent its
|
|
1197
1216
|
* headers and part of its body, so there is no failure the transport could still convert into
|
|
1198
1217
|
* a different answer; the honest end of a broken stream is a closed one, and the fault itself
|
|
1199
1218
|
* is already legible on `server.emitter`'s `error` event, which is where a contained fault
|
|
@@ -1225,7 +1244,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1225
1244
|
|
|
1226
1245
|
/**
|
|
1227
1246
|
* The stdio CLIENT transport for the Model Context Protocol — a
|
|
1228
|
-
* {@link
|
|
1247
|
+
* {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over
|
|
1229
1248
|
* newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
1230
1249
|
* import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
|
|
1231
1250
|
* import('./WebSocketClientTransport.js').WebSocketClientTransport}.
|
|
@@ -1238,22 +1257,40 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1238
1257
|
* - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's
|
|
1239
1258
|
* `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is
|
|
1240
1259
|
* decoded whole and a final line written without a trailing newline still arrives. Each framed
|
|
1241
|
-
* line is decoded and delivered
|
|
1242
|
-
* {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (
|
|
1260
|
+
* line is decoded and delivered through the shared {@link dispatchLines} helper — a well-formed
|
|
1261
|
+
* {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (never throws).
|
|
1243
1262
|
* - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
|
|
1244
1263
|
* through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
|
|
1245
1264
|
* host reports the line handled rather than the moment the write is queued. The supervisor never
|
|
1246
1265
|
* rejects — it answers `false` for a channel that was closed, destroyed, or ended, and for a write
|
|
1247
1266
|
* that failed — so a `false` answer REJECTS here with the same not-connected error a transport
|
|
1248
1267
|
* that was never started raises. A dead peer surfaces at the caller instead of vanishing.
|
|
1249
|
-
* - **`close()`**
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1268
|
+
* - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
|
|
1269
|
+
* (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
|
|
1270
|
+
* `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
|
|
1271
|
+
* its own to get its line pump back: the stream ends under the pump rather than throwing at it.
|
|
1272
|
+
* A line the supervisor had already framed behind the one being delivered is dropped rather than
|
|
1273
|
+
* emitted onto a transport whose teardown has begun. A `close()` issued while that teardown runs
|
|
1274
|
+
* joins it rather than opening a second one, so it resolves only after `close` has fired, and a
|
|
1275
|
+
* `start()` issued while it runs waits behind the same barrier, so lifetimes never overlap. A
|
|
1276
|
+
* descendant can retain an inherited stdout pipe after the child exits; the supervisor's `drain`
|
|
1277
|
+
* bound cuts that wait off, so this transport's `close()` settles within that bound rather than
|
|
1278
|
+
* on the descendant. The termination itself belongs to the host: a POSIX host signals the
|
|
1279
|
+
* child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
|
|
1280
|
+
* route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the
|
|
1281
|
+
* tree with `taskkill /F /T`, which nothing in the child can intercept.
|
|
1282
|
+
* - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail
|
|
1283
|
+
* while the child runs, and the value the supervisor froze at that child's terminal moment
|
|
1284
|
+
* afterwards. The reference is held past that moment and replaced only by the next `start()`,
|
|
1285
|
+
* which is what keeps a post-`close()` read stable without a private copy: the frozen value
|
|
1286
|
+
* never moves again, so a detached descendant writing to the inherited stderr after the cutoff
|
|
1287
|
+
* cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
|
|
1288
|
+
* byte bound.
|
|
1289
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
|
|
1254
1290
|
* emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
|
|
1255
|
-
* fault, including the child spawn cause the supervisor surfaces
|
|
1256
|
-
*
|
|
1291
|
+
* fault, including the child spawn cause the supervisor surfaces and the notice that this
|
|
1292
|
+
* lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
|
|
1293
|
+
* listener-error channel.
|
|
1257
1294
|
*
|
|
1258
1295
|
* @example
|
|
1259
1296
|
* ```ts
|
|
@@ -1262,23 +1299,92 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1262
1299
|
* await client.connect() // start() spawns the child, then the MCP initialize runs over stdio
|
|
1263
1300
|
* ```
|
|
1264
1301
|
*/
|
|
1265
|
-
export declare class StdioClientTransport implements
|
|
1302
|
+
export declare class StdioClientTransport implements StdioClientTransportInterface {
|
|
1266
1303
|
#private;
|
|
1267
1304
|
constructor(options: StdioClientTransportOptions);
|
|
1268
1305
|
get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
|
|
1269
1306
|
get session(): string | undefined;
|
|
1270
1307
|
get duplex(): boolean;
|
|
1308
|
+
get evidence(): string | undefined;
|
|
1271
1309
|
start(): Promise<void>;
|
|
1272
1310
|
send(message: JSONRPCMessage_2): Promise<void>;
|
|
1273
1311
|
close(): Promise<void>;
|
|
1274
1312
|
}
|
|
1275
1313
|
|
|
1314
|
+
/**
|
|
1315
|
+
* The contract `createStdioClientTransport` returns — a {@link MCPClientTransportInterface}
|
|
1316
|
+
* that also reports the supervised child's stderr tail, the diagnostic a child that dies at
|
|
1317
|
+
* startup leaves behind.
|
|
1318
|
+
*
|
|
1319
|
+
* @remarks
|
|
1320
|
+
* This contract adds `evidence` and changes nothing else: `emitter`, `session`, `duplex`,
|
|
1321
|
+
* `start`, `send`, and `close` are the shared client-transport surface, unchanged. It sits here
|
|
1322
|
+
* rather than on {@link MCPClientTransportInterface} because a transport that supervises no child —
|
|
1323
|
+
* Streamable HTTP, WebSocket, a `MessagePort` pair — has no such tail, and a member every one
|
|
1324
|
+
* of them answers `undefined` to forever is a stdio detail rather than a shared contract. A
|
|
1325
|
+
* consumer that widens this value back to {@link MCPClientTransportInterface}, including by
|
|
1326
|
+
* reading `client.transport`, loses the reader and must keep the original reference.
|
|
1327
|
+
*/
|
|
1328
|
+
export declare interface StdioClientTransportInterface extends MCPClientTransportInterface {
|
|
1329
|
+
/**
|
|
1330
|
+
* The supervised child's decoded stderr tail — live while a child is held, and the value
|
|
1331
|
+
* captured at that child's end afterwards.
|
|
1332
|
+
*
|
|
1333
|
+
* @remarks
|
|
1334
|
+
* - **Readings.** `undefined` while no child has run and none has been captured — before the
|
|
1335
|
+
* first `start()`. The live tail while a child is held, which reads `''` from the moment
|
|
1336
|
+
* that child is spawned until it writes. The captured tail after the child ended, whether
|
|
1337
|
+
* it exited on its own or `close()` terminated it, and `''` there for a child that ran and
|
|
1338
|
+
* wrote nothing — an empty tail is a real reading of a silent child, distinct from the
|
|
1339
|
+
* absent one.
|
|
1340
|
+
* - **Lifetime.** The tail follows the child that produced it. The supervisor FREEZES it at
|
|
1341
|
+
* that child's terminal moment — the moment `close()`'s teardown resolves past, and the
|
|
1342
|
+
* moment the exit that fires this transport's `close` settles at — and this transport keeps
|
|
1343
|
+
* reading that same child afterwards. The frozen value never moves again, which is what
|
|
1344
|
+
* makes a late read stable: a detached descendant holding the child's inherited stderr can
|
|
1345
|
+
* still write bytes after `close()` resolves, and those bytes reach no reading this
|
|
1346
|
+
* transport reports. The next `start()` replaces the held child, so a replacement never
|
|
1347
|
+
* reports its predecessor's stderr as current. Lifetimes never overlap: a `start()` issued
|
|
1348
|
+
* while a `close()` is still tearing down waits for that teardown to report `close` before
|
|
1349
|
+
* it opens the next one, so an older tail cannot arrive over a newer one however the calls
|
|
1350
|
+
* interleave. Read the tail before you open a replacement, and read how far it reaches
|
|
1351
|
+
* inside one `close` emit off the way the lifetime ended. An explicit `close()` still holds
|
|
1352
|
+
* its teardown barrier while those listeners run, so a `start()` one of them calls parks
|
|
1353
|
+
* behind it and every later listener reads the ended child's frozen tail. A natural exit
|
|
1354
|
+
* holds that barrier only across the `error` it reports at that end, so a restart begun
|
|
1355
|
+
* THERE parks until `close` has been delivered, while a `close` listener that calls
|
|
1356
|
+
* `start()` opens the next lifetime itself and replaces the value every listener after it
|
|
1357
|
+
* would have read.
|
|
1358
|
+
* - **What the close path carries.** The frozen value is what the supervisor had received by
|
|
1359
|
+
* that terminal moment, not the child's complete output.
|
|
1360
|
+
* Windows ends the tree with `taskkill /F /T`, which nothing in the child can intercept: a
|
|
1361
|
+
* `SIGTERM` handler never runs there, so the bytes it would have written never exist. A
|
|
1362
|
+
* child that ends on its own closes its stderr first, and THAT tail is complete.
|
|
1363
|
+
* Where that moment arrived at the supervisor's `drain` bound rather than at the child's
|
|
1364
|
+
* own stream close, the tail stops at the cutoff and later diagnostics may have existed;
|
|
1365
|
+
* the transport emits an `error` naming that lifetime, so a partial tail reads as partial.
|
|
1366
|
+
* - **Bound.** The supervisor keeps the END of the child's raw stderr bytes, at most
|
|
1367
|
+
* `@orkestrel/process`'s {@link import('@orkestrel/process').PROCESS_EVIDENCE} (2048
|
|
1368
|
+
* bytes under 0.0.6). A child that writes more than the bound loses its earliest output
|
|
1369
|
+
* and keeps its last, which is the half that names why it died. The bound counts raw
|
|
1370
|
+
* bytes before decoding rather than characters, so multibyte output yields fewer
|
|
1371
|
+
* characters than an ASCII run over the same byte window. The kept bytes never begin
|
|
1372
|
+
* inside a multibyte sequence: where the cut lands mid-character the start retreats to
|
|
1373
|
+
* that character's first byte, so the tail decodes without a replacement character and
|
|
1374
|
+
* can hold a few bytes fewer than the bound.
|
|
1375
|
+
* - **A spawn fault leaves no tail.** A spawn that produced no child wrote no stderr, so
|
|
1376
|
+
* `evidence` reads `''` for that lifetime. Its cause — the host's `ENOENT` for a command
|
|
1377
|
+
* that does not resolve — arrives on the `error` event instead.
|
|
1378
|
+
*/
|
|
1379
|
+
readonly evidence: string | undefined;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1276
1382
|
/**
|
|
1277
1383
|
* Options for `createStdioClientTransport` — the child process to spawn as a
|
|
1278
1384
|
* stdio-framed MCP server (newline-delimited JSON-RPC over `stdin`/`stdout`).
|
|
1279
1385
|
*
|
|
1280
1386
|
* @remarks
|
|
1281
|
-
* - `command` — the executable to spawn (
|
|
1387
|
+
* - `command` — the executable to spawn (for example, `'node'`, `'./my-mcp-server'`). REQUIRED.
|
|
1282
1388
|
* - `args` — the command-line arguments passed to `command`; defaults to none.
|
|
1283
1389
|
* - `env` — environment variable overrides MERGED over the parent `process.env` for the
|
|
1284
1390
|
* spawned child (the composed `@orkestrel/process` supervisor's merge semantics): when
|
|
@@ -1316,22 +1422,31 @@ export declare interface StdioServerOptions {
|
|
|
1316
1422
|
* import('./WebSocketServerTransport.js').WebSocketServerTransport}.
|
|
1317
1423
|
*
|
|
1318
1424
|
* @remarks
|
|
1319
|
-
* - **Reuses `MCPClientTransportInterface
|
|
1425
|
+
* - **Reuses `MCPClientTransportInterface`.** The same generic carrier the HTTP
|
|
1320
1426
|
* and WebSocket server transports implement — `emitter` (`message` / `close` /
|
|
1321
1427
|
* `error`), `start`, `send`, `close`. `session` is `undefined` (the stateless v1).
|
|
1322
1428
|
* - **Inbound (`message`).** `start()` subscribes to `input`'s `data` event; each
|
|
1323
1429
|
* chunk is folded through the shared {@link extractLines} line-framing helper
|
|
1324
1430
|
* (buffering a partial trailing line across reads), and every complete line is
|
|
1325
|
-
* decoded and delivered
|
|
1431
|
+
* decoded and delivered through the shared {@link dispatchLines} helper — a
|
|
1326
1432
|
* well-formed {@link JSONRPCMessage} re-emits on `message`, a malformed line
|
|
1327
|
-
* emits `error` (
|
|
1433
|
+
* emits `error` (never throws). `input`'s `close` bridges to this
|
|
1328
1434
|
* transport's `close`.
|
|
1329
1435
|
* - **Outbound (`send`).** `send(message)` writes one newline-terminated
|
|
1330
1436
|
* `JSON.stringify`d line to `output`.
|
|
1331
|
-
* - **`close()`**
|
|
1332
|
-
*
|
|
1333
|
-
*
|
|
1334
|
-
*
|
|
1437
|
+
* - **`close()`** removes this transport's input subscriptions and fires its `close`
|
|
1438
|
+
* event (idempotent). It pauses the input only when the caller was not already reading
|
|
1439
|
+
* it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this
|
|
1440
|
+
* transport's own is removed — so a process holding `process.stdin` can exit, and a
|
|
1441
|
+
* caller's own flow is never stopped underneath it. The transport preserves flowing versus
|
|
1442
|
+
* non-flowing state and restores every caller-owned listener. A Node stream that had never been
|
|
1443
|
+
* read starts with `readableFlowing === null` and is left non-flowing (`false`), because Node
|
|
1444
|
+
* exposes no public operation that restores `null` after data consumption starts. Attaching a
|
|
1445
|
+
* later `data` listener does not resume that stream; the caller must call `resume()` before the
|
|
1446
|
+
* listener receives data. The injected streams are owned by the caller (typically
|
|
1447
|
+
* `process.stdin`/`process.stdout`), so the transport never destroys, ends, or blanket-clears
|
|
1448
|
+
* them.
|
|
1449
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
|
|
1335
1450
|
* emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
|
|
1336
1451
|
* fault), distinct from the emitter's own listener-error channel.
|
|
1337
1452
|
*/
|
|
@@ -1347,13 +1462,13 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
|
|
|
1347
1462
|
}
|
|
1348
1463
|
|
|
1349
1464
|
/**
|
|
1350
|
-
*
|
|
1465
|
+
* Reads the path (without the query string) of a raw `node:http` protocol-upgrade request —
|
|
1351
1466
|
* the `createWebSocketServer` upgrade-path match.
|
|
1352
1467
|
*
|
|
1353
1468
|
* @remarks
|
|
1354
1469
|
* A `node:http` {@link import('node:http').IncomingMessage}'s `url` is the request TARGET
|
|
1355
|
-
* (`'/mcp?x=1'`), narrowed with `isString` (
|
|
1356
|
-
* absent target; it is parsed against a
|
|
1470
|
+
* (`'/mcp?x=1'`), narrowed with `isString` (never `as`) and defaulting to `'/'` for an
|
|
1471
|
+
* absent target; it is parsed against a placeholder base (only the pathname matters for the upgrade
|
|
1357
1472
|
* decision) and the `pathname` returned. The upgrade handler compares this against its
|
|
1358
1473
|
* configured `path` to decide whether to claim the socket. Total — never throws on an
|
|
1359
1474
|
* adversarial / absent target.
|
|
@@ -1377,7 +1492,8 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
|
|
|
1377
1492
|
* event, and VALIDATES `Sec-WebSocket-Accept === computeWebSocketAccept(key)` (the D2 helper)
|
|
1378
1493
|
* — a mismatch (or a non-`101` response, or a request error) REJECTS `start()` and the socket
|
|
1379
1494
|
* is destroyed. On success it wraps the raw upgraded socket in `createNodeWebSocket({ socket,
|
|
1380
|
-
* head })` (CLIENT mode — no key → frames are MASKED per §5.3) and bridges its
|
|
1495
|
+
* head })` (CLIENT mode — no key → frames are MASKED per RFC 6455 §5.3) and bridges its
|
|
1496
|
+
* `message`.
|
|
1381
1497
|
* - **The arriving socket is RE-ASKED for, never assumed.** `start()` suspends across that
|
|
1382
1498
|
* connect and upgrade, so it re-checks the transport's state before installing anything: a
|
|
1383
1499
|
* concurrent `start()` that already installed a socket, or a {@link close} that ended the
|
|
@@ -1386,15 +1502,18 @@ export declare function upgradeRequestPath(request: IncomingMessage): string;
|
|
|
1386
1502
|
* `start()` calls still resolve; exactly one socket is ever bound.
|
|
1387
1503
|
* - **Inbound (`message`).** Each decoded text frame is `JSON.parse`d (guarded) and narrowed
|
|
1388
1504
|
* with `parseJSONRPCMessage` — a {@link JSONRPCMessage} re-emits on this transport's `message`
|
|
1389
|
-
* event (the reply the {@link import('@
|
|
1390
|
-
* non-JSON / non-message frame surfaces on `error` and is dropped
|
|
1505
|
+
* event (the reply the {@link import('@orkestrel/mcp').MCPClientInterface} correlates by `id`); a
|
|
1506
|
+
* non-JSON / non-message frame surfaces on `error` and is dropped. The socket's `close`
|
|
1391
1507
|
* / `error` bridge to this transport's events.
|
|
1392
1508
|
* - **Outbound (`send`).** `send(message)` writes one masked text frame.
|
|
1393
|
-
* - **`close()`**
|
|
1509
|
+
* - **`close()`** unsubscribes from the socket, closes it, and fires `close` (idempotent). An
|
|
1510
|
+
* upgrade still on the wire is DESTROYED, so a `close()` during the handshake ends the
|
|
1511
|
+
* transport at once instead of waiting for a peer that may never answer — the suspended
|
|
1512
|
+
* `start()` resolves, because the close is the outcome its caller asked for.
|
|
1394
1513
|
* - **URL scheme.** `options.url` accepts a `ws://` / `wss://` URL or an `http://` / `https://`
|
|
1395
1514
|
* one; a `ws(s)` scheme is converted to `http(s)` for the underlying upgrade request (`wss`
|
|
1396
|
-
* → TLS
|
|
1397
|
-
* - **Observable
|
|
1515
|
+
* → TLS through `node:https`). Either reaches the same endpoint.
|
|
1516
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); every emit
|
|
1398
1517
|
* the emitter isolates a listener throw (a buggy observer never corrupts the transport);
|
|
1399
1518
|
* `error` is a DOMAIN event (a transport-level fault).
|
|
1400
1519
|
*
|
|
@@ -1425,7 +1544,7 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
1425
1544
|
* `wss://` URL OR an `http://` / `https://` one (a `ws(s)` scheme is converted to `http(s)`
|
|
1426
1545
|
* for the underlying `node:http(s)` upgrade request; either reaches the same endpoint).
|
|
1427
1546
|
* REQUIRED.
|
|
1428
|
-
* - `headers` — extra request headers merged onto the upgrade `GET` (
|
|
1547
|
+
* - `headers` — extra request headers merged onto the upgrade `GET` (for example, an `Authorization`
|
|
1429
1548
|
* bearer for a guarded server). The transport always sets `Connection: Upgrade`,
|
|
1430
1549
|
* `Upgrade: websocket`, a random `Sec-WebSocket-Key`, `Sec-WebSocket-Version: 13`, and
|
|
1431
1550
|
* `Sec-WebSocket-Protocol: mcp`; a header supplied here is merged on top.
|
|
@@ -1435,7 +1554,7 @@ export declare class WebSocketClientTransport implements MCPClientTransportInter
|
|
|
1435
1554
|
* This face owns its own `node:http(s)` upgrade request, so it can set any header on it. A
|
|
1436
1555
|
* page cannot — the native `WebSocket` constructor takes a URL and subprotocols and nothing
|
|
1437
1556
|
* else — so a `headers` key over there would be an option that silently did nothing. Do not
|
|
1438
|
-
* "harmonize" the
|
|
1557
|
+
* "harmonize" the option shapes.
|
|
1439
1558
|
*/
|
|
1440
1559
|
export declare interface WebSocketClientTransportOptions {
|
|
1441
1560
|
readonly url: string;
|
|
@@ -1457,10 +1576,9 @@ export declare interface WebSocketClientTransportOptions {
|
|
|
1457
1576
|
* {@link import('./constants.js').DEFAULT_MCP_PATH} (`'/mcp'`, the same path the HTTP
|
|
1458
1577
|
* transport mounts at). A protocol-upgrade request to any OTHER path is DECLINED
|
|
1459
1578
|
* (the handler returns `false`, so the spine fans it to the next handler or destroys it).
|
|
1460
|
-
* - `subprotocol` — the WebSocket subprotocol
|
|
1579
|
+
* - `subprotocol` — the WebSocket subprotocol selected in the `101` handshake's
|
|
1461
1580
|
* `Sec-WebSocket-Protocol`; defaults to {@link import('./constants.js').MCP_WEBSOCKET_SUBPROTOCOL}
|
|
1462
|
-
* (`'mcp'`). It is
|
|
1463
|
-
* endpoint is distinguishable from another WebSocket on the same path.
|
|
1581
|
+
* (`'mcp'`). It is sent only when the client's offer contains that token.
|
|
1464
1582
|
*
|
|
1465
1583
|
* Auth / origin policy is deliberately ABSENT: like the HTTP transport, the WebSocket
|
|
1466
1584
|
* transport is MECHANISM — compose a guard IN FRONT (a `Server.upgrade` handler registered
|
|
@@ -1480,7 +1598,7 @@ export declare interface WebSocketServerOptions {
|
|
|
1480
1598
|
* {@link import('./WebSocketClientTransport.js').WebSocketClientTransport} reuses.
|
|
1481
1599
|
*
|
|
1482
1600
|
* @remarks
|
|
1483
|
-
* - **Reuses `MCPClientTransportInterface
|
|
1601
|
+
* - **Reuses `MCPClientTransportInterface`.** It IS the same generic carrier the HTTP
|
|
1484
1602
|
* client transport implements — `emitter` (`message` / `close` / `error`), `start`,
|
|
1485
1603
|
* `send`, `close` — so the WebSocket server and client both speak ONE transport contract,
|
|
1486
1604
|
* no near-duplicate sibling interface. `session` is `undefined` (the stateless v1; a
|
|
@@ -1489,16 +1607,18 @@ export declare interface WebSocketServerOptions {
|
|
|
1489
1607
|
* - **Inbound (`message`).** `start()` subscribes to the socket's `message` event; each text
|
|
1490
1608
|
* frame is `JSON.parse`d inside a try/catch and narrowed with `parseJSONRPCMessage` — a
|
|
1491
1609
|
* well-formed {@link JSONRPCMessage} is re-emitted on this transport's `message` event (the
|
|
1492
|
-
* parsed envelope the {@link import('@
|
|
1493
|
-
* a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown
|
|
1610
|
+
* parsed envelope the {@link import('@orkestrel/mcp').MCPServerInterface} pump dispatches), while
|
|
1611
|
+
* a non-JSON or non-message frame is surfaced on `error` and DROPPED, never thrown. It
|
|
1494
1612
|
* also bridges the socket's `close` → this transport's `close`, and the socket's `error`.
|
|
1495
1613
|
* - **Outbound (`send`).** `send(message)` writes one text frame
|
|
1496
1614
|
* (`nodeWs.send(JSON.stringify(message))`); the underlying wrapper no-ops a write on a
|
|
1497
1615
|
* non-open socket, so a closed connection drops silently rather than throwing.
|
|
1498
|
-
* - **`close()`**
|
|
1499
|
-
*
|
|
1500
|
-
* once).
|
|
1501
|
-
*
|
|
1616
|
+
* - **`close()`** removes the subscriptions `start()` installed on the socket, closes the
|
|
1617
|
+
* underlying socket (the RFC 6455 close handshake), and fires the transport's `close` event
|
|
1618
|
+
* (idempotent — a second `close`, or a socket-driven close, emits once). A frame that arrives
|
|
1619
|
+
* between that release and the peer's close echo reaches nothing: the socket-driven close path
|
|
1620
|
+
* releases the same way, so a closed transport is never subscribed to a live socket.
|
|
1621
|
+
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the emitter
|
|
1502
1622
|
* isolates a listener throw (a buggy observer never corrupts the bridge). `error` is a
|
|
1503
1623
|
* DOMAIN event (a transport-level fault), distinct from the emitter's listener-error channel.
|
|
1504
1624
|
*/
|