@ai-ecoverse/mcpcat 0.12.0 → 0.14.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,24 +9175,22 @@ 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
- const getSeaDir = globalThis.__mcpcat_get_sea_asset_dir;
8647
- const seaDir = getSeaDir?.();
8648
- const wasmPath = seaDir ? `${seaDir}/quickjs.wasm` : await resolveWasmPath(platform2);
8649
- const workerUrl = seaDir ? new URL(`file://${seaDir}/codemode-worker.mjs`) : new URL("./codemode-worker.mjs", import.meta.url);
9186
+ const wasmPath = await resolveWasmPath(platform2);
8650
9187
  return new CodemodeSandbox2({
8651
9188
  tools,
8652
9189
  globals,
8653
9190
  timeoutMs,
9191
+ memoryLimitBytes,
8654
9192
  wasm: loadQuickJSWasm2(wasmPath),
8655
- workerUrl
9193
+ workerUrl: new URL("./codemode-worker.mjs", import.meta.url)
8656
9194
  });
8657
9195
  }
8658
9196
  async function resolveWasmPath(platform2) {
@@ -8726,6 +9264,14 @@ function containsPlaceholders(value) {
8726
9264
  function expandPlaceholders(value, env) {
8727
9265
  return value.replace(PLACEHOLDER, (_, name) => env[name] ?? "");
8728
9266
  }
9267
+ function missingPlaceholders(value, env) {
9268
+ const missing = [];
9269
+ for (const m of value.matchAll(PLACEHOLDER)) {
9270
+ const name = m[1];
9271
+ if (env[name] === void 0 && !missing.includes(name)) missing.push(name);
9272
+ }
9273
+ return missing;
9274
+ }
8729
9275
  function expandPlaceholdersStrict(value, env) {
8730
9276
  let hasUndefined = false;
8731
9277
  const result = value.replace(PLACEHOLDER, (_, name) => {
@@ -8738,6 +9284,20 @@ function expandPlaceholdersStrict(value, env) {
8738
9284
  if (hasUndefined) return void 0;
8739
9285
  return result;
8740
9286
  }
9287
+ function expandHeaders(headers, env, warn) {
9288
+ if (!headers) return void 0;
9289
+ const result = {};
9290
+ for (const [k, v] of Object.entries(headers)) {
9291
+ const missing = missingPlaceholders(v, env);
9292
+ if (missing.length > 0) {
9293
+ if (warn)
9294
+ warn(`header "${k}": unset variable${missing.length > 1 ? "s" : ""} ${missing.join(", ")}`);
9295
+ continue;
9296
+ }
9297
+ result[k] = expandPlaceholders(v, env);
9298
+ }
9299
+ return Object.keys(result).length > 0 ? result : void 0;
9300
+ }
8741
9301
 
8742
9302
  // src/config/parse.ts
8743
9303
  var SERVER_NAME = /^[A-Za-z0-9_-]+$/;
@@ -8928,7 +9488,12 @@ async function readFile2(fs, path) {
8928
9488
  try {
8929
9489
  const data = await fs.readFile(path);
8930
9490
  return new TextDecoder().decode(data);
8931
- } catch {
9491
+ } catch (err) {
9492
+ if (err.code === "EACCES") {
9493
+ const e = new Error(`permission denied: ${path}`);
9494
+ e.code = "EACCES";
9495
+ throw e;
9496
+ }
8932
9497
  return void 0;
8933
9498
  }
8934
9499
  }
@@ -9093,6 +9658,13 @@ async function removeStaleLock(fs, lockPath) {
9093
9658
  async function acquireLock(fs, configPath) {
9094
9659
  const lockPath = `${configPath}.lock`;
9095
9660
  const start = Date.now();
9661
+ const parentDir = lockPath.slice(0, lockPath.lastIndexOf("/"));
9662
+ if (parentDir) {
9663
+ try {
9664
+ await fs.mkdir(parentDir);
9665
+ } catch {
9666
+ }
9667
+ }
9096
9668
  while (true) {
9097
9669
  try {
9098
9670
  await fs.mkdirExclusive(lockPath);
@@ -9154,7 +9726,15 @@ async function editMcpServers(fs, path, edit) {
9154
9726
  }
9155
9727
  const tmpPath = `${path}.tmp.${Date.now()}`;
9156
9728
  await fs.writeFile(tmpPath, output);
9157
- await fs.rename(tmpPath, path);
9729
+ try {
9730
+ await fs.rename(tmpPath, path);
9731
+ } catch (renameErr) {
9732
+ try {
9733
+ if (fs.unlink) await fs.unlink(tmpPath);
9734
+ } catch {
9735
+ }
9736
+ throw renameErr;
9737
+ }
9158
9738
  return { changed: true };
9159
9739
  } finally {
9160
9740
  await release();
@@ -9531,6 +10111,31 @@ async function getCandidate(fs, stateDir2, id) {
9531
10111
  }
9532
10112
 
9533
10113
  // src/discovery/probe.ts
10114
+ var LOOPBACK_RE = /^(127\.\d+\.\d+\.\d+|\[?::1\]?|localhost)$/i;
10115
+ var LINK_LOCAL_RE = /^(169\.254\.|fe80:)/i;
10116
+ var PRIVATE_RE = /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.|fc|fd)/i;
10117
+ var LOCALHOST_SUFFIX_RE = /\.localhost$/i;
10118
+ var SLICC_INTERNAL_RE = /^host\.slicc\.internal$/i;
10119
+ function isPrivateOrLoopback(hostname) {
10120
+ const bare = hostname.replace(/^\[|\]$/g, "");
10121
+ return LOOPBACK_RE.test(bare) || LINK_LOCAL_RE.test(bare) || PRIVATE_RE.test(bare) || LOCALHOST_SUFFIX_RE.test(bare) || SLICC_INTERNAL_RE.test(bare);
10122
+ }
10123
+ function isSameOrigin(base, candidate) {
10124
+ try {
10125
+ const a = new URL(base);
10126
+ const b = new URL(candidate);
10127
+ return a.origin === b.origin;
10128
+ } catch {
10129
+ return false;
10130
+ }
10131
+ }
10132
+ function isUserUrlPrivate(userUrl) {
10133
+ try {
10134
+ return isPrivateOrLoopback(new URL(userUrl).hostname);
10135
+ } catch {
10136
+ return false;
10137
+ }
10138
+ }
9534
10139
  function isJsonContentType(ct) {
9535
10140
  if (!ct) return false;
9536
10141
  const lower = ct.toLowerCase();
@@ -9665,13 +10270,44 @@ async function probeWellKnown(fetcher, origin, page) {
9665
10270
  }
9666
10271
  return events;
9667
10272
  }
9668
- async function probeLinkHeaders(fetcher, targetUrl, origin, page) {
10273
+ async function probeLinkHeaders(fetcher, targetUrl, origin, page, userUrlIsPrivate) {
9669
10274
  const events = [];
9670
10275
  const targetResult = await probeUrl(fetcher, targetUrl);
9671
10276
  if (!targetResult) return events;
9672
10277
  const links = parseLinkHeaders(targetResult.headers);
9673
10278
  for (const link of links) {
9674
10279
  const resolved = new URL(link.url, targetUrl).href;
10280
+ if (!isSameOrigin(targetUrl, resolved)) {
10281
+ events.push(
10282
+ makeEvent(
10283
+ link.rel,
10284
+ origin,
10285
+ resolved,
10286
+ "link-header",
10287
+ page,
10288
+ "invalid",
10289
+ null,
10290
+ `skipped: cross-origin Link target (${new URL(resolved).origin})`
10291
+ )
10292
+ );
10293
+ continue;
10294
+ }
10295
+ const resolvedHost = new URL(resolved).hostname;
10296
+ if (!userUrlIsPrivate && isPrivateOrLoopback(resolvedHost)) {
10297
+ events.push(
10298
+ makeEvent(
10299
+ link.rel,
10300
+ origin,
10301
+ resolved,
10302
+ "link-header",
10303
+ page,
10304
+ "invalid",
10305
+ null,
10306
+ `skipped: private/loopback target (${resolvedHost})`
10307
+ )
10308
+ );
10309
+ continue;
10310
+ }
9675
10311
  const classifier = link.rel === "ard" ? classifyArd : link.rel === "ai-catalog" ? classifyAiCatalog : null;
9676
10312
  if (!classifier) continue;
9677
10313
  const linkResult = await probeUrl(fetcher, resolved, "application/json");
@@ -9768,9 +10404,10 @@ async function probeDiscover(platform2, targetUrl) {
9768
10404
  }
9769
10405
  const page = targetUrl;
9770
10406
  const fetcher = platform2.fetch;
10407
+ const userPrivate = isUserUrlPrivate(targetUrl);
9771
10408
  const events = [
9772
10409
  ...await probeWellKnown(fetcher, origin, page),
9773
- ...await probeLinkHeaders(fetcher, targetUrl, origin, page),
10410
+ ...await probeLinkHeaders(fetcher, targetUrl, origin, page, userPrivate),
9774
10411
  ...await probeMcp(fetcher, origin, page, targetUrl)
9775
10412
  ];
9776
10413
  return { events };
@@ -10027,13 +10664,27 @@ function renderCallResultJson(result) {
10027
10664
  return JSON.stringify(result, null, 2);
10028
10665
  }
10029
10666
  async function renderCallResult(platform2, result, options) {
10667
+ const r = options?.redact ?? ((s) => s);
10668
+ if (options?.outputPath) {
10669
+ const content = options.json ? renderCallResultJson(result) : renderCallResultText(result, options);
10670
+ await platform2.fs.writeFile(options.outputPath, content);
10671
+ if (result.isError) {
10672
+ await platform2.write(
10673
+ 2,
10674
+ r(`${result.isError ? "(tool reported error)" : ""}
10675
+ `)
10676
+ );
10677
+ return 1;
10678
+ }
10679
+ return 0;
10680
+ }
10030
10681
  if (options?.json) {
10031
10682
  const json = renderCallResultJson(result);
10032
10683
  if (result.isError) {
10033
10684
  await platform2.write(
10034
10685
  2,
10035
- `${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
10036
- `
10686
+ r(`${JSON.stringify({ error: { kind: "tool_error", message: json } }, null, 2)}
10687
+ `)
10037
10688
  );
10038
10689
  return 1;
10039
10690
  }
@@ -10043,8 +10694,8 @@ async function renderCallResult(platform2, result, options) {
10043
10694
  }
10044
10695
  const text = renderCallResultText(result, options);
10045
10696
  if (result.isError) {
10046
- await platform2.write(2, `${text || "(tool reported error)"}
10047
- `);
10697
+ await platform2.write(2, r(`${text || "(tool reported error)"}
10698
+ `));
10048
10699
  return 1;
10049
10700
  }
10050
10701
  if (text) {
@@ -10061,13 +10712,33 @@ async function renderInfoJson(platform2, info) {
10061
10712
  await platform2.write(1, `${JSON.stringify(info, null, 2)}
10062
10713
  `);
10063
10714
  }
10064
- async function renderErrorJson(platform2, kind, message, code2, data) {
10065
- const errorObj = { kind, message };
10715
+ async function renderErrorJson(platform2, kind, message, code2, data, redact2) {
10716
+ const r = redact2 ?? ((s) => s);
10717
+ const errorObj = { kind, message: r(message) };
10066
10718
  if (code2 !== void 0) errorObj.code = code2;
10067
10719
  if (data !== void 0) errorObj.data = data;
10068
10720
  await platform2.write(2, `${JSON.stringify({ error: errorObj }, null, 2)}
10069
10721
  `);
10070
10722
  }
10723
+ async function renderResourcesJson(platform2, resources, templates) {
10724
+ await platform2.write(
10725
+ 1,
10726
+ `${JSON.stringify({ resources, resourceTemplates: templates }, null, 2)}
10727
+ `
10728
+ );
10729
+ }
10730
+ async function renderPromptsJson(platform2, prompts) {
10731
+ await platform2.write(1, `${JSON.stringify({ prompts }, null, 2)}
10732
+ `);
10733
+ }
10734
+ async function renderPromptResultJson(platform2, result) {
10735
+ await platform2.write(1, `${JSON.stringify(result, null, 2)}
10736
+ `);
10737
+ }
10738
+ async function renderReadResourceJson(platform2, result) {
10739
+ await platform2.write(1, `${JSON.stringify(result, null, 2)}
10740
+ `);
10741
+ }
10071
10742
 
10072
10743
  // src/serve/catalog.ts
10073
10744
  var NAME_RE = /^[A-Za-z][\w-]*$/;
@@ -10835,38 +11506,46 @@ function hasOwn(value, key) {
10835
11506
  }
10836
11507
 
10837
11508
  // 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
- }
11509
+ function isLoopbackBind(host) {
11510
+ return host === "127.0.0.1" || host === "localhost" || host === "::1" || host === "[::1]";
10850
11511
  }
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;
11512
+ function buildServerOrigins(host, port) {
11513
+ const origins = /* @__PURE__ */ new Set();
11514
+ origins.add(`http://${host}:${port}`);
11515
+ origins.add(`http://${port}.kernel.localhost`);
11516
+ if (isLoopbackBind(host)) {
11517
+ origins.add(`http://localhost:${port}`);
11518
+ origins.add(`http://127.0.0.1:${port}`);
11519
+ origins.add(`http://[::1]:${port}`);
11520
+ }
11521
+ return origins;
10857
11522
  }
10858
- function checkOrigin(origin, allowedOrigins, boundLoopback) {
11523
+ function checkOrigin(origin, serverOrigins, allowedOrigins) {
10859
11524
  if (origin === void 0) return { ok: true };
11525
+ if (serverOrigins.has(origin)) return { ok: true };
10860
11526
  if (allowedOrigins.has(origin)) return { ok: true };
10861
- if (isLoopbackOrigin(origin)) return { ok: true };
10862
- if (!boundLoopback) return { ok: true };
10863
11527
  return { ok: false, reason: `origin ${origin} is not allowed` };
10864
11528
  }
10865
- function checkHost(host, boundLoopback) {
10866
- if (!boundLoopback) return { ok: true };
11529
+ function buildAllowedHosts(host, port, extraHosts) {
11530
+ const hosts = /* @__PURE__ */ new Set();
11531
+ hosts.add(`${host}:${port}`);
11532
+ hosts.add(host);
11533
+ hosts.add(`${port}.kernel.localhost`);
11534
+ const loopback2 = ["localhost", "127.0.0.1", "[::1]"];
11535
+ for (const h of loopback2) {
11536
+ hosts.add(h);
11537
+ hosts.add(`${h}:${port}`);
11538
+ }
11539
+ for (const h of extraHosts) hosts.add(h);
11540
+ return hosts;
11541
+ }
11542
+ function checkHost(host, allowedHosts) {
10867
11543
  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` };
11544
+ if (allowedHosts.has(host)) return { ok: true };
11545
+ const hostname = host.replace(/:\d+$/, "");
11546
+ if (allowedHosts.has(hostname)) return { ok: true };
11547
+ if (hostname.endsWith(".localhost")) return { ok: true };
11548
+ return { ok: false, reason: `host ${host} is not allowed` };
10870
11549
  }
10871
11550
 
10872
11551
  // src/serve/proxy.ts
@@ -11103,6 +11782,8 @@ function parseSingleFlag(args2, i, opts) {
11103
11782
  return parsed.next;
11104
11783
  } else if (arg === "--json") {
11105
11784
  opts.json = true;
11785
+ } else if (arg === "--no-auth") {
11786
+ opts.noAuth = true;
11106
11787
  } else if (!arg.startsWith("-")) {
11107
11788
  opts.commands.push(parseCommandTarget(arg));
11108
11789
  } else {
@@ -11123,6 +11804,9 @@ function parseValueFlag(args2, i, opts) {
11123
11804
  case "--allow-origin":
11124
11805
  opts.allowOrigins.push(args2[++i]);
11125
11806
  break;
11807
+ case "--allow-host":
11808
+ opts.allowHosts.push(args2[++i]);
11809
+ break;
11126
11810
  case "--env":
11127
11811
  opts.envAllow.push(args2[++i]);
11128
11812
  break;
@@ -11161,7 +11845,9 @@ function parseServeFlags(args2) {
11161
11845
  host: "127.0.0.1",
11162
11846
  port: 0,
11163
11847
  bearer: void 0,
11848
+ noAuth: false,
11164
11849
  allowOrigins: [],
11850
+ allowHosts: [],
11165
11851
  envAllow: [],
11166
11852
  toolTemplates: [],
11167
11853
  callTimeout: CALL_TIMEOUT_MS,
@@ -11190,6 +11876,20 @@ function constantTimeEqual(a, b) {
11190
11876
  }
11191
11877
  return mismatch === 0;
11192
11878
  }
11879
+ function generateToken() {
11880
+ const bytes = new Uint8Array(32);
11881
+ crypto.getRandomValues(bytes);
11882
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
11883
+ }
11884
+ function checkContentType(ct) {
11885
+ if (!ct) return false;
11886
+ return ct.toLowerCase().startsWith("application/json");
11887
+ }
11888
+ function checkAcceptHeader(accept) {
11889
+ if (!accept) return false;
11890
+ const lower = accept.toLowerCase();
11891
+ return lower.includes("application/json") || lower.includes("text/event-stream") || lower.includes("*/*");
11892
+ }
11193
11893
  function buildChildEnv(platform2, envAllow) {
11194
11894
  const env = {};
11195
11895
  for (const key of ALLOWED_ENV_KEYS) {
@@ -11238,7 +11938,7 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
11238
11938
  }
11239
11939
  const stdoutChunks = [];
11240
11940
  const stderrChunks = [];
11241
- const readStream2 = async (stream, into) => {
11941
+ const readStream3 = async (stream, into) => {
11242
11942
  const r = stream.getReader();
11243
11943
  try {
11244
11944
  for (; ; ) {
@@ -11254,8 +11954,8 @@ async function runCommand(platform2, cmdPath, argv, childEnv, stdin, timeoutMs,
11254
11954
  (resolve) => setTimeout(() => resolve("timeout"), timeoutMs)
11255
11955
  );
11256
11956
  const work = Promise.all([
11257
- readStream2(proc.stdout, stdoutChunks),
11258
- readStream2(proc.stderr, stderrChunks),
11957
+ readStream3(proc.stdout, stdoutChunks),
11958
+ readStream3(proc.stderr, stderrChunks),
11259
11959
  proc.wait()
11260
11960
  ]);
11261
11961
  const result = await Promise.race([work, timer]);
@@ -11487,7 +12187,10 @@ function createToolExecutor(platform2, clis, allTools, childEnv, options, serveC
11487
12187
  }
11488
12188
  async function runServe(platform2, options, version) {
11489
12189
  let resolvedBearer;
11490
- if (options.bearer) {
12190
+ let generatedBearer = false;
12191
+ if (options._resolvedBearer) {
12192
+ resolvedBearer = options._resolvedBearer;
12193
+ } else if (options.bearer) {
11491
12194
  const expanded = expandBearerToken(options.bearer, platform2.env);
11492
12195
  if (!expanded) {
11493
12196
  await platform2.write(
@@ -11497,9 +12200,31 @@ async function runServe(platform2, options, version) {
11497
12200
  return 2;
11498
12201
  }
11499
12202
  resolvedBearer = expanded;
12203
+ } else if (options.noAuth) {
12204
+ if (options.transport === "http" && !isLoopbackBind(options.host)) {
12205
+ await platform2.write(2, "mcpcat serve: --no-auth is only allowed on loopback\n");
12206
+ return 2;
12207
+ }
12208
+ if (options.transport === "http") {
12209
+ await platform2.write(
12210
+ 2,
12211
+ "mcpcat serve: warning: running without authentication on loopback\n"
12212
+ );
12213
+ }
12214
+ resolvedBearer = void 0;
12215
+ } else if (options.transport === "http") {
12216
+ if (platform2.kind === "kernel") {
12217
+ await platform2.write(
12218
+ 2,
12219
+ "mcpcat serve: kernel HTTP serve requires --bearer '${VAR}' or --no-auth\n"
12220
+ );
12221
+ return 2;
12222
+ }
12223
+ resolvedBearer = generateToken();
12224
+ generatedBearer = true;
11500
12225
  }
11501
12226
  if (options.proxy) {
11502
- return runProxyServe(platform2, options, version, resolvedBearer);
12227
+ return runProxyServe(platform2, options, version, resolvedBearer, generatedBearer);
11503
12228
  }
11504
12229
  const serveCwd = options.cwd ?? platform2.cwd;
11505
12230
  const childEnv = buildChildEnv(platform2, options.envAllow);
@@ -11536,12 +12261,35 @@ async function runServe(platform2, options, version) {
11536
12261
  executor.invoke,
11537
12262
  serverName,
11538
12263
  version,
11539
- resolvedBearer
12264
+ resolvedBearer,
12265
+ generatedBearer
11540
12266
  );
11541
12267
  }
12268
+ async function publishBearer(platform2, token, port, listenUrl, json) {
12269
+ if (!json && platform2.isatty(2)) {
12270
+ await platform2.write(2, `mcpcat serve: bearer ${token}
12271
+ `);
12272
+ return void 0;
12273
+ }
12274
+ const dir = stateDir(platform2);
12275
+ await platform2.fs.mkdir(dir, { mode: 448 });
12276
+ const tokenPath = `${dir}/serve-${port}.token`;
12277
+ try {
12278
+ await platform2.fs.unlink?.(tokenPath);
12279
+ } catch {
12280
+ }
12281
+ await platform2.fs.writeFile(tokenPath, token, { mode: 384 });
12282
+ await platform2.write(2, `mcpcat serve: bearer saved to ${tokenPath}
12283
+ `);
12284
+ if (json) {
12285
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl, bearerFile: tokenPath })}
12286
+ `);
12287
+ }
12288
+ return tokenPath;
12289
+ }
11542
12290
  function resolveProxyStdio(target, version) {
11543
12291
  const rest = target.slice(6);
11544
- const parts = rest.split(/\s+/).filter(Boolean);
12292
+ const parts = splitShellStyle(rest);
11545
12293
  return { command: parts[0], args: parts.slice(1), version };
11546
12294
  }
11547
12295
  function resolveProxyUrl(target, headers, version) {
@@ -11611,7 +12359,7 @@ async function buildProxyConnectOpts(platform2, options, version) {
11611
12359
  }
11612
12360
  return { connectOpts, hiddenValues };
11613
12361
  }
11614
- async function runProxyServe(platform2, options, version, resolvedBearer) {
12362
+ async function runProxyServe(platform2, options, version, resolvedBearer, generatedBearer) {
11615
12363
  const built = await buildProxyConnectOpts(platform2, options, version);
11616
12364
  if ("error" in built) {
11617
12365
  await platform2.write(2, `mcpcat serve --proxy: ${built.error}
@@ -11629,7 +12377,7 @@ async function runProxyServe(platform2, options, version, resolvedBearer) {
11629
12377
  if (options.transport === "stdio") {
11630
12378
  return runProxyStdio(platform2, proxy);
11631
12379
  }
11632
- return runProxyHttp(platform2, options, proxy, resolvedBearer);
12380
+ return runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer);
11633
12381
  }
11634
12382
  async function runProxyStdio(platform2, proxy) {
11635
12383
  const reader = platform2.stdin.getReader();
@@ -11660,12 +12408,12 @@ async function runProxyStdio(platform2, proxy) {
11660
12408
  }
11661
12409
  return 0;
11662
12410
  }
11663
- async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
12411
+ async function runProxyHttp(platform2, options, proxy, resolvedBearer, generatedBearer) {
11664
12412
  if (!platform2.listen) {
11665
12413
  await platform2.write(2, "mcpcat serve --proxy: HTTP transport requires platform.listen\n");
11666
12414
  return 3;
11667
12415
  }
11668
- const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
12416
+ const boundLoopback = isLoopbackBind(options.host);
11669
12417
  if (!boundLoopback && !resolvedBearer) {
11670
12418
  await platform2.write(
11671
12419
  2,
@@ -11679,10 +12427,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
11679
12427
  port: options.port
11680
12428
  });
11681
12429
  const actualPort = listener.port;
12430
+ const serverOrigins = buildServerOrigins(options.host, actualPort);
12431
+ const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
11682
12432
  const listenUrl = `http://${options.host}:${actualPort}/mcp`;
11683
- if (options.json) {
11684
- await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
12433
+ let tokenFilePath;
12434
+ if (generatedBearer && resolvedBearer) {
12435
+ tokenFilePath = await publishBearer(
12436
+ platform2,
12437
+ resolvedBearer,
12438
+ actualPort,
12439
+ listenUrl,
12440
+ options.json
12441
+ );
12442
+ } else {
12443
+ if (options.json) {
12444
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
11685
12445
  `);
12446
+ }
11686
12447
  }
11687
12448
  await platform2.write(2, `mcpcat serve --proxy: listening on ${listenUrl}
11688
12449
  `);
@@ -11695,12 +12456,23 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
11695
12456
  shutdownRequested = true;
11696
12457
  listener.close();
11697
12458
  void proxy.shutdown();
12459
+ if (tokenFilePath) {
12460
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
12461
+ });
12462
+ }
11698
12463
  };
11699
12464
  if (platform2.onSignal) {
11700
12465
  platform2.onSignal("SIGINT", shutdown);
11701
12466
  platform2.onSignal("SIGTERM", shutdown);
11702
12467
  }
11703
- const handler = (request) => handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopback, resolvedBearer);
12468
+ const handler = (request) => handleProxyMcpRequest(
12469
+ request,
12470
+ proxy,
12471
+ serverOrigins,
12472
+ allowedOrigins,
12473
+ allowedHosts,
12474
+ resolvedBearer
12475
+ );
11704
12476
  try {
11705
12477
  for await (const connection of listener) {
11706
12478
  if (shutdownRequested) break;
@@ -11718,20 +12490,40 @@ async function runProxyHttp(platform2, options, proxy, resolvedBearer) {
11718
12490
  }
11719
12491
  return 0;
11720
12492
  }
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
- }
12493
+ async function handleProxyMcpRequest(request, proxy, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
12494
+ if (request.target !== "/mcp") {
12495
+ return {
12496
+ status: 404,
12497
+ reason: "Not Found",
12498
+ headers: { "content-type": "text/plain" },
12499
+ body: "Not Found"
12500
+ };
12501
+ }
12502
+ if (request.method !== "POST") {
12503
+ return {
12504
+ status: 405,
12505
+ reason: "Method Not Allowed",
12506
+ headers: { "content-type": "text/plain", allow: "POST" },
12507
+ body: "Method Not Allowed"
12508
+ };
12509
+ }
12510
+ if (!checkContentType(request.headers["content-type"])) {
12511
+ return {
12512
+ status: 415,
12513
+ reason: "Unsupported Media Type",
12514
+ headers: { "content-type": "text/plain" },
12515
+ body: "Content-Type must be application/json"
12516
+ };
12517
+ }
12518
+ if (!checkAcceptHeader(request.headers.accept)) {
12519
+ return {
12520
+ status: 406,
12521
+ reason: "Not Acceptable",
12522
+ headers: { "content-type": "text/plain" },
12523
+ body: "Accept must include application/json or text/event-stream"
12524
+ };
11733
12525
  }
11734
- const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
12526
+ const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
11735
12527
  if (!originCheck.ok) {
11736
12528
  return {
11737
12529
  status: 403,
@@ -11740,7 +12532,7 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
11740
12532
  body: originCheck.reason ?? "Forbidden"
11741
12533
  };
11742
12534
  }
11743
- const hostCheck = checkHost(request.headers.host, boundLoopback);
12535
+ const hostCheck = checkHost(request.headers.host, allowedHosts);
11744
12536
  if (!hostCheck.ok) {
11745
12537
  return {
11746
12538
  status: 403,
@@ -11749,21 +12541,17 @@ async function handleProxyMcpRequest(request, proxy, allowedOrigins, boundLoopba
11749
12541
  body: hostCheck.reason ?? "Forbidden"
11750
12542
  };
11751
12543
  }
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
- };
12544
+ if (resolvedBearer) {
12545
+ const authHeader = request.headers.authorization;
12546
+ const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
12547
+ if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
12548
+ return {
12549
+ status: 401,
12550
+ reason: "Unauthorized",
12551
+ headers: { "content-type": "text/plain" },
12552
+ body: "Unauthorized"
12553
+ };
12554
+ }
11767
12555
  }
11768
12556
  const outcome = await proxy.handle(request.body);
11769
12557
  return {
@@ -11801,12 +12589,12 @@ async function runStdioServer(platform2, tools, call, serverName, version) {
11801
12589
  }
11802
12590
  return 0;
11803
12591
  }
11804
- async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer) {
12592
+ async function runHttpServer(platform2, options, tools, call, serverName, version, resolvedBearer, generatedBearer) {
11805
12593
  if (!platform2.listen) {
11806
12594
  await platform2.write(2, "mcpcat serve: HTTP transport requires platform.listen\n");
11807
12595
  return 3;
11808
12596
  }
11809
- const boundLoopback = options.host === "127.0.0.1" || options.host === "localhost" || options.host === "::1";
12597
+ const boundLoopback = isLoopbackBind(options.host);
11810
12598
  if (!boundLoopback && !resolvedBearer) {
11811
12599
  await platform2.write(2, "mcpcat serve: --bearer is required when binding off loopback\n");
11812
12600
  return 2;
@@ -11814,10 +12602,23 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11814
12602
  const allowedOrigins = new Set(options.allowOrigins);
11815
12603
  const listener = await platform2.listen({ host: options.host, port: options.port });
11816
12604
  const actualPort = listener.port;
12605
+ const serverOrigins = buildServerOrigins(options.host, actualPort);
12606
+ const allowedHosts = buildAllowedHosts(options.host, actualPort, options.allowHosts);
11817
12607
  const listenUrl = `http://${options.host}:${actualPort}/mcp`;
11818
- if (options.json) {
11819
- await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
12608
+ let tokenFilePath;
12609
+ if (generatedBearer && resolvedBearer) {
12610
+ tokenFilePath = await publishBearer(
12611
+ platform2,
12612
+ resolvedBearer,
12613
+ actualPort,
12614
+ listenUrl,
12615
+ options.json
12616
+ );
12617
+ } else {
12618
+ if (options.json) {
12619
+ await platform2.write(1, `${JSON.stringify({ url: listenUrl })}
11820
12620
  `);
12621
+ }
11821
12622
  }
11822
12623
  await platform2.write(2, `mcpcat serve: listening on ${listenUrl}
11823
12624
  `);
@@ -11827,6 +12628,10 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11827
12628
  shutdownRequested = true;
11828
12629
  listener.close();
11829
12630
  void killInflight();
12631
+ if (tokenFilePath) {
12632
+ platform2.fs.unlink?.(tokenFilePath).catch(() => {
12633
+ });
12634
+ }
11830
12635
  };
11831
12636
  if (platform2.onSignal) {
11832
12637
  platform2.onSignal("SIGINT", shutdown);
@@ -11838,8 +12643,9 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11838
12643
  call,
11839
12644
  serverName,
11840
12645
  version,
12646
+ serverOrigins,
11841
12647
  allowedOrigins,
11842
- boundLoopback,
12648
+ allowedHosts,
11843
12649
  resolvedBearer
11844
12650
  );
11845
12651
  try {
@@ -11859,20 +12665,40 @@ async function runHttpServer(platform2, options, tools, call, serverName, versio
11859
12665
  }
11860
12666
  return 0;
11861
12667
  }
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
- }
12668
+ async function handleMcpRequest(request, tools, call, serverName, version, serverOrigins, allowedOrigins, allowedHosts, resolvedBearer) {
12669
+ if (request.target !== "/mcp") {
12670
+ return {
12671
+ status: 404,
12672
+ reason: "Not Found",
12673
+ headers: { "content-type": "text/plain" },
12674
+ body: "Not Found"
12675
+ };
12676
+ }
12677
+ if (request.method !== "POST") {
12678
+ return {
12679
+ status: 405,
12680
+ reason: "Method Not Allowed",
12681
+ headers: { "content-type": "text/plain", allow: "POST" },
12682
+ body: "Method Not Allowed"
12683
+ };
12684
+ }
12685
+ if (!checkContentType(request.headers["content-type"])) {
12686
+ return {
12687
+ status: 415,
12688
+ reason: "Unsupported Media Type",
12689
+ headers: { "content-type": "text/plain" },
12690
+ body: "Content-Type must be application/json"
12691
+ };
12692
+ }
12693
+ if (!checkAcceptHeader(request.headers.accept)) {
12694
+ return {
12695
+ status: 406,
12696
+ reason: "Not Acceptable",
12697
+ headers: { "content-type": "text/plain" },
12698
+ body: "Accept must include application/json or text/event-stream"
12699
+ };
11874
12700
  }
11875
- const originCheck = checkOrigin(request.headers.origin, allowedOrigins, boundLoopback);
12701
+ const originCheck = checkOrigin(request.headers.origin, serverOrigins, allowedOrigins);
11876
12702
  if (!originCheck.ok) {
11877
12703
  return {
11878
12704
  status: 403,
@@ -11881,7 +12707,7 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
11881
12707
  body: originCheck.reason ?? "Forbidden"
11882
12708
  };
11883
12709
  }
11884
- const hostCheck = checkHost(request.headers.host, boundLoopback);
12710
+ const hostCheck = checkHost(request.headers.host, allowedHosts);
11885
12711
  if (!hostCheck.ok) {
11886
12712
  return {
11887
12713
  status: 403,
@@ -11890,21 +12716,17 @@ async function handleMcpRequest(request, tools, call, serverName, version, allow
11890
12716
  body: hostCheck.reason ?? "Forbidden"
11891
12717
  };
11892
12718
  }
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
- };
12719
+ if (resolvedBearer) {
12720
+ const authHeader = request.headers.authorization;
12721
+ const provided = authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : "";
12722
+ if (!provided || !constantTimeEqual(provided, resolvedBearer)) {
12723
+ return {
12724
+ status: 401,
12725
+ reason: "Unauthorized",
12726
+ headers: { "content-type": "text/plain" },
12727
+ body: "Unauthorized"
12728
+ };
12729
+ }
11908
12730
  }
11909
12731
  const outcome = await handleJsonRpc(request.body, tools, call, serverName, version);
11910
12732
  return {
@@ -12231,8 +13053,16 @@ var PiFileTokenSource = class {
12231
13053
  };
12232
13054
 
12233
13055
  // src/cli/auth-verbs.ts
12234
- function createTokenSource(platform2) {
12235
- return new PiFileTokenSource(platform2);
13056
+ var resolvedSource;
13057
+ async function createTokenSource(platform2) {
13058
+ if (isKernel(platform2)) return new PiFileTokenSource(platform2);
13059
+ if (resolvedSource) return resolvedSource;
13060
+ if (await jerkAvailable(platform2)) {
13061
+ resolvedSource = new JerkTokenSource(platform2);
13062
+ } else {
13063
+ resolvedSource = new PiFileTokenSource(platform2);
13064
+ }
13065
+ return resolvedSource;
12236
13066
  }
12237
13067
  function resolveServerName(target) {
12238
13068
  try {
@@ -12260,7 +13090,80 @@ async function resolveLoginTarget(platform2, target) {
12260
13090
  const url = target.startsWith("http") ? target : `https://${target}`;
12261
13091
  return { name: resolved.name, serverUrl: url, oauth: void 0 };
12262
13092
  }
12263
- throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
13093
+ throw new Error(`unknown target '${target}': not a URL and not found in mcp.json`);
13094
+ }
13095
+ function jerkMcpId(name) {
13096
+ if (name.startsWith("mcp:")) return name;
13097
+ if (name.startsWith("url:")) return `mcp:${name}`;
13098
+ return `mcp:${name}`;
13099
+ }
13100
+ async function readStream2(stream) {
13101
+ const reader = stream.getReader();
13102
+ const chunks = [];
13103
+ for (; ; ) {
13104
+ const { done, value } = await reader.read();
13105
+ if (done) break;
13106
+ chunks.push(value);
13107
+ }
13108
+ const total = chunks.reduce((s, c) => s + c.length, 0);
13109
+ const buf = new Uint8Array(total);
13110
+ let off = 0;
13111
+ for (const c of chunks) {
13112
+ buf.set(c, off);
13113
+ off += c.length;
13114
+ }
13115
+ return new TextDecoder().decode(buf);
13116
+ }
13117
+ async function jerkLogin(platform2, target, args2) {
13118
+ const resolved = await resolveLoginTarget(platform2, target);
13119
+ const id = jerkMcpId(resolved.name);
13120
+ const argv = ["jerk", id, "create"];
13121
+ const scopeIdx = args2.indexOf("--scope");
13122
+ if (scopeIdx !== -1 && args2[scopeIdx + 1]) {
13123
+ argv.push("--scope", args2[scopeIdx + 1]);
13124
+ }
13125
+ if (args2.includes("--no-browser")) {
13126
+ argv.push("--no-browser");
13127
+ }
13128
+ const proc = await platform2.spawn({ argv });
13129
+ proc.stdin.close().catch(() => {
13130
+ });
13131
+ const [, errText, result] = await Promise.all([
13132
+ readStream2(proc.stdout),
13133
+ readStream2(proc.stderr),
13134
+ proc.wait()
13135
+ ]);
13136
+ if (result.status === 0) {
13137
+ await platform2.write(1, `Signed in to ${target}
13138
+ `);
13139
+ return 0;
13140
+ }
13141
+ if (errText.trim()) {
13142
+ await platform2.write(2, errText);
13143
+ }
13144
+ if (result.status === JERK_EXIT_SIGN_IN) return 4;
13145
+ return result.status;
13146
+ }
13147
+ async function jerkLogout(platform2, target) {
13148
+ const resolved = await resolveLoginTarget(platform2, target);
13149
+ const id = jerkMcpId(resolved.name);
13150
+ const argv = ["jerk", id, "drop", "--yes"];
13151
+ const proc = await platform2.spawn({ argv });
13152
+ proc.stdin.close().catch(() => {
13153
+ });
13154
+ const [, , result] = await Promise.all([
13155
+ readStream2(proc.stdout),
13156
+ readStream2(proc.stderr),
13157
+ proc.wait()
13158
+ ]);
13159
+ if (result.status === 0) {
13160
+ await platform2.write(1, `Signed out of ${target}
13161
+ `);
13162
+ } else {
13163
+ await platform2.write(1, `No credentials stored for ${target}
13164
+ `);
13165
+ }
13166
+ return 0;
12264
13167
  }
12265
13168
  async function runLogin(platform2, args2, source) {
12266
13169
  if (isKernel(platform2)) {
@@ -12270,13 +13173,22 @@ async function runLogin(platform2, args2, source) {
12270
13173
  );
12271
13174
  return 4;
12272
13175
  }
12273
- const noBrowser = args2.includes("--no-browser");
12274
13176
  const positional = args2.filter((a) => !a.startsWith("-"));
12275
13177
  if (positional.length === 0) {
12276
13178
  await platform2.write(2, "mcpcat: login requires a target\n");
12277
13179
  return 2;
12278
13180
  }
12279
13181
  const target = positional[0];
13182
+ if (source instanceof JerkTokenSource) {
13183
+ try {
13184
+ return await jerkLogin(platform2, target, args2);
13185
+ } catch (err) {
13186
+ await platform2.write(2, `mcpcat: login failed: ${err.message}
13187
+ `);
13188
+ return 4;
13189
+ }
13190
+ }
13191
+ const noBrowser = args2.includes("--no-browser");
12280
13192
  let resolved;
12281
13193
  try {
12282
13194
  resolved = await resolveLoginTarget(platform2, target);
@@ -12358,6 +13270,15 @@ async function runLogout(platform2, args2, source) {
12358
13270
  return 2;
12359
13271
  }
12360
13272
  const target = positional[0];
13273
+ if (source instanceof JerkTokenSource) {
13274
+ try {
13275
+ return await jerkLogout(platform2, target);
13276
+ } catch (err) {
13277
+ await platform2.write(2, `mcpcat: logout failed: ${err.message}
13278
+ `);
13279
+ return 1;
13280
+ }
13281
+ }
12361
13282
  let resolved;
12362
13283
  try {
12363
13284
  resolved = await resolveLoginTarget(platform2, target);
@@ -12730,6 +13651,49 @@ function checkAgentWarnings(name, config) {
12730
13651
  return warnings;
12731
13652
  }
12732
13653
 
13654
+ // src/serve/seal.ts
13655
+ async function sealSecret(platform2, name, value, options) {
13656
+ if (!platform2.seal) {
13657
+ throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
13658
+ }
13659
+ const request = JSON.stringify({
13660
+ v: 1,
13661
+ op: "seal",
13662
+ kind: "mcp",
13663
+ name,
13664
+ value,
13665
+ server: options.server,
13666
+ url: options.url
13667
+ });
13668
+ let responseText;
13669
+ try {
13670
+ responseText = await platform2.seal(request);
13671
+ } catch (err) {
13672
+ const code2 = err.code;
13673
+ if (code2 === "ENOENT" || code2 === "ENOTCONN") {
13674
+ throw Object.assign(new Error("vault device not available"), { sealErrno: "ENOTCONN" });
13675
+ }
13676
+ throw Object.assign(new Error(`vault: ${err.message}`), {
13677
+ sealErrno: code2 ?? "EACCES"
13678
+ });
13679
+ }
13680
+ const response = JSON.parse(responseText);
13681
+ if (response.error) {
13682
+ throw Object.assign(new Error(response.error), { sealErrno: response.errno ?? "EACCES" });
13683
+ }
13684
+ return { placeholder: response.placeholder };
13685
+ }
13686
+ function sealExitCode(errno) {
13687
+ if (errno === "EEXIST") return 2;
13688
+ return 4;
13689
+ }
13690
+ function sealErrorMessage(errno, name) {
13691
+ if (errno === "EEXIST") return `a non-bridge secret named ${name} already exists`;
13692
+ if (errno === "ENOTCONN") return "no vault available; use --bearer '${VAR}' or --no-auth";
13693
+ if (errno === "EACCES") return "vault locked; use --bearer '${VAR}' or --no-auth";
13694
+ return `vault error: ${errno}`;
13695
+ }
13696
+
12733
13697
  // src/cli/config-verbs.ts
12734
13698
  function parseHeader(hdr) {
12735
13699
  const colonIdx = hdr.indexOf(":");
@@ -12751,6 +13715,10 @@ function parseAddFlagAt(flagArgs, i, flags) {
12751
13715
  flags.noProbe = true;
12752
13716
  return { next: i + 1 };
12753
13717
  }
13718
+ if (arg === "--no-auth") {
13719
+ flags.noAuth = true;
13720
+ return { next: i + 1 };
13721
+ }
12754
13722
  if (arg === "--bridge") {
12755
13723
  flags.bridge = true;
12756
13724
  return { next: i + 1 };
@@ -12796,6 +13764,7 @@ function parseAddFlags(args2) {
12796
13764
  local: false,
12797
13765
  noProbe: false,
12798
13766
  bridge: false,
13767
+ noAuth: false,
12799
13768
  headers: [],
12800
13769
  envPairs: [],
12801
13770
  positional: [],
@@ -13158,6 +14127,11 @@ async function runImport(platform2, args2) {
13158
14127
  }
13159
14128
  return 0;
13160
14129
  }
14130
+ function generateBridgeToken() {
14131
+ const bytes = new Uint8Array(32);
14132
+ crypto.getRandomValues(bytes);
14133
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
14134
+ }
13161
14135
  async function runBridge(platform2, args2, version) {
13162
14136
  if (platform2.kind === "host") {
13163
14137
  await platform2.write(
@@ -13184,16 +14158,56 @@ async function runBridge(platform2, args2, version) {
13184
14158
  if (flags.bearer) {
13185
14159
  await platform2.write(
13186
14160
  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"
14161
+ "mcpcat add --bridge: --bearer is not supported for bridge; authentication is automatic via the vault\n"
13188
14162
  );
13189
14163
  return 2;
13190
14164
  }
13191
14165
  const serveOptions = parseServeFlags(["--proxy", "__bridge__", "--http", "0", "--json"]);
13192
14166
  serveOptions.proxyCommand = flags.cmdArgs;
13193
14167
  const path = flags.configFile ?? (flags.local ? projectConfigPath(platform2) : userConfigPath(platform2));
14168
+ if (flags.noAuth) {
14169
+ await platform2.write(
14170
+ 2,
14171
+ "mcpcat add --bridge: warning: running without authentication on loopback\n"
14172
+ );
14173
+ serveOptions.noAuth = true;
14174
+ serveOptions.onListening = async (actualPort) => {
14175
+ const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
14176
+ const httpConfig = { url: bridgeUrl };
14177
+ if (flags.description) httpConfig.description = flags.description;
14178
+ if (flags.exposure) httpConfig.exposure = flags.exposure;
14179
+ const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
14180
+ await platform2.write(
14181
+ 1,
14182
+ `${replaced ? "replaced" : "added"} ${name} in ${path}
14183
+ bridge does not survive a reload; runs in the foreground (start with &)
14184
+ `
14185
+ );
14186
+ };
14187
+ return runServe(platform2, serveOptions, version);
14188
+ }
14189
+ const token = generateBridgeToken();
14190
+ const secretName = `MCP_${name.toUpperCase().replace(/[^A-Z0-9]/g, "_")}_TOKEN`;
14191
+ serveOptions._resolvedBearer = token;
13194
14192
  serveOptions.onListening = async (actualPort) => {
14193
+ if (actualPort === 9222) {
14194
+ await platform2.write(2, "mcpcat add --bridge: port 9222 is reserved; please retry\n");
14195
+ return;
14196
+ }
13195
14197
  const bridgeUrl = `http://${actualPort}.kernel.localhost/mcp`;
13196
- const httpConfig = { url: bridgeUrl };
14198
+ try {
14199
+ await sealSecret(platform2, secretName, token, { server: name, url: bridgeUrl });
14200
+ } catch (err) {
14201
+ const errno = err.sealErrno ?? "EACCES";
14202
+ const code2 = sealExitCode(errno);
14203
+ await platform2.write(2, `mcpcat add --bridge: ${sealErrorMessage(errno, secretName)}
14204
+ `);
14205
+ platform2.exit(code2);
14206
+ }
14207
+ const httpConfig = {
14208
+ url: bridgeUrl,
14209
+ headers: { Authorization: "Bearer ${" + secretName + "}" }
14210
+ };
13197
14211
  if (flags.description) httpConfig.description = flags.description;
13198
14212
  if (flags.exposure) httpConfig.exposure = flags.exposure;
13199
14213
  const { replaced } = await addServer(platform2.fs, path, name, httpConfig);
@@ -13330,8 +14344,18 @@ var CONFIG_VERBS = {
13330
14344
  exposure: true,
13331
14345
  import: true
13332
14346
  };
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"]);
14347
+ var UNIMPLEMENTED_VERBS = /* @__PURE__ */ new Set(["complete", "task"]);
14348
+ var CLIENT_VERBS = /* @__PURE__ */ new Set([
14349
+ "info",
14350
+ "tools",
14351
+ "call",
14352
+ "describe",
14353
+ "serve",
14354
+ "resources",
14355
+ "read",
14356
+ "prompts",
14357
+ "prompt"
14358
+ ]);
13335
14359
  function helpText(version) {
13336
14360
  return `mcpcat ${version} \u2013 a curl for the Model Context Protocol
13337
14361
 
@@ -13370,6 +14394,7 @@ Options:
13370
14394
  -H, --header 'N: V' Extra header
13371
14395
  -d <json> Tool arguments as JSON (or @file, @- for stdin)
13372
14396
  -m, --timeout <s> Request timeout (default 60)
14397
+ --memory <MiB> Code mode memory limit (default 256, max 1024)
13373
14398
  --protocol-version <v> Pin protocol version
13374
14399
  --schema Print raw JSON Schema (describe)
13375
14400
  --ts TypeScript declarations (describe)
@@ -13386,6 +14411,31 @@ Options:
13386
14411
  -h, --help Show this help
13387
14412
  `;
13388
14413
  }
14414
+ var VERBS_WITH_TOOL = /* @__PURE__ */ new Set(["call", "prompt", "describe", "read"]);
14415
+ var VERBS_WITH_TARGET = /* @__PURE__ */ new Set([
14416
+ "info",
14417
+ "tools",
14418
+ "call",
14419
+ "describe",
14420
+ "serve",
14421
+ "grep",
14422
+ "code",
14423
+ "discover",
14424
+ "login",
14425
+ "logout",
14426
+ "resources",
14427
+ "read",
14428
+ "prompts",
14429
+ "prompt"
14430
+ ]);
14431
+ function resolveMaxPositional(positional) {
14432
+ if (positional.length === 0) return 3;
14433
+ const first = positional[0];
14434
+ if (!VERBS.includes(first)) return 2;
14435
+ if (VERBS_WITH_TOOL.has(first)) return 3;
14436
+ if (VERBS_WITH_TARGET.has(first)) return 2;
14437
+ return 2;
14438
+ }
13389
14439
  function parseFlags(args2) {
13390
14440
  const headers = {};
13391
14441
  let dataArg;
@@ -13400,10 +14450,16 @@ function parseFlags(args2) {
13400
14450
  const serverTargets = [];
13401
14451
  let allowShell = false;
13402
14452
  let timeoutMs;
14453
+ let memoryLimitMiB;
13403
14454
  let verbose = false;
13404
14455
  let silent = false;
13405
14456
  const positional = [];
13406
14457
  let trailingCommand;
14458
+ let respondArg;
14459
+ const roots = [];
14460
+ let outputPath;
14461
+ let noValidate = false;
14462
+ const rest = [];
13407
14463
  for (let i = 0; i < args2.length; i++) {
13408
14464
  const arg = args2[i];
13409
14465
  if (arg === "--") {
@@ -13444,8 +14500,41 @@ function parseFlags(args2) {
13444
14500
  silent = true;
13445
14501
  } else if ((arg === "-m" || arg === "--timeout") && args2[i + 1]) {
13446
14502
  timeoutMs = Number(args2[++i]) * 1e3;
14503
+ } else if (arg === "--respond" && args2[i + 1]) {
14504
+ respondArg = args2[++i];
14505
+ } else if (arg === "--root" && args2[i + 1]) {
14506
+ roots.push(args2[++i]);
14507
+ } else if ((arg === "-o" || arg === "--output") && args2[i + 1]) {
14508
+ outputPath = args2[++i];
14509
+ } else if (arg === "--memory" && args2[i + 1]) {
14510
+ memoryLimitMiB = Number(args2[++i]);
14511
+ } else if (arg === "--no-validate") {
14512
+ noValidate = true;
13447
14513
  } else if (!arg.startsWith("-")) {
13448
14514
  positional.push(arg);
14515
+ const maxPositional = resolveMaxPositional(positional);
14516
+ if (positional.length >= maxPositional) {
14517
+ rest.push(...args2.slice(i + 1));
14518
+ break;
14519
+ }
14520
+ }
14521
+ }
14522
+ const toolArgs = [];
14523
+ for (let j = 0; j < rest.length; j++) {
14524
+ const ra = rest[j];
14525
+ if (ra === "-d" && rest[j + 1] !== void 0) {
14526
+ dataArg = rest[++j];
14527
+ } else if (ra === "--json") {
14528
+ jsonOutput = true;
14529
+ } else if (ra === "--no-validate") {
14530
+ noValidate = true;
14531
+ } else if ((ra === "-o" || ra === "--output") && rest[j + 1] !== void 0) {
14532
+ outputPath = rest[++j];
14533
+ } else if (ra === "--help" || ra === "-h") {
14534
+ toolArgs.length = 0;
14535
+ break;
14536
+ } else {
14537
+ toolArgs.push(ra);
13449
14538
  }
13450
14539
  }
13451
14540
  return {
@@ -13462,10 +14551,16 @@ function parseFlags(args2) {
13462
14551
  serverTargets,
13463
14552
  allowShell,
13464
14553
  timeoutMs,
14554
+ memoryLimitMiB,
13465
14555
  verbose,
13466
14556
  silent,
13467
14557
  positional,
13468
- trailingCommand
14558
+ trailingCommand,
14559
+ respondArg,
14560
+ roots,
14561
+ outputPath,
14562
+ noValidate,
14563
+ rest: toolArgs
13469
14564
  };
13470
14565
  }
13471
14566
  function resolveVerb(positional, trailingCommand) {
@@ -13500,6 +14595,7 @@ function parseArgs(args2) {
13500
14595
  const resolved = resolveVerb(flags.positional, flags.trailingCommand);
13501
14596
  return {
13502
14597
  ...resolved,
14598
+ toolArgs: flags.rest,
13503
14599
  headers: flags.headers,
13504
14600
  dataArg: flags.dataArg,
13505
14601
  protocolVersion: flags.protocolVersion,
@@ -13513,8 +14609,13 @@ function parseArgs(args2) {
13513
14609
  serverTargets: flags.serverTargets,
13514
14610
  allowShell: flags.allowShell,
13515
14611
  timeoutMs: flags.timeoutMs,
14612
+ memoryLimitMiB: flags.memoryLimitMiB,
13516
14613
  verbose: flags.verbose,
13517
- silent: flags.silent
14614
+ silent: flags.silent,
14615
+ noValidate: flags.noValidate,
14616
+ respondArg: flags.respondArg,
14617
+ roots: flags.roots,
14618
+ outputPath: flags.outputPath
13518
14619
  };
13519
14620
  }
13520
14621
  function formatToolLine(tool) {
@@ -13637,7 +14738,8 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
13637
14738
  const tools = await conn.listTools();
13638
14739
  const tool = tools.find((t) => t.name === toolName);
13639
14740
  const schema = tool?.inputSchema ?? { type: "object", properties: {} };
13640
- const c = coerceArgsBySchema(toolArgs, schema, { noValidate: opts.noValidate });
14741
+ const skipRequired = opts.noValidate || !!dataArg;
14742
+ const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
13641
14743
  if (!c.ok) {
13642
14744
  await writeErr(platform2, opts.json ?? false, "usage", c.error);
13643
14745
  return EXIT_USAGE;
@@ -13646,8 +14748,11 @@ async function runCall(platform2, conn, toolName, dataArg, toolArgs, opts) {
13646
14748
  }
13647
14749
  return renderCallResult(
13648
14750
  platform2,
13649
- await conn.callTool(toolName, fin, { onProgress: opts.onProgress }),
13650
- { json: opts.json }
14751
+ await conn.callTool(toolName, fin, {
14752
+ onProgress: opts.onProgress,
14753
+ inputResponses: opts.inputResponses
14754
+ }),
14755
+ { json: opts.json, outputPath: opts.outputPath }
13651
14756
  );
13652
14757
  }
13653
14758
  async function runDescribe(platform2, conn, toolName, showSchema, json) {
@@ -13684,19 +14789,168 @@ async function runDescribe(platform2, conn, toolName, showSchema, json) {
13684
14789
  }
13685
14790
  return EXIT_OK;
13686
14791
  }
13687
- async function runVerb(platform2, parsed, conn, onProgress) {
14792
+ async function runResources(platform2, conn, json) {
14793
+ const resources = await conn.listResources();
14794
+ const templates = await conn.listResourceTemplates();
14795
+ if (json) {
14796
+ await renderResourcesJson(platform2, resources, templates);
14797
+ return EXIT_OK;
14798
+ }
14799
+ if (resources.length === 0 && templates.length === 0) {
14800
+ await platform2.write(1, "(no resources)\n");
14801
+ return EXIT_OK;
14802
+ }
14803
+ for (const r of resources) {
14804
+ const desc = r.description ? ` ${r.description}` : "";
14805
+ const mime = r.mimeType ? ` [${r.mimeType}]` : "";
14806
+ await platform2.write(1, `${r.uri} ${r.name}${mime}${desc}
14807
+ `);
14808
+ }
14809
+ if (templates.length > 0) {
14810
+ if (resources.length > 0) await platform2.write(1, "\n");
14811
+ await platform2.write(1, "Templates:\n");
14812
+ for (const t of templates) {
14813
+ const desc = t.description ? ` ${t.description}` : "";
14814
+ await platform2.write(1, ` ${t.uriTemplate} ${t.name}${desc}
14815
+ `);
14816
+ }
14817
+ }
14818
+ return EXIT_OK;
14819
+ }
14820
+ async function runRead(platform2, conn, uri, json, outputPath) {
14821
+ const result = await conn.readResource(uri);
14822
+ if (json) {
14823
+ await renderReadResourceJson(platform2, result);
14824
+ return EXIT_OK;
14825
+ }
14826
+ for (const content of result.contents) {
14827
+ if ("text" in content) {
14828
+ if (outputPath) {
14829
+ await platform2.fs.writeFile(outputPath, content.text);
14830
+ await platform2.write(1, `${outputPath}
14831
+ `);
14832
+ } else {
14833
+ await platform2.write(1, `${content.text}
14834
+ `);
14835
+ }
14836
+ } else if ("blob" in content) {
14837
+ if (outputPath) {
14838
+ const bytes = Uint8Array.from(atob(content.blob), (c) => c.charCodeAt(0));
14839
+ await platform2.fs.writeFile(outputPath, bytes);
14840
+ await platform2.write(1, `${outputPath}
14841
+ `);
14842
+ } else {
14843
+ await platform2.write(1, `${content.blob}
14844
+ `);
14845
+ }
14846
+ }
14847
+ }
14848
+ return EXIT_OK;
14849
+ }
14850
+ async function runPrompts(platform2, conn, json) {
14851
+ const prompts = await conn.listPrompts();
14852
+ if (json) {
14853
+ await renderPromptsJson(platform2, prompts);
14854
+ return EXIT_OK;
14855
+ }
14856
+ if (prompts.length === 0) {
14857
+ await platform2.write(1, "(no prompts)\n");
14858
+ return EXIT_OK;
14859
+ }
14860
+ for (const p of prompts) {
14861
+ const desc = p.description ? ` ${p.description}` : "";
14862
+ await platform2.write(1, `${p.name}${desc}
14863
+ `);
14864
+ }
14865
+ return EXIT_OK;
14866
+ }
14867
+ async function runPrompt(platform2, conn, name, dataArg, toolArgs, json) {
14868
+ let args2 = {};
14869
+ if (dataArg) {
14870
+ args2 = await readDataArg(platform2, dataArg);
14871
+ }
14872
+ const prompts = await conn.listPrompts();
14873
+ const prompt = prompts.find((p) => p.name === name);
14874
+ const schema = {
14875
+ type: "object",
14876
+ properties: {},
14877
+ required: []
14878
+ };
14879
+ if (prompt?.arguments) {
14880
+ const props = {};
14881
+ const req = [];
14882
+ for (const a of prompt.arguments) {
14883
+ props[a.name] = { type: "string", description: a.description };
14884
+ if (a.required) req.push(a.name);
14885
+ }
14886
+ schema.properties = props;
14887
+ schema.required = req;
14888
+ }
14889
+ if (toolArgs.length > 0) {
14890
+ const skipRequired = !!dataArg;
14891
+ const c = coerceArgsBySchema(toolArgs, schema, { noValidate: skipRequired });
14892
+ if (!c.ok) {
14893
+ await writeErr(platform2, json, "usage", c.error);
14894
+ return EXIT_USAGE;
14895
+ }
14896
+ args2 = mergeArgs(args2, c.value);
14897
+ } else if (!dataArg) {
14898
+ const required = Array.isArray(schema.required) ? schema.required : [];
14899
+ for (const r of required) {
14900
+ if (!Object.hasOwn(args2, r)) {
14901
+ await writeErr(platform2, json, "usage", `missing required flag --${r}`);
14902
+ return EXIT_USAGE;
14903
+ }
14904
+ }
14905
+ }
14906
+ const result = await conn.getPrompt(name, args2);
14907
+ if (json) {
14908
+ await renderPromptResultJson(platform2, result);
14909
+ return EXIT_OK;
14910
+ }
14911
+ if (result.description) {
14912
+ await platform2.write(1, `${result.description}
14913
+
14914
+ `);
14915
+ }
14916
+ for (const msg of result.messages) {
14917
+ const text = msg.content?.text ?? (msg.content?.data ? `[${msg.content.mimeType ?? "binary"}]` : "");
14918
+ await platform2.write(1, `[${msg.role}] ${text}
14919
+ `);
14920
+ }
14921
+ return EXIT_OK;
14922
+ }
14923
+ async function runVerb(platform2, parsed, conn, onProgress, respondData) {
13688
14924
  switch (parsed.verb) {
13689
14925
  case "info":
13690
14926
  return runInfo(platform2, conn, parsed.jsonOutput);
13691
14927
  case "tools":
13692
14928
  return runTools(platform2, conn, parsed.jsonOutput);
13693
14929
  case "call":
13694
- return runCall(platform2, conn, parsed.toolName, parsed.dataArg, [], {
14930
+ return runCall(platform2, conn, parsed.toolName, parsed.dataArg, parsed.toolArgs, {
13695
14931
  json: parsed.jsonOutput,
13696
- onProgress
14932
+ noValidate: parsed.noValidate,
14933
+ outputPath: parsed.outputPath,
14934
+ onProgress,
14935
+ inputResponses: respondData
13697
14936
  });
13698
14937
  case "describe":
13699
14938
  return runDescribe(platform2, conn, parsed.toolName, parsed.showSchema, parsed.jsonOutput);
14939
+ case "resources":
14940
+ return runResources(platform2, conn, parsed.jsonOutput);
14941
+ case "read":
14942
+ return runRead(platform2, conn, parsed.toolName, parsed.jsonOutput, parsed.outputPath);
14943
+ case "prompts":
14944
+ return runPrompts(platform2, conn, parsed.jsonOutput);
14945
+ case "prompt":
14946
+ return runPrompt(
14947
+ platform2,
14948
+ conn,
14949
+ parsed.toolName,
14950
+ parsed.dataArg,
14951
+ parsed.toolArgs,
14952
+ parsed.jsonOutput
14953
+ );
13700
14954
  default:
13701
14955
  await platform2.write(2, `mcpcat: unknown command '${parsed.verb}'
13702
14956
  `);
@@ -13834,6 +15088,18 @@ async function resolveTargetNames(platform2, parsed) {
13834
15088
  return config.list().filter((e) => e.config.enabled !== false).map((e) => e.name);
13835
15089
  }
13836
15090
  async function dispatchMultiTarget(platform2, parsed, version) {
15091
+ let activeCleanup;
15092
+ async function killTreeAndExit(exitCode) {
15093
+ if (activeCleanup) {
15094
+ try {
15095
+ await Promise.race([activeCleanup(), new Promise((r) => setTimeout(r, 3e3))]);
15096
+ } catch {
15097
+ }
15098
+ }
15099
+ platform2.exit(exitCode);
15100
+ }
15101
+ platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
15102
+ platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
13837
15103
  const config = await MergedConfigSource.load(platform2);
13838
15104
  if (parsed.verb === "code") {
13839
15105
  let source;
@@ -13865,10 +15131,12 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13865
15131
  parsed,
13866
15132
  version
13867
15133
  );
15134
+ activeCleanup = cleanup;
13868
15135
  try {
13869
15136
  return await runCode(targets, source, {
13870
15137
  json: parsed.jsonOutput,
13871
15138
  timeoutMs: parsed.timeoutMs,
15139
+ memoryLimitMiB: parsed.memoryLimitMiB,
13872
15140
  allowShell: parsed.allowShell,
13873
15141
  platform: platform2
13874
15142
  });
@@ -13894,6 +15162,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13894
15162
  parsed,
13895
15163
  version
13896
15164
  );
15165
+ activeCleanup = cleanup;
13897
15166
  try {
13898
15167
  return await runGrep(platform2, pattern, targets, {
13899
15168
  rank: parsed.rank,
@@ -13912,6 +15181,7 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13912
15181
  parsed,
13913
15182
  version
13914
15183
  );
15184
+ activeCleanup = cleanup;
13915
15185
  try {
13916
15186
  return await runDescribeTs(platform2, targets, parsed.tsBudget);
13917
15187
  } finally {
@@ -13921,6 +15191,18 @@ async function dispatchMultiTarget(platform2, parsed, version) {
13921
15191
  return 2;
13922
15192
  }
13923
15193
  async function run(platform2, version, args2) {
15194
+ try {
15195
+ return await runInner(platform2, version, args2);
15196
+ } catch (err) {
15197
+ if (err.code === "EACCES") {
15198
+ await platform2.write(2, `mcpcat: ${err.message}
15199
+ `);
15200
+ return EXIT_USAGE;
15201
+ }
15202
+ throw err;
15203
+ }
15204
+ }
15205
+ async function runInner(platform2, version, args2) {
13924
15206
  if (args2.includes("--version") || args2.includes("-V")) {
13925
15207
  await platform2.write(1, `${version}
13926
15208
  `);
@@ -13947,11 +15229,11 @@ async function run(platform2, version, args2) {
13947
15229
  const discResult = await dispatchDiscoveryVerb(platform2, parsed, args2);
13948
15230
  if (discResult !== void 0) return discResult;
13949
15231
  if (parsed.verb === "login") {
13950
- const source = createTokenSource(platform2);
15232
+ const source = await createTokenSource(platform2);
13951
15233
  return runLogin(platform2, args2.slice(1), source);
13952
15234
  }
13953
15235
  if (parsed.verb === "logout") {
13954
- const source = createTokenSource(platform2);
15236
+ const source = await createTokenSource(platform2);
13955
15237
  return runLogout(platform2, args2.slice(1), source);
13956
15238
  }
13957
15239
  if (UNIMPLEMENTED_VERBS.has(parsed.verb)) {
@@ -13974,6 +15256,14 @@ async function run(platform2, version, args2) {
13974
15256
  await platform2.write(2, "mcpcat: call requires a tool name\n");
13975
15257
  return 2;
13976
15258
  }
15259
+ if (parsed.verb === "read" && !parsed.toolName) {
15260
+ await platform2.write(2, "mcpcat: read requires a URI\n");
15261
+ return 2;
15262
+ }
15263
+ if (parsed.verb === "prompt" && !parsed.toolName) {
15264
+ await platform2.write(2, "mcpcat: prompt requires a name\n");
15265
+ return 2;
15266
+ }
13977
15267
  if (!parsed.target) {
13978
15268
  await platform2.write(2, `mcpcat: ${parsed.verb} requires a target
13979
15269
  `);
@@ -13984,13 +15274,19 @@ async function run(platform2, version, args2) {
13984
15274
  function expandAndCollect(headers, env) {
13985
15275
  const secrets = [];
13986
15276
  const result = {};
15277
+ const unset = [];
13987
15278
  for (const [k, v] of Object.entries(headers)) {
15279
+ const missing = missingPlaceholders(v, env);
15280
+ if (missing.length > 0) {
15281
+ unset.push(...missing);
15282
+ continue;
15283
+ }
13988
15284
  const e = expandPlaceholders(v, env);
13989
15285
  result[k] = e;
13990
15286
  if (e !== v) secrets.push(e);
13991
15287
  if (v.length > 0) secrets.push(v);
13992
15288
  }
13993
- return { expanded: result, secrets };
15289
+ return { expanded: result, secrets, unset };
13994
15290
  }
13995
15291
  function classifyError(err) {
13996
15292
  if (err instanceof InputRequiredError) {
@@ -14002,6 +15298,9 @@ function classifyError(err) {
14002
15298
  if (err instanceof McpAuthError) {
14003
15299
  return { code: EXIT_AUTH, kind: "auth", msg: err.message };
14004
15300
  }
15301
+ if (err instanceof JerkSignInRequiredError) {
15302
+ return { code: EXIT_AUTH, kind: "auth", msg: err.message };
15303
+ }
14005
15304
  if (err instanceof AuthStepUpError) {
14006
15305
  const scopeHint = err.scope ? ` --scope ${err.scope}` : "";
14007
15306
  return {
@@ -14023,25 +15322,112 @@ function classifyError(err) {
14023
15322
  }
14024
15323
  return { code: EXIT_RPC_ERROR, kind: "rpc_error", msg };
14025
15324
  }
14026
- function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
15325
+ async function resolveAuthProvider(platform2, hasAuthHeader, url, target, config) {
14027
15326
  if (hasAuthHeader || !url) return void 0;
14028
- const source = createTokenSource(platform2);
15327
+ const source = await createTokenSource(platform2);
14029
15328
  const entry = config.entry(target);
14030
15329
  const oauth = entry && "url" in entry.config ? entry.config.oauth : void 0;
14031
15330
  const name = entry ? target : `url:${new URL(url).origin}${new URL(url).pathname}`;
14032
15331
  return createMcpAuthProvider({ name, serverUrl: url, source, oauth, platform: platform2 });
14033
15332
  }
15333
+ function classifyConnectionError(err, target, interrupted) {
15334
+ if (err instanceof McpAuthError || err instanceof AuthStepUpError || err instanceof JerkSignInRequiredError) {
15335
+ return classifyError(err);
15336
+ }
15337
+ if (err.name === "McpOAuthAuthorizationRequiredError" || err.name === "McpAuthRequiredError") {
15338
+ return { code: EXIT_AUTH, kind: "auth", msg: `unauthorized. Run: mcpcat login ${target}` };
15339
+ }
15340
+ if (interrupted) {
15341
+ return { code: EXIT_INTERRUPTED, kind: "interrupted", msg: "interrupted" };
15342
+ }
15343
+ const msg = transportHint(err) ?? `connection failed: ${err.message}`;
15344
+ return { code: EXIT_CONNECTION, kind: "connection", msg };
15345
+ }
15346
+ function buildRoots(rootArgs, isStdio, cwd) {
15347
+ const roots = rootArgs.map((r) => ({
15348
+ uri: r.startsWith("file://") ? r : `file://${r}`
15349
+ }));
15350
+ if (roots.length === 0 && isStdio) {
15351
+ roots.push({ uri: `file://${cwd}` });
15352
+ }
15353
+ return roots.length > 0 ? roots : void 0;
15354
+ }
15355
+ async function loadRespondData(platform2, respondArg, json) {
15356
+ if (!respondArg) return { data: void 0 };
15357
+ try {
15358
+ return { data: await readDataArg(platform2, respondArg) };
15359
+ } catch (err) {
15360
+ await writeErr(
15361
+ platform2,
15362
+ json,
15363
+ "usage",
15364
+ `invalid --respond argument: ${err.message}`
15365
+ );
15366
+ return { exitCode: EXIT_USAGE };
15367
+ }
15368
+ }
15369
+ async function handleInputRequired(platform2, conn, err, respondData, json) {
15370
+ const parsed2 = JSON.parse(err.requestJson);
15371
+ const inputReq = parseInputRequest(parsed2.result ?? parsed2);
15372
+ if (!inputReq) {
15373
+ await platform2.write(2, `${err.requestJson}
15374
+ `);
15375
+ return EXIT_INPUT_REQUIRED;
15376
+ }
15377
+ const isTty = platform2.isatty(0);
15378
+ const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
15379
+ if ("exitCode" in elicitResult) {
15380
+ await platform2.write(2, `${err.requestJson}
15381
+ `);
15382
+ return EXIT_INPUT_REQUIRED;
15383
+ }
15384
+ try {
15385
+ const retryResult = await conn.callTool(parsed2.name, {
15386
+ ...parsed2.arguments,
15387
+ _meta: { inputResponses: elicitResult.answers }
15388
+ });
15389
+ return renderCallResult(platform2, retryResult, { json });
15390
+ } catch (retryErr) {
15391
+ const e2 = classifyError(retryErr);
15392
+ await writeErr(platform2, json, e2.kind, e2.msg);
15393
+ return e2.code;
15394
+ }
15395
+ }
14034
15396
  async function runClientVerb(platform2, version, parsed) {
14035
- const { expanded, secrets } = expandAndCollect(parsed.headers, platform2.env);
15397
+ const { expanded, secrets, unset } = expandAndCollect(parsed.headers, platform2.env);
15398
+ if (unset.length > 0) {
15399
+ await platform2.write(
15400
+ 2,
15401
+ `mcpcat: unset variable${unset.length > 1 ? "s" : ""} in -H: ${unset.join(", ")}
15402
+ `
15403
+ );
15404
+ return EXIT_USAGE;
15405
+ }
14036
15406
  const verbose = createVerboseLogger(platform2, parsed.verbose);
14037
15407
  for (const s of secrets) verbose.secrets.add(s);
14038
15408
  const timeoutMs = parsed.timeoutMs;
14039
15409
  const sigintController = new AbortController();
14040
15410
  let interrupted = false;
15411
+ let termExitCode = EXIT_INTERRUPTED;
15412
+ let activeConn;
15413
+ async function killTreeAndExit(exitCode) {
15414
+ interrupted = true;
15415
+ termExitCode = exitCode;
15416
+ sigintController.abort();
15417
+ if (activeConn) {
15418
+ try {
15419
+ await Promise.race([activeConn.close(), new Promise((r) => setTimeout(r, 3e3))]);
15420
+ } catch {
15421
+ }
15422
+ }
15423
+ platform2.exit(exitCode);
15424
+ }
14041
15425
  platform2.onSignal?.("SIGINT", () => {
14042
15426
  interrupted = true;
14043
15427
  sigintController.abort();
14044
15428
  });
15429
+ platform2.onSignal?.("SIGTERM", () => void killTreeAndExit(143));
15430
+ platform2.onSignal?.("SIGHUP", () => void killTreeAndExit(129));
14045
15431
  const showProgress = !parsed.silent && (parsed.verbose || platform2.isatty(2));
14046
15432
  const onProgress = (progress, total, message) => {
14047
15433
  if (!showProgress) return;
@@ -14060,59 +15446,91 @@ async function runClientVerb(platform2, version, parsed) {
14060
15446
  await writeErr(platform2, parsed.jsonOutput, "usage", err.message);
14061
15447
  return EXIT_USAGE;
14062
15448
  }
14063
- const hasAuthHeader = "Authorization" in expanded;
14064
- const authProvider = resolveAuthProvider(platform2, hasAuthHeader, resolved.url, target, config);
15449
+ const configHeaders = expandHeaders(resolved.headers, platform2.env, (msg) => {
15450
+ platform2.write(2, `mcpcat: warning: ${msg}
15451
+ `).catch(() => {
15452
+ });
15453
+ });
15454
+ const configSecrets = collectConfigSecrets(resolved.headers, platform2.env);
15455
+ for (const s of configSecrets) verbose.secrets.add(s);
15456
+ const allSecrets = /* @__PURE__ */ new Set([...secrets, ...configSecrets]);
15457
+ const redact2 = (text) => {
15458
+ let result = text;
15459
+ for (const secret of allSecrets) {
15460
+ if (secret.length > 0) {
15461
+ while (result.includes(secret)) {
15462
+ result = result.replace(secret, "[REDACTED]");
15463
+ }
15464
+ }
15465
+ }
15466
+ return result;
15467
+ };
15468
+ const mergedHeaders = { ...configHeaders, ...expanded };
15469
+ const hasAuthHeader = "Authorization" in mergedHeaders;
15470
+ const authProvider = await resolveAuthProvider(
15471
+ platform2,
15472
+ hasAuthHeader,
15473
+ resolved.url,
15474
+ target,
15475
+ config
15476
+ );
15477
+ const roots = buildRoots(parsed.roots, !!resolved.command, platform2.cwd);
15478
+ let respondData;
15479
+ if (parsed.respondArg) {
15480
+ const loaded = await loadRespondData(platform2, parsed.respondArg, parsed.jsonOutput);
15481
+ if ("exitCode" in loaded) return loaded.exitCode;
15482
+ respondData = loaded.data;
15483
+ }
14065
15484
  let conn;
14066
15485
  try {
14067
15486
  conn = await connect(platform2, {
14068
15487
  ...resolved,
14069
- headers: { ...resolved.headers, ...expanded },
15488
+ headers: mergedHeaders,
14070
15489
  protocolVersion: parsed.protocolVersion,
14071
15490
  version,
14072
15491
  timeoutMs,
14073
15492
  signal: sigintController.signal,
14074
15493
  verbose,
14075
- authProvider
15494
+ authProvider,
15495
+ roots
14076
15496
  });
15497
+ activeConn = conn;
14077
15498
  } 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(
15499
+ if (isSpawnNotFound(err)) {
15500
+ await writeErrRedacted(
14085
15501
  platform2,
14086
15502
  parsed.jsonOutput,
14087
- "auth",
14088
- `unauthorized. Run: mcpcat login ${target}`
15503
+ "connection",
15504
+ `command not found: ${resolved.command}`,
15505
+ redact2
14089
15506
  );
14090
- return EXIT_AUTH;
15507
+ return EXIT_CONNECTION;
14091
15508
  }
14092
- const msg = transportHint(err) ?? `connection failed: ${err.message}`;
14093
- if (interrupted) {
15509
+ const e = classifyConnectionError(err, target, interrupted);
15510
+ if (e.code === EXIT_INTERRUPTED) {
14094
15511
  await platform2.write(2, "mcpcat: interrupted\n");
14095
- return EXIT_INTERRUPTED;
15512
+ return termExitCode;
14096
15513
  }
14097
- await writeErr(platform2, parsed.jsonOutput, "connection", msg);
14098
- return EXIT_CONNECTION;
15514
+ await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
15515
+ return e.code;
14099
15516
  }
14100
15517
  try {
14101
- const code2 = await runVerb(platform2, parsed, conn, onProgress);
15518
+ const code2 = await runVerb(platform2, parsed, conn, onProgress, respondData);
14102
15519
  if (interrupted) {
14103
15520
  await platform2.write(2, "mcpcat: interrupted\n");
14104
- return EXIT_INTERRUPTED;
15521
+ return termExitCode;
14105
15522
  }
14106
15523
  return code2;
14107
15524
  } catch (err) {
14108
15525
  if (interrupted) {
14109
15526
  await platform2.write(2, "mcpcat: interrupted\n");
14110
- return EXIT_INTERRUPTED;
15527
+ return termExitCode;
15528
+ }
15529
+ if (err instanceof InputRequiredError) {
15530
+ return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
14111
15531
  }
14112
15532
  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);
15533
+ await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
14116
15534
  return e.code;
14117
15535
  } finally {
14118
15536
  try {
@@ -14126,53 +15544,85 @@ async function writeErr(p, json, kind, msg) {
14126
15544
  else await p.write(2, `mcpcat: ${msg}
14127
15545
  `);
14128
15546
  }
15547
+ async function writeErrRedacted(p, json, kind, msg, redact2) {
15548
+ if (json) await renderErrorJson(p, kind, msg, void 0, void 0, redact2);
15549
+ else await p.write(2, `mcpcat: ${redact2(msg)}
15550
+ `);
15551
+ }
15552
+ function collectConfigSecrets(headers, env) {
15553
+ if (!headers) return [];
15554
+ const secrets = [];
15555
+ for (const [, v] of Object.entries(headers)) {
15556
+ const expanded = expandPlaceholders(v, env);
15557
+ if (expanded !== v && expanded.length > 0) secrets.push(expanded);
15558
+ if (v.length > 0 && v !== expanded) secrets.push(v);
15559
+ }
15560
+ return secrets;
15561
+ }
15562
+ function isSpawnNotFound(err) {
15563
+ if (!(err instanceof Error)) return false;
15564
+ const code2 = err.code;
15565
+ if (code2 === "ENOENT" || code2 === "EACCES") return true;
15566
+ const msg = err.message;
15567
+ return msg.includes("ENOENT") || msg.includes("EACCES");
15568
+ }
14129
15569
 
14130
15570
  // src/platform/host.ts
14131
15571
  import { spawn as cpSpawn } from "node:child_process";
14132
15572
  import { constants } from "node:fs";
14133
- import { access, mkdir, readFile as readFile3, rename, rmdir, stat, writeFile } from "node:fs/promises";
15573
+ import { access, mkdir, readFile as readFile3, rename, rmdir, stat, unlink, writeFile } from "node:fs/promises";
14134
15574
  import { createServer as createServer2 } from "node:net";
14135
15575
  import { Readable, Writable } from "node:stream";
14136
15576
  import * as tty from "node:tty";
14137
15577
  var USE_PROCESS_GROUPS2 = process.platform !== "win32";
14138
15578
  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
- });
15579
+ return new Promise((resolve, reject) => {
15580
+ let rejected = false;
15581
+ const child = cpSpawn(options.argv[0], options.argv.slice(1), {
15582
+ env: options.env ?? process.env,
15583
+ cwd: options.cwd,
15584
+ stdio: ["pipe", "pipe", "pipe"],
15585
+ detached: USE_PROCESS_GROUPS2
15586
+ });
15587
+ child.on("error", (err) => {
15588
+ if (!rejected) {
15589
+ rejected = true;
15590
+ reject(err);
15591
+ }
15592
+ });
15593
+ const stdout = Readable.toWeb(child.stdout);
15594
+ const stderr = Readable.toWeb(child.stderr);
15595
+ const stdin = Writable.toWeb(child.stdin);
15596
+ const closePromise = new Promise((res) => {
15597
+ child.on("close", (code2, signal) => {
15598
+ res({
15599
+ status: signal ? 128 + (signalNumber(signal) ?? 0) : code2 ?? 1,
15600
+ signal: signal ?? void 0
14159
15601
  });
14160
15602
  });
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 {
15603
+ });
15604
+ if (child.pid !== void 0) {
15605
+ resolve({
15606
+ stdin,
15607
+ stdout,
15608
+ stderr,
15609
+ wait: () => closePromise,
15610
+ kill(signal) {
15611
+ const sig = signal ?? "SIGTERM";
15612
+ const pid = child.pid;
15613
+ if (USE_PROCESS_GROUPS2 && pid !== void 0) {
15614
+ try {
15615
+ process.kill(-pid, sig);
15616
+ return;
15617
+ } catch {
15618
+ }
15619
+ }
15620
+ try {
15621
+ child.kill(sig);
15622
+ } catch {
15623
+ }
14170
15624
  }
14171
- }
14172
- try {
14173
- child.kill(sig);
14174
- } catch {
14175
- }
15625
+ });
14176
15626
  }
14177
15627
  });
14178
15628
  }
@@ -14193,8 +15643,11 @@ var hostFs = {
14193
15643
  async writeFile(path, data, options) {
14194
15644
  await writeFile(path, data, options?.mode !== void 0 ? { mode: options.mode } : void 0);
14195
15645
  },
14196
- async mkdir(path) {
14197
- await mkdir(path, { recursive: true });
15646
+ async mkdir(path, options) {
15647
+ await mkdir(path, {
15648
+ recursive: true,
15649
+ ...options?.mode !== void 0 ? { mode: options.mode } : {}
15650
+ });
14198
15651
  },
14199
15652
  async rename(from, to) {
14200
15653
  await rename(from, to);
@@ -14216,6 +15669,9 @@ var hostFs = {
14216
15669
  },
14217
15670
  async rmdir(path) {
14218
15671
  await rmdir(path);
15672
+ },
15673
+ async unlink(path) {
15674
+ await unlink(path);
14219
15675
  }
14220
15676
  };
14221
15677
  function createStdinStream() {
@@ -14327,5 +15783,14 @@ function createHostPlatform() {
14327
15783
  // src/host.ts
14328
15784
  var platform = createHostPlatform();
14329
15785
  var args = process.argv.slice(2);
14330
- var code = await run(platform, "0.12.0", args);
15786
+ try {
15787
+ process.stdout.on("error", () => {
15788
+ });
15789
+ process.stderr.on("error", () => {
15790
+ });
15791
+ process.on("SIGPIPE", () => {
15792
+ });
15793
+ } catch {
15794
+ }
15795
+ var code = await run(platform, "0.14.0", args);
14331
15796
  process.exit(code);