@orkestrel/mcp 0.0.17 → 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.
@@ -1231,22 +1231,29 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1231
1231
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
1232
1232
  *
1233
1233
  * @remarks
1234
- * - **Spawns the server.** `start()` runs `node:child_process`'s `spawn(options.command,
1235
- * options.args, { env: options.env, stdio: ['pipe', 'pipe', 'inherit'] })` the
1236
- * child's `stdin`/`stdout` are piped for the JSON-RPC channel, its `stderr` inherits
1237
- * the parent's (diagnostics pass through, never parsed as protocol).
1238
- * - **Inbound (`message`).** Each `stdout` chunk is folded through the shared
1239
- * {@link extractLines} line-framing helper (buffering a partial trailing line
1240
- * across reads); every complete line is decoded and delivered via the shared
1241
- * {@link dispatchLines} helper — a well-formed {@link JSONRPCMessage} emits
1242
- * `message`, a malformed line emits `error` (§14, never throws). The child's
1243
- * `close` bridges to this transport's `close`.
1244
- * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1245
- * `JSON.stringify`d line to the child's `stdin`.
1246
- * - **`close()`** kills the child process and fires `close` (idempotent).
1234
+ * - **Composes `@orkestrel/process`.** `start()` builds one supervised
1235
+ * {@link import('@orkestrel/process/server').Process} with `writable: true`, so the child's
1236
+ * `stdin`/`stdout` are the JSON-RPC channel and its `stderr` is retained as bounded evidence
1237
+ * rather than parsed as protocol. The supervisor owns spawn, framing, and termination.
1238
+ * - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's
1239
+ * `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is
1240
+ * decoded whole and a final line written without a trailing newline still arrives. Each framed
1241
+ * line is decoded and delivered via the shared {@link dispatchLines} helper — a well-formed
1242
+ * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (§14, never throws).
1243
+ * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1244
+ * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1245
+ * host reports the line handled rather than the moment the write is queued. The supervisor never
1246
+ * rejects it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1247
+ * that failed — so a `false` answer REJECTS here with the same not-connected error a transport
1248
+ * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
1249
+ * - **`close()`** terminates the child through the supervisor's bounded `SIGTERM` → grace →
1250
+ * `SIGKILL` group-kill, awaits its observed exit, tears down the supervisor, and fires `close`
1251
+ * once (idempotent). On a POSIX host the child leads its own process group, so the group-kill
1252
+ * reaches its grandchildren rather than orphaning them.
1247
1253
  * - **Observable (§13).** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1248
1254
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1249
- * fault), distinct from the emitter's own listener-error channel.
1255
+ * fault, including the child spawn cause the supervisor surfaces), distinct from the emitter's
1256
+ * own listener-error channel.
1250
1257
  *
1251
1258
  * @example
1252
1259
  * ```ts
@@ -1273,11 +1280,11 @@ export declare class StdioClientTransport implements MCPClientTransportInterface
1273
1280
  * @remarks
1274
1281
  * - `command` — the executable to spawn (e.g. `'node'`, `'./my-mcp-server'`). REQUIRED.
1275
1282
  * - `args` — the command-line arguments passed to `command`; defaults to none.
1276
- * - `env` — the environment variables for the spawned child, passed straight to
1277
- * `node:child_process`'s `spawn`; when OMITTED the child inherits the full
1278
- * `process.env` (the `spawn` default), when PROVIDED it REPLACES the inherited
1279
- * environment entirely (`spawn` semantics) a caller wanting to extend rather
1280
- * than replace spreads `process.env` into `env` themselves.
1283
+ * - `env` — environment variable overrides MERGED over the parent `process.env` for the
1284
+ * spawned child (the composed `@orkestrel/process` supervisor's merge semantics): when
1285
+ * OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
1286
+ * the inherited value while every unlisted key is still inherited. This transport cannot
1287
+ * REPLACE the inherited environment entirely the supervisor always merges over the parent.
1281
1288
  */
1282
1289
  export declare interface StdioClientTransportOptions {
1283
1290
  readonly command: string;
@@ -1231,22 +1231,29 @@ export declare const SSE_KEEPALIVE_COMMENT = "keepalive";
1231
1231
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
1232
1232
  *
1233
1233
  * @remarks
1234
- * - **Spawns the server.** `start()` runs `node:child_process`'s `spawn(options.command,
1235
- * options.args, { env: options.env, stdio: ['pipe', 'pipe', 'inherit'] })` the
1236
- * child's `stdin`/`stdout` are piped for the JSON-RPC channel, its `stderr` inherits
1237
- * the parent's (diagnostics pass through, never parsed as protocol).
1238
- * - **Inbound (`message`).** Each `stdout` chunk is folded through the shared
1239
- * {@link extractLines} line-framing helper (buffering a partial trailing line
1240
- * across reads); every complete line is decoded and delivered via the shared
1241
- * {@link dispatchLines} helper — a well-formed {@link JSONRPCMessage} emits
1242
- * `message`, a malformed line emits `error` (§14, never throws). The child's
1243
- * `close` bridges to this transport's `close`.
1244
- * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1245
- * `JSON.stringify`d line to the child's `stdin`.
1246
- * - **`close()`** kills the child process and fires `close` (idempotent).
1234
+ * - **Composes `@orkestrel/process`.** `start()` builds one supervised
1235
+ * {@link import('@orkestrel/process/server').Process} with `writable: true`, so the child's
1236
+ * `stdin`/`stdout` are the JSON-RPC channel and its `stderr` is retained as bounded evidence
1237
+ * rather than parsed as protocol. The supervisor owns spawn, framing, and termination.
1238
+ * - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's
1239
+ * `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is
1240
+ * decoded whole and a final line written without a trailing newline still arrives. Each framed
1241
+ * line is decoded and delivered via the shared {@link dispatchLines} helper — a well-formed
1242
+ * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (§14, never throws).
1243
+ * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1244
+ * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1245
+ * host reports the line handled rather than the moment the write is queued. The supervisor never
1246
+ * rejects it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1247
+ * that failed — so a `false` answer REJECTS here with the same not-connected error a transport
1248
+ * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
1249
+ * - **`close()`** terminates the child through the supervisor's bounded `SIGTERM` → grace →
1250
+ * `SIGKILL` group-kill, awaits its observed exit, tears down the supervisor, and fires `close`
1251
+ * once (idempotent). On a POSIX host the child leads its own process group, so the group-kill
1252
+ * reaches its grandchildren rather than orphaning them.
1247
1253
  * - **Observable (§13).** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1248
1254
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1249
- * fault), distinct from the emitter's own listener-error channel.
1255
+ * fault, including the child spawn cause the supervisor surfaces), distinct from the emitter's
1256
+ * own listener-error channel.
1250
1257
  *
1251
1258
  * @example
1252
1259
  * ```ts
@@ -1273,11 +1280,11 @@ export declare class StdioClientTransport implements MCPClientTransportInterface
1273
1280
  * @remarks
1274
1281
  * - `command` — the executable to spawn (e.g. `'node'`, `'./my-mcp-server'`). REQUIRED.
1275
1282
  * - `args` — the command-line arguments passed to `command`; defaults to none.
1276
- * - `env` — the environment variables for the spawned child, passed straight to
1277
- * `node:child_process`'s `spawn`; when OMITTED the child inherits the full
1278
- * `process.env` (the `spawn` default), when PROVIDED it REPLACES the inherited
1279
- * environment entirely (`spawn` semantics) a caller wanting to extend rather
1280
- * than replace spreads `process.env` into `env` themselves.
1283
+ * - `env` — environment variable overrides MERGED over the parent `process.env` for the
1284
+ * spawned child (the composed `@orkestrel/process` supervisor's merge semantics): when
1285
+ * OMITTED the child inherits the full `process.env`, when PROVIDED each named key overrides
1286
+ * the inherited value while every unlisted key is still inherited. This transport cannot
1287
+ * REPLACE the inherited environment entirely the supervisor always merges over the parent.
1281
1288
  */
1282
1289
  export declare interface StdioClientTransportOptions {
1283
1290
  readonly command: string;
@@ -7,7 +7,8 @@ import { randomBytes } from "node:crypto";
7
7
  import { request } from "node:http";
8
8
  import { request as request$1 } from "node:https";
9
9
  import { WEBSOCKET_VERSION, computeWebSocketAccept, createNodeWebSocket } from "@orkestrel/websocket";
10
- import { spawn } from "node:child_process";
10
+ import { Process } from "@orkestrel/process/server";
11
+ import { PROCESS_GRACE } from "@orkestrel/process";
11
12
  //#region src/server/constants.ts
12
13
  /**
13
14
  * The Streamable-HTTP transport header that carries the MCP session id. When a {@link
@@ -1296,22 +1297,29 @@ var WebSocketClientTransport = class {
1296
1297
  * import('./WebSocketClientTransport.js').WebSocketClientTransport}.
1297
1298
  *
1298
1299
  * @remarks
1299
- * - **Spawns the server.** `start()` runs `node:child_process`'s `spawn(options.command,
1300
- * options.args, { env: options.env, stdio: ['pipe', 'pipe', 'inherit'] })` the
1301
- * child's `stdin`/`stdout` are piped for the JSON-RPC channel, its `stderr` inherits
1302
- * the parent's (diagnostics pass through, never parsed as protocol).
1303
- * - **Inbound (`message`).** Each `stdout` chunk is folded through the shared
1304
- * {@link extractLines} line-framing helper (buffering a partial trailing line
1305
- * across reads); every complete line is decoded and delivered via the shared
1306
- * {@link dispatchLines} helper — a well-formed {@link JSONRPCMessage} emits
1307
- * `message`, a malformed line emits `error` (§14, never throws). The child's
1308
- * `close` bridges to this transport's `close`.
1309
- * - **Outbound (`send`).** `send(message)` writes one newline-terminated
1310
- * `JSON.stringify`d line to the child's `stdin`.
1311
- * - **`close()`** kills the child process and fires `close` (idempotent).
1300
+ * - **Composes `@orkestrel/process`.** `start()` builds one supervised
1301
+ * {@link import('@orkestrel/process/server').Process} with `writable: true`, so the child's
1302
+ * `stdin`/`stdout` are the JSON-RPC channel and its `stderr` is retained as bounded evidence
1303
+ * rather than parsed as protocol. The supervisor owns spawn, framing, and termination.
1304
+ * - **Inbound (`message`).** Standard output is drained eagerly through the supervisor's
1305
+ * `readline`-framed `lines` iterable, so a multi-byte UTF-8 sequence split across two reads is
1306
+ * decoded whole and a final line written without a trailing newline still arrives. Each framed
1307
+ * line is decoded and delivered via the shared {@link dispatchLines} helper — a well-formed
1308
+ * {@link JSONRPCMessage} emits `message`, a malformed line emits `error` (§14, never throws).
1309
+ * - **Outbound (`send`).** `send(message)` writes one newline-terminated `JSON.stringify`d line
1310
+ * through the supervisor's `send` and AWAITS its answer, so this promise settles only after the
1311
+ * host reports the line handled rather than the moment the write is queued. The supervisor never
1312
+ * rejects it answers `false` for a channel that was closed, destroyed, or ended, and for a write
1313
+ * that failed — so a `false` answer REJECTS here with the same not-connected error a transport
1314
+ * that was never started raises. A dead peer surfaces at the caller instead of vanishing.
1315
+ * - **`close()`** terminates the child through the supervisor's bounded `SIGTERM` → grace →
1316
+ * `SIGKILL` group-kill, awaits its observed exit, tears down the supervisor, and fires `close`
1317
+ * once (idempotent). On a POSIX host the child leads its own process group, so the group-kill
1318
+ * reaches its grandchildren rather than orphaning them.
1312
1319
  * - **Observable (§13).** Owns the `emitter` ({@link MCPClientTransportEventMap}); the
1313
1320
  * emitter isolates a listener throw; `error` is a DOMAIN event (a transport-level
1314
- * fault), distinct from the emitter's own listener-error channel.
1321
+ * fault, including the child spawn cause the supervisor surfaces), distinct from the emitter's
1322
+ * own listener-error channel.
1315
1323
  *
1316
1324
  * @example
1317
1325
  * ```ts
@@ -1325,8 +1333,7 @@ var StdioClientTransport = class {
1325
1333
  #command;
1326
1334
  #args;
1327
1335
  #env;
1328
- #child = void 0;
1329
- #buffer = "";
1336
+ #process = void 0;
1330
1337
  #closed = false;
1331
1338
  constructor(options) {
1332
1339
  this.#emitter = new Emitter();
@@ -1342,44 +1349,45 @@ var StdioClientTransport = class {
1342
1349
  return true;
1343
1350
  }
1344
1351
  async start() {
1345
- if (this.#child !== void 0) return;
1352
+ if (this.#process !== void 0) return;
1346
1353
  this.#closed = false;
1347
- this.#buffer = "";
1348
- const child = spawn(this.#command, [...this.#args], {
1349
- env: this.#env,
1350
- stdio: [
1351
- "pipe",
1352
- "pipe",
1353
- "inherit"
1354
- ]
1354
+ const child = new Process({
1355
+ command: {
1356
+ file: this.#command,
1357
+ arguments: [...this.#args],
1358
+ ...this.#env === void 0 ? {} : { environment: this.#env }
1359
+ },
1360
+ workspace: process.cwd(),
1361
+ grace: PROCESS_GRACE,
1362
+ writable: true
1355
1363
  });
1356
- this.#child = child;
1357
- child.stdout.on("data", (chunk) => this.#receive(chunk.toString()));
1358
- child.on("close", () => this.#onClose(child));
1359
- child.on("error", (error) => this.#emitter.emit("error", error));
1364
+ this.#process = child;
1365
+ child.emitter.on("error", (cause) => this.#emitter.emit("error", cause));
1366
+ child.exit.then(() => this.#onExit(child));
1367
+ this.#pump(child);
1360
1368
  }
1361
1369
  async send(message) {
1362
- const child = this.#child;
1363
- if (child === void 0) throw new Error("stdio transport is not connected");
1364
- child.stdin.write(`${JSON.stringify(message)}\n`);
1370
+ const child = this.#process;
1371
+ if (!(child === void 0 ? false : await child.send(JSON.stringify(message)))) throw new Error("stdio transport is not connected");
1365
1372
  }
1366
1373
  async close() {
1367
1374
  if (this.#closed) return;
1368
1375
  this.#closed = true;
1369
- const child = this.#child;
1370
- this.#child = void 0;
1371
- if (child !== void 0) child.kill();
1376
+ const child = this.#process;
1377
+ this.#process = void 0;
1378
+ if (child !== void 0) await child.destroy();
1372
1379
  this.#emitter.emit("close");
1373
1380
  }
1374
- #receive(chunk) {
1375
- const { lines, remainder } = extractLines(this.#buffer, chunk);
1376
- this.#buffer = remainder;
1377
- dispatchLines(this.#emitter, lines);
1381
+ async #pump(child) {
1382
+ for await (const line of child.lines) {
1383
+ if (this.#process !== child) return;
1384
+ dispatchLines(this.#emitter, [line]);
1385
+ }
1378
1386
  }
1379
- #onClose(child) {
1380
- if (this.#closed || this.#child !== child) return;
1387
+ #onExit(child) {
1388
+ if (this.#closed || this.#process !== child) return;
1381
1389
  this.#closed = true;
1382
- this.#child = void 0;
1390
+ this.#process = void 0;
1383
1391
  this.#emitter.emit("close");
1384
1392
  }
1385
1393
  };