@orkestrel/mcp 0.0.20 → 0.0.22
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/dist/src/server/index.cjs +111 -41
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +158 -20
- package/dist/src/server/index.d.ts +158 -20
- package/dist/src/server/index.js +111 -42
- package/dist/src/server/index.js.map +1 -1
- package/package.json +16 -14
|
@@ -324,22 +324,28 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
324
324
|
|
|
325
325
|
/**
|
|
326
326
|
* Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
327
|
-
* — a {@link
|
|
327
|
+
* — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
|
|
328
328
|
* over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
329
329
|
* createHTTPClientTransport} and {@link createWebSocketClientTransport}.
|
|
330
330
|
*
|
|
331
331
|
* @remarks
|
|
332
332
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
|
|
333
333
|
* spawns `options.command` with `options.args` and `options.env`, piping its
|
|
334
|
-
* `stdin`/`stdout` for the JSON-RPC channel
|
|
335
|
-
*
|
|
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
|
|
336
337
|
* newline-terminated line to the child's `stdin`; each decoded reply line from the
|
|
337
338
|
* child's `stdout` is surfaced on the transport's `message` event for the client's
|
|
338
|
-
* id correlation.
|
|
339
|
+
* id correlation. That write is bounded: a child that stays alive without ever reading
|
|
340
|
+
* its `stdin` fills the pipe, and `options.delivery` is how long the unconfirmed write
|
|
341
|
+
* waits before the `send` rejects. An omitted `delivery` selects {@link
|
|
342
|
+
* import('./constants.js').DEFAULT_MCP_DELIVERY}; an explicit `0` removes the bound.
|
|
339
343
|
*
|
|
340
344
|
* @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
|
|
341
|
-
* and optional `
|
|
342
|
-
*
|
|
345
|
+
* optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
|
|
346
|
+
* `stdin` write; see {@link StdioClientTransportOptions}
|
|
347
|
+
* @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
|
|
348
|
+
* whose `evidence` carries the supervised child's bounded stderr tail
|
|
343
349
|
*
|
|
344
350
|
* @example
|
|
345
351
|
* ```ts
|
|
@@ -353,7 +359,7 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
353
359
|
* const tools = await client.tools()
|
|
354
360
|
* ```
|
|
355
361
|
*/
|
|
356
|
-
export declare function createStdioClientTransport(options: StdioClientTransportOptions):
|
|
362
|
+
export declare function createStdioClientTransport(options: StdioClientTransportOptions): StdioClientTransportInterface;
|
|
357
363
|
|
|
358
364
|
/**
|
|
359
365
|
* Creates the MCP stdio transport INGRESS — pumps a transport-agnostic {@link
|
|
@@ -496,6 +502,19 @@ export declare function createWebSocketServer(mcp: MCPDispatcherInterface, optio
|
|
|
496
502
|
*/
|
|
497
503
|
export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
|
|
498
504
|
|
|
505
|
+
/**
|
|
506
|
+
* The default bound in milliseconds on one unconfirmed write to a stdio client transport's
|
|
507
|
+
* child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.
|
|
508
|
+
*
|
|
509
|
+
* @remarks
|
|
510
|
+
* Ten seconds. The load-bearing property is the ordering, not the magnitude: this bound stays
|
|
511
|
+
* BELOW {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never
|
|
512
|
+
* reads fails as an undeliverable message while the request that carried it is still open,
|
|
513
|
+
* rather than being masked by that request's own deadline expiring first. Override per
|
|
514
|
+
* transport with `delivery`; an explicit `0` there removes the bound.
|
|
515
|
+
*/
|
|
516
|
+
export declare const DEFAULT_MCP_DELIVERY = 10000;
|
|
517
|
+
|
|
499
518
|
/**
|
|
500
519
|
* The default interval in milliseconds between SSE keepalive comments on held-open MCP
|
|
501
520
|
* responses.
|
|
@@ -1242,7 +1261,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1242
1261
|
|
|
1243
1262
|
/**
|
|
1244
1263
|
* The stdio CLIENT transport for the Model Context Protocol — a
|
|
1245
|
-
* {@link
|
|
1264
|
+
* {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over
|
|
1246
1265
|
* newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
1247
1266
|
* import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
|
|
1248
1267
|
* import('./WebSocketClientTransport.js').WebSocketClientTransport}.
|
|
@@ -1260,19 +1279,36 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1260
1279
|
* - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
|
|
1261
1280
|
* through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
|
|
1262
1281
|
* host reports the line handled rather than the moment the write is queued. The supervisor never
|
|
1263
|
-
* rejects — it answers `false` for a channel that was closed, destroyed, or ended,
|
|
1264
|
-
* that failed
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
* the
|
|
1282
|
+
* rejects — it answers `false` for a channel that was closed, destroyed, or ended, for a write
|
|
1283
|
+
* that failed, or for one that remained unconfirmed through `delivery`. A call made without a
|
|
1284
|
+
* live child rejects as not connected; a `false` answer from a live child rejects as unable to
|
|
1285
|
+
* deliver. The supervisor does not disclose which cause produced that answer.
|
|
1286
|
+
* - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
|
|
1287
|
+
* (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
|
|
1288
|
+
* `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
|
|
1289
|
+
* its own to get its line pump back: the stream ends under the pump rather than throwing at it.
|
|
1290
|
+
* A line the supervisor had already framed behind the one being delivered is dropped rather than
|
|
1291
|
+
* emitted onto a transport whose teardown has begun. A `close()` issued while that teardown runs
|
|
1292
|
+
* joins it rather than opening a second one, so it resolves only after `close` has fired, and a
|
|
1293
|
+
* `start()` issued while it runs waits behind the same barrier, so lifetimes never overlap. A
|
|
1294
|
+
* descendant can retain an inherited stdout pipe after the child exits; the supervisor's `drain`
|
|
1295
|
+
* bound cuts that wait off, so this transport's `close()` settles within that bound rather than
|
|
1296
|
+
* on the descendant. The termination itself belongs to the host: a POSIX host signals the
|
|
1297
|
+
* child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
|
|
1298
|
+
* route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the
|
|
1299
|
+
* tree with `taskkill /F /T`, which nothing in the child can intercept.
|
|
1300
|
+
* - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail
|
|
1301
|
+
* while the child runs, and the value the supervisor froze at that child's terminal moment
|
|
1302
|
+
* afterwards. The reference is held past that moment and replaced only by the next `start()`,
|
|
1303
|
+
* which is what keeps a post-`close()` read stable without a private copy: the frozen value
|
|
1304
|
+
* never moves again, so a detached descendant writing to the inherited stderr after the cutoff
|
|
1305
|
+
* cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
|
|
1306
|
+
* byte bound.
|
|
1272
1307
|
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
|
|
1273
1308
|
* emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
|
|
1274
|
-
* fault, including the child spawn cause the supervisor surfaces
|
|
1275
|
-
*
|
|
1309
|
+
* fault, including the child spawn cause the supervisor surfaces and the notice that this
|
|
1310
|
+
* lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
|
|
1311
|
+
* listener-error channel.
|
|
1276
1312
|
*
|
|
1277
1313
|
* @example
|
|
1278
1314
|
* ```ts
|
|
@@ -1281,17 +1317,96 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1281
1317
|
* await client.connect() // start() spawns the child, then the MCP initialize runs over stdio
|
|
1282
1318
|
* ```
|
|
1283
1319
|
*/
|
|
1284
|
-
export declare class StdioClientTransport implements
|
|
1320
|
+
export declare class StdioClientTransport implements StdioClientTransportInterface {
|
|
1285
1321
|
#private;
|
|
1286
1322
|
constructor(options: StdioClientTransportOptions);
|
|
1287
1323
|
get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
|
|
1288
1324
|
get session(): string | undefined;
|
|
1289
1325
|
get duplex(): boolean;
|
|
1326
|
+
get evidence(): string | undefined;
|
|
1290
1327
|
start(): Promise<void>;
|
|
1328
|
+
/**
|
|
1329
|
+
* Sends one newline-delimited JSON-RPC message to the live child.
|
|
1330
|
+
*
|
|
1331
|
+
* @param message - The message to write to the child's `stdin`
|
|
1332
|
+
* @returns Resolves when the supervisor confirms the write
|
|
1333
|
+
* @throws Thrown with `stdio transport is not connected` when no live child is available before
|
|
1334
|
+
* the write
|
|
1335
|
+
* @throws Thrown with `stdio transport could not deliver the message` when a live child's write
|
|
1336
|
+
* resolves `false`
|
|
1337
|
+
*/
|
|
1291
1338
|
send(message: JSONRPCMessage_2): Promise<void>;
|
|
1292
1339
|
close(): Promise<void>;
|
|
1293
1340
|
}
|
|
1294
1341
|
|
|
1342
|
+
/**
|
|
1343
|
+
* The contract `createStdioClientTransport` returns — a {@link MCPClientTransportInterface}
|
|
1344
|
+
* that also reports the supervised child's stderr tail, the diagnostic a child that dies at
|
|
1345
|
+
* startup leaves behind.
|
|
1346
|
+
*
|
|
1347
|
+
* @remarks
|
|
1348
|
+
* This contract adds `evidence` and changes nothing else: `emitter`, `session`, `duplex`,
|
|
1349
|
+
* `start`, `send`, and `close` are the shared client-transport surface, unchanged. It sits here
|
|
1350
|
+
* rather than on {@link MCPClientTransportInterface} because a transport that supervises no child —
|
|
1351
|
+
* Streamable HTTP, WebSocket, a `MessagePort` pair — has no such tail, and a member every one
|
|
1352
|
+
* of them answers `undefined` to forever is a stdio detail rather than a shared contract. A
|
|
1353
|
+
* consumer that widens this value back to {@link MCPClientTransportInterface}, including by
|
|
1354
|
+
* reading `client.transport`, loses the reader and must keep the original reference.
|
|
1355
|
+
*/
|
|
1356
|
+
export declare interface StdioClientTransportInterface extends MCPClientTransportInterface {
|
|
1357
|
+
/**
|
|
1358
|
+
* The supervised child's decoded stderr tail — live while a child is held, and the value
|
|
1359
|
+
* captured at that child's end afterwards.
|
|
1360
|
+
*
|
|
1361
|
+
* @remarks
|
|
1362
|
+
* - **Readings.** `undefined` while no child has run and none has been captured — before the
|
|
1363
|
+
* first `start()`. The live tail while a child is held, which reads `''` from the moment
|
|
1364
|
+
* that child is spawned until it writes. The captured tail after the child ended, whether
|
|
1365
|
+
* it exited on its own or `close()` terminated it, and `''` there for a child that ran and
|
|
1366
|
+
* wrote nothing — an empty tail is a real reading of a silent child, distinct from the
|
|
1367
|
+
* absent one.
|
|
1368
|
+
* - **Lifetime.** The tail follows the child that produced it. The supervisor FREEZES it at
|
|
1369
|
+
* that child's terminal moment — the moment `close()`'s teardown resolves past, and the
|
|
1370
|
+
* moment the exit that fires this transport's `close` settles at — and this transport keeps
|
|
1371
|
+
* reading that same child afterwards. The frozen value never moves again, which is what
|
|
1372
|
+
* makes a late read stable: a detached descendant holding the child's inherited stderr can
|
|
1373
|
+
* still write bytes after `close()` resolves, and those bytes reach no reading this
|
|
1374
|
+
* transport reports. The next `start()` replaces the held child, so a replacement never
|
|
1375
|
+
* reports its predecessor's stderr as current. Lifetimes never overlap: a `start()` issued
|
|
1376
|
+
* while a `close()` is still tearing down waits for that teardown to report `close` before
|
|
1377
|
+
* it opens the next one, so an older tail cannot arrive over a newer one however the calls
|
|
1378
|
+
* interleave. Read the tail before you open a replacement, and read how far it reaches
|
|
1379
|
+
* inside one `close` emit off the way the lifetime ended. An explicit `close()` still holds
|
|
1380
|
+
* its teardown barrier while those listeners run, so a `start()` one of them calls parks
|
|
1381
|
+
* behind it and every later listener reads the ended child's frozen tail. A natural exit
|
|
1382
|
+
* holds that barrier only across the `error` it reports at that end, so a restart begun
|
|
1383
|
+
* THERE parks until `close` has been delivered, while a `close` listener that calls
|
|
1384
|
+
* `start()` opens the next lifetime itself and replaces the value every listener after it
|
|
1385
|
+
* would have read.
|
|
1386
|
+
* - **What the close path carries.** The frozen value is what the supervisor had received by
|
|
1387
|
+
* that terminal moment, not the child's complete output.
|
|
1388
|
+
* Windows ends the tree with `taskkill /F /T`, which nothing in the child can intercept: a
|
|
1389
|
+
* `SIGTERM` handler never runs there, so the bytes it would have written never exist. A
|
|
1390
|
+
* child that ends on its own closes its stderr first, and THAT tail is complete.
|
|
1391
|
+
* Where that moment arrived at the supervisor's `drain` bound rather than at the child's
|
|
1392
|
+
* own stream close, the tail stops at the cutoff and later diagnostics may have existed;
|
|
1393
|
+
* the transport emits an `error` naming that lifetime, so a partial tail reads as partial.
|
|
1394
|
+
* - **Bound.** The supervisor keeps the END of the child's raw stderr bytes, at most
|
|
1395
|
+
* `@orkestrel/process`'s {@link import('@orkestrel/process').PROCESS_EVIDENCE} (2048
|
|
1396
|
+
* bytes under 0.0.6). A child that writes more than the bound loses its earliest output
|
|
1397
|
+
* and keeps its last, which is the half that names why it died. The bound counts raw
|
|
1398
|
+
* bytes before decoding rather than characters, so multibyte output yields fewer
|
|
1399
|
+
* characters than an ASCII run over the same byte window. The kept bytes never begin
|
|
1400
|
+
* inside a multibyte sequence: where the cut lands mid-character the start retreats to
|
|
1401
|
+
* that character's first byte, so the tail decodes without a replacement character and
|
|
1402
|
+
* can hold a few bytes fewer than the bound.
|
|
1403
|
+
* - **A spawn fault leaves no tail.** A spawn that produced no child wrote no stderr, so
|
|
1404
|
+
* `evidence` reads `''` for that lifetime. Its cause — the host's `ENOENT` for a command
|
|
1405
|
+
* that does not resolve — arrives on the `error` event instead.
|
|
1406
|
+
*/
|
|
1407
|
+
readonly evidence: string | undefined;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1295
1410
|
/**
|
|
1296
1411
|
* Options for `createStdioClientTransport` — the child process to spawn as a
|
|
1297
1412
|
* stdio-framed MCP server (newline-delimited JSON-RPC over `stdin`/`stdout`).
|
|
@@ -1304,11 +1419,34 @@ export declare class StdioClientTransport implements MCPClientTransportInterface
|
|
|
1304
1419
|
* OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
|
|
1305
1420
|
* the inherited value while every unlisted key is still inherited. This transport cannot
|
|
1306
1421
|
* REPLACE the inherited environment entirely — the supervisor always merges over the parent.
|
|
1422
|
+
* - `delivery` — the bound in milliseconds on one unconfirmed write to the child's `stdin`;
|
|
1423
|
+
* an explicit `0` opts out. Defaults to {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
|
|
1307
1424
|
*/
|
|
1308
1425
|
export declare interface StdioClientTransportOptions {
|
|
1309
1426
|
readonly command: string;
|
|
1310
1427
|
readonly args?: readonly string[];
|
|
1311
1428
|
readonly env?: Readonly<Record<string, string>>;
|
|
1429
|
+
/**
|
|
1430
|
+
* Bounds the wait on one unconfirmed `send` write to the child's `stdin`, in milliseconds.
|
|
1431
|
+
*
|
|
1432
|
+
* @remarks
|
|
1433
|
+
* A full pipe is the case this bounds: a live child that never reads its `stdin` leaves the
|
|
1434
|
+
* kernel unable to confirm the write, and the supervisor holds that write open. When the
|
|
1435
|
+
* window elapses the supervisor answers that write `false`, which this transport surfaces as
|
|
1436
|
+
* a rejection, so the caller learns the message did not land instead of waiting on a peer
|
|
1437
|
+
* that will never read it. Default: {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
|
|
1438
|
+
*
|
|
1439
|
+
* An explicit `0` opts out: the bound is off, and an unconfirmed write stays pending until
|
|
1440
|
+
* the channel faults or teardown settles it. Omission does NOT opt out here, which is where
|
|
1441
|
+
* this option diverges from the supervisor's own `delivery` on {@link
|
|
1442
|
+
* import('@orkestrel/process').ProcessOptions} — omitted THERE disables the bound, omitted
|
|
1443
|
+
* HERE selects the default.
|
|
1444
|
+
*
|
|
1445
|
+
* An out-of-range value surfaces at `start()` rather than at construction. This transport
|
|
1446
|
+
* forwards the value verbatim and adds no validator of its own, so the supervisor's own timer
|
|
1447
|
+
* range is what rejects it, at the moment it spawns the child.
|
|
1448
|
+
*/
|
|
1449
|
+
readonly delivery?: number;
|
|
1312
1450
|
}
|
|
1313
1451
|
|
|
1314
1452
|
/**
|
|
@@ -324,22 +324,28 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
324
324
|
|
|
325
325
|
/**
|
|
326
326
|
* Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
|
|
327
|
-
* — a {@link
|
|
327
|
+
* — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
|
|
328
328
|
* over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
329
329
|
* createHTTPClientTransport} and {@link createWebSocketClientTransport}.
|
|
330
330
|
*
|
|
331
331
|
* @remarks
|
|
332
332
|
* Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
|
|
333
333
|
* spawns `options.command` with `options.args` and `options.env`, piping its
|
|
334
|
-
* `stdin`/`stdout` for the JSON-RPC channel
|
|
335
|
-
*
|
|
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
|
|
336
337
|
* newline-terminated line to the child's `stdin`; each decoded reply line from the
|
|
337
338
|
* child's `stdout` is surfaced on the transport's `message` event for the client's
|
|
338
|
-
* id correlation.
|
|
339
|
+
* id correlation. That write is bounded: a child that stays alive without ever reading
|
|
340
|
+
* its `stdin` fills the pipe, and `options.delivery` is how long the unconfirmed write
|
|
341
|
+
* waits before the `send` rejects. An omitted `delivery` selects {@link
|
|
342
|
+
* import('./constants.js').DEFAULT_MCP_DELIVERY}; an explicit `0` removes the bound.
|
|
339
343
|
*
|
|
340
344
|
* @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
|
|
341
|
-
* and optional `
|
|
342
|
-
*
|
|
345
|
+
* optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
|
|
346
|
+
* `stdin` write; see {@link StdioClientTransportOptions}
|
|
347
|
+
* @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
|
|
348
|
+
* whose `evidence` carries the supervised child's bounded stderr tail
|
|
343
349
|
*
|
|
344
350
|
* @example
|
|
345
351
|
* ```ts
|
|
@@ -353,7 +359,7 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
|
|
|
353
359
|
* const tools = await client.tools()
|
|
354
360
|
* ```
|
|
355
361
|
*/
|
|
356
|
-
export declare function createStdioClientTransport(options: StdioClientTransportOptions):
|
|
362
|
+
export declare function createStdioClientTransport(options: StdioClientTransportOptions): StdioClientTransportInterface;
|
|
357
363
|
|
|
358
364
|
/**
|
|
359
365
|
* Creates the MCP stdio transport INGRESS — pumps a transport-agnostic {@link
|
|
@@ -496,6 +502,19 @@ export declare function createWebSocketServer(mcp: MCPDispatcherInterface, optio
|
|
|
496
502
|
*/
|
|
497
503
|
export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
|
|
498
504
|
|
|
505
|
+
/**
|
|
506
|
+
* The default bound in milliseconds on one unconfirmed write to a stdio client transport's
|
|
507
|
+
* child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.
|
|
508
|
+
*
|
|
509
|
+
* @remarks
|
|
510
|
+
* Ten seconds. The load-bearing property is the ordering, not the magnitude: this bound stays
|
|
511
|
+
* BELOW {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never
|
|
512
|
+
* reads fails as an undeliverable message while the request that carried it is still open,
|
|
513
|
+
* rather than being masked by that request's own deadline expiring first. Override per
|
|
514
|
+
* transport with `delivery`; an explicit `0` there removes the bound.
|
|
515
|
+
*/
|
|
516
|
+
export declare const DEFAULT_MCP_DELIVERY = 10000;
|
|
517
|
+
|
|
499
518
|
/**
|
|
500
519
|
* The default interval in milliseconds between SSE keepalive comments on held-open MCP
|
|
501
520
|
* responses.
|
|
@@ -1242,7 +1261,7 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1242
1261
|
|
|
1243
1262
|
/**
|
|
1244
1263
|
* The stdio CLIENT transport for the Model Context Protocol — a
|
|
1245
|
-
* {@link
|
|
1264
|
+
* {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over
|
|
1246
1265
|
* newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
|
|
1247
1266
|
* import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
|
|
1248
1267
|
* import('./WebSocketClientTransport.js').WebSocketClientTransport}.
|
|
@@ -1260,19 +1279,36 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1260
1279
|
* - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
|
|
1261
1280
|
* through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
|
|
1262
1281
|
* host reports the line handled rather than the moment the write is queued. The supervisor never
|
|
1263
|
-
* rejects — it answers `false` for a channel that was closed, destroyed, or ended,
|
|
1264
|
-
* that failed
|
|
1265
|
-
*
|
|
1266
|
-
*
|
|
1267
|
-
*
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1270
|
-
*
|
|
1271
|
-
* the
|
|
1282
|
+
* rejects — it answers `false` for a channel that was closed, destroyed, or ended, for a write
|
|
1283
|
+
* that failed, or for one that remained unconfirmed through `delivery`. A call made without a
|
|
1284
|
+
* live child rejects as not connected; a `false` answer from a live child rejects as unable to
|
|
1285
|
+
* deliver. The supervisor does not disclose which cause produced that answer.
|
|
1286
|
+
* - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
|
|
1287
|
+
* (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
|
|
1288
|
+
* `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
|
|
1289
|
+
* its own to get its line pump back: the stream ends under the pump rather than throwing at it.
|
|
1290
|
+
* A line the supervisor had already framed behind the one being delivered is dropped rather than
|
|
1291
|
+
* emitted onto a transport whose teardown has begun. A `close()` issued while that teardown runs
|
|
1292
|
+
* joins it rather than opening a second one, so it resolves only after `close` has fired, and a
|
|
1293
|
+
* `start()` issued while it runs waits behind the same barrier, so lifetimes never overlap. A
|
|
1294
|
+
* descendant can retain an inherited stdout pipe after the child exits; the supervisor's `drain`
|
|
1295
|
+
* bound cuts that wait off, so this transport's `close()` settles within that bound rather than
|
|
1296
|
+
* on the descendant. The termination itself belongs to the host: a POSIX host signals the
|
|
1297
|
+
* child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
|
|
1298
|
+
* route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the
|
|
1299
|
+
* tree with `taskkill /F /T`, which nothing in the child can intercept.
|
|
1300
|
+
* - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail
|
|
1301
|
+
* while the child runs, and the value the supervisor froze at that child's terminal moment
|
|
1302
|
+
* afterwards. The reference is held past that moment and replaced only by the next `start()`,
|
|
1303
|
+
* which is what keeps a post-`close()` read stable without a private copy: the frozen value
|
|
1304
|
+
* never moves again, so a detached descendant writing to the inherited stderr after the cutoff
|
|
1305
|
+
* cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
|
|
1306
|
+
* byte bound.
|
|
1272
1307
|
* - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
|
|
1273
1308
|
* emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
|
|
1274
|
-
* fault, including the child spawn cause the supervisor surfaces
|
|
1275
|
-
*
|
|
1309
|
+
* fault, including the child spawn cause the supervisor surfaces and the notice that this
|
|
1310
|
+
* lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
|
|
1311
|
+
* listener-error channel.
|
|
1276
1312
|
*
|
|
1277
1313
|
* @example
|
|
1278
1314
|
* ```ts
|
|
@@ -1281,17 +1317,96 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
|
|
|
1281
1317
|
* await client.connect() // start() spawns the child, then the MCP initialize runs over stdio
|
|
1282
1318
|
* ```
|
|
1283
1319
|
*/
|
|
1284
|
-
export declare class StdioClientTransport implements
|
|
1320
|
+
export declare class StdioClientTransport implements StdioClientTransportInterface {
|
|
1285
1321
|
#private;
|
|
1286
1322
|
constructor(options: StdioClientTransportOptions);
|
|
1287
1323
|
get emitter(): EmitterInterface<MCPClientTransportEventMap_2>;
|
|
1288
1324
|
get session(): string | undefined;
|
|
1289
1325
|
get duplex(): boolean;
|
|
1326
|
+
get evidence(): string | undefined;
|
|
1290
1327
|
start(): Promise<void>;
|
|
1328
|
+
/**
|
|
1329
|
+
* Sends one newline-delimited JSON-RPC message to the live child.
|
|
1330
|
+
*
|
|
1331
|
+
* @param message - The message to write to the child's `stdin`
|
|
1332
|
+
* @returns Resolves when the supervisor confirms the write
|
|
1333
|
+
* @throws Thrown with `stdio transport is not connected` when no live child is available before
|
|
1334
|
+
* the write
|
|
1335
|
+
* @throws Thrown with `stdio transport could not deliver the message` when a live child's write
|
|
1336
|
+
* resolves `false`
|
|
1337
|
+
*/
|
|
1291
1338
|
send(message: JSONRPCMessage_2): Promise<void>;
|
|
1292
1339
|
close(): Promise<void>;
|
|
1293
1340
|
}
|
|
1294
1341
|
|
|
1342
|
+
/**
|
|
1343
|
+
* The contract `createStdioClientTransport` returns — a {@link MCPClientTransportInterface}
|
|
1344
|
+
* that also reports the supervised child's stderr tail, the diagnostic a child that dies at
|
|
1345
|
+
* startup leaves behind.
|
|
1346
|
+
*
|
|
1347
|
+
* @remarks
|
|
1348
|
+
* This contract adds `evidence` and changes nothing else: `emitter`, `session`, `duplex`,
|
|
1349
|
+
* `start`, `send`, and `close` are the shared client-transport surface, unchanged. It sits here
|
|
1350
|
+
* rather than on {@link MCPClientTransportInterface} because a transport that supervises no child —
|
|
1351
|
+
* Streamable HTTP, WebSocket, a `MessagePort` pair — has no such tail, and a member every one
|
|
1352
|
+
* of them answers `undefined` to forever is a stdio detail rather than a shared contract. A
|
|
1353
|
+
* consumer that widens this value back to {@link MCPClientTransportInterface}, including by
|
|
1354
|
+
* reading `client.transport`, loses the reader and must keep the original reference.
|
|
1355
|
+
*/
|
|
1356
|
+
export declare interface StdioClientTransportInterface extends MCPClientTransportInterface {
|
|
1357
|
+
/**
|
|
1358
|
+
* The supervised child's decoded stderr tail — live while a child is held, and the value
|
|
1359
|
+
* captured at that child's end afterwards.
|
|
1360
|
+
*
|
|
1361
|
+
* @remarks
|
|
1362
|
+
* - **Readings.** `undefined` while no child has run and none has been captured — before the
|
|
1363
|
+
* first `start()`. The live tail while a child is held, which reads `''` from the moment
|
|
1364
|
+
* that child is spawned until it writes. The captured tail after the child ended, whether
|
|
1365
|
+
* it exited on its own or `close()` terminated it, and `''` there for a child that ran and
|
|
1366
|
+
* wrote nothing — an empty tail is a real reading of a silent child, distinct from the
|
|
1367
|
+
* absent one.
|
|
1368
|
+
* - **Lifetime.** The tail follows the child that produced it. The supervisor FREEZES it at
|
|
1369
|
+
* that child's terminal moment — the moment `close()`'s teardown resolves past, and the
|
|
1370
|
+
* moment the exit that fires this transport's `close` settles at — and this transport keeps
|
|
1371
|
+
* reading that same child afterwards. The frozen value never moves again, which is what
|
|
1372
|
+
* makes a late read stable: a detached descendant holding the child's inherited stderr can
|
|
1373
|
+
* still write bytes after `close()` resolves, and those bytes reach no reading this
|
|
1374
|
+
* transport reports. The next `start()` replaces the held child, so a replacement never
|
|
1375
|
+
* reports its predecessor's stderr as current. Lifetimes never overlap: a `start()` issued
|
|
1376
|
+
* while a `close()` is still tearing down waits for that teardown to report `close` before
|
|
1377
|
+
* it opens the next one, so an older tail cannot arrive over a newer one however the calls
|
|
1378
|
+
* interleave. Read the tail before you open a replacement, and read how far it reaches
|
|
1379
|
+
* inside one `close` emit off the way the lifetime ended. An explicit `close()` still holds
|
|
1380
|
+
* its teardown barrier while those listeners run, so a `start()` one of them calls parks
|
|
1381
|
+
* behind it and every later listener reads the ended child's frozen tail. A natural exit
|
|
1382
|
+
* holds that barrier only across the `error` it reports at that end, so a restart begun
|
|
1383
|
+
* THERE parks until `close` has been delivered, while a `close` listener that calls
|
|
1384
|
+
* `start()` opens the next lifetime itself and replaces the value every listener after it
|
|
1385
|
+
* would have read.
|
|
1386
|
+
* - **What the close path carries.** The frozen value is what the supervisor had received by
|
|
1387
|
+
* that terminal moment, not the child's complete output.
|
|
1388
|
+
* Windows ends the tree with `taskkill /F /T`, which nothing in the child can intercept: a
|
|
1389
|
+
* `SIGTERM` handler never runs there, so the bytes it would have written never exist. A
|
|
1390
|
+
* child that ends on its own closes its stderr first, and THAT tail is complete.
|
|
1391
|
+
* Where that moment arrived at the supervisor's `drain` bound rather than at the child's
|
|
1392
|
+
* own stream close, the tail stops at the cutoff and later diagnostics may have existed;
|
|
1393
|
+
* the transport emits an `error` naming that lifetime, so a partial tail reads as partial.
|
|
1394
|
+
* - **Bound.** The supervisor keeps the END of the child's raw stderr bytes, at most
|
|
1395
|
+
* `@orkestrel/process`'s {@link import('@orkestrel/process').PROCESS_EVIDENCE} (2048
|
|
1396
|
+
* bytes under 0.0.6). A child that writes more than the bound loses its earliest output
|
|
1397
|
+
* and keeps its last, which is the half that names why it died. The bound counts raw
|
|
1398
|
+
* bytes before decoding rather than characters, so multibyte output yields fewer
|
|
1399
|
+
* characters than an ASCII run over the same byte window. The kept bytes never begin
|
|
1400
|
+
* inside a multibyte sequence: where the cut lands mid-character the start retreats to
|
|
1401
|
+
* that character's first byte, so the tail decodes without a replacement character and
|
|
1402
|
+
* can hold a few bytes fewer than the bound.
|
|
1403
|
+
* - **A spawn fault leaves no tail.** A spawn that produced no child wrote no stderr, so
|
|
1404
|
+
* `evidence` reads `''` for that lifetime. Its cause — the host's `ENOENT` for a command
|
|
1405
|
+
* that does not resolve — arrives on the `error` event instead.
|
|
1406
|
+
*/
|
|
1407
|
+
readonly evidence: string | undefined;
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1295
1410
|
/**
|
|
1296
1411
|
* Options for `createStdioClientTransport` — the child process to spawn as a
|
|
1297
1412
|
* stdio-framed MCP server (newline-delimited JSON-RPC over `stdin`/`stdout`).
|
|
@@ -1304,11 +1419,34 @@ export declare class StdioClientTransport implements MCPClientTransportInterface
|
|
|
1304
1419
|
* OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
|
|
1305
1420
|
* the inherited value while every unlisted key is still inherited. This transport cannot
|
|
1306
1421
|
* REPLACE the inherited environment entirely — the supervisor always merges over the parent.
|
|
1422
|
+
* - `delivery` — the bound in milliseconds on one unconfirmed write to the child's `stdin`;
|
|
1423
|
+
* an explicit `0` opts out. Defaults to {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
|
|
1307
1424
|
*/
|
|
1308
1425
|
export declare interface StdioClientTransportOptions {
|
|
1309
1426
|
readonly command: string;
|
|
1310
1427
|
readonly args?: readonly string[];
|
|
1311
1428
|
readonly env?: Readonly<Record<string, string>>;
|
|
1429
|
+
/**
|
|
1430
|
+
* Bounds the wait on one unconfirmed `send` write to the child's `stdin`, in milliseconds.
|
|
1431
|
+
*
|
|
1432
|
+
* @remarks
|
|
1433
|
+
* A full pipe is the case this bounds: a live child that never reads its `stdin` leaves the
|
|
1434
|
+
* kernel unable to confirm the write, and the supervisor holds that write open. When the
|
|
1435
|
+
* window elapses the supervisor answers that write `false`, which this transport surfaces as
|
|
1436
|
+
* a rejection, so the caller learns the message did not land instead of waiting on a peer
|
|
1437
|
+
* that will never read it. Default: {@link import('./constants.js').DEFAULT_MCP_DELIVERY}.
|
|
1438
|
+
*
|
|
1439
|
+
* An explicit `0` opts out: the bound is off, and an unconfirmed write stays pending until
|
|
1440
|
+
* the channel faults or teardown settles it. Omission does NOT opt out here, which is where
|
|
1441
|
+
* this option diverges from the supervisor's own `delivery` on {@link
|
|
1442
|
+
* import('@orkestrel/process').ProcessOptions} — omitted THERE disables the bound, omitted
|
|
1443
|
+
* HERE selects the default.
|
|
1444
|
+
*
|
|
1445
|
+
* An out-of-range value surfaces at `start()` rather than at construction. This transport
|
|
1446
|
+
* forwards the value verbatim and adds no validator of its own, so the supervisor's own timer
|
|
1447
|
+
* range is what rejects it, at the moment it spawns the child.
|
|
1448
|
+
*/
|
|
1449
|
+
readonly delivery?: number;
|
|
1312
1450
|
}
|
|
1313
1451
|
|
|
1314
1452
|
/**
|