@wrongstack/mcp 0.282.0 → 0.282.2

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/index.d.ts CHANGED
@@ -12,6 +12,14 @@ interface MCPClientOptions {
12
12
  headers?: Record<string, string> | undefined;
13
13
  startupTimeoutMs?: number | undefined;
14
14
  requestTimeoutMs?: number | undefined;
15
+ /**
16
+ * Allowlist of env var names to forward from the parent process (process.env)
17
+ * to the child. Values are resolved at spawn time and merged into `env`
18
+ * via the `extra` path of `buildChildEnv` (unfiltered). This is how built-in
19
+ * MCP server presets (GitHub, Slack, Brave Search, …) get their API tokens
20
+ * without storing them in config.json or being scrubbed by the secret filter.
21
+ */
22
+ passthroughEnv?: string[] | undefined;
15
23
  }
16
24
  type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'failed'
17
25
  /** Lazy server: registered from a cached manifest, process not spawned. */
@@ -291,6 +299,8 @@ interface McpServerInput {
291
299
  permission?: Permission | undefined;
292
300
  /** Lazy connect — spawn the process only on first tool call (see config). */
293
301
  lazy?: boolean | undefined;
302
+ /** Env var names to forward from parent process at spawn time. */
303
+ passthroughEnv?: string[] | undefined;
294
304
  }
295
305
  /** Projected view of one server, merging disk config with live registry state. */
296
306
  interface McpServerInfo {
package/dist/index.js CHANGED
@@ -624,30 +624,38 @@ var SSETransport = class extends BaseHTTPTransport {
624
624
  signal: timeoutSignal.signal
625
625
  };
626
626
  this.applyTlsAgent(fetchOpts);
627
- const res = await fetch(this.url, fetchOpts);
628
- if (!res.ok) {
629
- throw new ToolError({
630
- message: `HTTP ${res.status}: ${res.statusText}`,
631
- code: "TOOL_EXECUTION_FAILED",
632
- toolName: method,
633
- context: { transport: "sse", url: this.url, status: res.status, statusText: res.statusText }
634
- });
635
- }
636
- let data;
637
627
  try {
638
- data = await res.json();
639
- } catch (err) {
640
- throw new ToolError({
641
- message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
642
- code: "TOOL_EXECUTION_FAILED",
643
- toolName: method,
644
- context: { transport: "sse", url: this.url, phase: "parse-json" },
645
- cause: err
646
- });
628
+ const res = await fetch(this.url, fetchOpts);
629
+ if (!res.ok) {
630
+ throw new ToolError({
631
+ message: `HTTP ${res.status}: ${res.statusText}`,
632
+ code: "TOOL_EXECUTION_FAILED",
633
+ toolName: method,
634
+ context: {
635
+ transport: "sse",
636
+ url: this.url,
637
+ status: res.status,
638
+ statusText: res.statusText
639
+ }
640
+ });
641
+ }
642
+ let data;
643
+ try {
644
+ data = await res.json();
645
+ } catch (err) {
646
+ throw new ToolError({
647
+ message: `Invalid JSON-RPC response: ${err instanceof Error ? err.message : "parse failed"}`,
648
+ code: "TOOL_EXECUTION_FAILED",
649
+ toolName: method,
650
+ context: { transport: "sse", url: this.url, phase: "parse-json" },
651
+ cause: err
652
+ });
653
+ }
654
+ const result = assertMatchingJsonRpcResult(data, id, method);
655
+ return { jsonrpc: "2.0", id, result: result.result, error: result.error };
656
+ } finally {
657
+ timeoutSignal.dispose();
647
658
  }
648
- const result = assertMatchingJsonRpcResult(data, id, method);
649
- timeoutSignal.dispose();
650
- return { jsonrpc: "2.0", id, result: result.result, error: result.error };
651
659
  }
652
660
  async close() {
653
661
  if (this.state === "disconnected") return;
@@ -916,9 +924,18 @@ var MCPClient = class {
916
924
  throw new Error('MCP stdio transport requires "command"');
917
925
  }
918
926
  this.rxBuffer = "";
927
+ const extraEnv = { ...this.opts.env };
928
+ if (this.opts.passthroughEnv) {
929
+ for (const name of this.opts.passthroughEnv) {
930
+ const val = process.env[name];
931
+ if (val !== void 0) {
932
+ extraEnv[name] = val;
933
+ }
934
+ }
935
+ }
919
936
  const isWin = process.platform === "win32";
920
937
  const rawArgs = this.opts.args ?? [];
921
- const spawnEnv = buildChildEnv({ extra: this.opts.env });
938
+ const spawnEnv = buildChildEnv({ extra: extraEnv });
922
939
  const stdio = ["pipe", "pipe", "pipe"];
923
940
  const child = isWin ? spawn([this.opts.command, ...rawArgs].map(quoteWindowsArg).join(" "), {
924
941
  env: spawnEnv,
@@ -944,8 +961,9 @@ var MCPClient = class {
944
961
  }
945
962
  }
946
963
  });
947
- child.on("error", () => {
964
+ child.on("error", (err) => {
948
965
  this.state = "failed";
966
+ this.failPending(`MCP "${this.opts.name}" child error: ${toErrorMessage(err)}`);
949
967
  });
950
968
  const initialize = await this.request(
951
969
  "initialize",
@@ -1145,8 +1163,16 @@ var MCPClient = class {
1145
1163
  },
1146
1164
  timer
1147
1165
  });
1166
+ const stdin = this.child?.stdin;
1167
+ if (!stdin || stdin.destroyed) {
1168
+ const pending = this.pending.get(id);
1169
+ this.pending.delete(id);
1170
+ if (pending) clearTimeout(pending.timer);
1171
+ reject(new Error(`MCP "${this.opts.name}" request "${method}": stdin not writable`));
1172
+ return;
1173
+ }
1148
1174
  try {
1149
- this.child?.stdin?.write(JSON.stringify(req) + "\n");
1175
+ stdin.write(JSON.stringify(req) + "\n");
1150
1176
  } catch (err) {
1151
1177
  const pending = this.pending.get(id);
1152
1178
  this.pending.delete(id);
@@ -1181,7 +1207,14 @@ var MCPClient = class {
1181
1207
  if (this._drainPending) {
1182
1208
  this._lastNotifySkipped = true;
1183
1209
  console.warn(
1184
- `[MCP] notify("${method}") skipped: stdin buffer backpressure (already waiting for drain)`
1210
+ JSON.stringify({
1211
+ level: "warn",
1212
+ event: "mcp.notify_skipped_backpressure",
1213
+ server: this.opts.name,
1214
+ method,
1215
+ message: "stdin buffer backpressure (already waiting for drain)",
1216
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1217
+ })
1185
1218
  );
1186
1219
  return;
1187
1220
  }
@@ -1813,7 +1846,8 @@ var MCPRegistry = class _MCPRegistry {
1813
1846
  url: slot.cfg.url,
1814
1847
  headers: slot.cfg.headers,
1815
1848
  startupTimeoutMs: slot.cfg.startupTimeoutMs,
1816
- requestTimeoutMs: slot.cfg.requestTimeoutMs
1849
+ requestTimeoutMs: slot.cfg.requestTimeoutMs,
1850
+ passthroughEnv: slot.cfg.passthroughEnv
1817
1851
  });
1818
1852
  if (slot.cfg.transport === "stdio") {
1819
1853
  client.addExitListener(this.onChildExit);
@@ -1897,9 +1931,14 @@ async function readConfig(path2) {
1897
1931
  }
1898
1932
  async function writeConfig(path2, cfg) {
1899
1933
  const raw = JSON.stringify(cfg, null, 2);
1900
- const tmp = `${path2}.tmp`;
1934
+ const tmp = `${path2}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
1901
1935
  await fs.writeFile(tmp, raw, "utf8");
1902
- await fs.rename(tmp, path2);
1936
+ try {
1937
+ await fs.rename(tmp, path2);
1938
+ } catch (err) {
1939
+ await fs.rm(tmp, { force: true }).catch(() => void 0);
1940
+ throw err;
1941
+ }
1903
1942
  }
1904
1943
  function isMcpServerRecord(value) {
1905
1944
  return !!value && typeof value === "object" && !Array.isArray(value);
@@ -1943,6 +1982,8 @@ function buildConfig(input, base) {
1943
1982
  if (enabled !== void 0) cfg.enabled = enabled;
1944
1983
  const lazy = input.lazy ?? base?.lazy;
1945
1984
  if (lazy !== void 0) cfg.lazy = lazy;
1985
+ const passthroughEnv = input.passthroughEnv ?? base?.passthroughEnv;
1986
+ if (passthroughEnv !== void 0) cfg.passthroughEnv = passthroughEnv;
1946
1987
  return cfg;
1947
1988
  }
1948
1989
  function projectServer(name, cfg, registry) {