@ai-ecoverse/mcpcat 0.12.0 → 0.13.0

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.
@@ -6344,6 +6344,133 @@ var McpOAuthProvider = class {
6344
6344
  }
6345
6345
  };
6346
6346
 
6347
+ // src/auth/jerk-token-source.ts
6348
+ var MIN_VERSION = [0, 13, 0];
6349
+ var VERSION_TIMEOUT_MS = 2e3;
6350
+ var cachedAvailable;
6351
+ function parseVersion(raw) {
6352
+ const match = raw.trim().match(/(\d+)\.(\d+)\.(\d+)/);
6353
+ if (!match) return void 0;
6354
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
6355
+ }
6356
+ function versionSatisfied(actual) {
6357
+ for (let i = 0; i < 3; i++) {
6358
+ if (actual[i] > MIN_VERSION[i]) return true;
6359
+ if (actual[i] < MIN_VERSION[i]) return false;
6360
+ }
6361
+ return true;
6362
+ }
6363
+ async function readAll(stream) {
6364
+ const reader = stream.getReader();
6365
+ const chunks = [];
6366
+ for (; ; ) {
6367
+ const { done, value } = await reader.read();
6368
+ if (done) break;
6369
+ chunks.push(value);
6370
+ }
6371
+ const total = chunks.reduce((s, c) => s + c.length, 0);
6372
+ const buf = new Uint8Array(total);
6373
+ let off = 0;
6374
+ for (const c of chunks) {
6375
+ buf.set(c, off);
6376
+ off += c.length;
6377
+ }
6378
+ return new TextDecoder().decode(buf);
6379
+ }
6380
+ async function jerkOnPath(platform2) {
6381
+ const pathVar = platform2.env.PATH ?? platform2.env.Path ?? "";
6382
+ const sep = pathVar.includes(";") ? ";" : ":";
6383
+ const dirs = pathVar.split(sep).filter(Boolean);
6384
+ for (const dir of dirs) {
6385
+ if (await platform2.fs.exists(`${dir}/jerk`)) return true;
6386
+ }
6387
+ return false;
6388
+ }
6389
+ async function jerkAvailable(platform2) {
6390
+ if (cachedAvailable !== void 0) return cachedAvailable;
6391
+ if (!await jerkOnPath(platform2)) {
6392
+ cachedAvailable = false;
6393
+ return false;
6394
+ }
6395
+ try {
6396
+ const proc = await platform2.spawn({ argv: ["jerk", "--version"] });
6397
+ const timeout = new Promise((_, reject) => {
6398
+ setTimeout(() => reject(new Error("timeout")), VERSION_TIMEOUT_MS);
6399
+ });
6400
+ const work = Promise.all([readAll(proc.stdout), proc.wait()]).then(([out, result]) => {
6401
+ proc.stderr.cancel().catch(() => {
6402
+ });
6403
+ if (result.status !== 0) return false;
6404
+ const ver = parseVersion(out);
6405
+ return ver !== void 0 && versionSatisfied(ver);
6406
+ });
6407
+ cachedAvailable = await Promise.race([work, timeout]);
6408
+ } catch {
6409
+ cachedAvailable = false;
6410
+ }
6411
+ return cachedAvailable;
6412
+ }
6413
+ function jerkId(name) {
6414
+ if (name.startsWith("mcp:")) return name;
6415
+ if (name.startsWith("url:")) return `mcp:${name}`;
6416
+ return `mcp:${name}`;
6417
+ }
6418
+ var JERK_EXIT_SIGN_IN = 3;
6419
+ var JERK_EXIT_UNAVAILABLE = 4;
6420
+ var JerkSignInRequiredError = class extends Error {
6421
+ id;
6422
+ scope;
6423
+ constructor(id, scope) {
6424
+ const scopeHint = scope ? ` --scope ${scope}` : "";
6425
+ super(`sign-in required. Run: jerk ${id} create${scopeHint}`);
6426
+ this.name = "JerkSignInRequiredError";
6427
+ this.id = id;
6428
+ this.scope = scope;
6429
+ }
6430
+ };
6431
+ var JerkTokenSource = class {
6432
+ platform;
6433
+ constructor(platform2) {
6434
+ this.platform = platform2;
6435
+ }
6436
+ async getToken(name, serverUrl, scope) {
6437
+ const id = jerkId(name);
6438
+ const argv = ["jerk", id, "get", "--reveal", "--json"];
6439
+ if (scope) {
6440
+ argv.push("--scope", scope);
6441
+ }
6442
+ const proc = await this.platform.spawn({ argv });
6443
+ const [outRaw, , result] = await Promise.all([
6444
+ readAll(proc.stdout),
6445
+ readAll(proc.stderr),
6446
+ proc.wait()
6447
+ ]);
6448
+ if (result.status === JERK_EXIT_SIGN_IN) {
6449
+ throw new JerkSignInRequiredError(id, scope);
6450
+ }
6451
+ if (result.status === JERK_EXIT_UNAVAILABLE) {
6452
+ return void 0;
6453
+ }
6454
+ if (result.status !== 0) {
6455
+ return void 0;
6456
+ }
6457
+ const parsed = JSON.parse(outRaw.trim());
6458
+ if (!parsed.token) return void 0;
6459
+ return {
6460
+ accessToken: parsed.token,
6461
+ expiresAt: parsed.expires_at
6462
+ };
6463
+ }
6464
+ async load(_name, _serverUrl) {
6465
+ return void 0;
6466
+ }
6467
+ async save(_name, _serverUrl, _state) {
6468
+ }
6469
+ async remove(_name, _serverUrl) {
6470
+ return false;
6471
+ }
6472
+ };
6473
+
6347
6474
  // src/auth/auth-provider.ts
6348
6475
  var CIMD_URL = "https://mcpcat.sliccy.ai/oauth/client.json";
6349
6476
  var CLIENT_NAME = "mcpcat";
@@ -6396,6 +6523,47 @@ var AuthStepUpError = class extends Error {
6396
6523
  }
6397
6524
  };
6398
6525
  function createMcpAuthProvider(options) {
6526
+ if (options.source instanceof JerkTokenSource) {
6527
+ return createJerkAuthProvider({ ...options, source: options.source });
6528
+ }
6529
+ return createPiAuthProvider(options);
6530
+ }
6531
+ function createJerkAuthProvider(options) {
6532
+ const { name, serverUrl, source } = options;
6533
+ let currentToken;
6534
+ return {
6535
+ async token() {
6536
+ const result = await source.getToken(name, serverUrl);
6537
+ currentToken = result?.accessToken;
6538
+ return currentToken;
6539
+ },
6540
+ async onUnauthorized(context) {
6541
+ const challenge = parseWwwAuthenticate(context.response.headers.get("www-authenticate"));
6542
+ if (challenge.error === "insufficient_scope") {
6543
+ try {
6544
+ const result2 = await source.getToken(name, serverUrl, challenge.scope);
6545
+ if (result2) {
6546
+ currentToken = result2.accessToken;
6547
+ return;
6548
+ }
6549
+ } catch (err) {
6550
+ if (err instanceof JerkSignInRequiredError) {
6551
+ throw new AuthStepUpError(challenge.scope, options.name);
6552
+ }
6553
+ throw err;
6554
+ }
6555
+ throw new AuthStepUpError(challenge.scope, options.name);
6556
+ }
6557
+ const result = await source.getToken(name, serverUrl);
6558
+ if (result) {
6559
+ currentToken = result.accessToken;
6560
+ return;
6561
+ }
6562
+ throw new McpOAuthAuthorizationRequiredError();
6563
+ }
6564
+ };
6565
+ }
6566
+ function createPiAuthProvider(options) {
6399
6567
  const { name, serverUrl, source, oauth, platform: platform2 } = options;
6400
6568
  const store = new SourceBackedStore(source, name, serverUrl);
6401
6569
  let refreshing;
@@ -7841,6 +8009,53 @@ function buildServerInfo(discoverResult2) {
7841
8009
  instructions: discoverResult2?.instructions
7842
8010
  };
7843
8011
  }
8012
+ function buildStatelessMethods(requestFn, discoverResult2) {
8013
+ let serverInfoCache;
8014
+ return {
8015
+ async info() {
8016
+ if (serverInfoCache) return serverInfoCache;
8017
+ serverInfoCache = buildServerInfo(discoverResult2);
8018
+ return serverInfoCache;
8019
+ },
8020
+ async listTools() {
8021
+ return (await requestFn("tools/list", {}))?.tools ?? [];
8022
+ },
8023
+ async callTool(name, args2, _callOptions) {
8024
+ const params = {
8025
+ name,
8026
+ arguments: args2 ?? {}
8027
+ };
8028
+ if (_callOptions?.inputResponses) {
8029
+ params._inputResponses = _callOptions.inputResponses;
8030
+ }
8031
+ const result = await requestFn("tools/call", params);
8032
+ if ((result.resultType ?? "complete") === "input_required") {
8033
+ throw new InputRequiredError(
8034
+ JSON.stringify({ name, arguments: args2 ?? {}, result }, null, 2)
8035
+ );
8036
+ }
8037
+ return result;
8038
+ },
8039
+ async listResources() {
8040
+ return (await requestFn("resources/list", {}))?.resources ?? [];
8041
+ },
8042
+ async listResourceTemplates() {
8043
+ return (await requestFn("resources/templates/list", {}))?.resourceTemplates ?? [];
8044
+ },
8045
+ async readResource(uri) {
8046
+ return await requestFn("resources/read", { uri });
8047
+ },
8048
+ async listPrompts() {
8049
+ return (await requestFn("prompts/list", {}))?.prompts ?? [];
8050
+ },
8051
+ async getPrompt(name, args2) {
8052
+ return await requestFn("prompts/get", {
8053
+ name,
8054
+ arguments: args2 ?? {}
8055
+ });
8056
+ }
8057
+ };
8058
+ }
7844
8059
  async function createStatelessConnection(options) {
7845
8060
  const {
7846
8061
  url,
@@ -7944,11 +8159,16 @@ async function createStatelessConnection(options) {
7944
8159
  protocolVersion: negotiatedVersion ?? STATELESS_PROTOCOL_VERSION,
7945
8160
  clientInfo
7946
8161
  };
8162
+ const cleanParams = { ...params };
8163
+ if (cleanParams._inputResponses) {
8164
+ meta.inputResponses = cleanParams._inputResponses;
8165
+ delete cleanParams._inputResponses;
8166
+ }
7947
8167
  const body = JSON.stringify({
7948
8168
  jsonrpc: "2.0",
7949
8169
  id,
7950
8170
  method,
7951
- params: { ...params, _meta: meta }
8171
+ params: { ...cleanParams, _meta: meta }
7952
8172
  });
7953
8173
  for (let attempt = 0; attempt < 2; attempt++) {
7954
8174
  const { res, token } = await doFetch(method, params, body);
@@ -7977,28 +8197,9 @@ async function createStatelessConnection(options) {
7977
8197
  );
7978
8198
  }
7979
8199
  negotiatedVersion = STATELESS_PROTOCOL_VERSION;
7980
- let serverInfoCache;
8200
+ const extra = buildStatelessMethods(request, discoverResult2);
7981
8201
  return {
7982
- async info() {
7983
- if (serverInfoCache) return serverInfoCache;
7984
- serverInfoCache = buildServerInfo(discoverResult2);
7985
- return serverInfoCache;
7986
- },
7987
- async listTools() {
7988
- return (await request("tools/list", {}))?.tools ?? [];
7989
- },
7990
- async callTool(name, args2, _callOptions) {
7991
- const result = await request("tools/call", {
7992
- name,
7993
- arguments: args2 ?? {}
7994
- });
7995
- if ((result.resultType ?? "complete") === "input_required") {
7996
- throw new InputRequiredError(
7997
- JSON.stringify({ name, arguments: args2 ?? {}, result }, null, 2)
7998
- );
7999
- }
8000
- return result;
8001
- },
8202
+ ...extra,
8002
8203
  async close() {
8003
8204
  }
8004
8205
  };
@@ -8048,7 +8249,9 @@ async function createPiConnection(options) {
8048
8249
  const clientOptions = {
8049
8250
  name: "mcpcat",
8050
8251
  version: options.version ?? "0.0.0",
8051
- requestTimeoutMs: options.timeoutMs
8252
+ requestTimeoutMs: options.timeoutMs,
8253
+ capabilities: { elicitation: {} },
8254
+ roots: options.roots
8052
8255
  };
8053
8256
  const client = new McpClient(clientOptions);
8054
8257
  const transport = buildTransport(options);
@@ -8094,7 +8297,32 @@ async function createPiConnection(options) {
8094
8297
  inner?.(p);
8095
8298
  };
8096
8299
  }
8097
- return client.callTool(name, args2, o);
8300
+ const callArgs = callOptions?.inputResponses ? { ...args2, _meta: { inputResponses: callOptions.inputResponses } } : args2;
8301
+ return client.callTool(name, callArgs, o);
8302
+ },
8303
+ async listResources() {
8304
+ return client.listResources(reqOpts2());
8305
+ },
8306
+ async listResourceTemplates() {
8307
+ return client.listResourceTemplates(reqOpts2());
8308
+ },
8309
+ async readResource(uri) {
8310
+ return client.readResource(uri, reqOpts2());
8311
+ },
8312
+ async listPrompts() {
8313
+ const result = await client.request(
8314
+ "prompts/list",
8315
+ {},
8316
+ reqOpts2()
8317
+ );
8318
+ return result?.prompts ?? [];
8319
+ },
8320
+ async getPrompt(name, args2) {
8321
+ return client.request(
8322
+ "prompts/get",
8323
+ { name, arguments: args2 ?? {} },
8324
+ reqOpts2()
8325
+ );
8098
8326
  },
8099
8327
  async close() {
8100
8328
  await client.close();
@@ -8112,6 +8340,192 @@ async function createPiConnection(options) {
8112
8340
  }
8113
8341
  }
8114
8342
 
8343
+ // src/client/stdio-stateless.ts
8344
+ var PROTOCOL_VERSION = "2026-07-28";
8345
+ function createStdioJsonRpc(proc, platform2, options) {
8346
+ const encoder2 = new TextEncoder();
8347
+ const decoder3 = new TextDecoder();
8348
+ let nextId2 = 1;
8349
+ let readBuffer = "";
8350
+ const reader = proc.stdout.getReader();
8351
+ let negotiatedVersion;
8352
+ const { version = "0.0.0", timeoutMs = 6e4, signal: externalSignal, verbose } = options;
8353
+ async function readLine2() {
8354
+ while (true) {
8355
+ const nlIdx = readBuffer.indexOf("\n");
8356
+ if (nlIdx >= 0) {
8357
+ const line = readBuffer.slice(0, nlIdx).trim();
8358
+ readBuffer = readBuffer.slice(nlIdx + 1);
8359
+ if (line) return line;
8360
+ continue;
8361
+ }
8362
+ const { done, value } = await reader.read();
8363
+ if (done) throw new Error("stdio server closed unexpectedly");
8364
+ readBuffer += decoder3.decode(value, { stream: true });
8365
+ }
8366
+ }
8367
+ async function send(msg) {
8368
+ const writer = proc.stdin.getWriter();
8369
+ try {
8370
+ await writer.write(encoder2.encode(`${msg}
8371
+ `));
8372
+ } finally {
8373
+ writer.releaseLock();
8374
+ }
8375
+ }
8376
+ async function request(method, params) {
8377
+ const id = nextId2++;
8378
+ const meta = {
8379
+ protocolVersion: negotiatedVersion ?? PROTOCOL_VERSION,
8380
+ clientInfo: { name: "mcpcat", version }
8381
+ };
8382
+ const cleanParams = { ...params };
8383
+ if (cleanParams._inputResponses) {
8384
+ meta.inputResponses = cleanParams._inputResponses;
8385
+ delete cleanParams._inputResponses;
8386
+ }
8387
+ const body = JSON.stringify({
8388
+ jsonrpc: "2.0",
8389
+ id,
8390
+ method,
8391
+ params: { ...cleanParams, _meta: meta }
8392
+ });
8393
+ if (verbose?.enabled) verbose.request(method, body);
8394
+ await send(body);
8395
+ const deadline = Date.now() + timeoutMs;
8396
+ while (Date.now() < deadline) {
8397
+ const remaining = deadline - Date.now();
8398
+ if (externalSignal?.aborted) throw new McpTimeoutError2(method, timeoutMs);
8399
+ const linePromise = readLine2();
8400
+ const timeoutPromise = new Promise(
8401
+ (_, reject) => setTimeout(() => reject(new McpTimeoutError2(method, timeoutMs)), remaining)
8402
+ );
8403
+ const line = await Promise.race([linePromise, timeoutPromise]);
8404
+ const parsed = JSON.parse(line);
8405
+ if (verbose?.enabled) verbose.response(line);
8406
+ if (parsed.id === id) {
8407
+ if (parsed.error) {
8408
+ throw new StatelessRpcError(parsed.error.code, parsed.error.message, parsed.error.data);
8409
+ }
8410
+ return parsed.result;
8411
+ }
8412
+ }
8413
+ throw new McpTimeoutError2(method, timeoutMs);
8414
+ }
8415
+ return {
8416
+ request,
8417
+ close() {
8418
+ try {
8419
+ reader.releaseLock();
8420
+ } catch {
8421
+ }
8422
+ }
8423
+ };
8424
+ }
8425
+ function buildServerInfo2(discoverResult2) {
8426
+ return {
8427
+ name: discoverResult2?.serverInfo?.name,
8428
+ version: discoverResult2?.serverInfo?.version,
8429
+ title: discoverResult2?.serverInfo?.title,
8430
+ protocolVersion: PROTOCOL_VERSION,
8431
+ capabilities: discoverResult2?.capabilities ?? {},
8432
+ instructions: discoverResult2?.instructions
8433
+ };
8434
+ }
8435
+ function buildConnectionMethods(rpc, discoverResult2, proc) {
8436
+ return {
8437
+ async info() {
8438
+ return buildServerInfo2(discoverResult2);
8439
+ },
8440
+ async listTools() {
8441
+ return (await rpc.request("tools/list", {}))?.tools ?? [];
8442
+ },
8443
+ async callTool(name, args2, callOptions) {
8444
+ const params = { name, arguments: args2 ?? {} };
8445
+ if (callOptions?.inputResponses) {
8446
+ params._inputResponses = callOptions.inputResponses;
8447
+ }
8448
+ const result = await rpc.request("tools/call", params);
8449
+ if ((result.resultType ?? "complete") === "input_required") {
8450
+ throw new InputRequiredError(
8451
+ JSON.stringify({ name, arguments: args2 ?? {}, result }, null, 2)
8452
+ );
8453
+ }
8454
+ return result;
8455
+ },
8456
+ async listResources() {
8457
+ return (await rpc.request("resources/list", {}))?.resources ?? [];
8458
+ },
8459
+ async listResourceTemplates() {
8460
+ return (await rpc.request("resources/templates/list", {}))?.resourceTemplates ?? [];
8461
+ },
8462
+ async readResource(uri) {
8463
+ return await rpc.request("resources/read", { uri });
8464
+ },
8465
+ async listPrompts() {
8466
+ return (await rpc.request("prompts/list", {}))?.prompts ?? [];
8467
+ },
8468
+ async getPrompt(name, args2) {
8469
+ return await rpc.request("prompts/get", { name, arguments: args2 ?? {} });
8470
+ },
8471
+ async close() {
8472
+ rpc.close();
8473
+ try {
8474
+ proc.kill("SIGTERM");
8475
+ } catch {
8476
+ }
8477
+ }
8478
+ };
8479
+ }
8480
+ async function createStdioStatelessConnection(options) {
8481
+ const inheritedEnv = {
8482
+ ...options.platform.env,
8483
+ ...options.env
8484
+ };
8485
+ const proc = await options.platform.spawn({
8486
+ argv: [options.command, ...options.args ?? []],
8487
+ env: inheritedEnv,
8488
+ cwd: options.cwd
8489
+ });
8490
+ void readStderr(proc, options.verbose);
8491
+ const rpc = createStdioJsonRpc(proc, options.platform, {
8492
+ version: options.version,
8493
+ timeoutMs: options.timeoutMs,
8494
+ signal: options.signal,
8495
+ verbose: options.verbose
8496
+ });
8497
+ const discoverRaw = await rpc.request("server/discover", {
8498
+ protocolVersion: PROTOCOL_VERSION
8499
+ });
8500
+ const discoverResult2 = discoverRaw;
8501
+ const supported = (discoverResult2?.supportedVersions ?? []).filter(
8502
+ (v) => typeof v === "string"
8503
+ );
8504
+ if (!supported.includes(PROTOCOL_VERSION)) {
8505
+ proc.kill("SIGTERM");
8506
+ throw new Error(
8507
+ `Server does not support ${PROTOCOL_VERSION} (advertised: ${supported.join(", ") || "none"})`
8508
+ );
8509
+ }
8510
+ return buildConnectionMethods(rpc, discoverResult2, proc);
8511
+ }
8512
+ async function readStderr(proc, verbose) {
8513
+ if (!verbose?.enabled) return;
8514
+ const reader = proc.stderr.getReader();
8515
+ const decoder3 = new TextDecoder();
8516
+ try {
8517
+ while (true) {
8518
+ const { done, value } = await reader.read();
8519
+ if (done) break;
8520
+ const chunk = decoder3.decode(value, { stream: true });
8521
+ for (const line of chunk.split("\n")) {
8522
+ if (line.trim()) verbose.server(line);
8523
+ }
8524
+ }
8525
+ } catch {
8526
+ }
8527
+ }
8528
+
8115
8529
  // src/client/connect.ts
8116
8530
  async function connect(platform2, options) {
8117
8531
  const shared = {
@@ -8122,11 +8536,32 @@ async function connect(platform2, options) {
8122
8536
  platform: platform2
8123
8537
  };
8124
8538
  if (options.command) {
8539
+ if (!options.protocolVersion || options.protocolVersion.startsWith("2026")) {
8540
+ try {
8541
+ return await createStdioStatelessConnection({
8542
+ command: options.command,
8543
+ args: options.args,
8544
+ env: options.env,
8545
+ cwd: options.cwd,
8546
+ platform: platform2,
8547
+ version: options.version,
8548
+ timeoutMs: options.timeoutMs,
8549
+ signal: options.signal,
8550
+ verbose: options.verbose
8551
+ });
8552
+ } catch (err) {
8553
+ const isMethodNotFound = err instanceof StatelessRpcError && err.code === -32601;
8554
+ const isUnsupported = err instanceof Error && err.message.includes("does not support");
8555
+ if (options.protocolVersion?.startsWith("2026")) throw err;
8556
+ if (!isMethodNotFound && !isUnsupported) throw err;
8557
+ }
8558
+ }
8125
8559
  return createPiConnection({
8126
8560
  command: options.command,
8127
8561
  args: options.args,
8128
8562
  env: options.env,
8129
8563
  cwd: options.cwd,
8564
+ roots: options.roots,
8130
8565
  ...shared
8131
8566
  });
8132
8567
  }
@@ -8169,6 +8604,102 @@ async function connect(platform2, options) {
8169
8604
  }
8170
8605
  }
8171
8606
 
8607
+ // src/client/elicit.ts
8608
+ function readLine(platform2) {
8609
+ return new Promise((resolve, reject) => {
8610
+ const reader = platform2.stdin.getReader();
8611
+ const chunks = [];
8612
+ function pump() {
8613
+ reader.read().then(({ done, value }) => {
8614
+ if (done) {
8615
+ reader.releaseLock();
8616
+ resolve(new TextDecoder().decode(concatChunks(chunks)).split("\n")[0] ?? "");
8617
+ return;
8618
+ }
8619
+ chunks.push(value);
8620
+ const text = new TextDecoder().decode(concatChunks(chunks));
8621
+ if (text.includes("\n")) {
8622
+ reader.releaseLock();
8623
+ resolve(text.split("\n")[0]);
8624
+ return;
8625
+ }
8626
+ pump();
8627
+ }, reject);
8628
+ }
8629
+ pump();
8630
+ });
8631
+ }
8632
+ function concatChunks(chunks) {
8633
+ const total = chunks.reduce((s, c) => s + c.length, 0);
8634
+ const merged = new Uint8Array(total);
8635
+ let offset = 0;
8636
+ for (const c of chunks) {
8637
+ merged.set(c, offset);
8638
+ offset += c.length;
8639
+ }
8640
+ return merged;
8641
+ }
8642
+ function coerceValue(raw, type) {
8643
+ if (!type || type === "string") return raw;
8644
+ if (type === "integer") return Number.parseInt(raw, 10);
8645
+ if (type === "number") return Number.parseFloat(raw);
8646
+ if (type === "boolean") return raw === "true" || raw === "1" || raw === "yes";
8647
+ return raw;
8648
+ }
8649
+ async function promptForSchema(platform2, schema) {
8650
+ const answers = {};
8651
+ const properties = schema.properties ?? {};
8652
+ const required = new Set(schema.required ?? []);
8653
+ for (const [key, prop] of Object.entries(properties)) {
8654
+ const req = required.has(key) ? " (required)" : "";
8655
+ const desc = prop.description ? ` - ${prop.description}` : "";
8656
+ const def = prop.default !== void 0 ? ` [${String(prop.default)}]` : "";
8657
+ const choices = prop.enum ? ` (${prop.enum.join("|")})` : "";
8658
+ await platform2.write(2, `${key}${req}${desc}${choices}${def}: `);
8659
+ const line = await readLine(platform2);
8660
+ const value = line.trim();
8661
+ if (value === "" && prop.default !== void 0) {
8662
+ answers[key] = prop.default;
8663
+ } else if (value !== "") {
8664
+ answers[key] = coerceValue(value, prop.type);
8665
+ }
8666
+ }
8667
+ return answers;
8668
+ }
8669
+ function parseInputRequest(result) {
8670
+ const ir = result.inputRequest;
8671
+ if (!ir && !result.inputRequests) return void 0;
8672
+ if (ir) return ir;
8673
+ const reqs = result.inputRequests;
8674
+ if (Array.isArray(reqs) && reqs.length > 0) return reqs[0];
8675
+ return void 0;
8676
+ }
8677
+ async function handleElicitation(platform2, inputRequest, isTty, respondData) {
8678
+ if (inputRequest.url) {
8679
+ if (isTty) {
8680
+ await platform2.write(2, `Open this URL to continue: ${inputRequest.url}
8681
+ `);
8682
+ await platform2.write(2, "Press Enter when done...");
8683
+ await readLine(platform2);
8684
+ return { answers: {} };
8685
+ }
8686
+ return { exitCode: 6 };
8687
+ }
8688
+ if (respondData) {
8689
+ return { answers: respondData };
8690
+ }
8691
+ if (!isTty) {
8692
+ return { exitCode: 6 };
8693
+ }
8694
+ const schema = inputRequest.schema ?? { type: "object", properties: {} };
8695
+ if (inputRequest.message) {
8696
+ await platform2.write(2, `${inputRequest.message}
8697
+ `);
8698
+ }
8699
+ const answers = await promptForSchema(platform2, schema);
8700
+ return { answers };
8701
+ }
8702
+
8172
8703
  // src/client/targets.ts
8173
8704
  function splitShellStyle(input) {
8174
8705
  const result = [];
@@ -8578,10 +9109,19 @@ function buildShellGlobal(platform2) {
8578
9109
  }
8579
9110
  };
8580
9111
  }
9112
+ var DEFAULT_MEMORY_LIMIT_MIB = 256;
9113
+ var MAX_MEMORY_LIMIT_MIB = 1024;
8581
9114
  async function runCode(targets, source, options) {
8582
9115
  const { platform: platform2, json } = options;
8583
9116
  const { code: code2, options: sourceOptions } = parseCodemodeSource(source);
8584
- const timeoutMs = sourceOptions.timeoutMs ?? options.timeoutMs ?? 3e5;
9117
+ const callerTimeoutMs = options.timeoutMs ?? 3e5;
9118
+ const scriptTimeoutMs = sourceOptions.timeoutMs;
9119
+ const timeoutMs = scriptTimeoutMs !== void 0 ? Math.min(callerTimeoutMs, scriptTimeoutMs) : callerTimeoutMs;
9120
+ const memoryLimitMiB = Math.min(
9121
+ options.memoryLimitMiB ?? DEFAULT_MEMORY_LIMIT_MIB,
9122
+ MAX_MEMORY_LIMIT_MIB
9123
+ );
9124
+ const memoryLimitBytes = memoryLimitMiB * 1024 * 1024;
8585
9125
  const { connections, allDeclaredTools, namespaces } = await collectTools(targets);
8586
9126
  const searchDocs = buildSearchDocs(allDeclaredTools, namespaces);
8587
9127
  const stateDir2 = options.stateDir ?? `${platform2.env.XDG_STATE_HOME ?? `${platform2.env.HOME ?? "."}/.local/state`}/mcpcat`;
@@ -8591,7 +9131,7 @@ async function runCode(targets, source, options) {
8591
9131
  ...buildDiscoveryGlobals(allDeclaredTools, namespaces, searchDocs),
8592
9132
  ...options.allowShell ? [buildShellGlobal(platform2)] : []
8593
9133
  ];
8594
- const sandbox = await createSandbox(platform2, toolImpls, globals, timeoutMs);
9134
+ const sandbox = await createSandbox(platform2, toolImpls, globals, timeoutMs, memoryLimitBytes);
8595
9135
  try {
8596
9136
  const result = await sandbox.execute(code2, { store });
8597
9137
  if (result.ok) {
@@ -8635,12 +9175,12 @@ async function formatOutput(platform2, result, json) {
8635
9175
  const isTimeout = !result.ok && result.error && result.error.kind === "timeout";
8636
9176
  return result.ok ? 0 : isTimeout ? 124 : 1;
8637
9177
  }
8638
- async function createSandbox(platform2, tools, globals, timeoutMs) {
9178
+ async function createSandbox(platform2, tools, globals, timeoutMs, memoryLimitBytes) {
8639
9179
  const isKernel2 = platform2.packageDir !== void 0;
8640
9180
  if (isKernel2) {
8641
9181
  const { InlineCodemodeSandbox: InlineCodemodeSandbox2 } = await Promise.resolve().then(() => (init_portable(), portable_exports));
8642
9182
  const wasm = await loadWasmKernel(platform2);
8643
- return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm });
9183
+ return new InlineCodemodeSandbox2({ tools, globals, timeoutMs, wasm, memoryLimitBytes });
8644
9184
  }
8645
9185
  const { CodemodeSandbox: CodemodeSandbox2, loadQuickJSWasm: loadQuickJSWasm2 } = await Promise.resolve().then(() => (init_dist2(), dist_exports));
8646
9186
  const getSeaDir = globalThis.__mcpcat_get_sea_asset_dir;
@@ -8651,6 +9191,7 @@ async function createSandbox(platform2, tools, globals, timeoutMs) {
8651
9191
  tools,
8652
9192
  globals,
8653
9193
  timeoutMs,
9194
+ memoryLimitBytes,
8654
9195
  wasm: loadQuickJSWasm2(wasmPath),
8655
9196
  workerUrl
8656
9197
  });
@@ -8726,6 +9267,14 @@ function containsPlaceholders(value) {
8726
9267
  function expandPlaceholders(value, env) {
8727
9268
  return value.replace(PLACEHOLDER, (_, name) => env[name] ?? "");
8728
9269
  }
9270
+ function missingPlaceholders(value, env) {
9271
+ const missing = [];
9272
+ for (const m of value.matchAll(PLACEHOLDER)) {
9273
+ const name = m[1];
9274
+ if (env[name] === void 0 && !missing.includes(name)) missing.push(name);
9275
+ }
9276
+ return missing;
9277
+ }
8729
9278
  function expandPlaceholdersStrict(value, env) {
8730
9279
  let hasUndefined = false;
8731
9280
  const result = value.replace(PLACEHOLDER, (_, name) => {
@@ -8738,6 +9287,20 @@ function expandPlaceholdersStrict(value, env) {
8738
9287
  if (hasUndefined) return void 0;
8739
9288
  return result;
8740
9289
  }
9290
+ function expandHeaders(headers, env, warn) {
9291
+ if (!headers) return void 0;
9292
+ const result = {};
9293
+ for (const [k, v] of Object.entries(headers)) {
9294
+ const missing = missingPlaceholders(v, env);
9295
+ if (missing.length > 0) {
9296
+ if (warn)
9297
+ warn(`header "${k}": unset variable${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
9298
+ continue;
9299
+ }
9300
+ result[k] = expandPlaceholders(v, env);
9301
+ }
9302
+ return Object.keys(result).length > 0 ? result : void 0;
9303
+ }
8741
9304
 
8742
9305
  // src/config/parse.ts
8743
9306
  var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
@@ -8928,7 +9491,12 @@ async function readFile2(fs, path) {
8928
9491
  try {
8929
9492
  const data = await fs.readFile(path);
8930
9493
  return new TextDecoder().decode(data);
8931
- } catch {
9494
+ } catch (err) {
9495
+ if (err.code === "EACCES") {
9496
+ const e = new Error(`permission denied: ${path}`);
9497
+ e.code = "EACCES";
9498
+ throw e;
9499
+ }
8932
9500
  return void 0;
8933
9501
  }
8934
9502
  }
@@ -9093,6 +9661,13 @@ async function removeStaleLock(fs, lockPath) {
9093
9661
  async function acquireLock(fs, configPath) {
9094
9662
  const lockPath = `${configPath}.lock`;
9095
9663
  const start = Date.now();
9664
+ const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
9665
+ if (parentDir) {
9666
+ try {
9667
+ await fs.mkdir(parentDir);
9668
+ } catch {
9669
+ }
9670
+ }
9096
9671
  while (true) {
9097
9672
  try {
9098
9673
  await fs.mkdirExclusive(lockPath);
@@ -9154,7 +9729,15 @@ async function editMcpServers(fs, path, edit) {
9154
9729
  }
9155
9730
  const tmpPath = `${path}.tmp.${Date.now()}`;
9156
9731
  await fs.writeFile(tmpPath, output);
9157
- await fs.rename(tmpPath, path);
9732
+ try {
9733
+ await fs.rename(tmpPath, path);
9734
+ } catch (renameErr) {
9735
+ try {
9736
+ if (fs.unlink) await fs.unlink(tmpPath);
9737
+ } catch {
9738
+ }
9739
+ throw renameErr;
9740
+ }
9158
9741
  return { changed: true };
9159
9742
  } finally {
9160
9743
  await release();
@@ -9531,6 +10114,31 @@ async function getCandidate(fs, stateDir2, id) {
9531
10114
  }
9532
10115
 
9533
10116
  // src/discovery/probe.ts
10117
+ var LOOPBACK_RE = /^(127\.\d+\.\d+\.\d+|\[?::1\]?|localhost)$/i;
10118
+ var LINK_LOCAL_RE = /^(169\.254\.|fe80:)/i;
10119
+ var PRIVATE_RE = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fc|fd)/i;
10120
+ var LOCALHOST_SUFFIX_RE = /\.localhost$/i;
10121
+ var SLICC_INTERNAL_RE = /^host\.slicc\.internal$/i;
10122
+ function isPrivateOrLoopback(hostname) {
10123
+ const bare = hostname.replace(/^\[|\]$/g, "");
10124
+ return LOOPBACK_RE.test(bare) || LINK_LOCAL_RE.test(bare) || PRIVATE_RE.test(bare) || LOCALHOST_SUFFIX_RE.test(bare) || SLICC_INTERNAL_RE.test(bare);
10125
+ }
10126
+ function isSameOrigin(base, candidate) {
10127
+ try {
10128
+ const a = new URL(base);
10129
+ const b = new URL(candidate);
10130
+ return a.origin === b.origin;
10131
+ } catch {
10132
+ return false;
10133
+ }
10134
+ }
10135
+ function isUserUrlPrivate(userUrl) {
10136
+ try {
10137
+ return isPrivateOrLoopback(new URL(userUrl).hostname);
10138
+ } catch {
10139
+ return false;
10140
+ }
10141
+ }
9534
10142
  function isJsonContentType(ct) {
9535
10143
  if (!ct) return false;
9536
10144
  const lower = ct.toLowerCase();
@@ -9665,13 +10273,44 @@ async function probeWellKnown(fetcher, origin, page) {
9665
10273
  }
9666
10274
  return events;
9667
10275
  }
9668
- async function probeLinkHeaders(fetcher, targetUrl, origin, page) {
10276
+ async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPrivate) {
9669
10277
  const events = [];
9670
10278
  const targetResult = await probeUrl(fetcher, targetUrl);
9671
10279
  if (!targetResult) return events;
9672
10280
  const links = parseLinkHeaders(targetResult.headers);
9673
10281
  for (const link of links) {
9674
10282
  const resolved = new URL(link.url, targetUrl).href;
10283
+ if (!isSameOrigin(targetUrl, resolved)) {
10284
+ events.push(
10285
+ makeEvent(
10286
+ link.rel,
10287
+ origin,
10288
+ resolved,
10289
+ "link-header",
10290
+ page,
10291
+ "invalid",
10292
+ null,
10293
+ `skipped: cross-origin Link target (${new URL(resolved).origin})`
10294
+ )
10295
+ );
10296
+ continue;
10297
+ }
10298
+ const resolvedHost = new URL(resolved).hostname;
10299
+ if (!userUrlIsPrivate && isPrivateOrLoopback(resolvedHost)) {
10300
+ events.push(
10301
+ makeEvent(
10302
+ link.rel,
10303
+ origin,
10304
+ resolved,
10305
+ "link-header",
10306
+ page,
10307
+ "invalid",
10308
+ null,
10309
+ `skipped: private/loopback target (${resolvedHost})`
10310
+ )
10311
+ );
10312
+ continue;
10313
+ }
9675
10314
  const classifier = link.rel === "ard" ? classifyArd : link.rel === "ai-catalog" ? classifyAiCatalog : null;
9676
10315
  if (!classifier) continue;
9677
10316
  const linkResult = await probeUrl(fetcher, resolved, "application/json");
@@ -9768,9 +10407,10 @@ async function probeDiscover(platform2, targetUrl) {
9768
10407
  }
9769
10408
  const page = targetUrl;
9770
10409
  const fetcher = platform2.fetch;
10410
+ const userPrivate = isUserUrlPrivate(targetUrl);
9771
10411
  const events = [
9772
10412
  ...await probeWellKnown(fetcher, origin, page),
9773
- ...await probeLinkHeaders(fetcher, targetUrl, origin, page),
10413
+ ...await probeLinkHeaders(fetcher, targetUrl, origin, page, userPrivate),
9774
10414
  ...await probeMcp(fetcher, origin, page, targetUrl)
9775
10415
  ];
9776
10416
  return { events };
@@ -10027,13 +10667,27 @@ function renderCallResultJson(result) {
10027
10667
  return JSON.stringify(result, null, 2);
10028
10668
  }
10029
10669
  async function renderCallResult(platform2, result, options) {
10670
+ const r = options?.redact ?? ((s) => s);
10671
+ if (options?.outputPath) {
10672
+ const content = options.json ? renderCallResultJson(result) : renderCallResultText(result, options);
10673
+ await platform2.fs.writeFile(options.outputPath, content);
10674
+ if (result.isError) {
10675
+ await platform2.write(
10676
+ 2,
10677
+ r(`${result.isError ? "(tool reported error)" : ""}
10678
+ `)
10679
+ );
10680
+ return 1;
10681
+ }
10682
+ return 0;
10683
+ }
10030
10684
  if (options?.json) {
10031
10685
  const json = renderCallResultJson(result);
10032
10686
  if (result.isError) {
10033
10687
  await platform2.write(
10034
10688
  2,
10035
- `${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
10036
- `
10689
+ r(`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
10690
+ `)
10037
10691
  );
10038
10692
  return 1;
10039
10693
  }
@@ -10043,8 +10697,8 @@ async function renderCallResult(platform2, result, options) {
10043
10697
  }
10044
10698
  const text = renderCallResultText(result, options);
10045
10699
  if (result.isError) {
10046
- await platform2.write(2, `${text || "(tool reported error)"}
10047
- `);
10700
+ await platform2.write(2, r(`${text || "(tool reported error)"}
10701
+ `));
10048
10702
  return 1;
10049
10703
  }
10050
10704
  if (text) {
@@ -10061,13 +10715,33 @@ async function renderInfoJson(platform2, info) {
10061
10715
  await platform2.write(1, `${JSON.stringify(info, null, 2)}
10062
10716
  `);
10063
10717
  }
10064
- async function renderErrorJson(platform2, kind, message, code2, data) {
10065
- const errorObj = { kind, message };
10718
+ async function renderErrorJson(platform2, kind, message, code2, data, redact2) {
10719
+ const r = redact2 ?? ((s) => s);
10720
+ const errorObj = { kind, message: r(message) };
10066
10721
  if (code2 !== void 0) errorObj.code = code2;
10067
10722
  if (data !== void 0) errorObj.data = data;
10068
10723
  await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
10069
10724
  `);
10070
10725
  }
10726
+ async function renderResourcesJson(platform2, resources, templates) {
10727
+ await platform2.write(
10728
+ 1,
10729
+ `${JSON.stringify({ resources, resourceTemplates: templates }, null, 2)}
10730
+ `
10731
+ );
10732
+ }
10733
+ async function renderPromptsJson(platform2, prompts) {
10734
+ await platform2.write(1, `${JSON.stringify({ prompts }, null, 2)}
10735
+ `);
10736
+ }
10737
+ async function renderPromptResultJson(platform2, result) {
10738
+ await platform2.write(1, `${JSON.stringify(result, null, 2)}
10739
+ `);
10740
+ }
10741
+ async function renderReadResourceJson(platform2, result) {
10742
+ await platform2.write(1, `${JSON.stringify(result, null, 2)}
10743
+ `);
10744
+ }
10071
10745
 
10072
10746
  // src/serve/catalog.ts
10073
10747
  var NAME_RE = /^[A-Za-z][\w-]*$/;
@@ -10835,38 +11509,46 @@ function hasOwn(value, key) {
10835
11509
  }
10836
11510
 
10837
11511
  // src/serve/origin.ts
10838
- var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
10839
- function isLoopbackOrigin(origin) {
10840
- try {
10841
- const url = new URL(origin);
10842
- const hostname = url.hostname;
10843
- if (LOOPBACK_HOSTS.has(hostname)) return true;
10844
- if (hostname === "::1") return true;
10845
- if (hostname.endsWith(".localhost")) return true;
10846
- return false;
10847
- } catch {
10848
- return false;
10849
- }
11512
+ function isLoopbackBind(host) {
11513
+ return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
10850
11514
  }
10851
- function isLoopbackHost(host) {
10852
- const hostname = host.replace(/:\d+$/, "");
10853
- if (LOOPBACK_HOSTS.has(hostname)) return true;
10854
- if (hostname === "::1") return true;
10855
- if (hostname.endsWith(".localhost")) return true;
10856
- return false;
11515
+ function buildServerOrigins(host, port) {
11516
+ const origins = /* @__PURE__ */ new Set();
11517
+ origins.add(`http://${host}:${port}`);
11518
+ origins.add(`http://${port}.kernel.localhost`);
11519
+ if (isLoopbackBind(host)) {
11520
+ origins.add(`http://localhost:${port}`);
11521
+ origins.add(`http://127.0.0.1:${port}`);
11522
+ origins.add(`http://[::1]:${port}`);
11523
+ }
11524
+ return origins;
10857
11525
  }
10858
- function checkOrigin(origin, allowedOrigins, boundLoopback) {
11526
+ function checkOrigin(origin, serverOrigins, allowedOrigins) {
10859
11527
  if (origin === void 0) return { ok: true };
11528
+ if (serverOrigins.has(origin)) return { ok: true };
10860
11529
  if (allowedOrigins.has(origin)) return { ok: true };
10861
- if (isLoopbackOrigin(origin)) return { ok: true };
10862
- if (!boundLoopback) return { ok: true };
10863
11530
  return { ok: false, reason: `origin ${origin} is not allowed` };
10864
11531
  }
10865
- function checkHost(host, boundLoopback) {
10866
- if (!boundLoopback) return { ok: true };
11532
+ function buildAllowedHosts(host, port, extraHosts) {
11533
+ const hosts = /* @__PURE__ */ new Set();
11534
+ hosts.add(`${host}:${port}`);
11535
+ hosts.add(host);
11536
+ hosts.add(`${port}.kernel.localhost`);
11537
+ const loopback2 = ["localhost", "127.0.0.1", "[::1]"];
11538
+ for (const h of loopback2) {
11539
+ hosts.add(h);
11540
+ hosts.add(`${h}:${port}`);
11541
+ }
11542
+ for (const h of extraHosts) hosts.add(h);
11543
+ return hosts;
11544
+ }
11545
+ function checkHost(host, allowedHosts) {
10867
11546
  if (!host) return { ok: false, reason: "missing Host header" };
10868
- if (isLoopbackHost(host)) return { ok: true };
10869
- return { ok: false, reason: `host ${host} is not loopback` };
11547
+ if (allowedHosts.has(host)) return { ok: true };
11548
+ const hostname = host.replace(/:\d+$/, "");
11549
+ if (allowedHosts.has(hostname)) return { ok: true };
11550
+ if (hostname.endsWith(".localhost")) return { ok: true };
11551
+ return { ok: false, reason: `host ${host} is not allowed` };
10870
11552
  }
10871
11553
 
10872
11554
  // src/serve/proxy.ts
@@ -11103,6 +11785,8 @@ function parseSingleFlag(args2, i, opts) {
11103
11785
  return parsed.next;
11104
11786
  } else if (arg === "--json") {
11105
11787
  opts.json = true;
11788
+ } else if (arg === "--no-auth") {
11789
+ opts.noAuth = true;
11106
11790
  } else if (!arg.startsWith("-")) {
11107
11791
  opts.commands.push(parseCommandTarget(arg));
11108
11792
  } else {
@@ -11123,6 +11807,9 @@ function parseValueFlag(args2, i, opts) {
11123
11807
  case "--allow-origin":
11124
11808
  opts.allowOrigins.push(args2[++i]);
11125
11809
  break;
11810
+ case "--allow-host":
11811
+ opts.allowHosts.push(args2[++i]);
11812
+ break;
11126
11813
  case "--env":
11127
11814
  opts.envAllow.push(args2[++i]);
11128
11815
  break;
@@ -11161,7 +11848,9 @@ function parseServeFlags(args2) {
11161
11848
  host: "127.0.0.1",
11162
11849
  port: 0,
11163
11850
  bearer: void 0,
11851
+ noAuth: false,
11164
11852
  allowOrigins: [],
11853
+ allowHosts: [],
11165
11854
  envAllow: [],
11166
11855
  toolTemplates: [],
11167
11856
  callTimeout: CALL_TIMEOUT_MS,
@@ -11190,6 +11879,20 @@ function constantTimeEqual(a, b) {
11190
11879
  }
11191
11880
  return mismatch === 0;
11192
11881
  }
11882
+ function generateToken() {
11883
+ const bytes = new Uint8Array(32);
11884
+ crypto.getRandomValues(bytes);
11885
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
11886
+ }
11887
+ function checkContentType(ct) {
11888
+ if (!ct) return false;
11889
+ return ct.toLowerCase().startsWith("application/json");
11890
+ }
11891
+ function checkAcceptHeader(accept) {
11892
+ if (!accept) return false;
11893
+ const lower = accept.toLowerCase();
11894
+ return lower.includes("application/json") || lower.includes("text/event-stream") || lower.includes("*/*");
11895
+ }
11193
11896
  function buildChildEnv(platform2, envAllow) {
11194
11897
  const env = {};
11195
11898
  for (const key of ALLOWED_ENV_KEYS) {
@@ -11238,7 +11941,7 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
11238
11941
  }
11239
11942
  const stdoutChunks = [];
11240
11943
  const stderrChunks = [];
11241
- const readStream2 = async (stream, into) => {
11944
+ const readStream3 = async (stream, into) => {
11242
11945
  const r = stream.getReader();
11243
11946
  try {
11244
11947
  for (; ; ) {
@@ -11254,8 +11957,8 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
11254
11957
  (resolve) => setTimeout(() => resolve("timeout"), timeoutMs)
11255
11958
  );
11256
11959
  const work = Promise.all([
11257
- readStream2(proc.stdout, stdoutChunks),
11258
- readStream2(proc.stderr, stderrChunks),
11960
+ readStream3(proc.stdout, stdoutChunks),
11961
+ readStream3(proc.stderr, stderrChunks),
11259
11962
  proc.wait()
11260
11963
  ]);
11261
11964
  const result = await Promise.race([work, timer]);
@@ -11487,7 +12190,10 @@ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveC
11487
12190
  }
11488
12191
  async function runServe(platform2, options, version) {
11489
12192
  let resolvedBearer;
11490
- if (options.bearer) {
12193
+ let generatedBearer = false;
12194
+ if (options._resolvedBearer) {
12195
+ resolvedBearer = options._resolvedBearer;
12196
+ } else if (options.bearer) {
11491
12197
  const expanded = expandBearerToken(options.bearer, platform2.env);
11492
12198
  if (!expanded) {
11493
12199
  await platform2.write(
@@ -11497,9 +12203,31 @@ async function runServe(platform2, options, version) {
11497
12203
  return 2;
11498
12204
  }
11499
12205
  resolvedBearer = expanded;
12206
+ } else if (options.noAuth) {
12207
+ if (options.transport === "http" && !isLoopbackBind(options.host)) {
12208
+ await platform2.write(2, "mcpcat serve: --no-auth is only allowed on loopback\n");
12209
+ return 2;
12210
+ }
12211
+ if (options.transport === "http") {
12212
+ await platform2.write(
12213
+ 2,
12214
+ "mcpcat serve: warning: running without authentication on loopback\n"
12215
+ );
12216
+ }
12217
+ resolvedBearer = void 0;
12218
+ } else if (options.transport === "http") {
12219
+ if (platform2.kind === "kernel") {
12220
+ await platform2.write(
12221
+ 2,
12222
+ "mcpcat serve: kernel HTTP serve requires --bearer '${VAR}' or --no-auth\n"
12223
+ );
12224
+ return 2;
12225
+ }
12226
+ resolvedBearer = generateToken();
12227
+ generatedBearer = true;
11500
12228
  }
11501
12229
  if (options.proxy) {
11502
- return runProxyServe(platform2, options, version, resolvedBearer);
12230
+ return runProxyServe(platform2, options, version, resolvedBearer, generatedBearer);
11503
12231
  }
11504
12232
  const serveCwd = options.cwd ?? platform2.cwd;
11505
12233
  const childEnv = buildChildEnv(platform2, options.envAllow);
@@ -11536,12 +12264,35 @@ async function runServe(platform2, options, version) {
11536
12264
  executor.invoke,
11537
12265
  serverName,
11538
12266
  version,
11539
- resolvedBearer
12267
+ resolvedBearer,
12268
+ generatedBearer
11540
12269
  );
11541
12270
  }
12271
+ async function publishBearer(platform2, token, port, listenUrl, json) {
12272
+ if (!json && platform2.isatty(2)) {
12273
+ await platform2.write(2, `mcpcat serve: bearer ${token}
12274
+ `);
12275
+ return void 0;
12276
+ }
12277
+ const dir = stateDir(platform2);
12278
+ await platform2.fs.mkdir(dir, { mode: 448 });
12279
+ const tokenPath = `${dir}/serve-${port}.token`;
12280
+ try {
12281
+ await platform2.fs.unlink?.(tokenPath);
12282
+ } catch {
12283
+ }
12284
+ await platform2.fs.writeFile(tokenPath, token, { mode: 384 });
12285
+ await platform2.write(2, `mcpcat serve: bearer saved to ${tokenPath}
12286
+ `);
12287
+ if (json) {
12288
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl, bearerFile: tokenPath })}
12289
+ `);
12290
+ }
12291
+ return tokenPath;
12292
+ }
11542
12293
  function resolveProxyStdio(target, version) {
11543
12294
  const rest = target.slice(6);
11544
- const parts = rest.split(/\s+/).filter(Boolean);
12295
+ const parts = splitShellStyle(rest);
11545
12296
  return { command: parts[0], args: parts.slice(1), version };
11546
12297
  }
11547
12298
  function resolveProxyUrl(target, headers, version) {
@@ -11611,7 +12362,7 @@ async function buildProxyConnectOpts(platform2, options, version) {
11611
12362
  }
11612
12363
  return { connectOpts, hiddenValues };
11613
12364
  }
11614
- async function runProxyServe(platform2, options, version, resolvedBearer) {
12365
+ async function runProxyServe(platform2, options, version, resolvedBearer, generatedBearer) {
11615
12366
  const built = await buildProxyConnectOpts(platform2, options, version);
11616
12367
  if ("error" in built) {
11617
12368
  await platform2.write(2, `mcpcat serve --proxy: ${built.error}
@@ -11629,7 +12380,7 @@ async function runProxyServe(platform2, options, version, resolvedBearer) {
11629
12380
  if (options.transport === "stdio") {
11630
12381
  return runProxyStdio(platform2, proxy);
11631
12382
  }
11632
- return runProxyHttp(platform2, options, proxy, resolvedBearer);
12383
+ return runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer);
11633
12384
  }
11634
12385
  async function runProxyStdio(platform2, proxy) {
11635
12386
  const reader = platform2.stdin.getReader();
@@ -11660,12 +12411,12 @@ async function runProxyStdio(platform2, proxy) {
11660
12411
  }
11661
12412
  return 0;
11662
12413
  }
11663
- async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
12414
+ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer) {
11664
12415
  if (!platform2.listen) {
11665
12416
  await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
11666
12417
  return 3;
11667
12418
  }
11668
- const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
12419
+ const boundLoopback = isLoopbackBind(options.host);
11669
12420
  if (!boundLoopback && !resolvedBearer) {
11670
12421
  await platform2.write(
11671
12422
  2,
@@ -11679,10 +12430,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
11679
12430
  port: options.port
11680
12431
  });
11681
12432
  const actualPort = listener.port;
12433
+ const serverOrigins = buildServerOrigins(options.host, actualPort);
12434
+ const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
11682
12435
  const listenUrl = `http://${options.host}:${actualPort}/mcp`;
11683
- if (options.json) {
11684
- await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
12436
+ let tokenFilePath;
12437
+ if (generatedBearer && resolvedBearer) {
12438
+ tokenFilePath = await publishBearer(
12439
+ platform2,
12440
+ resolvedBearer,
12441
+ actualPort,
12442
+ listenUrl,
12443
+ options.json
12444
+ );
12445
+ } else {
12446
+ if (options.json) {
12447
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
11685
12448
  `);
12449
+ }
11686
12450
  }
11687
12451
  await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
11688
12452
  `);
@@ -11695,12 +12459,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
11695
12459
  shutdownRequested = true;
11696
12460
  listener.close();
11697
12461
  void proxy.shutdown();
12462
+ if (tokenFilePath) {
12463
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
12464
+ });
12465
+ }
11698
12466
  };
11699
12467
  if (platform2.onSignal) {
11700
12468
  platform2.onSignal("SIGINT", shutdown);
11701
12469
  platform2.onSignal("SIGTERM", shutdown);
11702
12470
  }
11703
- const handler = (request) => handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer);
12471
+ const handler = (request) => handleProxyMcpRequest(
12472
+ request,
12473
+ proxy,
12474
+ serverOrigins,
12475
+ allowedOrigins,
12476
+ allowedHosts,
12477
+ resolvedBearer
12478
+ );
11704
12479
  try {
11705
12480
  for await (const connection of listener) {
11706
12481
  if (shutdownRequested) break;
@@ -11718,20 +12493,40 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
11718
12493
  }
11719
12494
  return 0;
11720
12495
  }
11721
- async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer) {
11722
- if (resolvedBearer) {
11723
- const authHeader = request.headers.authorization;
11724
- const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
11725
- if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
11726
- return {
11727
- status: 401,
11728
- reason: "Unauthorized",
11729
- headers: { "content-type": "text/plain" },
11730
- body: "Unauthorized"
11731
- };
11732
- }
12496
+ async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
12497
+ if (request.target !== "/mcp") {
12498
+ return {
12499
+ status: 404,
12500
+ reason: "Not Found",
12501
+ headers: { "content-type": "text/plain" },
12502
+ body: "Not Found"
12503
+ };
12504
+ }
12505
+ if (request.method !== "POST") {
12506
+ return {
12507
+ status: 405,
12508
+ reason: "Method Not Allowed",
12509
+ headers: { "content-type": "text/plain", allow: "POST" },
12510
+ body: "Method Not Allowed"
12511
+ };
12512
+ }
12513
+ if (!checkContentType(request.headers["content-type"])) {
12514
+ return {
12515
+ status: 415,
12516
+ reason: "Unsupported Media Type",
12517
+ headers: { "content-type": "text/plain" },
12518
+ body: "Content-Type must be application/json"
12519
+ };
12520
+ }
12521
+ if (!checkAcceptHeader(request.headers.accept)) {
12522
+ return {
12523
+ status: 406,
12524
+ reason: "Not Acceptable",
12525
+ headers: { "content-type": "text/plain" },
12526
+ body: "Accept must include application/json or text/event-stream"
12527
+ };
11733
12528
  }
11734
- const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
12529
+ const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
11735
12530
  if (!originCheck.ok) {
11736
12531
  return {
11737
12532
  status: 403,
@@ -11740,7 +12535,7 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
11740
12535
  body: originCheck.reason ?? "Forbidden"
11741
12536
  };
11742
12537
  }
11743
- const hostCheck = checkHost(request.headers.host, boundLoopback);
12538
+ const hostCheck = checkHost(request.headers.host, allowedHosts);
11744
12539
  if (!hostCheck.ok) {
11745
12540
  return {
11746
12541
  status: 403,
@@ -11749,21 +12544,17 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
11749
12544
  body: hostCheck.reason ?? "Forbidden"
11750
12545
  };
11751
12546
  }
11752
- if (request.target !== "/mcp") {
11753
- return {
11754
- status: 404,
11755
- reason: "Not Found",
11756
- headers: { "content-type": "text/plain" },
11757
- body: "Not Found"
11758
- };
11759
- }
11760
- if (request.method !== "POST") {
11761
- return {
11762
- status: 405,
11763
- reason: "Method Not Allowed",
11764
- headers: { "content-type": "text/plain", allow: "POST" },
11765
- body: "Method Not Allowed"
11766
- };
12547
+ if (resolvedBearer) {
12548
+ const authHeader = request.headers.authorization;
12549
+ const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
12550
+ if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
12551
+ return {
12552
+ status: 401,
12553
+ reason: "Unauthorized",
12554
+ headers: { "content-type": "text/plain" },
12555
+ body: "Unauthorized"
12556
+ };
12557
+ }
11767
12558
  }
11768
12559
  const outcome = await proxy.handle(request.body);
11769
12560
  return {
@@ -11801,12 +12592,12 @@ async function runStdioServer(platform2, tools, call, serverName, version) {
11801
12592
  }
11802
12593
  return 0;
11803
12594
  }
11804
- async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer) {
12595
+ async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer, generatedBearer) {
11805
12596
  if (!platform2.listen) {
11806
12597
  await platform2.write(2, "mcpcat serve: HTTP transport requires platform.listen\n");
11807
12598
  return 3;
11808
12599
  }
11809
- const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
12600
+ const boundLoopback = isLoopbackBind(options.host);
11810
12601
  if (!boundLoopback && !resolvedBearer) {
11811
12602
  await platform2.write(2, "mcpcat serve: --bearer is required when binding off loopback\n");
11812
12603
  return 2;
@@ -11814,10 +12605,23 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11814
12605
  const allowedOrigins = new Set(options.allowOrigins);
11815
12606
  const listener = await platform2.listen({ host: options.host, port: options.port });
11816
12607
  const actualPort = listener.port;
12608
+ const serverOrigins = buildServerOrigins(options.host, actualPort);
12609
+ const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
11817
12610
  const listenUrl = `http://${options.host}:${actualPort}/mcp`;
11818
- if (options.json) {
11819
- await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
12611
+ let tokenFilePath;
12612
+ if (generatedBearer && resolvedBearer) {
12613
+ tokenFilePath = await publishBearer(
12614
+ platform2,
12615
+ resolvedBearer,
12616
+ actualPort,
12617
+ listenUrl,
12618
+ options.json
12619
+ );
12620
+ } else {
12621
+ if (options.json) {
12622
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
11820
12623
  `);
12624
+ }
11821
12625
  }
11822
12626
  await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
11823
12627
  `);
@@ -11827,6 +12631,10 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11827
12631
  shutdownRequested = true;
11828
12632
  listener.close();
11829
12633
  void killInflight();
12634
+ if (tokenFilePath) {
12635
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
12636
+ });
12637
+ }
11830
12638
  };
11831
12639
  if (platform2.onSignal) {
11832
12640
  platform2.onSignal("SIGINT", shutdown);
@@ -11838,8 +12646,9 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11838
12646
  call,
11839
12647
  serverName,
11840
12648
  version,
12649
+ serverOrigins,
11841
12650
  allowedOrigins,
11842
- boundLoopback,
12651
+ allowedHosts,
11843
12652
  resolvedBearer
11844
12653
  );
11845
12654
  try {
@@ -11859,20 +12668,40 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11859
12668
  }
11860
12669
  return 0;
11861
12670
  }
11862
- async function handleMcpRequest(request, tools, call, serverName, version, allowedOrigins, boundLoopback, resolvedBearer) {
11863
- if (resolvedBearer) {
11864
- const authHeader = request.headers.authorization;
11865
- const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
11866
- if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
11867
- return {
11868
- status: 401,
11869
- reason: "Unauthorized",
11870
- headers: { "content-type": "text/plain" },
11871
- body: "Unauthorized"
11872
- };
11873
- }
12671
+ async function handleMcpRequest(request, tools, call, serverName, version, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
12672
+ if (request.target !== "/mcp") {
12673
+ return {
12674
+ status: 404,
12675
+ reason: "Not Found",
12676
+ headers: { "content-type": "text/plain" },
12677
+ body: "Not Found"
12678
+ };
12679
+ }
12680
+ if (request.method !== "POST") {
12681
+ return {
12682
+ status: 405,
12683
+ reason: "Method Not Allowed",
12684
+ headers: { "content-type": "text/plain", allow: "POST" },
12685
+ body: "Method Not Allowed"
12686
+ };
12687
+ }
12688
+ if (!checkContentType(request.headers["content-type"])) {
12689
+ return {
12690
+ status: 415,
12691
+ reason: "Unsupported Media Type",
12692
+ headers: { "content-type": "text/plain" },
12693
+ body: "Content-Type must be application/json"
12694
+ };
12695
+ }
12696
+ if (!checkAcceptHeader(request.headers.accept)) {
12697
+ return {
12698
+ status: 406,
12699
+ reason: "Not Acceptable",
12700
+ headers: { "content-type": "text/plain" },
12701
+ body: "Accept must include application/json or text/event-stream"
12702
+ };
11874
12703
  }
11875
- const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
12704
+ const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
11876
12705
  if (!originCheck.ok) {
11877
12706
  return {
11878
12707
  status: 403,
@@ -11881,7 +12710,7 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
11881
12710
  body: originCheck.reason ?? "Forbidden"
11882
12711
  };
11883
12712
  }
11884
- const hostCheck = checkHost(request.headers.host, boundLoopback);
12713
+ const hostCheck = checkHost(request.headers.host, allowedHosts);
11885
12714
  if (!hostCheck.ok) {
11886
12715
  return {
11887
12716
  status: 403,
@@ -11890,21 +12719,17 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
11890
12719
  body: hostCheck.reason ?? "Forbidden"
11891
12720
  };
11892
12721
  }
11893
- if (request.target !== "/mcp") {
11894
- return {
11895
- status: 404,
11896
- reason: "Not Found",
11897
- headers: { "content-type": "text/plain" },
11898
- body: "Not Found"
11899
- };
11900
- }
11901
- if (request.method !== "POST") {
11902
- return {
11903
- status: 405,
11904
- reason: "Method Not Allowed",
11905
- headers: { "content-type": "text/plain", allow: "POST" },
11906
- body: "Method Not Allowed"
11907
- };
12722
+ if (resolvedBearer) {
12723
+ const authHeader = request.headers.authorization;
12724
+ const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
12725
+ if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
12726
+ return {
12727
+ status: 401,
12728
+ reason: "Unauthorized",
12729
+ headers: { "content-type": "text/plain" },
12730
+ body: "Unauthorized"
12731
+ };
12732
+ }
11908
12733
  }
11909
12734
  const outcome = await handleJsonRpc(request.body, tools, call, serverName, version);
11910
12735
  return {
@@ -12231,8 +13056,16 @@ var PiFileTokenSource = class {
12231
13056
  };
12232
13057
 
12233
13058
  // src/cli/auth-verbs.ts
12234
- function createTokenSource(platform2) {
12235
- return new PiFileTokenSource(platform2);
13059
+ var resolvedSource;
13060
+ async function createTokenSource(platform2) {
13061
+ if (isKernel(platform2)) return new PiFileTokenSource(platform2);
13062
+ if (resolvedSource) return resolvedSource;
13063
+ if (await jerkAvailable(platform2)) {
13064
+ resolvedSource = new JerkTokenSource(platform2);
13065
+ } else {
13066
+ resolvedSource = new PiFileTokenSource(platform2);
13067
+ }
13068
+ return resolvedSource;
12236
13069
  }
12237
13070
  function resolveServerName(target) {
12238
13071
  try {
@@ -12260,7 +13093,80 @@ async function resolveLoginTarget(platform2, target) {
12260
13093
  const url = target.startsWith("http") ? target : `https://${target}`;
12261
13094
  return { name: resolved.name, serverUrl: url, oauth: void 0 };
12262
13095
  }
12263
- throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
13096
+ throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
13097
+ }
13098
+ function jerkMcpId(name) {
13099
+ if (name.startsWith("mcp:")) return name;
13100
+ if (name.startsWith("url:")) return `mcp:${name}`;
13101
+ return `mcp:${name}`;
13102
+ }
13103
+ async function readStream2(stream) {
13104
+ const reader = stream.getReader();
13105
+ const chunks = [];
13106
+ for (; ; ) {
13107
+ const { done, value } = await reader.read();
13108
+ if (done) break;
13109
+ chunks.push(value);
13110
+ }
13111
+ const total = chunks.reduce((s, c) => s + c.length, 0);
13112
+ const buf = new Uint8Array(total);
13113
+ let off = 0;
13114
+ for (const c of chunks) {
13115
+ buf.set(c, off);
13116
+ off += c.length;
13117
+ }
13118
+ return new TextDecoder().decode(buf);
13119
+ }
13120
+ async function jerkLogin(platform2, target, args2) {
13121
+ const resolved = await resolveLoginTarget(platform2, target);
13122
+ const id = jerkMcpId(resolved.name);
13123
+ const argv = ["jerk", id, "create"];
13124
+ const scopeIdx = args2.indexOf("--scope");
13125
+ if (scopeIdx !== -1 && args2[scopeIdx + 1]) {
13126
+ argv.push("--scope", args2[scopeIdx + 1]);
13127
+ }
13128
+ if (args2.includes("--no-browser")) {
13129
+ argv.push("--no-browser");
13130
+ }
13131
+ const proc = await platform2.spawn({ argv });
13132
+ proc.stdin.close().catch(() => {
13133
+ });
13134
+ const [, errText, result] = await Promise.all([
13135
+ readStream2(proc.stdout),
13136
+ readStream2(proc.stderr),
13137
+ proc.wait()
13138
+ ]);
13139
+ if (result.status === 0) {
13140
+ await platform2.write(1, `Signed in to ${target}
13141
+ `);
13142
+ return 0;
13143
+ }
13144
+ if (errText.trim()) {
13145
+ await platform2.write(2, errText);
13146
+ }
13147
+ if (result.status === JERK_EXIT_SIGN_IN) return 4;
13148
+ return result.status;
13149
+ }
13150
+ async function jerkLogout(platform2, target) {
13151
+ const resolved = await resolveLoginTarget(platform2, target);
13152
+ const id = jerkMcpId(resolved.name);
13153
+ const argv = ["jerk", id, "drop", "--yes"];
13154
+ const proc = await platform2.spawn({ argv });
13155
+ proc.stdin.close().catch(() => {
13156
+ });
13157
+ const [, , result] = await Promise.all([
13158
+ readStream2(proc.stdout),
13159
+ readStream2(proc.stderr),
13160
+ proc.wait()
13161
+ ]);
13162
+ if (result.status === 0) {
13163
+ await platform2.write(1, `Signed out of ${target}
13164
+ `);
13165
+ } else {
13166
+ await platform2.write(1, `No credentials stored for ${target}
13167
+ `);
13168
+ }
13169
+ return 0;
12264
13170
  }
12265
13171
  async function runLogin(platform2, args2, source) {
12266
13172
  if (isKernel(platform2)) {
@@ -12270,13 +13176,22 @@ async function runLogin(platform2, args2, source) {
12270
13176
  );
12271
13177
  return 4;
12272
13178
  }
12273
- const noBrowser = args2.includes("--no-browser");
12274
13179
  const positional = args2.filter((a) => !a.startsWith("-"));
12275
13180
  if (positional.length === 0) {
12276
13181
  await platform2.write(2, "mcpcat: login requires a target\n");
12277
13182
  return 2;
12278
13183
  }
12279
13184
  const target = positional[0];
13185
+ if (source instanceof JerkTokenSource) {
13186
+ try {
13187
+ return await jerkLogin(platform2, target, args2);
13188
+ } catch (err) {
13189
+ await platform2.write(2, `mcpcat: login failed: ${err.message}
13190
+ `);
13191
+ return 4;
13192
+ }
13193
+ }
13194
+ const noBrowser = args2.includes("--no-browser");
12280
13195
  let resolved;
12281
13196
  try {
12282
13197
  resolved = await resolveLoginTarget(platform2, target);
@@ -12358,6 +13273,15 @@ async function runLogout(platform2, args2, source) {
12358
13273
  return 2;
12359
13274
  }
12360
13275
  const target = positional[0];
13276
+ if (source instanceof JerkTokenSource) {
13277
+ try {
13278
+ return await jerkLogout(platform2, target);
13279
+ } catch (err) {
13280
+ await platform2.write(2, `mcpcat: logout failed: ${err.message}
13281
+ `);
13282
+ return 1;
13283
+ }
13284
+ }
12361
13285
  let resolved;
12362
13286
  try {
12363
13287
  resolved = await resolveLoginTarget(platform2, target);
@@ -12730,6 +13654,49 @@ function checkAgentWarnings(name, config) {
12730
13654
  return warnings;
12731
13655
  }
12732
13656
 
13657
+ // src/serve/seal.ts
13658
+ async function sealSecret(platform2, name, value, options) {
13659
+ if (!platform2.seal) {
13660
+ throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
13661
+ }
13662
+ const request = JSON.stringify({
13663
+ v: 1,
13664
+ op: "seal",
13665
+ kind: "mcp",
13666
+ name,
13667
+ value,
13668
+ server: options.server,
13669
+ url: options.url
13670
+ });
13671
+ let responseText;
13672
+ try {
13673
+ responseText = await platform2.seal(request);
13674
+ } catch (err) {
13675
+ const code2 = err.code;
13676
+ if (code2 === "ENOENT" || code2 === "ENOTCONN") {
13677
+ throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
13678
+ }
13679
+ throw Object.assign(new Error(`vault: ${err.message}`), {
13680
+ sealErrno: code2 ?? "EACCES"
13681
+ });
13682
+ }
13683
+ const response = JSON.parse(responseText);
13684
+ if (response.error) {
13685
+ throw Object.assign(new Error(response.error), { sealErrno: response.errno ?? "EACCES" });
13686
+ }
13687
+ return { placeholder: response.placeholder };
13688
+ }
13689
+ function sealExitCode(errno) {
13690
+ if (errno === "EEXIST") return 2;
13691
+ return 4;
13692
+ }
13693
+ function sealErrorMessage(errno, name) {
13694
+ if (errno === "EEXIST") return `a non-bridge secret named ${name} already exists`;
13695
+ if (errno === "ENOTCONN") return "no vault available; use --bearer '${VAR}' or --no-auth";
13696
+ if (errno === "EACCES") return "vault locked; use --bearer '${VAR}' or --no-auth";
13697
+ return `vault error: ${errno}`;
13698
+ }
13699
+
12733
13700
  // src/cli/config-verbs.ts
12734
13701
  function parseHeader(hdr) {
12735
13702
  const colonIdx = hdr.indexOf(":");
@@ -12751,6 +13718,10 @@ function parseAddFlagAt(flagArgs, i, flags) {
12751
13718
  flags.noProbe = true;
12752
13719
  return { next: i + 1 };
12753
13720
  }
13721
+ if (arg === "--no-auth") {
13722
+ flags.noAuth = true;
13723
+ return { next: i + 1 };
13724
+ }
12754
13725
  if (arg === "--bridge") {
12755
13726
  flags.bridge = true;
12756
13727
  return { next: i + 1 };
@@ -12796,6 +13767,7 @@ function parseAddFlags(args2) {
12796
13767
  local: false,
12797
13768
  noProbe: false,
12798
13769
  bridge: false,
13770
+ noAuth: false,
12799
13771
  headers: [],
12800
13772
  envPairs: [],
12801
13773
  positional: [],
@@ -13158,6 +14130,11 @@ async function runImport(platform2, args2) {
13158
14130
  }
13159
14131
  return 0;
13160
14132
  }
14133
+ function generateBridgeToken() {
14134
+ const bytes = new Uint8Array(32);
14135
+ crypto.getRandomValues(bytes);
14136
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
14137
+ }
13161
14138
  async function runBridge(platform2, args2, version) {
13162
14139
  if (platform2.kind === "host") {
13163
14140
  await platform2.write(
@@ -13184,16 +14161,56 @@ async function runBridge(platform2, args2, version) {
13184
14161
  if (flags.bearer) {
13185
14162
  await platform2.write(
13186
14163
  2,
13187
- "mcpcat add --bridge: --bearer is not needed on the kernel loopback.\nThe kernel loopback is only reachable from inside the kernel and the agent.\n"
14164
+ "mcpcat add --bridge: --bearer is not supported for bridge; authentication is automatic via the vault\n"
13188
14165
  );
13189
14166
  return 2;
13190
14167
  }
13191
14168
  const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
13192
14169
  serveOptions.proxyCommand = flags.cmdArgs;
13193
14170
  const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
14171
+ if (flags.noAuth) {
14172
+ await platform2.write(
14173
+ 2,
14174
+ "mcpcat add --bridge: warning: running without authentication on loopback\n"
14175
+ );
14176
+ serveOptions.noAuth = true;
14177
+ serveOptions.onListening = async (actualPort) => {
14178
+ const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
14179
+ const httpConfig = { url: bridgeUrl };
14180
+ if (flags.description) httpConfig.description = flags.description;
14181
+ if (flags.exposure) httpConfig.exposure = flags.exposure;
14182
+ const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
14183
+ await platform2.write(
14184
+ 1,
14185
+ `${replaced ? "replaced" : "added"} ${name} in ${path}
14186
+ bridge does not survive a reload; runs in the foreground (start with &)
14187
+ `
14188
+ );
14189
+ };
14190
+ return runServe(platform2, serveOptions, version);
14191
+ }
14192
+ const token = generateBridgeToken();
14193
+ const secretName = `MCP_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_TOKEN`;
14194
+ serveOptions._resolvedBearer = token;
13194
14195
  serveOptions.onListening = async (actualPort) => {
14196
+ if (actualPort === 9222) {
14197
+ await platform2.write(2, "mcpcat add --bridge: port 9222 is reserved; please retry\n");
14198
+ return;
14199
+ }
13195
14200
  const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
13196
- const httpConfig = { url: bridgeUrl };
14201
+ try {
14202
+ await sealSecret(platform2, secretName, token, { server: name, url: bridgeUrl });
14203
+ } catch (err) {
14204
+ const errno = err.sealErrno ?? "EACCES";
14205
+ const code2 = sealExitCode(errno);
14206
+ await platform2.write(2, `mcpcat add --bridge: ${sealErrorMessage(errno, secretName)}
14207
+ `);
14208
+ platform2.exit(code2);
14209
+ }
14210
+ const httpConfig = {
14211
+ url: bridgeUrl,
14212
+ headers: { Authorization: "Bearer ${" + secretName + "}" }
14213
+ };
13197
14214
  if (flags.description) httpConfig.description = flags.description;
13198
14215
  if (flags.exposure) httpConfig.exposure = flags.exposure;
13199
14216
  const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
@@ -13330,8 +14347,18 @@ var CONFIG_VERBS = {
13330
14347
  exposure: true,
13331
14348
  import: true
13332
14349
  };
13333
- var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["resources", "read", "prompts", "prompt", "complete", "task"]);
13334
- var CLIENT_VERBS = /* @__PURE__ */ new Set(["info", "tools", "call", "describe", "serve"]);
14350
+ var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["complete", "task"]);
14351
+ var CLIENT_VERBS = /* @__PURE__ */ new Set([
14352
+ "info",
14353
+ "tools",
14354
+ "call",
14355
+ "describe",
14356
+ "serve",
14357
+ "resources",
14358
+ "read",
14359
+ "prompts",
14360
+ "prompt"
14361
+ ]);
13335
14362
  function helpText(version) {
13336
14363
  return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
13337
14364
 
@@ -13370,6 +14397,7 @@ Options:
13370
14397
  -H, --header 'N: V' Extra header
13371
14398
  -d <json> Tool arguments as JSON (or @file, @- for stdin)
13372
14399
  -m, --timeout <s> Request timeout (default 60)
14400
+ --memory <MiB> Code mode memory limit (default 256, max 1024)
13373
14401
  --protocol-version <v> Pin protocol version
13374
14402
  --schema Print raw JSON Schema (describe)
13375
14403
  --ts TypeScript declarations (describe)
@@ -13386,6 +14414,31 @@ Options:
13386
14414
  -h, --help Show this help
13387
14415
  `;
13388
14416
  }
14417
+ var VERBS_WITH_TOOL = /* @__PURE__ */ new Set(["call", "prompt", "describe", "read"]);
14418
+ var VERBS_WITH_TARGET = /* @__PURE__ */ new Set([
14419
+ "info",
14420
+ "tools",
14421
+ "call",
14422
+ "describe",
14423
+ "serve",
14424
+ "grep",
14425
+ "code",
14426
+ "discover",
14427
+ "login",
14428
+ "logout",
14429
+ "resources",
14430
+ "read",
14431
+ "prompts",
14432
+ "prompt"
14433
+ ]);
14434
+ function resolveMaxPositional(positional) {
14435
+ if (positional.length === 0) return 3;
14436
+ const first = positional[0];
14437
+ if (!VERBS.includes(first)) return 2;
14438
+ if (VERBS_WITH_TOOL.has(first)) return 3;
14439
+ if (VERBS_WITH_TARGET.has(first)) return 2;
14440
+ return 2;
14441
+ }
13389
14442
  function parseFlags(args2) {
13390
14443
  const headers = {};
13391
14444
  let dataArg;
@@ -13400,10 +14453,16 @@ function parseFlags(args2) {
13400
14453
  const serverTargets = [];
13401
14454
  let allowShell = false;
13402
14455
  let timeoutMs;
14456
+ let memoryLimitMiB;
13403
14457
  let verbose = false;
13404
14458
  let silent = false;
13405
14459
  const positional = [];
13406
14460
  let trailingCommand;
14461
+ let respondArg;
14462
+ const roots = [];
14463
+ let outputPath;
14464
+ let noValidate = false;
14465
+ const rest = [];
13407
14466
  for (let i = 0; i < args2.length; i++) {
13408
14467
  const arg = args2[i];
13409
14468
  if (arg === "--") {
@@ -13444,8 +14503,41 @@ function parseFlags(args2) {
13444
14503
  silent = true;
13445
14504
  } else if ((arg === "-m" || arg === "--timeout") && args2[i + 1]) {
13446
14505
  timeoutMs = Number(args2[++i]) * 1e3;
14506
+ } else if (arg === "--respond" && args2[i + 1]) {
14507
+ respondArg = args2[++i];
14508
+ } else if (arg === "--root" && args2[i + 1]) {
14509
+ roots.push(args2[++i]);
14510
+ } else if ((arg === "-o" || arg === "--output") && args2[i + 1]) {
14511
+ outputPath = args2[++i];
14512
+ } else if (arg === "--memory" && args2[i + 1]) {
14513
+ memoryLimitMiB = Number(args2[++i]);
14514
+ } else if (arg === "--no-validate") {
14515
+ noValidate = true;
13447
14516
  } else if (!arg.startsWith("-")) {
13448
14517
  positional.push(arg);
14518
+ const maxPositional = resolveMaxPositional(positional);
14519
+ if (positional.length >= maxPositional) {
14520
+ rest.push(...args2.slice(i + 1));
14521
+ break;
14522
+ }
14523
+ }
14524
+ }
14525
+ const toolArgs = [];
14526
+ for (let j = 0; j < rest.length; j++) {
14527
+ const ra = rest[j];
14528
+ if (ra === "-d" && rest[j + 1] !== void 0) {
14529
+ dataArg = rest[++j];
14530
+ } else if (ra === "--json") {
14531
+ jsonOutput = true;
14532
+ } else if (ra === "--no-validate") {
14533
+ noValidate = true;
14534
+ } else if ((ra === "-o" || ra === "--output") && rest[j + 1] !== void 0) {
14535
+ outputPath = rest[++j];
14536
+ } else if (ra === "--help" || ra === "-h") {
14537
+ toolArgs.length = 0;
14538
+ break;
14539
+ } else {
14540
+ toolArgs.push(ra);
13449
14541
  }
13450
14542
  }
13451
14543
  return {
@@ -13462,10 +14554,16 @@ function parseFlags(args2) {
13462
14554
  serverTargets,
13463
14555
  allowShell,
13464
14556
  timeoutMs,
14557
+ memoryLimitMiB,
13465
14558
  verbose,
13466
14559
  silent,
13467
14560
  positional,
13468
- trailingCommand
14561
+ trailingCommand,
14562
+ respondArg,
14563
+ roots,
14564
+ outputPath,
14565
+ noValidate,
14566
+ rest: toolArgs
13469
14567
  };
13470
14568
  }
13471
14569
  function resolveVerb(positional, trailingCommand) {
@@ -13500,6 +14598,7 @@ function parseArgs(args2) {
13500
14598
  const resolved = resolveVerb(flags.positional, flags.trailingCommand);
13501
14599
  return {
13502
14600
  ...resolved,
14601
+ toolArgs: flags.rest,
13503
14602
  headers: flags.headers,
13504
14603
  dataArg: flags.dataArg,
13505
14604
  protocolVersion: flags.protocolVersion,
@@ -13513,8 +14612,13 @@ function parseArgs(args2) {
13513
14612
  serverTargets: flags.serverTargets,
13514
14613
  allowShell: flags.allowShell,
13515
14614
  timeoutMs: flags.timeoutMs,
14615
+ memoryLimitMiB: flags.memoryLimitMiB,
13516
14616
  verbose: flags.verbose,
13517
- silent: flags.silent
14617
+ silent: flags.silent,
14618
+ noValidate: flags.noValidate,
14619
+ respondArg: flags.respondArg,
14620
+ roots: flags.roots,
14621
+ outputPath: flags.outputPath
13518
14622
  };
13519
14623
  }
13520
14624
  function formatToolLine(tool) {
@@ -13637,7 +14741,8 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
13637
14741
  const tools = await conn.listTools();
13638
14742
  const tool = tools.find((t) => t.name === toolName);
13639
14743
  const schema = tool?.inputSchema ?? { type: "object", properties: {} };
13640
- const c = coerceArgsBySchema(toolArgs, schema, { noValidate: opts.noValidate });
14744
+ const skipRequired = opts.noValidate || !!dataArg;
14745
+ const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
13641
14746
  if (!c.ok) {
13642
14747
  await writeErr(platform2, opts.json ?? false, "usage", c.error);
13643
14748
  return EXIT_USAGE;
@@ -13646,8 +14751,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
13646
14751
  }
13647
14752
  return renderCallResult(
13648
14753
  platform2,
13649
- await conn.callTool(toolName, fin, { onProgress: opts.onProgress }),
13650
- { json: opts.json }
14754
+ await conn.callTool(toolName, fin, {
14755
+ onProgress: opts.onProgress,
14756
+ inputResponses: opts.inputResponses
14757
+ }),
14758
+ { json: opts.json, outputPath: opts.outputPath }
13651
14759
  );
13652
14760
  }
13653
14761
  async function runDescribe(platform2, conn, toolName, showSchema, json) {
@@ -13684,19 +14792,168 @@ async function runDescribe(platform2, conn, toolName, showSchema, json) {
13684
14792
  }
13685
14793
  return EXIT_OK;
13686
14794
  }
13687
- async function runVerb(platform2, parsed, conn, onProgress) {
14795
+ async function runResources(platform2, conn, json) {
14796
+ const resources = await conn.listResources();
14797
+ const templates = await conn.listResourceTemplates();
14798
+ if (json) {
14799
+ await renderResourcesJson(platform2, resources, templates);
14800
+ return EXIT_OK;
14801
+ }
14802
+ if (resources.length === 0 && templates.length === 0) {
14803
+ await platform2.write(1, "(no resources)\n");
14804
+ return EXIT_OK;
14805
+ }
14806
+ for (const r of resources) {
14807
+ const desc = r.description ? ` ${r.description}` : "";
14808
+ const mime = r.mimeType ? ` [${r.mimeType}]` : "";
14809
+ await platform2.write(1, `${r.uri} ${r.name}${mime}${desc}
14810
+ `);
14811
+ }
14812
+ if (templates.length > 0) {
14813
+ if (resources.length > 0) await platform2.write(1, "\n");
14814
+ await platform2.write(1, "Templates:\n");
14815
+ for (const t of templates) {
14816
+ const desc = t.description ? ` ${t.description}` : "";
14817
+ await platform2.write(1, ` ${t.uriTemplate} ${t.name}${desc}
14818
+ `);
14819
+ }
14820
+ }
14821
+ return EXIT_OK;
14822
+ }
14823
+ async function runRead(platform2, conn, uri, json, outputPath) {
14824
+ const result = await conn.readResource(uri);
14825
+ if (json) {
14826
+ await renderReadResourceJson(platform2, result);
14827
+ return EXIT_OK;
14828
+ }
14829
+ for (const content of result.contents) {
14830
+ if ("text" in content) {
14831
+ if (outputPath) {
14832
+ await platform2.fs.writeFile(outputPath, content.text);
14833
+ await platform2.write(1, `${outputPath}
14834
+ `);
14835
+ } else {
14836
+ await platform2.write(1, `${content.text}
14837
+ `);
14838
+ }
14839
+ } else if ("blob" in content) {
14840
+ if (outputPath) {
14841
+ const bytes = Uint8Array.from(atob(content.blob), (c) => c.charCodeAt(0));
14842
+ await platform2.fs.writeFile(outputPath, bytes);
14843
+ await platform2.write(1, `${outputPath}
14844
+ `);
14845
+ } else {
14846
+ await platform2.write(1, `${content.blob}
14847
+ `);
14848
+ }
14849
+ }
14850
+ }
14851
+ return EXIT_OK;
14852
+ }
14853
+ async function runPrompts(platform2, conn, json) {
14854
+ const prompts = await conn.listPrompts();
14855
+ if (json) {
14856
+ await renderPromptsJson(platform2, prompts);
14857
+ return EXIT_OK;
14858
+ }
14859
+ if (prompts.length === 0) {
14860
+ await platform2.write(1, "(no prompts)\n");
14861
+ return EXIT_OK;
14862
+ }
14863
+ for (const p of prompts) {
14864
+ const desc = p.description ? ` ${p.description}` : "";
14865
+ await platform2.write(1, `${p.name}${desc}
14866
+ `);
14867
+ }
14868
+ return EXIT_OK;
14869
+ }
14870
+ async function runPrompt(platform2, conn, name, dataArg, toolArgs, json) {
14871
+ let args2 = {};
14872
+ if (dataArg) {
14873
+ args2 = await readDataArg(platform2, dataArg);
14874
+ }
14875
+ const prompts = await conn.listPrompts();
14876
+ const prompt = prompts.find((p) => p.name === name);
14877
+ const schema = {
14878
+ type: "object",
14879
+ properties: {},
14880
+ required: []
14881
+ };
14882
+ if (prompt?.arguments) {
14883
+ const props = {};
14884
+ const req = [];
14885
+ for (const a of prompt.arguments) {
14886
+ props[a.name] = { type: "string", description: a.description };
14887
+ if (a.required) req.push(a.name);
14888
+ }
14889
+ schema.properties = props;
14890
+ schema.required = req;
14891
+ }
14892
+ if (toolArgs.length > 0) {
14893
+ const skipRequired = !!dataArg;
14894
+ const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
14895
+ if (!c.ok) {
14896
+ await writeErr(platform2, json, "usage", c.error);
14897
+ return EXIT_USAGE;
14898
+ }
14899
+ args2 = mergeArgs(args2, c.value);
14900
+ } else if (!dataArg) {
14901
+ const required = Array.isArray(schema.required) ? schema.required : [];
14902
+ for (const r of required) {
14903
+ if (!Object.hasOwn(args2, r)) {
14904
+ await writeErr(platform2, json, "usage", `missing required flag --${r}`);
14905
+ return EXIT_USAGE;
14906
+ }
14907
+ }
14908
+ }
14909
+ const result = await conn.getPrompt(name, args2);
14910
+ if (json) {
14911
+ await renderPromptResultJson(platform2, result);
14912
+ return EXIT_OK;
14913
+ }
14914
+ if (result.description) {
14915
+ await platform2.write(1, `${result.description}
14916
+
14917
+ `);
14918
+ }
14919
+ for (const msg of result.messages) {
14920
+ const text = msg.content?.text ?? (msg.content?.data ? `[${msg.content.mimeType ?? "binary"}]` : "");
14921
+ await platform2.write(1, `[${msg.role}] ${text}
14922
+ `);
14923
+ }
14924
+ return EXIT_OK;
14925
+ }
14926
+ async function runVerb(platform2, parsed, conn, onProgress, respondData) {
13688
14927
  switch (parsed.verb) {
13689
14928
  case "info":
13690
14929
  return runInfo(platform2, conn, parsed.jsonOutput);
13691
14930
  case "tools":
13692
14931
  return runTools(platform2, conn, parsed.jsonOutput);
13693
14932
  case "call":
13694
- return runCall(platform2, conn, parsed.toolName, parsed.dataArg, [], {
14933
+ return runCall(platform2, conn, parsed.toolName, parsed.dataArg, parsed.toolArgs, {
13695
14934
  json: parsed.jsonOutput,
13696
- onProgress
14935
+ noValidate: parsed.noValidate,
14936
+ outputPath: parsed.outputPath,
14937
+ onProgress,
14938
+ inputResponses: respondData
13697
14939
  });
13698
14940
  case "describe":
13699
14941
  return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
14942
+ case "resources":
14943
+ return runResources(platform2, conn, parsed.jsonOutput);
14944
+ case "read":
14945
+ return runRead(platform2, conn, parsed.toolName, parsed.jsonOutput, parsed.outputPath);
14946
+ case "prompts":
14947
+ return runPrompts(platform2, conn, parsed.jsonOutput);
14948
+ case "prompt":
14949
+ return runPrompt(
14950
+ platform2,
14951
+ conn,
14952
+ parsed.toolName,
14953
+ parsed.dataArg,
14954
+ parsed.toolArgs,
14955
+ parsed.jsonOutput
14956
+ );
13700
14957
  default:
13701
14958
  await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
13702
14959
  `);
@@ -13834,6 +15091,18 @@ async function resolveTargetNames(platform2, parsed) {
13834
15091
  return config.list().filter((e) => e.config.enabled !== false).map((e) => e.name);
13835
15092
  }
13836
15093
  async function dispatchMultiTarget(platform2, parsed, version) {
15094
+ let activeCleanup;
15095
+ async function killTreeAndExit(exitCode) {
15096
+ if (activeCleanup) {
15097
+ try {
15098
+ await Promise.race([activeCleanup(), new Promise((r) => setTimeout(r, 3e3))]);
15099
+ } catch {
15100
+ }
15101
+ }
15102
+ platform2.exit(exitCode);
15103
+ }
15104
+ platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
15105
+ platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
13837
15106
  const config = await MergedConfigSource.load(platform2);
13838
15107
  if (parsed.verb === "code") {
13839
15108
  let source;
@@ -13865,10 +15134,12 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13865
15134
  parsed,
13866
15135
  version
13867
15136
  );
15137
+ activeCleanup = cleanup;
13868
15138
  try {
13869
15139
  return await runCode(targets, source, {
13870
15140
  json: parsed.jsonOutput,
13871
15141
  timeoutMs: parsed.timeoutMs,
15142
+ memoryLimitMiB: parsed.memoryLimitMiB,
13872
15143
  allowShell: parsed.allowShell,
13873
15144
  platform: platform2
13874
15145
  });
@@ -13894,6 +15165,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13894
15165
  parsed,
13895
15166
  version
13896
15167
  );
15168
+ activeCleanup = cleanup;
13897
15169
  try {
13898
15170
  return await runGrep(platform2, pattern, targets, {
13899
15171
  rank: parsed.rank,
@@ -13912,6 +15184,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13912
15184
  parsed,
13913
15185
  version
13914
15186
  );
15187
+ activeCleanup = cleanup;
13915
15188
  try {
13916
15189
  return await runDescribeTs(platform2, targets, parsed.tsBudget);
13917
15190
  } finally {
@@ -13921,6 +15194,18 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13921
15194
  return 2;
13922
15195
  }
13923
15196
  async function run(platform2, version, args2) {
15197
+ try {
15198
+ return await runInner(platform2, version, args2);
15199
+ } catch (err) {
15200
+ if (err.code === "EACCES") {
15201
+ await platform2.write(2, `mcpcat: ${err.message}
15202
+ `);
15203
+ return EXIT_USAGE;
15204
+ }
15205
+ throw err;
15206
+ }
15207
+ }
15208
+ async function runInner(platform2, version, args2) {
13924
15209
  if (args2.includes("--version") || args2.includes("-V")) {
13925
15210
  await platform2.write(1, `${version}
13926
15211
  `);
@@ -13947,11 +15232,11 @@ async function run(platform2, version, args2) {
13947
15232
  const discResult = await dispatchDiscoveryVerb(platform2, parsed, args2);
13948
15233
  if (discResult !== void 0) return discResult;
13949
15234
  if (parsed.verb === "login") {
13950
- const source = createTokenSource(platform2);
15235
+ const source = await createTokenSource(platform2);
13951
15236
  return runLogin(platform2, args2.slice(1), source);
13952
15237
  }
13953
15238
  if (parsed.verb === "logout") {
13954
- const source = createTokenSource(platform2);
15239
+ const source = await createTokenSource(platform2);
13955
15240
  return runLogout(platform2, args2.slice(1), source);
13956
15241
  }
13957
15242
  if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
@@ -13974,6 +15259,14 @@ async function run(platform2, version, args2) {
13974
15259
  await platform2.write(2, "mcpcat: call requires a tool name\n");
13975
15260
  return 2;
13976
15261
  }
15262
+ if (parsed.verb === "read" && !parsed.toolName) {
15263
+ await platform2.write(2, "mcpcat: read requires a URI\n");
15264
+ return 2;
15265
+ }
15266
+ if (parsed.verb === "prompt" && !parsed.toolName) {
15267
+ await platform2.write(2, "mcpcat: prompt requires a name\n");
15268
+ return 2;
15269
+ }
13977
15270
  if (!parsed.target) {
13978
15271
  await platform2.write(2, `mcpcat: ${parsed.verb} requires a target
13979
15272
  `);
@@ -13984,13 +15277,19 @@ async function run(platform2, version, args2) {
13984
15277
  function expandAndCollect(headers, env) {
13985
15278
  const secrets = [];
13986
15279
  const result = {};
15280
+ const unset = [];
13987
15281
  for (const [k, v] of Object.entries(headers)) {
15282
+ const missing = missingPlaceholders(v, env);
15283
+ if (missing.length > 0) {
15284
+ unset.push(...missing);
15285
+ continue;
15286
+ }
13988
15287
  const e = expandPlaceholders(v, env);
13989
15288
  result[k] = e;
13990
15289
  if (e !== v) secrets.push(e);
13991
15290
  if (v.length > 0) secrets.push(v);
13992
15291
  }
13993
- return { expanded: result, secrets };
15292
+ return { expanded: result, secrets, unset };
13994
15293
  }
13995
15294
  function classifyError(err) {
13996
15295
  if (err instanceof InputRequiredError) {
@@ -14002,6 +15301,9 @@ function classifyError(err) {
14002
15301
  if (err instanceof McpAuthError) {
14003
15302
  return { code: EXIT_AUTH, kind: "auth", msg: err.message };
14004
15303
  }
15304
+ if (err instanceof JerkSignInRequiredError) {
15305
+ return { code: EXIT_AUTH, kind: "auth", msg: err.message };
15306
+ }
14005
15307
  if (err instanceof AuthStepUpError) {
14006
15308
  const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
14007
15309
  return {
@@ -14023,25 +15325,112 @@ function classifyError(err) {
14023
15325
  }
14024
15326
  return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
14025
15327
  }
14026
- function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
15328
+ async function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
14027
15329
  if (hasAuthHeader || !url) return void 0;
14028
- const source = createTokenSource(platform2);
15330
+ const source = await createTokenSource(platform2);
14029
15331
  const entry = config.entry(target);
14030
15332
  const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
14031
15333
  const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
14032
15334
  return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
14033
15335
  }
15336
+ function classifyConnectionError(err, target, interrupted) {
15337
+ if (err instanceof McpAuthError || err instanceof AuthStepUpError || err instanceof JerkSignInRequiredError) {
15338
+ return classifyError(err);
15339
+ }
15340
+ if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
15341
+ return { code: EXIT_AUTH, kind: "auth", msg: `unauthorized. Run: mcpcat login ${target}` };
15342
+ }
15343
+ if (interrupted) {
15344
+ return { code: EXIT_INTERRUPTED, kind: "interrupted", msg: "interrupted" };
15345
+ }
15346
+ const msg = transportHint(err) ?? `connection failed: ${err.message}`;
15347
+ return { code: EXIT_CONNECTION, kind: "connection", msg };
15348
+ }
15349
+ function buildRoots(rootArgs, isStdio, cwd) {
15350
+ const roots = rootArgs.map((r) => ({
15351
+ uri: r.startsWith("file://") ? r : `file://${r}`
15352
+ }));
15353
+ if (roots.length === 0 && isStdio) {
15354
+ roots.push({ uri: `file://${cwd}` });
15355
+ }
15356
+ return roots.length > 0 ? roots : void 0;
15357
+ }
15358
+ async function loadRespondData(platform2, respondArg, json) {
15359
+ if (!respondArg) return { data: void 0 };
15360
+ try {
15361
+ return { data: await readDataArg(platform2, respondArg) };
15362
+ } catch (err) {
15363
+ await writeErr(
15364
+ platform2,
15365
+ json,
15366
+ "usage",
15367
+ `invalid --respond argument: ${err.message}`
15368
+ );
15369
+ return { exitCode: EXIT_USAGE };
15370
+ }
15371
+ }
15372
+ async function handleInputRequired(platform2, conn, err, respondData, json) {
15373
+ const parsed2 = JSON.parse(err.requestJson);
15374
+ const inputReq = parseInputRequest(parsed2.result ?? parsed2);
15375
+ if (!inputReq) {
15376
+ await platform2.write(2, `${err.requestJson}
15377
+ `);
15378
+ return EXIT_INPUT_REQUIRED;
15379
+ }
15380
+ const isTty = platform2.isatty(0);
15381
+ const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
15382
+ if ("exitCode" in elicitResult) {
15383
+ await platform2.write(2, `${err.requestJson}
15384
+ `);
15385
+ return EXIT_INPUT_REQUIRED;
15386
+ }
15387
+ try {
15388
+ const retryResult = await conn.callTool(parsed2.name, {
15389
+ ...parsed2.arguments,
15390
+ _meta: { inputResponses: elicitResult.answers }
15391
+ });
15392
+ return renderCallResult(platform2, retryResult, { json });
15393
+ } catch (retryErr) {
15394
+ const e2 = classifyError(retryErr);
15395
+ await writeErr(platform2, json, e2.kind, e2.msg);
15396
+ return e2.code;
15397
+ }
15398
+ }
14034
15399
  async function runClientVerb(platform2, version, parsed) {
14035
- const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
15400
+ const { expanded, secrets, unset } = expandAndCollect(parsed.headers, platform2.env);
15401
+ if (unset.length > 0) {
15402
+ await platform2.write(
15403
+ 2,
15404
+ `mcpcat: unset variable${unset.length > 1 ? "s" : ""} in -H: ${unset.join(", ")}
15405
+ `
15406
+ );
15407
+ return EXIT_USAGE;
15408
+ }
14036
15409
  const verbose = createVerboseLogger(platform2, parsed.verbose);
14037
15410
  for (const s of secrets) verbose.secrets.add(s);
14038
15411
  const timeoutMs = parsed.timeoutMs;
14039
15412
  const sigintController = new AbortController();
14040
15413
  let interrupted = false;
15414
+ let termExitCode = EXIT_INTERRUPTED;
15415
+ let activeConn;
15416
+ async function killTreeAndExit(exitCode) {
15417
+ interrupted = true;
15418
+ termExitCode = exitCode;
15419
+ sigintController.abort();
15420
+ if (activeConn) {
15421
+ try {
15422
+ await Promise.race([activeConn.close(), new Promise((r) => setTimeout(r, 3e3))]);
15423
+ } catch {
15424
+ }
15425
+ }
15426
+ platform2.exit(exitCode);
15427
+ }
14041
15428
  platform2.onSignal?.("SIGINT", () => {
14042
15429
  interrupted = true;
14043
15430
  sigintController.abort();
14044
15431
  });
15432
+ platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
15433
+ platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
14045
15434
  const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
14046
15435
  const onProgress = (progress, total, message) => {
14047
15436
  if (!showProgress) return;
@@ -14060,59 +15449,91 @@ async function runClientVerb(platform2, version, parsed) {
14060
15449
  await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
14061
15450
  return EXIT_USAGE;
14062
15451
  }
14063
- const hasAuthHeader = "Authorization" in expanded;
14064
- const authProvider = resolveAuthProvider(platform2, hasAuthHeader, resolved.url, target, config);
15452
+ const configHeaders = expandHeaders(resolved.headers, platform2.env, (msg) => {
15453
+ platform2.write(2, `mcpcat: warning: ${msg}
15454
+ `).catch(() => {
15455
+ });
15456
+ });
15457
+ const configSecrets = collectConfigSecrets(resolved.headers, platform2.env);
15458
+ for (const s of configSecrets) verbose.secrets.add(s);
15459
+ const allSecrets = /* @__PURE__ */ new Set([...secrets, ...configSecrets]);
15460
+ const redact2 = (text) => {
15461
+ let result = text;
15462
+ for (const secret of allSecrets) {
15463
+ if (secret.length > 0) {
15464
+ while (result.includes(secret)) {
15465
+ result = result.replace(secret, "[REDACTED]");
15466
+ }
15467
+ }
15468
+ }
15469
+ return result;
15470
+ };
15471
+ const mergedHeaders = { ...configHeaders, ...expanded };
15472
+ const hasAuthHeader = "Authorization" in mergedHeaders;
15473
+ const authProvider = await resolveAuthProvider(
15474
+ platform2,
15475
+ hasAuthHeader,
15476
+ resolved.url,
15477
+ target,
15478
+ config
15479
+ );
15480
+ const roots = buildRoots(parsed.roots, !!resolved.command, platform2.cwd);
15481
+ let respondData;
15482
+ if (parsed.respondArg) {
15483
+ const loaded = await loadRespondData(platform2, parsed.respondArg, parsed.jsonOutput);
15484
+ if ("exitCode" in loaded) return loaded.exitCode;
15485
+ respondData = loaded.data;
15486
+ }
14065
15487
  let conn;
14066
15488
  try {
14067
15489
  conn = await connect(platform2, {
14068
15490
  ...resolved,
14069
- headers: { ...resolved.headers, ...expanded },
15491
+ headers: mergedHeaders,
14070
15492
  protocolVersion: parsed.protocolVersion,
14071
15493
  version,
14072
15494
  timeoutMs,
14073
15495
  signal: sigintController.signal,
14074
15496
  verbose,
14075
- authProvider
15497
+ authProvider,
15498
+ roots
14076
15499
  });
15500
+ activeConn = conn;
14077
15501
  } catch (err) {
14078
- if (err instanceof McpAuthError || err instanceof AuthStepUpError) {
14079
- const e = classifyError(err);
14080
- await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
14081
- return EXIT_AUTH;
14082
- }
14083
- if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
14084
- await writeErr(
15502
+ if (isSpawnNotFound(err)) {
15503
+ await writeErrRedacted(
14085
15504
  platform2,
14086
15505
  parsed.jsonOutput,
14087
- "auth",
14088
- `unauthorized. Run: mcpcat login ${target}`
15506
+ "connection",
15507
+ `command not found: ${resolved.command}`,
15508
+ redact2
14089
15509
  );
14090
- return EXIT_AUTH;
15510
+ return EXIT_CONNECTION;
14091
15511
  }
14092
- const msg = transportHint(err) ?? `connection failed: ${err.message}`;
14093
- if (interrupted) {
15512
+ const e = classifyConnectionError(err, target, interrupted);
15513
+ if (e.code === EXIT_INTERRUPTED) {
14094
15514
  await platform2.write(2, "mcpcat: interrupted\n");
14095
- return EXIT_INTERRUPTED;
15515
+ return termExitCode;
14096
15516
  }
14097
- await writeErr(platform2, parsed.jsonOutput, "connection", msg);
14098
- return EXIT_CONNECTION;
15517
+ await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
15518
+ return e.code;
14099
15519
  }
14100
15520
  try {
14101
- const code2 = await runVerb(platform2, parsed, conn, onProgress);
15521
+ const code2 = await runVerb(platform2, parsed, conn, onProgress, respondData);
14102
15522
  if (interrupted) {
14103
15523
  await platform2.write(2, "mcpcat: interrupted\n");
14104
- return EXIT_INTERRUPTED;
15524
+ return termExitCode;
14105
15525
  }
14106
15526
  return code2;
14107
15527
  } catch (err) {
14108
15528
  if (interrupted) {
14109
15529
  await platform2.write(2, "mcpcat: interrupted\n");
14110
- return EXIT_INTERRUPTED;
15530
+ return termExitCode;
15531
+ }
15532
+ if (err instanceof InputRequiredError) {
15533
+ return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
14111
15534
  }
14112
15535
  const e = classifyError(err);
14113
- if (err instanceof InputRequiredError) await platform2.write(2, `${e.msg}
14114
- `);
14115
- else await writeErr(platform2, parsed.jsonOutput, e.kind, e.msg);
15536
+ await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
14116
15537
  return e.code;
14117
15538
  } finally {
14118
15539
  try {
@@ -14126,53 +15547,85 @@ async function writeErr(p, json, kind, msg) {
14126
15547
  else await p.write(2, `mcpcat: ${msg}
14127
15548
  `);
14128
15549
  }
15550
+ async function writeErrRedacted(p, json, kind, msg, redact2) {
15551
+ if (json) await renderErrorJson(p, kind, msg, void 0, void 0, redact2);
15552
+ else await p.write(2, `mcpcat: ${redact2(msg)}
15553
+ `);
15554
+ }
15555
+ function collectConfigSecrets(headers, env) {
15556
+ if (!headers) return [];
15557
+ const secrets = [];
15558
+ for (const [, v] of Object.entries(headers)) {
15559
+ const expanded = expandPlaceholders(v, env);
15560
+ if (expanded !== v && expanded.length > 0) secrets.push(expanded);
15561
+ if (v.length > 0 && v !== expanded) secrets.push(v);
15562
+ }
15563
+ return secrets;
15564
+ }
15565
+ function isSpawnNotFound(err) {
15566
+ if (!(err instanceof Error)) return false;
15567
+ const code2 = err.code;
15568
+ if (code2 === "ENOENT" || code2 === "EACCES") return true;
15569
+ const msg = err.message;
15570
+ return msg.includes("ENOENT") || msg.includes("EACCES");
15571
+ }
14129
15572
 
14130
15573
  // src/platform/host.ts
14131
15574
  import { spawn as cpSpawn } from "node:child_process";
14132
15575
  import { constants } from "node:fs";
14133
- import { access, mkdir, readFile as readFile3, rename, rmdir, stat, writeFile } from "node:fs/promises";
15576
+ import { access, mkdir, readFile as readFile3, rename, rmdir, stat, unlink, writeFile } from "node:fs/promises";
14134
15577
  import { createServer as createServer2 } from "node:net";
14135
15578
  import { Readable, Writable } from "node:stream";
14136
15579
  import * as tty from "node:tty";
14137
15580
  var USE_PROCESS_GROUPS2 = process.platform !== "win32";
14138
15581
  function nodeSpawn(options) {
14139
- const child = cpSpawn(options.argv[0], options.argv.slice(1), {
14140
- env: options.env ?? process.env,
14141
- cwd: options.cwd,
14142
- stdio: ["pipe", "pipe", "pipe"],
14143
- detached: USE_PROCESS_GROUPS2
14144
- });
14145
- const stdout = Readable.toWeb(child.stdout);
14146
- const stderr = Readable.toWeb(child.stderr);
14147
- const stdin = Writable.toWeb(child.stdin);
14148
- return Promise.resolve({
14149
- stdin,
14150
- stdout,
14151
- stderr,
14152
- wait() {
14153
- return new Promise((resolve) => {
14154
- child.on("close", (code2, signal) => {
14155
- resolve({
14156
- status: signal ? 128 + (signalNumber(signal) ?? 0) : code2 ?? 1,
14157
- signal: signal ?? void 0
14158
- });
15582
+ return new Promise((resolve, reject) => {
15583
+ let rejected = false;
15584
+ const child = cpSpawn(options.argv[0], options.argv.slice(1), {
15585
+ env: options.env ?? process.env,
15586
+ cwd: options.cwd,
15587
+ stdio: ["pipe", "pipe", "pipe"],
15588
+ detached: USE_PROCESS_GROUPS2
15589
+ });
15590
+ child.on("error", (err) => {
15591
+ if (!rejected) {
15592
+ rejected = true;
15593
+ reject(err);
15594
+ }
15595
+ });
15596
+ const stdout = Readable.toWeb(child.stdout);
15597
+ const stderr = Readable.toWeb(child.stderr);
15598
+ const stdin = Writable.toWeb(child.stdin);
15599
+ const closePromise = new Promise((res) => {
15600
+ child.on("close", (code2, signal) => {
15601
+ res({
15602
+ status: signal ? 128 + (signalNumber(signal) ?? 0) : code2 ?? 1,
15603
+ signal: signal ?? void 0
14159
15604
  });
14160
15605
  });
14161
- },
14162
- kill(signal) {
14163
- const sig = signal ?? "SIGTERM";
14164
- const pid = child.pid;
14165
- if (USE_PROCESS_GROUPS2 && pid !== void 0) {
14166
- try {
14167
- process.kill(-pid, sig);
14168
- return;
14169
- } catch {
15606
+ });
15607
+ if (child.pid !== void 0) {
15608
+ resolve({
15609
+ stdin,
15610
+ stdout,
15611
+ stderr,
15612
+ wait: () => closePromise,
15613
+ kill(signal) {
15614
+ const sig = signal ?? "SIGTERM";
15615
+ const pid = child.pid;
15616
+ if (USE_PROCESS_GROUPS2 && pid !== void 0) {
15617
+ try {
15618
+ process.kill(-pid, sig);
15619
+ return;
15620
+ } catch {
15621
+ }
15622
+ }
15623
+ try {
15624
+ child.kill(sig);
15625
+ } catch {
15626
+ }
14170
15627
  }
14171
- }
14172
- try {
14173
- child.kill(sig);
14174
- } catch {
14175
- }
15628
+ });
14176
15629
  }
14177
15630
  });
14178
15631
  }
@@ -14193,8 +15646,11 @@ var hostFs = {
14193
15646
  async writeFile(path, data, options) {
14194
15647
  await writeFile(path, data, options?.mode !== void 0 ? { mode: options.mode } : void 0);
14195
15648
  },
14196
- async mkdir(path) {
14197
- await mkdir(path, { recursive: true });
15649
+ async mkdir(path, options) {
15650
+ await mkdir(path, {
15651
+ recursive: true,
15652
+ ...options?.mode !== void 0 ? { mode: options.mode } : {}
15653
+ });
14198
15654
  },
14199
15655
  async rename(from, to) {
14200
15656
  await rename(from, to);
@@ -14216,6 +15672,9 @@ var hostFs = {
14216
15672
  },
14217
15673
  async rmdir(path) {
14218
15674
  await rmdir(path);
15675
+ },
15676
+ async unlink(path) {
15677
+ await unlink(path);
14219
15678
  }
14220
15679
  };
14221
15680
  function createStdinStream() {
@@ -14327,5 +15786,14 @@ function createHostPlatform() {
14327
15786
  // src/host.ts
14328
15787
  var platform = createHostPlatform();
14329
15788
  var args = process.argv.slice(2);
14330
- var code = await run(platform, "0.12.0", args);
15789
+ try {
15790
+ process.stdout.on("error", () => {
15791
+ });
15792
+ process.stderr.on("error", () => {
15793
+ });
15794
+ process.on("SIGPIPE", () => {
15795
+ });
15796
+ } catch {
15797
+ }
15798
+ var code = await run(platform, "0.13.0", args);
14331
15799
  process.exit(code);