@orkestrel/mcp 0.0.21 → 0.0.23

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.
@@ -336,10 +336,14 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
336
336
  * inherits it. Each JSON-RPC message the client `send`s is written as one
337
337
  * newline-terminated line to the child's `stdin`; each decoded reply line from the
338
338
  * child's `stdout` is surfaced on the transport's `message` event for the client's
339
- * 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.
340
343
  *
341
344
  * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
342
- * and optional `env`; see {@link StdioClientTransportOptions}
345
+ * optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
346
+ * `stdin` write; see {@link StdioClientTransportOptions}
343
347
  * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
344
348
  * whose `evidence` carries the supervised child's bounded stderr tail
345
349
  *
@@ -498,6 +502,19 @@ export declare function createWebSocketServer(mcp: MCPDispatcherInterface, optio
498
502
  */
499
503
  export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
500
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
+
501
518
  /**
502
519
  * The default interval in milliseconds between SSE keepalive comments on held-open MCP
503
520
  * responses.
@@ -1262,9 +1279,10 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1262
1279
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1263
1280
  * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1264
1281
  * host reports the line handled rather than the moment the write is queued. The supervisor never
1265
- * rejects — it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1266
- * that failed so a `false` answer REJECTS here with the same not-connected error a transport
1267
- * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
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.
1268
1286
  * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
1269
1287
  * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
1270
1288
  * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
@@ -1307,6 +1325,16 @@ export declare class StdioClientTransport implements StdioClientTransportInterfa
1307
1325
  get duplex(): boolean;
1308
1326
  get evidence(): string | undefined;
1309
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
+ */
1310
1338
  send(message: JSONRPCMessage_2): Promise<void>;
1311
1339
  close(): Promise<void>;
1312
1340
  }
@@ -1391,11 +1419,34 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
1391
1419
  * OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
1392
1420
  * the inherited value while every unlisted key is still inherited. This transport cannot
1393
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}.
1394
1424
  */
1395
1425
  export declare interface StdioClientTransportOptions {
1396
1426
  readonly command: string;
1397
1427
  readonly args?: readonly string[];
1398
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;
1399
1450
  }
1400
1451
 
1401
1452
  /**
@@ -1433,8 +1484,10 @@ export declare interface StdioServerOptions {
1433
1484
  * emits `error` (never throws). `input`'s `close` bridges to this
1434
1485
  * transport's `close`.
1435
1486
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1436
- * `JSON.stringify`d line to `output`.
1437
- * - **`close()`** removes this transport's input subscriptions and fires its `close`
1487
+ * `JSON.stringify`d line to `output` and awaits the writable completion callback. The
1488
+ * callback is the backpressure boundary and its error rejects the send.
1489
+ * - **`close()`** removes this transport's input and output subscriptions, rejects every
1490
+ * pending send, and fires its `close`
1438
1491
  * event (idempotent). It pauses the input only when the caller was not already reading
1439
1492
  * it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this
1440
1493
  * transport's own is removed — so a process holding `process.stdin` can exit, and a
@@ -1457,6 +1510,19 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
1457
1510
  get session(): string | undefined;
1458
1511
  get duplex(): boolean;
1459
1512
  start(): Promise<void>;
1513
+ /**
1514
+ * Sends one newline-delimited JSON-RPC message through the caller-owned output stream.
1515
+ *
1516
+ * @remarks
1517
+ * The writable completion callback is the backpressure boundary. This method awaits that
1518
+ * callback rather than adding a `drain` listener. Closing the transport rejects every send
1519
+ * whose callback has not settled.
1520
+ *
1521
+ * @param message - The message to serialize and write
1522
+ * @returns Resolves when the output confirms the write
1523
+ * @throws Thrown with `stdio transport is not connected` after the transport closes
1524
+ * @throws Thrown with the output callback error or synchronous write failure
1525
+ */
1460
1526
  send(message: JSONRPCMessage_2): Promise<void>;
1461
1527
  close(): Promise<void>;
1462
1528
  }
@@ -1633,4 +1699,22 @@ export declare class WebSocketServerTransport implements MCPClientTransportInter
1633
1699
  close(): Promise<void>;
1634
1700
  }
1635
1701
 
1702
+ /**
1703
+ * Writes one line to a Node writable stream and waits for its completion callback.
1704
+ *
1705
+ * @remarks
1706
+ * The completion callback is the writable channel's backpressure boundary. A callback error and
1707
+ * a synchronous `write` throw reject the returned promise with the original value.
1708
+ *
1709
+ * @param output - The writable stream that receives the line
1710
+ * @param line - The complete line to write
1711
+ * @returns Resolves when the stream confirms the write; rejects when the write fails
1712
+ *
1713
+ * @example
1714
+ * ```ts
1715
+ * await writeLine(process.stdout, '{"jsonrpc":"2.0","method":"ping"}\n')
1716
+ * ```
1717
+ */
1718
+ export declare function writeLine(output: NodeJS.WritableStream, line: string): Promise<void>;
1719
+
1636
1720
  export { }
@@ -336,10 +336,14 @@ export declare function createReadableStream<T>(pull: (controller: ReadableStrea
336
336
  * inherits it. Each JSON-RPC message the client `send`s is written as one
337
337
  * newline-terminated line to the child's `stdin`; each decoded reply line from the
338
338
  * child's `stdout` is surfaced on the transport's `message` event for the client's
339
- * 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.
340
343
  *
341
344
  * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
342
- * and optional `env`; see {@link StdioClientTransportOptions}
345
+ * optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
346
+ * `stdin` write; see {@link StdioClientTransportOptions}
343
347
  * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
344
348
  * whose `evidence` carries the supervised child's bounded stderr tail
345
349
  *
@@ -498,6 +502,19 @@ export declare function createWebSocketServer(mcp: MCPDispatcherInterface, optio
498
502
  */
499
503
  export declare function decodeEvent(data: string): JSONRPCMessage | undefined;
500
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
+
501
518
  /**
502
519
  * The default interval in milliseconds between SSE keepalive comments on held-open MCP
503
520
  * responses.
@@ -1262,9 +1279,10 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1262
1279
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1263
1280
  * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1264
1281
  * host reports the line handled rather than the moment the write is queued. The supervisor never
1265
- * rejects — it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1266
- * that failed so a `false` answer REJECTS here with the same not-connected error a transport
1267
- * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
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.
1268
1286
  * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
1269
1287
  * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
1270
1288
  * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
@@ -1307,6 +1325,16 @@ export declare class StdioClientTransport implements StdioClientTransportInterfa
1307
1325
  get duplex(): boolean;
1308
1326
  get evidence(): string | undefined;
1309
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
+ */
1310
1338
  send(message: JSONRPCMessage_2): Promise<void>;
1311
1339
  close(): Promise<void>;
1312
1340
  }
@@ -1391,11 +1419,34 @@ export declare interface StdioClientTransportInterface extends MCPClientTranspor
1391
1419
  * OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
1392
1420
  * the inherited value while every unlisted key is still inherited. This transport cannot
1393
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}.
1394
1424
  */
1395
1425
  export declare interface StdioClientTransportOptions {
1396
1426
  readonly command: string;
1397
1427
  readonly args?: readonly string[];
1398
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;
1399
1450
  }
1400
1451
 
1401
1452
  /**
@@ -1433,8 +1484,10 @@ export declare interface StdioServerOptions {
1433
1484
  * emits `error` (never throws). `input`'s `close` bridges to this
1434
1485
  * transport's `close`.
1435
1486
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1436
- * `JSON.stringify`d line to `output`.
1437
- * - **`close()`** removes this transport's input subscriptions and fires its `close`
1487
+ * `JSON.stringify`d line to `output` and awaits the writable completion callback. The
1488
+ * callback is the backpressure boundary and its error rejects the send.
1489
+ * - **`close()`** removes this transport's input and output subscriptions, rejects every
1490
+ * pending send, and fires its `close`
1438
1491
  * event (idempotent). It pauses the input only when the caller was not already reading
1439
1492
  * it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this
1440
1493
  * transport's own is removed — so a process holding `process.stdin` can exit, and a
@@ -1457,6 +1510,19 @@ export declare class StdioServerTransport implements MCPClientTransportInterface
1457
1510
  get session(): string | undefined;
1458
1511
  get duplex(): boolean;
1459
1512
  start(): Promise<void>;
1513
+ /**
1514
+ * Sends one newline-delimited JSON-RPC message through the caller-owned output stream.
1515
+ *
1516
+ * @remarks
1517
+ * The writable completion callback is the backpressure boundary. This method awaits that
1518
+ * callback rather than adding a `drain` listener. Closing the transport rejects every send
1519
+ * whose callback has not settled.
1520
+ *
1521
+ * @param message - The message to serialize and write
1522
+ * @returns Resolves when the output confirms the write
1523
+ * @throws Thrown with `stdio transport is not connected` after the transport closes
1524
+ * @throws Thrown with the output callback error or synchronous write failure
1525
+ */
1460
1526
  send(message: JSONRPCMessage_2): Promise<void>;
1461
1527
  close(): Promise<void>;
1462
1528
  }
@@ -1633,4 +1699,22 @@ export declare class WebSocketServerTransport implements MCPClientTransportInter
1633
1699
  close(): Promise<void>;
1634
1700
  }
1635
1701
 
1702
+ /**
1703
+ * Writes one line to a Node writable stream and waits for its completion callback.
1704
+ *
1705
+ * @remarks
1706
+ * The completion callback is the writable channel's backpressure boundary. A callback error and
1707
+ * a synchronous `write` throw reject the returned promise with the original value.
1708
+ *
1709
+ * @param output - The writable stream that receives the line
1710
+ * @param line - The complete line to write
1711
+ * @returns Resolves when the stream confirms the write; rejects when the write fails
1712
+ *
1713
+ * @example
1714
+ * ```ts
1715
+ * await writeLine(process.stdout, '{"jsonrpc":"2.0","method":"ping"}\n')
1716
+ * ```
1717
+ */
1718
+ export declare function writeLine(output: NodeJS.WritableStream, line: string): Promise<void>;
1719
+
1636
1720
  export { }
@@ -84,6 +84,18 @@ var DEFAULT_MCP_SESSION_CAPACITY = 1024;
84
84
  * this bounds the replay log paired with it.
85
85
  */
86
86
  var DEFAULT_MCP_SESSION_TTL = 3e5;
87
+ /**
88
+ * The default bound in milliseconds on one unconfirmed write to a stdio client transport's
89
+ * child `stdin` — the `delivery` a `createStdioClientTransport` caller who supplies none gets.
90
+ *
91
+ * @remarks
92
+ * Ten seconds. The load-bearing property is the ordering, not the magnitude: this bound stays
93
+ * BELOW {@link import('@orkestrel/mcp').DEFAULT_MCP_REQUEST_TIMEOUT}, so a write the child never
94
+ * reads fails as an undeliverable message while the request that carried it is still open,
95
+ * rather than being masked by that request's own deadline expiring first. Override per
96
+ * transport with `delivery`; an explicit `0` there removes the bound.
97
+ */
98
+ var DEFAULT_MCP_DELIVERY = 1e4;
87
99
  //#endregion
88
100
  //#region src/server/helpers.ts
89
101
  /**
@@ -352,6 +364,34 @@ function extractLines(buffer, chunk) {
352
364
  };
353
365
  }
354
366
  /**
367
+ * Writes one line to a Node writable stream and waits for its completion callback.
368
+ *
369
+ * @remarks
370
+ * The completion callback is the writable channel's backpressure boundary. A callback error and
371
+ * a synchronous `write` throw reject the returned promise with the original value.
372
+ *
373
+ * @param output - The writable stream that receives the line
374
+ * @param line - The complete line to write
375
+ * @returns Resolves when the stream confirms the write; rejects when the write fails
376
+ *
377
+ * @example
378
+ * ```ts
379
+ * await writeLine(process.stdout, '{"jsonrpc":"2.0","method":"ping"}\n')
380
+ * ```
381
+ */
382
+ function writeLine(output, line) {
383
+ return new Promise((resolve, reject) => {
384
+ try {
385
+ output.write(line, (error) => {
386
+ if (error === void 0 || error === null) resolve();
387
+ else reject(error);
388
+ });
389
+ } catch (error) {
390
+ reject(error);
391
+ }
392
+ });
393
+ }
394
+ /**
355
395
  * Decodes and delivers each complete newline-framed line onto a {@link
356
396
  * MCPClientTransportEventMap} emitter — the shared per-chunk dispatch step both stdio
357
397
  * transports run their framed lines through: the server transport frames with {@link
@@ -1380,9 +1420,10 @@ var WebSocketClientTransport = class {
1380
1420
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1381
1421
  * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1382
1422
  * host reports the line handled rather than the moment the write is queued. The supervisor never
1383
- * rejects — it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1384
- * that failed so a `false` answer REJECTS here with the same not-connected error a transport
1385
- * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
1423
+ * rejects — it answers `false` for a channel that was closed, destroyed, or ended, for a write
1424
+ * that failed, or for one that remained unconfirmed through `delivery`. A call made without a
1425
+ * live child rejects as not connected; a `false` answer from a live child rejects as unable to
1426
+ * deliver. The supervisor does not disclose which cause produced that answer.
1386
1427
  * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
1387
1428
  * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
1388
1429
  * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
@@ -1422,6 +1463,7 @@ var StdioClientTransport = class {
1422
1463
  #command;
1423
1464
  #args;
1424
1465
  #env;
1466
+ #delivery;
1425
1467
  #process = void 0;
1426
1468
  #closing = void 0;
1427
1469
  #closed = false;
@@ -1430,6 +1472,7 @@ var StdioClientTransport = class {
1430
1472
  this.#command = options.command;
1431
1473
  this.#args = options.args ?? [];
1432
1474
  this.#env = options.env;
1475
+ this.#delivery = options.delivery ?? 1e4;
1433
1476
  }
1434
1477
  get emitter() {
1435
1478
  return this.#emitter;
@@ -1461,6 +1504,7 @@ var StdioClientTransport = class {
1461
1504
  },
1462
1505
  workspace: process.cwd(),
1463
1506
  grace: PROCESS_GRACE,
1507
+ delivery: this.#delivery,
1464
1508
  writable: true
1465
1509
  });
1466
1510
  this.#process = child;
@@ -1468,9 +1512,20 @@ var StdioClientTransport = class {
1468
1512
  child.exit.then((exit) => this.#onExit(child, exit));
1469
1513
  this.#pump(child);
1470
1514
  }
1515
+ /**
1516
+ * Sends one newline-delimited JSON-RPC message to the live child.
1517
+ *
1518
+ * @param message - The message to write to the child's `stdin`
1519
+ * @returns Resolves when the supervisor confirms the write
1520
+ * @throws Thrown with `stdio transport is not connected` when no live child is available before
1521
+ * the write
1522
+ * @throws Thrown with `stdio transport could not deliver the message` when a live child's write
1523
+ * resolves `false`
1524
+ */
1471
1525
  async send(message) {
1472
1526
  const child = this.#closed ? void 0 : this.#process;
1473
- if (!(child === void 0 ? false : await child.send(JSON.stringify(message)))) throw new Error("stdio transport is not connected");
1527
+ if (child === void 0) throw new Error("stdio transport is not connected");
1528
+ if (!await child.send(JSON.stringify(message))) throw new Error("stdio transport could not deliver the message");
1474
1529
  }
1475
1530
  async close() {
1476
1531
  if (this.#closed && this.#closing === void 0) return;
@@ -1531,8 +1586,10 @@ var StdioClientTransport = class {
1531
1586
  * emits `error` (never throws). `input`'s `close` bridges to this
1532
1587
  * transport's `close`.
1533
1588
  * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1534
- * `JSON.stringify`d line to `output`.
1535
- * - **`close()`** removes this transport's input subscriptions and fires its `close`
1589
+ * `JSON.stringify`d line to `output` and awaits the writable completion callback. The
1590
+ * callback is the backpressure boundary and its error rejects the send.
1591
+ * - **`close()`** removes this transport's input and output subscriptions, rejects every
1592
+ * pending send, and fires its `close`
1536
1593
  * event (idempotent). It pauses the input only when the caller was not already reading
1537
1594
  * it at `start` (`readableFlowing !== true`) AND no `data` listener remains once this
1538
1595
  * transport's own is removed — so a process holding `process.stdin` can exit, and a
@@ -1555,6 +1612,7 @@ var StdioServerTransport = class {
1555
1612
  #data = (chunk) => this.#receive(chunk.toString());
1556
1613
  #ending = () => this.#onClose();
1557
1614
  #failure = (error) => this.#emitter.emit("error", error);
1615
+ #pending = /* @__PURE__ */ new Set();
1558
1616
  #buffer = "";
1559
1617
  #started = false;
1560
1618
  #closed = false;
@@ -1578,9 +1636,30 @@ var StdioServerTransport = class {
1578
1636
  this.#input.on("data", this.#data);
1579
1637
  this.#input.on("close", this.#ending);
1580
1638
  this.#input.on("error", this.#failure);
1639
+ this.#output.on("error", this.#failure);
1581
1640
  }
1641
+ /**
1642
+ * Sends one newline-delimited JSON-RPC message through the caller-owned output stream.
1643
+ *
1644
+ * @remarks
1645
+ * The writable completion callback is the backpressure boundary. This method awaits that
1646
+ * callback rather than adding a `drain` listener. Closing the transport rejects every send
1647
+ * whose callback has not settled.
1648
+ *
1649
+ * @param message - The message to serialize and write
1650
+ * @returns Resolves when the output confirms the write
1651
+ * @throws Thrown with `stdio transport is not connected` after the transport closes
1652
+ * @throws Thrown with the output callback error or synchronous write failure
1653
+ */
1582
1654
  async send(message) {
1583
- this.#output.write(`${JSON.stringify(message)}\n`);
1655
+ if (this.#closed) throw new Error("stdio transport is not connected");
1656
+ const pending = Promise.withResolvers();
1657
+ this.#pending.add(pending);
1658
+ try {
1659
+ await Promise.race([writeLine(this.#output, `${JSON.stringify(message)}\n`), pending.promise]);
1660
+ } finally {
1661
+ this.#pending.delete(pending);
1662
+ }
1584
1663
  }
1585
1664
  async close() {
1586
1665
  if (this.#closed) return;
@@ -1603,6 +1682,9 @@ var StdioServerTransport = class {
1603
1682
  this.#input.removeListener("data", this.#data);
1604
1683
  this.#input.removeListener("close", this.#ending);
1605
1684
  this.#input.removeListener("error", this.#failure);
1685
+ this.#output.removeListener("error", this.#failure);
1686
+ for (const pending of this.#pending) pending.reject(/* @__PURE__ */ new Error("stdio transport is not connected"));
1687
+ this.#pending.clear();
1606
1688
  if (!this.#flowing && this.#input.listenerCount("data") === 0) this.#input.pause();
1607
1689
  }
1608
1690
  };
@@ -1864,10 +1946,14 @@ function createWebSocketClientTransport(options) {
1864
1946
  * inherits it. Each JSON-RPC message the client `send`s is written as one
1865
1947
  * newline-terminated line to the child's `stdin`; each decoded reply line from the
1866
1948
  * child's `stdout` is surfaced on the transport's `message` event for the client's
1867
- * id correlation.
1949
+ * id correlation. That write is bounded: a child that stays alive without ever reading
1950
+ * its `stdin` fills the pipe, and `options.delivery` is how long the unconfirmed write
1951
+ * waits before the `send` rejects. An omitted `delivery` selects {@link
1952
+ * import('./constants.js').DEFAULT_MCP_DELIVERY}; an explicit `0` removes the bound.
1868
1953
  *
1869
1954
  * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
1870
- * and optional `env`; see {@link StdioClientTransportOptions}
1955
+ * optional `env`, and an optional `delivery` bound in milliseconds on an unconfirmed
1956
+ * `stdin` write; see {@link StdioClientTransportOptions}
1871
1957
  * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
1872
1958
  * whose `evidence` carries the supervised child's bounded stderr tail
1873
1959
  *
@@ -2106,6 +2192,6 @@ function createMCPSession(options) {
2106
2192
  };
2107
2193
  }
2108
2194
  //#endregion
2109
- export { DEFAULT_MCP_KEEPALIVE_INTERVAL, DEFAULT_MCP_PATH, DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL, HTTPClientTransport, HTTPDisconnect, MCPSession, MCP_METHOD_HEADER, MCP_NAME_HEADER, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_HEADER, MCP_WEBSOCKET_SUBPROTOCOL, SSE_BUFFERING_DISABLED, SSE_BUFFERING_HEADER, SSE_KEEPALIVE_COMMENT, StdioClientTransport, StdioServerTransport, WebSocketClientTransport, WebSocketServerTransport, acceptsEventStream, allowsOrigin, bridgeMessageTransport, createHTTPClientTransport, createMCPContinuation, createMCPPostHandler, createMCPRoutes, createMCPSession, createReadableStream, createStdioClientTransport, createStdioServer, createWebSocketClientTransport, createWebSocketServer, decodeEvent, dispatchLines, extractLines, inferHeaderIssue, inferLegacyVersion, inferStatus, readEventStream, readLastEventId, readSessionHeader, rejectUnknownSession, sendEventStream, upgradeRequestPath };
2195
+ export { DEFAULT_MCP_DELIVERY, DEFAULT_MCP_KEEPALIVE_INTERVAL, DEFAULT_MCP_PATH, DEFAULT_MCP_SESSION_CAPACITY, DEFAULT_MCP_SESSION_TTL, HTTPClientTransport, HTTPDisconnect, MCPSession, MCP_METHOD_HEADER, MCP_NAME_HEADER, MCP_PROTOCOL_VERSION_HEADER, MCP_SESSION_HEADER, MCP_WEBSOCKET_SUBPROTOCOL, SSE_BUFFERING_DISABLED, SSE_BUFFERING_HEADER, SSE_KEEPALIVE_COMMENT, StdioClientTransport, StdioServerTransport, WebSocketClientTransport, WebSocketServerTransport, acceptsEventStream, allowsOrigin, bridgeMessageTransport, createHTTPClientTransport, createMCPContinuation, createMCPPostHandler, createMCPRoutes, createMCPSession, createReadableStream, createStdioClientTransport, createStdioServer, createWebSocketClientTransport, createWebSocketServer, decodeEvent, dispatchLines, extractLines, inferHeaderIssue, inferLegacyVersion, inferStatus, readEventStream, readLastEventId, readSessionHeader, rejectUnknownSession, sendEventStream, upgradeRequestPath, writeLine };
2110
2196
 
2111
2197
  //# sourceMappingURL=index.js.map