@ai-ecoverse/mcpcat 0.17.4 → 0.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -26,14 +26,12 @@ mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Slicc
26
26
 
27
27
  ## Releases: certified = latest
28
28
 
29
- Every change that alters what mcpcat ships is certified before it reaches `main`. A pull request that touches `src/`, the build, the dependencies or the release workflows gets mcpcat's cert-review on a pack of its head, and again in the merge queue on the exact tree that lands on `main`; a FAIL blocks the merge. Docs-, test- and CI-only changes skip it as "not behaviour". A draft pull request is not reviewed: its `cert-review` is red with `draft: not reviewed, mark it ready for review`, and marking it ready starts the review (converted_to_draft cancels a running one). Mark ready = reviewed, and merges on green; a pull request held for anything stays a draft. For a PR that needs the coordinator's security read, the order is: the coordinator reads the draft, it is marked ready, the cert runs, then the queue. Each merge to `main` is then released to npm as `latest` in one step by semantic-release, with provenance, so `npm install @ai-ecoverse/mcpcat` and the installer always get certified code. The verdict line is a comment on the pull request and the `cert-review` commit status. It reads `certified …`, `cert-ci: FAIL …` when the reviewer or a check in code found a defect (status `failure`), or `cert-ci: INCOMPLETE …` when the review couldn't decide: the review job didn't succeed or was skipped, no verdict could be read, or the artifact or the runner's own `gh` was missing (status `error`). A fork's change to shipped behaviour also reads INCOMPLETE, since it gets no review. INCOMPLETE never certifies; re-run the failed jobs. A run that ends without `record` posting its status (a crashed plan, record or verdict job) still posts one on the head: `error` with its INCOMPLETE line, `failure` for a FAIL, or `pending` while the coordinator approval is missing. So the required `cert-review` check never waits forever, and a label change reuses only a `success` or `failure` verdict, never an INCOMPLETE one. The reviewer's verdict can add defects but never clear one found in code: `record` re-reads the suites' evidence (`stage.json`, `suites.json`), uploaded before the reviewer runs and bound by the artifact id and digest the review job output. A suite or security row that failed on the host or a pinned kernel, a changed test that fails on the head, an import in the kernel bundle or an unexplained rebuild difference is a FAIL. Missing suites, kernels or rows are INCOMPLETE. The NEGATIVE runs each changed test on the base's code: the subject's tests, helpers and fixtures with the base's `dist`, `scripts/` and `.github/workflows/`, all from the base sha, so a test of the cert tooling runs against main's tooling. The record names both shas. A changed test that also passes on the base is named in the line as `gaps: no-negative <file>`. Every artifact a later job reads (the tarball, the verdict, the suites' evidence) is downloaded by the artifact id its producer output, after `bind.mjs` checks the artifact's name, run and digest against that producer's outputs. Any other artifact from this attempt of the run under `mcpcat-tarball-`, `mcpcat-npm-tarball-` or `cert-review-` makes the line INCOMPLETE. An earlier attempt's leftovers, created before this attempt started, are never read.
29
+ Every change that alters what mcpcat ships is certified before it reaches `main`. A pull request that touches `src/`, the build, the dependencies or the release workflows gets mcpcat's cert-review on a pack of its head, and again in the merge queue on the exact tree that lands on `main`; a FAIL blocks the merge. Docs-, test- and CI-only changes skip it as "not behaviour". A draft pull request is not reviewed: its `cert-review` is red with `draft: not reviewed, mark it ready for review`, and marking it ready starts the review (converted_to_draft cancels a running one). Mark ready = reviewed, and merges on green; a pull request held for anything stays a draft. For a PR that needs the coordinator's security read, the order is: the coordinator reads the draft, it is marked ready, the cert runs, then the queue. Each merge to `main` is then released to npm as `latest` in one step by semantic-release, with provenance, so `npm install @ai-ecoverse/mcpcat` and the installer always get certified code. The verdict line is a comment on the pull request and the `cert-review` commit status. It reads `certified …`, `cert-ci: FAIL …` when the reviewer or a check in code found a defect (status `failure`), or `cert-ci: INCOMPLETE …` when the review couldn't decide: the review job didn't succeed or was skipped, no verdict could be read, or the artifact or the runner's own `gh` was missing (status `error`). A fork's change to shipped behaviour also reads INCOMPLETE, since it gets no review. INCOMPLETE never certifies; re-run the failed jobs. A run that ends without `record` posting its status (a crashed plan, record or verdict job) still posts one on the head: `error` with its INCOMPLETE line, or `failure` for a FAIL. So the required `cert-review` check never waits forever. The reviewer's verdict can add defects but never clear one found in code: `record` re-reads the suites' evidence (`stage.json`, `suites.json`), uploaded before the reviewer runs and bound by the artifact id and digest the review job output. A suite or security row that failed on the host or a pinned kernel, a changed test that fails on the head, an import in the kernel bundle or an unexplained rebuild difference is a FAIL. Missing suites, kernels or rows are INCOMPLETE. The NEGATIVE runs each changed test on the base's code: the subject's tests, helpers and fixtures with the base's `dist`, `scripts/` and `.github/workflows/`, all from the base sha, so a test of the cert tooling runs against main's tooling. The record names both shas. A changed test that also passes on the base is named in the line as `gaps: no-negative <file>`. Every artifact a later job reads (the tarball, the verdict, the suites' evidence) is downloaded by the artifact id its producer output, after `bind.mjs` checks the artifact's name, run and digest against that producer's outputs. Any other artifact from this attempt of the run under `mcpcat-tarball-`, `mcpcat-npm-tarball-` or `cert-review-` makes the line INCOMPLETE. An earlier attempt's leftovers, created before this attempt started, are never read.
30
30
 
31
31
  After each release, `cert-verify.yml` (on the Release run's success, or dispatched from `main` with a `version`) checks again that npm serves the SLSA subject built by `release.yml` on `main` from the tag, and that every pull request merged since the previous tag carries a green `cert-review` status on its head. Its hosted, write-only `notes` job then puts one line into the GitHub release notes, between `<!-- cert-verify -->` markers: `certified @ai-ecoverse/mcpcat@<version> sha256=<sha> (…; pr-certified since v<prev>: …)`, or `verified …` when some pull request lacks a green cert-review. A re-run with the same bytes leaves the notes as they are. Notes that already name another sha for the version, or npm bytes that don't verify, are a supply-chain alert: nothing is written and an issue is filed. Checks that couldn't run read `cert-ci: INCOMPLETE` and write nothing.
32
32
 
33
33
  Silence never passes: every gate in the workflows prints a verdict line, and its caller requires that line, not just an exit code. These are `all-green: PASS|FAIL …`, the cert job's `cert: kernel <v> — N kernel tests passed`, shipped.mjs's `behaviour:`/`not behaviour:`, release verify's `verified …`, and the installer tests' `All installer tests passed.`. A gate that exits 0 without its line fails.
34
34
 
35
- A pull request or merge group that touches the certification itself (the `cert-review` and `ci` workflows, `scripts/cert-review/` and `rows.json`, `scripts/cert.mjs`, `scripts/kernel-test-files.mjs`, and the cert-row, cert-gate, cert-review, cert-runner, cert-self-guard, cert-verify and cert-verdicts tests) fails `cert-review` with "needs coordinator review" until the label `cert-review-approved` is applied after its head arrived. The check is the shared [cert self-guard](https://github.com/ai-ecoverse/slicc-shared-web/tree/f342d7cf558c817ce517bbbbc4cc46df6eac2f97/.github/actions/cert-self-guard), pinned to a full sha in `cert-review.yml`. Only the lane coordinator applies the label, after reading the diff; workers never add or remove it. Any push removes it, so every new head needs a fresh approval; a label change re-runs the checks but reuses the recorded cert-review verdict of an unchanged head. To re-check an approval, run the label event, not the earlier push. The label is procedural: every agent acts as one admin account, so the permission check cannot tell a coordinator from a worker, and a deliberate rewrite of the workflow is not stopped. The `kernel-cert-*` pins in `package.json` are not guarded, because the guard matches whole files and guarding `package.json` would hold every Renovate bump for approval.
36
-
37
35
  ## Quick start
38
36
 
39
37
  ```sh
@@ -8453,22 +8453,20 @@ function mcpNameForMethod(method, params) {
8453
8453
  function makeAbort(timeoutMs, externalSignal) {
8454
8454
  const controller = new AbortController();
8455
8455
  const timer = setTimeout(() => controller.abort(), timeoutMs);
8456
- if (externalSignal) {
8457
- if (externalSignal.aborted) {
8456
+ const onAbort = () => {
8457
+ clearTimeout(timer);
8458
+ controller.abort();
8459
+ };
8460
+ if (externalSignal?.aborted) onAbort();
8461
+ else externalSignal?.addEventListener("abort", onAbort, { once: true });
8462
+ return {
8463
+ controller,
8464
+ stopTimer: () => clearTimeout(timer),
8465
+ detach: () => {
8458
8466
  clearTimeout(timer);
8459
- controller.abort();
8460
- } else {
8461
- externalSignal.addEventListener(
8462
- "abort",
8463
- () => {
8464
- clearTimeout(timer);
8465
- controller.abort();
8466
- },
8467
- { once: true }
8468
- );
8467
+ externalSignal?.removeEventListener("abort", onAbort);
8469
8468
  }
8470
- }
8471
- return { controller, timer };
8469
+ };
8472
8470
  }
8473
8471
  function logHeaders(verbose, dir, entries) {
8474
8472
  for (const [k, v] of entries) {
@@ -8586,7 +8584,7 @@ async function createStatelessConnection(options) {
8586
8584
  verbose.request(method, body);
8587
8585
  logHeaders(verbose, ">", Object.entries(headers));
8588
8586
  }
8589
- const { controller, timer } = makeAbort(timeoutMs, externalSignal);
8587
+ const { controller, stopTimer, detach } = makeAbort(timeoutMs, externalSignal);
8590
8588
  try {
8591
8589
  const res = await fetchImpl(url, {
8592
8590
  method: "POST",
@@ -8598,14 +8596,15 @@ async function createStatelessConnection(options) {
8598
8596
  verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
8599
8597
  logHeaders(verbose, "<", res.headers.entries());
8600
8598
  }
8601
- return { res, token };
8599
+ return { res, token, detach };
8602
8600
  } catch (err) {
8601
+ detach();
8603
8602
  if (controller.signal.aborted && !externalSignal?.aborted) {
8604
8603
  throw new McpTimeoutError2(method, timeoutMs);
8605
8604
  }
8606
8605
  throw err;
8607
8606
  } finally {
8608
- clearTimeout(timer);
8607
+ stopTimer();
8609
8608
  }
8610
8609
  }
8611
8610
  async function parseResponse(res, id) {
@@ -8654,19 +8653,22 @@ async function createStatelessConnection(options) {
8654
8653
  params: { ...cleanParams, _meta: meta }
8655
8654
  });
8656
8655
  for (let attempt = 0; attempt < 2; attempt++) {
8657
- const { res, token } = await doFetch(method, params, body);
8658
- if (res.status >= 400) {
8659
- try {
8660
- await handleErrorStatus(res, method);
8661
- } catch (err) {
8662
- if (err instanceof AuthRetryableError && attempt === 0) {
8663
- await handleAuthError(err, token);
8664
- continue;
8656
+ const { res, token, detach } = await doFetch(method, params, body);
8657
+ let retry;
8658
+ try {
8659
+ if (res.status >= 400) {
8660
+ try {
8661
+ await handleErrorStatus(res, method);
8662
+ } catch (err) {
8663
+ if (!(err instanceof AuthRetryableError && attempt === 0)) throw err;
8664
+ retry = err;
8665
8665
  }
8666
- throw err;
8667
8666
  }
8667
+ if (!retry) return await parseResponse(res, id);
8668
+ } finally {
8669
+ detach();
8668
8670
  }
8669
- return parseResponse(res, id);
8671
+ await handleAuthError(retry, token);
8670
8672
  }
8671
8673
  throw new McpAuthError(401, url);
8672
8674
  }
@@ -8858,6 +8860,19 @@ function createStdioJsonRpc(proc, platform2, options) {
8858
8860
  writer.releaseLock();
8859
8861
  }
8860
8862
  }
8863
+ function raceDeadline(linePromise, method, remaining) {
8864
+ let timer;
8865
+ let onAbort;
8866
+ const stop = new Promise((_, reject) => {
8867
+ timer = setTimeout(() => reject(new McpTimeoutError2(method, timeoutMs)), remaining);
8868
+ onAbort = () => reject(new McpTimeoutError2(method, timeoutMs));
8869
+ externalSignal?.addEventListener("abort", onAbort, { once: true });
8870
+ });
8871
+ return Promise.race([linePromise, stop]).finally(() => {
8872
+ clearTimeout(timer);
8873
+ if (onAbort) externalSignal?.removeEventListener("abort", onAbort);
8874
+ });
8875
+ }
8861
8876
  async function request(method, params) {
8862
8877
  const id = nextId2++;
8863
8878
  const meta = {
@@ -8881,11 +8896,7 @@ function createStdioJsonRpc(proc, platform2, options) {
8881
8896
  while (Date.now() < deadline) {
8882
8897
  const remaining = deadline - Date.now();
8883
8898
  if (externalSignal?.aborted) throw new McpTimeoutError2(method, timeoutMs);
8884
- const linePromise = readLine2();
8885
- const timeoutPromise = new Promise(
8886
- (_, reject) => setTimeout(() => reject(new McpTimeoutError2(method, timeoutMs)), remaining)
8887
- );
8888
- const line = await Promise.race([linePromise, timeoutPromise]);
8899
+ const line = await raceDeadline(readLine2(), method, remaining);
8889
8900
  const parsed = JSON.parse(line);
8890
8901
  if (verbose?.enabled) verbose.response(line);
8891
8902
  if (parsed.id === id) {
@@ -11136,6 +11147,23 @@ async function runDiscover(platform2, args2) {
11136
11147
  return 0;
11137
11148
  }
11138
11149
 
11150
+ // src/render/redact.ts
11151
+ function redactDeep(value, redact2) {
11152
+ if (typeof value === "string") return redact2(value);
11153
+ if (Array.isArray(value)) return value.map((v) => redactDeep(v, redact2));
11154
+ if (value && typeof value === "object") {
11155
+ return Object.fromEntries(
11156
+ Object.entries(value).map(([k, v]) => [redact2(k), redactDeep(v, redact2)])
11157
+ );
11158
+ }
11159
+ return value;
11160
+ }
11161
+ function redactWrites(platform2, redact2) {
11162
+ return Object.assign(Object.create(platform2), {
11163
+ write: (fd, data) => platform2.write(fd, typeof data === "string" ? redact2(data) : data)
11164
+ });
11165
+ }
11166
+
11139
11167
  // src/render/result.ts
11140
11168
  function formatBytes(n) {
11141
11169
  if (n < 1024) return `${n} bytes`;
@@ -16531,18 +16559,24 @@ async function loadRespondData(platform2, respondArg, json) {
16531
16559
  return { exitCode: EXIT_USAGE };
16532
16560
  }
16533
16561
  }
16534
- async function handleInputRequired(platform2, conn, err, respondData, json) {
16562
+ async function handleInputRequired(platform2, conn, err, respondData, json, redact2) {
16535
16563
  const parsed2 = JSON.parse(err.requestJson);
16564
+ const requestOut = sanitizeJson(JSON.stringify(redactDeep(parsed2, redact2), null, 2));
16536
16565
  const inputReq = parseInputRequest(parsed2.result ?? parsed2);
16537
16566
  if (!inputReq) {
16538
- await platform2.write(2, `${sanitizeJson(err.requestJson)}
16567
+ await platform2.write(2, `${requestOut}
16539
16568
  `);
16540
16569
  return EXIT_INPUT_REQUIRED;
16541
16570
  }
16542
16571
  const isTty = platform2.kind === "host" && platform2.isatty(0) && platform2.isatty(2);
16543
- const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
16572
+ const elicitResult = await handleElicitation(
16573
+ redactWrites(platform2, redact2),
16574
+ inputReq,
16575
+ isTty,
16576
+ respondData
16577
+ );
16544
16578
  if ("exitCode" in elicitResult) {
16545
- await platform2.write(2, `${sanitizeJson(err.requestJson)}
16579
+ await platform2.write(2, `${requestOut}
16546
16580
  `);
16547
16581
  return EXIT_INPUT_REQUIRED;
16548
16582
  }
@@ -16554,7 +16588,7 @@ async function handleInputRequired(platform2, conn, err, respondData, json) {
16554
16588
  return renderCallResult(platform2, retryResult, { json });
16555
16589
  } catch (retryErr) {
16556
16590
  const e2 = classifyError(retryErr);
16557
- await writeErr(platform2, json, e2.kind, e2.msg);
16591
+ await writeErrRedacted(platform2, json, e2.kind, e2.msg, redact2);
16558
16592
  return e2.code;
16559
16593
  }
16560
16594
  }
@@ -16630,11 +16664,7 @@ async function runClientVerb(platform2, version, parsed) {
16630
16664
  const redact2 = (text) => {
16631
16665
  let result = text;
16632
16666
  for (const secret of allSecrets) {
16633
- if (secret.length > 0) {
16634
- while (result.includes(secret)) {
16635
- result = result.replace(secret, "[REDACTED]");
16636
- }
16637
- }
16667
+ if (secret.length > 0) result = result.replaceAll(secret, "[REDACTED]");
16638
16668
  }
16639
16669
  return result;
16640
16670
  };
@@ -16712,7 +16742,7 @@ async function runClientVerb(platform2, version, parsed) {
16712
16742
  return termExitCode;
16713
16743
  }
16714
16744
  if (err instanceof InputRequiredError) {
16715
- return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
16745
+ return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput, redact2);
16716
16746
  }
16717
16747
  const e = classifyError(err);
16718
16748
  await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
@@ -17014,5 +17044,5 @@ try {
17014
17044
  });
17015
17045
  } catch {
17016
17046
  }
17017
- var code = await run(platform, "0.17.4", args);
17047
+ var code = await run(platform, "0.17.6", args);
17018
17048
  process.exit(code);
@@ -8074,22 +8074,20 @@ function mcpNameForMethod(method, params) {
8074
8074
  function makeAbort(timeoutMs, externalSignal) {
8075
8075
  const controller = new AbortController();
8076
8076
  const timer = setTimeout(() => controller.abort(), timeoutMs);
8077
- if (externalSignal) {
8078
- if (externalSignal.aborted) {
8077
+ const onAbort = () => {
8078
+ clearTimeout(timer);
8079
+ controller.abort();
8080
+ };
8081
+ if (externalSignal?.aborted) onAbort();
8082
+ else externalSignal?.addEventListener("abort", onAbort, { once: true });
8083
+ return {
8084
+ controller,
8085
+ stopTimer: () => clearTimeout(timer),
8086
+ detach: () => {
8079
8087
  clearTimeout(timer);
8080
- controller.abort();
8081
- } else {
8082
- externalSignal.addEventListener(
8083
- "abort",
8084
- () => {
8085
- clearTimeout(timer);
8086
- controller.abort();
8087
- },
8088
- { once: true }
8089
- );
8088
+ externalSignal?.removeEventListener("abort", onAbort);
8090
8089
  }
8091
- }
8092
- return { controller, timer };
8090
+ };
8093
8091
  }
8094
8092
  function logHeaders(verbose, dir, entries) {
8095
8093
  for (const [k, v] of entries) {
@@ -8207,7 +8205,7 @@ async function createStatelessConnection(options) {
8207
8205
  verbose.request(method, body);
8208
8206
  logHeaders(verbose, ">", Object.entries(headers));
8209
8207
  }
8210
- const { controller, timer } = makeAbort(timeoutMs, externalSignal);
8208
+ const { controller, stopTimer, detach } = makeAbort(timeoutMs, externalSignal);
8211
8209
  try {
8212
8210
  const res = await fetchImpl(url, {
8213
8211
  method: "POST",
@@ -8219,14 +8217,15 @@ async function createStatelessConnection(options) {
8219
8217
  verbose.http("<", `HTTP/1.1 ${res.status} ${res.statusText}`);
8220
8218
  logHeaders(verbose, "<", res.headers.entries());
8221
8219
  }
8222
- return { res, token };
8220
+ return { res, token, detach };
8223
8221
  } catch (err) {
8222
+ detach();
8224
8223
  if (controller.signal.aborted && !externalSignal?.aborted) {
8225
8224
  throw new McpTimeoutError2(method, timeoutMs);
8226
8225
  }
8227
8226
  throw err;
8228
8227
  } finally {
8229
- clearTimeout(timer);
8228
+ stopTimer();
8230
8229
  }
8231
8230
  }
8232
8231
  async function parseResponse(res, id) {
@@ -8275,19 +8274,22 @@ async function createStatelessConnection(options) {
8275
8274
  params: { ...cleanParams, _meta: meta }
8276
8275
  });
8277
8276
  for (let attempt = 0; attempt < 2; attempt++) {
8278
- const { res, token } = await doFetch(method, params, body);
8279
- if (res.status >= 400) {
8280
- try {
8281
- await handleErrorStatus(res, method);
8282
- } catch (err) {
8283
- if (err instanceof AuthRetryableError && attempt === 0) {
8284
- await handleAuthError(err, token);
8285
- continue;
8277
+ const { res, token, detach } = await doFetch(method, params, body);
8278
+ let retry;
8279
+ try {
8280
+ if (res.status >= 400) {
8281
+ try {
8282
+ await handleErrorStatus(res, method);
8283
+ } catch (err) {
8284
+ if (!(err instanceof AuthRetryableError && attempt === 0)) throw err;
8285
+ retry = err;
8286
8286
  }
8287
- throw err;
8288
8287
  }
8288
+ if (!retry) return await parseResponse(res, id);
8289
+ } finally {
8290
+ detach();
8289
8291
  }
8290
- return parseResponse(res, id);
8292
+ await handleAuthError(retry, token);
8291
8293
  }
8292
8294
  throw new McpAuthError(401, url);
8293
8295
  }
@@ -8479,6 +8481,19 @@ function createStdioJsonRpc(proc, platform2, options) {
8479
8481
  writer.releaseLock();
8480
8482
  }
8481
8483
  }
8484
+ function raceDeadline(linePromise, method, remaining) {
8485
+ let timer;
8486
+ let onAbort;
8487
+ const stop = new Promise((_, reject) => {
8488
+ timer = setTimeout(() => reject(new McpTimeoutError2(method, timeoutMs)), remaining);
8489
+ onAbort = () => reject(new McpTimeoutError2(method, timeoutMs));
8490
+ externalSignal?.addEventListener("abort", onAbort, { once: true });
8491
+ });
8492
+ return Promise.race([linePromise, stop]).finally(() => {
8493
+ clearTimeout(timer);
8494
+ if (onAbort) externalSignal?.removeEventListener("abort", onAbort);
8495
+ });
8496
+ }
8482
8497
  async function request(method, params) {
8483
8498
  const id = nextId2++;
8484
8499
  const meta = {
@@ -8502,11 +8517,7 @@ function createStdioJsonRpc(proc, platform2, options) {
8502
8517
  while (Date.now() < deadline) {
8503
8518
  const remaining = deadline - Date.now();
8504
8519
  if (externalSignal?.aborted) throw new McpTimeoutError2(method, timeoutMs);
8505
- const linePromise = readLine2();
8506
- const timeoutPromise = new Promise(
8507
- (_, reject) => setTimeout(() => reject(new McpTimeoutError2(method, timeoutMs)), remaining)
8508
- );
8509
- const line = await Promise.race([linePromise, timeoutPromise]);
8520
+ const line = await raceDeadline(readLine2(), method, remaining);
8510
8521
  const parsed = JSON.parse(line);
8511
8522
  if (verbose?.enabled) verbose.response(line);
8512
8523
  if (parsed.id === id) {
@@ -10757,6 +10768,23 @@ async function runDiscover(platform2, args) {
10757
10768
  return 0;
10758
10769
  }
10759
10770
 
10771
+ // src/render/redact.ts
10772
+ function redactDeep(value, redact2) {
10773
+ if (typeof value === "string") return redact2(value);
10774
+ if (Array.isArray(value)) return value.map((v) => redactDeep(v, redact2));
10775
+ if (value && typeof value === "object") {
10776
+ return Object.fromEntries(
10777
+ Object.entries(value).map(([k, v]) => [redact2(k), redactDeep(v, redact2)])
10778
+ );
10779
+ }
10780
+ return value;
10781
+ }
10782
+ function redactWrites(platform2, redact2) {
10783
+ return Object.assign(Object.create(platform2), {
10784
+ write: (fd, data) => platform2.write(fd, typeof data === "string" ? redact2(data) : data)
10785
+ });
10786
+ }
10787
+
10760
10788
  // src/render/result.ts
10761
10789
  function formatBytes(n) {
10762
10790
  if (n < 1024) return `${n} bytes`;
@@ -16152,18 +16180,24 @@ async function loadRespondData(platform2, respondArg, json) {
16152
16180
  return { exitCode: EXIT_USAGE };
16153
16181
  }
16154
16182
  }
16155
- async function handleInputRequired(platform2, conn, err, respondData, json) {
16183
+ async function handleInputRequired(platform2, conn, err, respondData, json, redact2) {
16156
16184
  const parsed2 = JSON.parse(err.requestJson);
16185
+ const requestOut = sanitizeJson(JSON.stringify(redactDeep(parsed2, redact2), null, 2));
16157
16186
  const inputReq = parseInputRequest(parsed2.result ?? parsed2);
16158
16187
  if (!inputReq) {
16159
- await platform2.write(2, `${sanitizeJson(err.requestJson)}
16188
+ await platform2.write(2, `${requestOut}
16160
16189
  `);
16161
16190
  return EXIT_INPUT_REQUIRED;
16162
16191
  }
16163
16192
  const isTty = platform2.kind === "host" && platform2.isatty(0) && platform2.isatty(2);
16164
- const elicitResult = await handleElicitation(platform2, inputReq, isTty, respondData);
16193
+ const elicitResult = await handleElicitation(
16194
+ redactWrites(platform2, redact2),
16195
+ inputReq,
16196
+ isTty,
16197
+ respondData
16198
+ );
16165
16199
  if ("exitCode" in elicitResult) {
16166
- await platform2.write(2, `${sanitizeJson(err.requestJson)}
16200
+ await platform2.write(2, `${requestOut}
16167
16201
  `);
16168
16202
  return EXIT_INPUT_REQUIRED;
16169
16203
  }
@@ -16175,7 +16209,7 @@ async function handleInputRequired(platform2, conn, err, respondData, json) {
16175
16209
  return renderCallResult(platform2, retryResult, { json });
16176
16210
  } catch (retryErr) {
16177
16211
  const e2 = classifyError(retryErr);
16178
- await writeErr(platform2, json, e2.kind, e2.msg);
16212
+ await writeErrRedacted(platform2, json, e2.kind, e2.msg, redact2);
16179
16213
  return e2.code;
16180
16214
  }
16181
16215
  }
@@ -16251,11 +16285,7 @@ async function runClientVerb(platform2, version, parsed) {
16251
16285
  const redact2 = (text) => {
16252
16286
  let result = text;
16253
16287
  for (const secret of allSecrets) {
16254
- if (secret.length > 0) {
16255
- while (result.includes(secret)) {
16256
- result = result.replace(secret, "[REDACTED]");
16257
- }
16258
- }
16288
+ if (secret.length > 0) result = result.replaceAll(secret, "[REDACTED]");
16259
16289
  }
16260
16290
  return result;
16261
16291
  };
@@ -16333,7 +16363,7 @@ async function runClientVerb(platform2, version, parsed) {
16333
16363
  return termExitCode;
16334
16364
  }
16335
16365
  if (err instanceof InputRequiredError) {
16336
- return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
16366
+ return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput, redact2);
16337
16367
  }
16338
16368
  const e = classifyError(err);
16339
16369
  await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
@@ -16736,7 +16766,7 @@ async function main(ctx) {
16736
16766
  packageDir: "/node_modules/@ai-ecoverse/mcpcat"
16737
16767
  });
16738
16768
  const args = ctx.argv.slice(1);
16739
- return run(platform2, "0.17.4", args);
16769
+ return run(platform2, "0.17.6", args);
16740
16770
  }
16741
16771
  export {
16742
16772
  main as default
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ai-ecoverse/mcpcat",
3
- "version": "0.17.4",
3
+ "version": "0.17.6",
4
4
  "description": "A curl for the Model Context Protocol",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -61,15 +61,15 @@
61
61
  "trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
62
62
  },
63
63
  "devDependencies": {
64
- "@ai-ecoverse/slicc-bios": "1.33.1",
65
- "@ai-ecoverse/slicc-kernel": "1.140.0",
66
- "@ai-ecoverse/slicc-shared-web": "1.29.2",
64
+ "@ai-ecoverse/slicc-bios": "1.36.1",
65
+ "@ai-ecoverse/slicc-kernel": "1.141.2",
66
+ "@ai-ecoverse/slicc-shared-web": "1.29.8",
67
67
  "@biomejs/biome": "2.5.15",
68
68
  "@earendil-works/pi-codemode": "1.2.0",
69
69
  "@earendil-works/pi-mcp": "1.2.0",
70
70
  "@types/node": "26.6.5",
71
71
  "esbuild": "0.28.2",
72
- "kernel-cert-new": "npm:@ai-ecoverse/slicc-kernel@1.140.0",
72
+ "kernel-cert-new": "npm:@ai-ecoverse/slicc-kernel@1.141.2",
73
73
  "lefthook": "2.2.2",
74
74
  "monocart-coverage-reports": "2.13.2",
75
75
  "proper-lockfile": "4.1.2",