@orkestrel/mcp 0.0.18 → 0.0.19

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.
@@ -8,7 +8,8 @@ let node_crypto = require("node:crypto");
8
8
  let node_http = require("node:http");
9
9
  let node_https = require("node:https");
10
10
  let _orkestrel_websocket = require("@orkestrel/websocket");
11
- let node_child_process = require("node:child_process");
11
+ let _orkestrel_process_server = require("@orkestrel/process/server");
12
+ let _orkestrel_process = require("@orkestrel/process");
12
13
  //#region src/server/constants.ts
13
14
  /**
14
15
  * The Streamable-HTTP transport header that carries the MCP session id. When a {@link
@@ -1297,22 +1298,29 @@ var WebSocketClientTransport = class {
1297
1298
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
1298
1299
  *
1299
1300
  * @remarks
1300
- * - **Spawns the server.** `start()` runs `node:child_process`'s `spawn(options.command,
1301
- * options.args, { env: options.env, stdio: ['pipe', 'pipe', 'inherit'] })` the
1302
- * child's `stdin`/`stdout` are piped for the JSON-RPC channel, its `stderr` inherits
1303
- * the parent's (diagnostics pass through, never parsed as protocol).
1304
- * - **Inbound (`message`).** Each `stdout` chunk is folded through the shared
1305
- * {@link extractLines} line-framing helper (buffering a partial trailing line
1306
- * across reads); every complete line is decoded and delivered via the shared
1307
- * {@link dispatchLines} helper — a well-formed {@link JSONRPCMessage} emits
1308
- * `message`, a malformed line emits `error` (§14, never throws). The child's
1309
- * `close` bridges to this transport's `close`.
1310
- * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1311
- * `JSON.stringify`d line to the child's `stdin`.
1312
- * - **`close()`** kills the child process and fires `close` (idempotent).
1301
+ * - **Composes `@orkestrel/process`.** `start()` builds one supervised
1302
+ * {@link import('@orkestrel/process/server').Process} with `writable: true`, so the child's
1303
+ * `stdin`/`stdout` are the JSON-RPC channel and its `stderr` is retained as bounded evidence
1304
+ * rather than parsed as protocol. The supervisor owns spawn, framing, and termination.
1305
+ * - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's
1306
+ * `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is
1307
+ * decoded whole and a final line written without a trailing newline still arrives. Each framed
1308
+ * line is decoded and delivered via the shared {@link dispatchLines} helper — a well-formed
1309
+ * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (§14, never throws).
1310
+ * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1311
+ * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1312
+ * host reports the line handled rather than the moment the write is queued. The supervisor never
1313
+ * rejects it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1314
+ * that failed — so a `false` answer REJECTS here with the same not-connected error a transport
1315
+ * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
1316
+ * - **`close()`** terminates the child through the supervisor's bounded `SIGTERM` → grace →
1317
+ * `SIGKILL` group-kill, awaits its observed exit, tears down the supervisor, and fires `close`
1318
+ * once (idempotent). On a POSIX host the child leads its own process group, so the group-kill
1319
+ * reaches its grandchildren rather than orphaning them.
1313
1320
  * - **Observable (§13).** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1314
1321
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1315
- * fault), distinct from the emitter's own listener-error channel.
1322
+ * fault, including the child spawn cause the supervisor surfaces), distinct from the emitter's
1323
+ * own listener-error channel.
1316
1324
  *
1317
1325
  * @example
1318
1326
  * ```ts
@@ -1326,8 +1334,7 @@ var StdioClientTransport = class {
1326
1334
  #command;
1327
1335
  #args;
1328
1336
  #env;
1329
- #child = void 0;
1330
- #buffer = "";
1337
+ #process = void 0;
1331
1338
  #closed = false;
1332
1339
  constructor(options) {
1333
1340
  this.#emitter = new _orkestrel_emitter.Emitter();
@@ -1343,44 +1350,45 @@ var StdioClientTransport = class {
1343
1350
  return true;
1344
1351
  }
1345
1352
  async start() {
1346
- if (this.#child !== void 0) return;
1353
+ if (this.#process !== void 0) return;
1347
1354
  this.#closed = false;
1348
- this.#buffer = "";
1349
- const child = (0, node_child_process.spawn)(this.#command, [...this.#args], {
1350
- env: this.#env,
1351
- stdio: [
1352
- "pipe",
1353
- "pipe",
1354
- "inherit"
1355
- ]
1355
+ const child = new _orkestrel_process_server.Process({
1356
+ command: {
1357
+ file: this.#command,
1358
+ arguments: [...this.#args],
1359
+ ...this.#env === void 0 ? {} : { environment: this.#env }
1360
+ },
1361
+ workspace: process.cwd(),
1362
+ grace: _orkestrel_process.PROCESS_GRACE,
1363
+ writable: true
1356
1364
  });
1357
- this.#child = child;
1358
- child.stdout.on("data", (chunk) => this.#receive(chunk.toString()));
1359
- child.on("close", () => this.#onClose(child));
1360
- child.on("error", (error) => this.#emitter.emit("error", error));
1365
+ this.#process = child;
1366
+ child.emitter.on("error", (cause) => this.#emitter.emit("error", cause));
1367
+ child.exit.then(() => this.#onExit(child));
1368
+ this.#pump(child);
1361
1369
  }
1362
1370
  async send(message) {
1363
- const child = this.#child;
1364
- if (child === void 0) throw new Error("stdio transport is not connected");
1365
- child.stdin.write(`${JSON.stringify(message)}\n`);
1371
+ const child = this.#process;
1372
+ if (!(child === void 0 ? false : await child.send(JSON.stringify(message)))) throw new Error("stdio transport is not connected");
1366
1373
  }
1367
1374
  async close() {
1368
1375
  if (this.#closed) return;
1369
1376
  this.#closed = true;
1370
- const child = this.#child;
1371
- this.#child = void 0;
1372
- if (child !== void 0) child.kill();
1377
+ const child = this.#process;
1378
+ this.#process = void 0;
1379
+ if (child !== void 0) await child.destroy();
1373
1380
  this.#emitter.emit("close");
1374
1381
  }
1375
- #receive(chunk) {
1376
- const { lines, remainder } = extractLines(this.#buffer, chunk);
1377
- this.#buffer = remainder;
1378
- dispatchLines(this.#emitter, lines);
1382
+ async #pump(child) {
1383
+ for await (const line of child.lines) {
1384
+ if (this.#process !== child) return;
1385
+ dispatchLines(this.#emitter, [line]);
1386
+ }
1379
1387
  }
1380
- #onClose(child) {
1381
- if (this.#closed || this.#child !== child) return;
1388
+ #onExit(child) {
1389
+ if (this.#closed || this.#process !== child) return;
1382
1390
  this.#closed = true;
1383
- this.#child = void 0;
1391
+ this.#process = void 0;
1384
1392
  this.#emitter.emit("close");
1385
1393
  }
1386
1394
  };