@ai-ecoverse/mcpcat 0.17.5 → 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 +1 -3
- package/dist/host/mcpcat.mjs +31 -12
- package/dist/kernel/mcpcat.mjs +31 -12
- package/package.json +3 -3
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
|
|
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
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -11147,6 +11147,23 @@ async function runDiscover(platform2, args2) {
|
|
|
11147
11147
|
return 0;
|
|
11148
11148
|
}
|
|
11149
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
|
+
|
|
11150
11167
|
// src/render/result.ts
|
|
11151
11168
|
function formatBytes(n) {
|
|
11152
11169
|
if (n < 1024) return `${n} bytes`;
|
|
@@ -16542,18 +16559,24 @@ async function loadRespondData(platform2, respondArg, json) {
|
|
|
16542
16559
|
return { exitCode: EXIT_USAGE };
|
|
16543
16560
|
}
|
|
16544
16561
|
}
|
|
16545
|
-
async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
16562
|
+
async function handleInputRequired(platform2, conn, err, respondData, json, redact2) {
|
|
16546
16563
|
const parsed2 = JSON.parse(err.requestJson);
|
|
16564
|
+
const requestOut = sanitizeJson(JSON.stringify(redactDeep(parsed2, redact2), null, 2));
|
|
16547
16565
|
const inputReq = parseInputRequest(parsed2.result ?? parsed2);
|
|
16548
16566
|
if (!inputReq) {
|
|
16549
|
-
await platform2.write(2, `${
|
|
16567
|
+
await platform2.write(2, `${requestOut}
|
|
16550
16568
|
`);
|
|
16551
16569
|
return EXIT_INPUT_REQUIRED;
|
|
16552
16570
|
}
|
|
16553
16571
|
const isTty = platform2.kind === "host" && platform2.isatty(0) && platform2.isatty(2);
|
|
16554
|
-
const elicitResult = await handleElicitation(
|
|
16572
|
+
const elicitResult = await handleElicitation(
|
|
16573
|
+
redactWrites(platform2, redact2),
|
|
16574
|
+
inputReq,
|
|
16575
|
+
isTty,
|
|
16576
|
+
respondData
|
|
16577
|
+
);
|
|
16555
16578
|
if ("exitCode" in elicitResult) {
|
|
16556
|
-
await platform2.write(2, `${
|
|
16579
|
+
await platform2.write(2, `${requestOut}
|
|
16557
16580
|
`);
|
|
16558
16581
|
return EXIT_INPUT_REQUIRED;
|
|
16559
16582
|
}
|
|
@@ -16565,7 +16588,7 @@ async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
|
16565
16588
|
return renderCallResult(platform2, retryResult, { json });
|
|
16566
16589
|
} catch (retryErr) {
|
|
16567
16590
|
const e2 = classifyError(retryErr);
|
|
16568
|
-
await
|
|
16591
|
+
await writeErrRedacted(platform2, json, e2.kind, e2.msg, redact2);
|
|
16569
16592
|
return e2.code;
|
|
16570
16593
|
}
|
|
16571
16594
|
}
|
|
@@ -16641,11 +16664,7 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16641
16664
|
const redact2 = (text) => {
|
|
16642
16665
|
let result = text;
|
|
16643
16666
|
for (const secret of allSecrets) {
|
|
16644
|
-
if (secret.length > 0)
|
|
16645
|
-
while (result.includes(secret)) {
|
|
16646
|
-
result = result.replace(secret, "[REDACTED]");
|
|
16647
|
-
}
|
|
16648
|
-
}
|
|
16667
|
+
if (secret.length > 0) result = result.replaceAll(secret, "[REDACTED]");
|
|
16649
16668
|
}
|
|
16650
16669
|
return result;
|
|
16651
16670
|
};
|
|
@@ -16723,7 +16742,7 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16723
16742
|
return termExitCode;
|
|
16724
16743
|
}
|
|
16725
16744
|
if (err instanceof InputRequiredError) {
|
|
16726
|
-
return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
|
|
16745
|
+
return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput, redact2);
|
|
16727
16746
|
}
|
|
16728
16747
|
const e = classifyError(err);
|
|
16729
16748
|
await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
|
|
@@ -17025,5 +17044,5 @@ try {
|
|
|
17025
17044
|
});
|
|
17026
17045
|
} catch {
|
|
17027
17046
|
}
|
|
17028
|
-
var code = await run(platform, "0.17.
|
|
17047
|
+
var code = await run(platform, "0.17.6", args);
|
|
17029
17048
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -10768,6 +10768,23 @@ async function runDiscover(platform2, args) {
|
|
|
10768
10768
|
return 0;
|
|
10769
10769
|
}
|
|
10770
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
|
+
|
|
10771
10788
|
// src/render/result.ts
|
|
10772
10789
|
function formatBytes(n) {
|
|
10773
10790
|
if (n < 1024) return `${n} bytes`;
|
|
@@ -16163,18 +16180,24 @@ async function loadRespondData(platform2, respondArg, json) {
|
|
|
16163
16180
|
return { exitCode: EXIT_USAGE };
|
|
16164
16181
|
}
|
|
16165
16182
|
}
|
|
16166
|
-
async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
16183
|
+
async function handleInputRequired(platform2, conn, err, respondData, json, redact2) {
|
|
16167
16184
|
const parsed2 = JSON.parse(err.requestJson);
|
|
16185
|
+
const requestOut = sanitizeJson(JSON.stringify(redactDeep(parsed2, redact2), null, 2));
|
|
16168
16186
|
const inputReq = parseInputRequest(parsed2.result ?? parsed2);
|
|
16169
16187
|
if (!inputReq) {
|
|
16170
|
-
await platform2.write(2, `${
|
|
16188
|
+
await platform2.write(2, `${requestOut}
|
|
16171
16189
|
`);
|
|
16172
16190
|
return EXIT_INPUT_REQUIRED;
|
|
16173
16191
|
}
|
|
16174
16192
|
const isTty = platform2.kind === "host" && platform2.isatty(0) && platform2.isatty(2);
|
|
16175
|
-
const elicitResult = await handleElicitation(
|
|
16193
|
+
const elicitResult = await handleElicitation(
|
|
16194
|
+
redactWrites(platform2, redact2),
|
|
16195
|
+
inputReq,
|
|
16196
|
+
isTty,
|
|
16197
|
+
respondData
|
|
16198
|
+
);
|
|
16176
16199
|
if ("exitCode" in elicitResult) {
|
|
16177
|
-
await platform2.write(2, `${
|
|
16200
|
+
await platform2.write(2, `${requestOut}
|
|
16178
16201
|
`);
|
|
16179
16202
|
return EXIT_INPUT_REQUIRED;
|
|
16180
16203
|
}
|
|
@@ -16186,7 +16209,7 @@ async function handleInputRequired(platform2, conn, err, respondData, json) {
|
|
|
16186
16209
|
return renderCallResult(platform2, retryResult, { json });
|
|
16187
16210
|
} catch (retryErr) {
|
|
16188
16211
|
const e2 = classifyError(retryErr);
|
|
16189
|
-
await
|
|
16212
|
+
await writeErrRedacted(platform2, json, e2.kind, e2.msg, redact2);
|
|
16190
16213
|
return e2.code;
|
|
16191
16214
|
}
|
|
16192
16215
|
}
|
|
@@ -16262,11 +16285,7 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16262
16285
|
const redact2 = (text) => {
|
|
16263
16286
|
let result = text;
|
|
16264
16287
|
for (const secret of allSecrets) {
|
|
16265
|
-
if (secret.length > 0)
|
|
16266
|
-
while (result.includes(secret)) {
|
|
16267
|
-
result = result.replace(secret, "[REDACTED]");
|
|
16268
|
-
}
|
|
16269
|
-
}
|
|
16288
|
+
if (secret.length > 0) result = result.replaceAll(secret, "[REDACTED]");
|
|
16270
16289
|
}
|
|
16271
16290
|
return result;
|
|
16272
16291
|
};
|
|
@@ -16344,7 +16363,7 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16344
16363
|
return termExitCode;
|
|
16345
16364
|
}
|
|
16346
16365
|
if (err instanceof InputRequiredError) {
|
|
16347
|
-
return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput);
|
|
16366
|
+
return handleInputRequired(platform2, conn, err, respondData, parsed.jsonOutput, redact2);
|
|
16348
16367
|
}
|
|
16349
16368
|
const e = classifyError(err);
|
|
16350
16369
|
await writeErrRedacted(platform2, parsed.jsonOutput, e.kind, e.msg, redact2);
|
|
@@ -16747,7 +16766,7 @@ async function main(ctx) {
|
|
|
16747
16766
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16748
16767
|
});
|
|
16749
16768
|
const args = ctx.argv.slice(1);
|
|
16750
|
-
return run(platform2, "0.17.
|
|
16769
|
+
return run(platform2, "0.17.6", args);
|
|
16751
16770
|
}
|
|
16752
16771
|
export {
|
|
16753
16772
|
main as default
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.17.
|
|
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,9 +61,9 @@
|
|
|
61
61
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
62
62
|
},
|
|
63
63
|
"devDependencies": {
|
|
64
|
-
"@ai-ecoverse/slicc-bios": "1.
|
|
64
|
+
"@ai-ecoverse/slicc-bios": "1.36.1",
|
|
65
65
|
"@ai-ecoverse/slicc-kernel": "1.141.2",
|
|
66
|
-
"@ai-ecoverse/slicc-shared-web": "1.29.
|
|
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",
|