@ai-ecoverse/mcpcat 0.11.0 → 0.13.0

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