@orkestrel/mcp 0.0.20 → 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.
@@ -1363,7 +1363,7 @@ var WebSocketClientTransport = class {
1363
1363
  //#region src/server/transports/StdioClientTransport.ts
1364
1364
  /**
1365
1365
  * The stdio CLIENT transport for the Model Context Protocol — a
1366
- * {@link MCPClientTransportInterface} that drives a CHILD PROCESS MCP server over
1366
+ * {@link StdioClientTransportInterface} that drives a CHILD PROCESS MCP server over
1367
1367
  * newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
1368
1368
  * import('./HTTPClientTransport.js').HTTPClientTransport} and {@link
1369
1369
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
@@ -1384,16 +1384,32 @@ var WebSocketClientTransport = class {
1384
1384
  * rejects — it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1385
1385
  * that failed — so a `false` answer REJECTS here with the same not-connected error a transport
1386
1386
  * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
1387
- * - **`close()`** releases this transport's line pump without waiting for the child's stdout
1388
- * iterator, then runs the supervisor's bounded `SIGTERM` grace `SIGKILL` group-kill and
1389
- * teardown before firing `close` once (idempotent). A descendant can retain an inherited stdout
1390
- * pipe after the child exits; the pump's release barrier keeps that substrate limit from keeping
1391
- * this transport's `close()` pending. On a POSIX host the child leads its own process group, so
1392
- * the group-kill reaches its grandchildren rather than orphaning them.
1387
+ * - **`close()`** runs the supervisor's bounded termination and teardown, then fires `close` once
1388
+ * (idempotent). That teardown reaches the child's TERMINAL MOMENT, where the supervisor freezes
1389
+ * `evidence`, ends `lines`, and settles `exit` together, so this transport needs no release of
1390
+ * its own to get its line pump back: the stream ends under the pump rather than throwing at it.
1391
+ * A line the supervisor had already framed behind the one being delivered is dropped rather than
1392
+ * emitted onto a transport whose teardown has begun. A `close()` issued while that teardown runs
1393
+ * joins it rather than opening a second one, so it resolves only after `close` has fired, and a
1394
+ * `start()` issued while it runs waits behind the same barrier, so lifetimes never overlap. A
1395
+ * descendant can retain an inherited stdout pipe after the child exits; the supervisor's `drain`
1396
+ * bound cuts that wait off, so this transport's `close()` settles within that bound rather than
1397
+ * on the descendant. The termination itself belongs to the host: a POSIX host signals the
1398
+ * child's own process group `SIGTERM`, waits the grace window, then `SIGKILL`s through the same
1399
+ * route, so the kill reaches grandchildren rather than orphaning them, while Windows ends the
1400
+ * tree with `taskkill /F /T`, which nothing in the child can intercept.
1401
+ * - **Evidence.** `evidence` reports that retained stderr tail off the HELD child — its live tail
1402
+ * while the child runs, and the value the supervisor froze at that child's terminal moment
1403
+ * afterwards. The reference is held past that moment and replaced only by the next `start()`,
1404
+ * which is what keeps a post-`close()` read stable without a private copy: the frozen value
1405
+ * never moves again, so a detached descendant writing to the inherited stderr after the cutoff
1406
+ * cannot grow it. See {@link StdioClientTransportInterface.evidence} for the readings and the
1407
+ * byte bound.
1393
1408
  * - **Observable.** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1394
1409
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1395
- * fault, including the child spawn cause the supervisor surfaces), distinct from the emitter's
1396
- * own listener-error channel.
1410
+ * fault, including the child spawn cause the supervisor surfaces and the notice that this
1411
+ * lifetime's `evidence` was cut off at the `drain` bound), distinct from the emitter's own
1412
+ * listener-error channel.
1397
1413
  *
1398
1414
  * @example
1399
1415
  * ```ts
@@ -1408,8 +1424,7 @@ var StdioClientTransport = class {
1408
1424
  #args;
1409
1425
  #env;
1410
1426
  #process = void 0;
1411
- #release = Promise.withResolvers();
1412
- #pumping = Promise.resolve();
1427
+ #closing = void 0;
1413
1428
  #closed = false;
1414
1429
  constructor(options) {
1415
1430
  this.#emitter = new _orkestrel_emitter.Emitter();
@@ -1424,10 +1439,21 @@ var StdioClientTransport = class {
1424
1439
  get duplex() {
1425
1440
  return true;
1426
1441
  }
1442
+ get evidence() {
1443
+ return this.#process?.evidence;
1444
+ }
1427
1445
  async start() {
1428
- if (this.#process !== void 0) return;
1446
+ let closing = this.#closing;
1447
+ while (closing !== void 0) {
1448
+ await closing;
1449
+ if (this.#closing === closing) {
1450
+ this.#closing = void 0;
1451
+ break;
1452
+ }
1453
+ closing = this.#closing;
1454
+ }
1455
+ if (this.#process !== void 0 && !this.#closed) return;
1429
1456
  this.#closed = false;
1430
- this.#release = Promise.withResolvers();
1431
1457
  const child = new _orkestrel_process_server.Process({
1432
1458
  command: {
1433
1459
  file: this.#command,
@@ -1440,39 +1466,49 @@ var StdioClientTransport = class {
1440
1466
  });
1441
1467
  this.#process = child;
1442
1468
  child.emitter.on("error", (cause) => this.#emitter.emit("error", cause));
1443
- child.exit.then(() => this.#onExit(child));
1444
- this.#pumping = this.#pump(child, this.#release.promise);
1469
+ child.exit.then((exit) => this.#onExit(child, exit));
1470
+ this.#pump(child);
1445
1471
  }
1446
1472
  async send(message) {
1447
- const child = this.#process;
1473
+ const child = this.#closed ? void 0 : this.#process;
1448
1474
  if (!(child === void 0 ? false : await child.send(JSON.stringify(message)))) throw new Error("stdio transport is not connected");
1449
1475
  }
1450
1476
  async close() {
1477
+ if (this.#closed && this.#closing === void 0) return;
1478
+ this.#closing ??= this.#teardown();
1479
+ await this.#closing;
1480
+ }
1481
+ async #teardown() {
1451
1482
  if (this.#closed) return;
1452
1483
  this.#closed = true;
1453
1484
  const child = this.#process;
1454
- const pumping = this.#pumping;
1455
- this.#release.resolve();
1456
- this.#process = void 0;
1457
- if (child !== void 0) await child.destroy();
1458
- await pumping;
1485
+ if (child !== void 0) {
1486
+ await child.destroy();
1487
+ this.#report(await child.exit);
1488
+ }
1459
1489
  this.#emitter.emit("close");
1460
1490
  }
1461
- async #pump(child, release) {
1462
- const iterator = child.lines[Symbol.asyncIterator]();
1463
- while (true) {
1464
- const next = await Promise.race([iterator.next(), release]);
1465
- if (next === void 0 || next.done) return;
1466
- if (this.#process !== child) return;
1467
- dispatchLines(this.#emitter, [next.value]);
1491
+ async #pump(child) {
1492
+ for await (const line of child.lines) {
1493
+ if (this.#closed || this.#process !== child) return;
1494
+ dispatchLines(this.#emitter, [line]);
1468
1495
  }
1469
1496
  }
1470
- #onExit(child) {
1471
- if (this.#closed || this.#process !== child) return;
1497
+ #onExit(child, exit) {
1498
+ if (this.#process !== child) return;
1499
+ if (this.#closed) return;
1472
1500
  this.#closed = true;
1473
- this.#process = void 0;
1501
+ const barrier = Promise.withResolvers();
1502
+ this.#closing ??= barrier.promise;
1503
+ this.#report(exit);
1504
+ barrier.resolve();
1505
+ if (this.#closing === barrier.promise) this.#closing = void 0;
1474
1506
  this.#emitter.emit("close");
1475
1507
  }
1508
+ #report(exit) {
1509
+ if (exit.drained) return;
1510
+ this.#emitter.emit("error", /* @__PURE__ */ new Error("stdio transport evidence may be incomplete: the child streams stayed open past the supervisor drain bound"));
1511
+ }
1476
1512
  };
1477
1513
  //#endregion
1478
1514
  //#region src/server/transports/StdioServerTransport.ts
@@ -1817,22 +1853,24 @@ function createWebSocketClientTransport(options) {
1817
1853
  }
1818
1854
  /**
1819
1855
  * Creates the stdio CLIENT transport for an {@link import('@orkestrel/mcp').MCPClientInterface}
1820
- * — a {@link MCPClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
1856
+ * — a {@link StdioClientTransportInterface} that spawns and drives a CHILD PROCESS MCP server
1821
1857
  * over newline-delimited JSON-RPC on `stdin`/`stdout`, the stdio sibling of {@link
1822
1858
  * createHTTPClientTransport} and {@link createWebSocketClientTransport}.
1823
1859
  *
1824
1860
  * @remarks
1825
1861
  * Hand it to `createMCPClient({ transport })`: `start()` (run by `client.connect()`)
1826
1862
  * spawns `options.command` with `options.args` and `options.env`, piping its
1827
- * `stdin`/`stdout` for the JSON-RPC channel (its `stderr` inherits the parent's for
1828
- * diagnostics). Each JSON-RPC message the client `send`s is written as one
1863
+ * `stdin`/`stdout` for the JSON-RPC channel. The child's `stderr` is piped too, and
1864
+ * retained as a bounded tail this transport reports as `evidence` the parent never
1865
+ * inherits it. Each JSON-RPC message the client `send`s is written as one
1829
1866
  * newline-terminated line to the child's `stdin`; each decoded reply line from the
1830
1867
  * child's `stdout` is surfaced on the transport's `message` event for the client's
1831
1868
  * id correlation.
1832
1869
  *
1833
1870
  * @param options - `command` (the executable to spawn; REQUIRED), optional `args`,
1834
1871
  * and optional `env`; see {@link StdioClientTransportOptions}
1835
- * @returns A working {@link MCPClientTransportInterface} over a child process's stdio
1872
+ * @returns A working {@link StdioClientTransportInterface} over a child process's stdio,
1873
+ * whose `evidence` carries the supervised child's bounded stderr tail
1836
1874
  *
1837
1875
  * @example
1838
1876
  * ```ts