@ai-ecoverse/mcpcat 0.15.9 → 0.17.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.
- package/README.md +14 -0
- package/dist/host/mcpcat.mjs +64 -39
- package/dist/kernel/mcpcat.mjs +66 -40
- package/dist/skills/mcpcat/SKILL.md +72 -0
- package/package.json +10 -5
package/README.md
CHANGED
|
@@ -24,6 +24,16 @@ npm install -g @ai-ecoverse/mcpcat
|
|
|
24
24
|
|
|
25
25
|
mcpcat runs natively on macOS, Linux and Windows (Node ≥ 24), and inside Sliccy Seven as a kernel program, with the same flags, the same output and the same config file in both places. Orphan-free shutdown of stdio server trees in the kernel needs slicc-kernel ≥ 1.74.0; on older kernels only the direct child is signalled. A SIGKILLed mcpcat runs no handler. On slicc-kernel ≥ 1.88.0, mcpcat spawns stdio servers and served commands with a parent death signal (like Linux `PR_SET_PDEATHSIG`), so the kernel SIGKILLs each server's process group when mcpcat dies. On older kernels, and on the host, where there is no portable equivalent, servers that exit on stdin EOF (most MCP servers) still stop, but servers that ignore it keep running.
|
|
26
26
|
|
|
27
|
+
## Releases: certified = latest
|
|
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". 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.
|
|
30
|
+
|
|
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
|
+
|
|
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
|
+
|
|
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
|
+
|
|
27
37
|
## Quick start
|
|
28
38
|
|
|
29
39
|
```sh
|
|
@@ -32,6 +42,10 @@ mcpcat weather get-forecast --lat 51.5 --lon -0.12
|
|
|
32
42
|
mcpcat serve --http 8808 ./scripts/jira.jsh
|
|
33
43
|
```
|
|
34
44
|
|
|
45
|
+
## Agent skill
|
|
46
|
+
|
|
47
|
+
The package ships an [Agent Skill](https://agentskills.io/specification) that teaches an agent mcpcat in one file: `dist/skills/mcpcat/SKILL.md`, built from [`src/skill/SKILL.txt`](src/skill/SKILL.txt) (the repo keeps no Markdown besides README.md and AGENTS.md). It covers targets, listing and calling tools, `mcp.json`, exit codes, and the secrets rule: `${NAME}` placeholders in single quotes, never a value in a command, a file or the conversation, and never a token in `mcp.json`. `mcpcat help --skill` prints the copy that matches the installed version. `package.json` declares it as a pi package skill (`"pi": { "skills": ["dist/skills"] }`). In seven, bios links it into `/usr/share/skills` for the certified catalog install. It is shipped behaviour under `src/`, so a change to it gets a cert review.
|
|
48
|
+
|
|
35
49
|
## License
|
|
36
50
|
|
|
37
51
|
Apache-2.0
|
package/dist/host/mcpcat.mjs
CHANGED
|
@@ -9113,6 +9113,12 @@ async function connect(platform2, options) {
|
|
|
9113
9113
|
}
|
|
9114
9114
|
}
|
|
9115
9115
|
|
|
9116
|
+
// src/render/sanitize.ts
|
|
9117
|
+
var CONTROL_RE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
9118
|
+
function sanitizeTerminal(text) {
|
|
9119
|
+
return text.replace(CONTROL_RE, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`);
|
|
9120
|
+
}
|
|
9121
|
+
|
|
9116
9122
|
// src/client/elicit.ts
|
|
9117
9123
|
function readLine(platform2) {
|
|
9118
9124
|
return new Promise((resolve, reject) => {
|
|
@@ -9164,7 +9170,7 @@ async function promptForSchema(platform2, schema) {
|
|
|
9164
9170
|
const desc = prop.description ? ` - ${prop.description}` : "";
|
|
9165
9171
|
const def = prop.default !== void 0 ? ` [${String(prop.default)}]` : "";
|
|
9166
9172
|
const choices = prop.enum ? ` (${prop.enum.join("|")})` : "";
|
|
9167
|
-
await platform2.write(2, `${key}${req}${desc}${choices}${def}: `);
|
|
9173
|
+
await platform2.write(2, sanitizeTerminal(`${key}${req}${desc}${choices}${def}: `));
|
|
9168
9174
|
const line = await readLine(platform2);
|
|
9169
9175
|
const value = line.trim();
|
|
9170
9176
|
if (value === "" && prop.default !== void 0) {
|
|
@@ -9186,8 +9192,8 @@ function parseInputRequest(result) {
|
|
|
9186
9192
|
async function handleElicitation(platform2, inputRequest, isTty, respondData) {
|
|
9187
9193
|
if (inputRequest.url) {
|
|
9188
9194
|
if (isTty) {
|
|
9189
|
-
await platform2.write(2, `Open this URL to continue: ${inputRequest.url}
|
|
9190
|
-
`);
|
|
9195
|
+
await platform2.write(2, sanitizeTerminal(`Open this URL to continue: ${inputRequest.url}
|
|
9196
|
+
`));
|
|
9191
9197
|
await platform2.write(2, "Press Enter when done...");
|
|
9192
9198
|
await readLine(platform2);
|
|
9193
9199
|
return { answers: {} };
|
|
@@ -9202,8 +9208,8 @@ async function handleElicitation(platform2, inputRequest, isTty, respondData) {
|
|
|
9202
9208
|
}
|
|
9203
9209
|
const schema = inputRequest.schema ?? { type: "object", properties: {} };
|
|
9204
9210
|
if (inputRequest.message) {
|
|
9205
|
-
await platform2.write(2, `${inputRequest.message}
|
|
9206
|
-
`);
|
|
9211
|
+
await platform2.write(2, sanitizeTerminal(`${inputRequest.message}
|
|
9212
|
+
`));
|
|
9207
9213
|
}
|
|
9208
9214
|
const answers = await promptForSchema(platform2, schema);
|
|
9209
9215
|
return { answers };
|
|
@@ -9724,8 +9730,11 @@ async function formatOutput(platform2, result, json) {
|
|
|
9724
9730
|
`);
|
|
9725
9731
|
}
|
|
9726
9732
|
if (!result.ok) {
|
|
9727
|
-
await platform2.write(
|
|
9728
|
-
|
|
9733
|
+
await platform2.write(
|
|
9734
|
+
2,
|
|
9735
|
+
sanitizeTerminal(`mcpcat: script failed: ${result.error.message}
|
|
9736
|
+
`)
|
|
9737
|
+
);
|
|
9729
9738
|
}
|
|
9730
9739
|
}
|
|
9731
9740
|
const isTimeout = !result.ok && result.error && result.error.kind === "timeout";
|
|
@@ -11187,8 +11196,8 @@ async function renderCallResult(platform2, result, options) {
|
|
|
11187
11196
|
}
|
|
11188
11197
|
const text = renderCallResultText(result, options);
|
|
11189
11198
|
if (result.isError) {
|
|
11190
|
-
await platform2.write(2, r(`${text || "(tool reported error)"}
|
|
11191
|
-
`));
|
|
11199
|
+
await platform2.write(2, sanitizeTerminal(r(`${text || "(tool reported error)"}
|
|
11200
|
+
`)));
|
|
11192
11201
|
return 1;
|
|
11193
11202
|
}
|
|
11194
11203
|
if (text) {
|
|
@@ -11239,17 +11248,17 @@ function sanitizeName(raw) {
|
|
|
11239
11248
|
const cleaned = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
11240
11249
|
return cleaned || "cli";
|
|
11241
11250
|
}
|
|
11242
|
-
function cliPrefix(
|
|
11251
|
+
function cliPrefix(help2, filePath, explicit) {
|
|
11243
11252
|
if (explicit) return sanitizeName(explicit);
|
|
11244
|
-
const fromUsage = usageProgramName(
|
|
11253
|
+
const fromUsage = usageProgramName(help2);
|
|
11245
11254
|
if (fromUsage) return sanitizeName(fromUsage);
|
|
11246
11255
|
const base = filePath.split("/").pop() ?? filePath;
|
|
11247
11256
|
return sanitizeName(base.replace(/\.jsh$/i, ""));
|
|
11248
11257
|
}
|
|
11249
|
-
function parseHelp(
|
|
11250
|
-
const block = commandsFromBlock(
|
|
11251
|
-
const program = usageProgramName(
|
|
11252
|
-
const commands = mergeCommandLists(block.commands, usageCommands(
|
|
11258
|
+
function parseHelp(help2) {
|
|
11259
|
+
const block = commandsFromBlock(help2);
|
|
11260
|
+
const program = usageProgramName(help2);
|
|
11261
|
+
const commands = mergeCommandLists(block.commands, usageCommands(help2, program));
|
|
11253
11262
|
return {
|
|
11254
11263
|
...program ? { program } : {},
|
|
11255
11264
|
commands: applyScopedFlags(commands, block.scoped),
|
|
@@ -11393,12 +11402,12 @@ function encodeFlag(flag, value) {
|
|
|
11393
11402
|
}
|
|
11394
11403
|
return { argv: [`--${flag.name}`, String(value)] };
|
|
11395
11404
|
}
|
|
11396
|
-
function commandsFromBlock(
|
|
11405
|
+
function commandsFromBlock(help2) {
|
|
11397
11406
|
let section = "none";
|
|
11398
11407
|
const commands = [];
|
|
11399
11408
|
const globalFlags = [];
|
|
11400
11409
|
const scoped = [];
|
|
11401
|
-
for (const rawLine of
|
|
11410
|
+
for (const rawLine of help2.split("\n")) {
|
|
11402
11411
|
const line = rawLine.replace(/\t/g, " ");
|
|
11403
11412
|
const header = classifyHeader(line);
|
|
11404
11413
|
if (header) {
|
|
@@ -11446,8 +11455,8 @@ function parseCommandLine(line) {
|
|
|
11446
11455
|
flags: spec.flags
|
|
11447
11456
|
};
|
|
11448
11457
|
}
|
|
11449
|
-
function usageProgramName(
|
|
11450
|
-
for (const raw of
|
|
11458
|
+
function usageProgramName(help2) {
|
|
11459
|
+
for (const raw of help2.split("\n")) {
|
|
11451
11460
|
if (!/^[\s>*]*usage:/i.test(raw)) continue;
|
|
11452
11461
|
const tokens = tokenize2(raw);
|
|
11453
11462
|
const name = tokens[0];
|
|
@@ -11455,9 +11464,9 @@ function usageProgramName(help) {
|
|
|
11455
11464
|
}
|
|
11456
11465
|
return void 0;
|
|
11457
11466
|
}
|
|
11458
|
-
function usageCommands(
|
|
11467
|
+
function usageCommands(help2, program) {
|
|
11459
11468
|
const commands = [];
|
|
11460
|
-
for (const raw of
|
|
11469
|
+
for (const raw of help2.split("\n")) {
|
|
11461
11470
|
const command = usageCommand(raw, program);
|
|
11462
11471
|
if (command) commands.push(command);
|
|
11463
11472
|
}
|
|
@@ -13296,8 +13305,8 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
13296
13305
|
return 3;
|
|
13297
13306
|
}
|
|
13298
13307
|
if (!resolved.tab) {
|
|
13299
|
-
await platform2.write(2, `mcpcat serve --webmcp: ${resolved.error}
|
|
13300
|
-
`);
|
|
13308
|
+
await platform2.write(2, sanitizeTerminal(`mcpcat serve --webmcp: ${resolved.error}
|
|
13309
|
+
`));
|
|
13301
13310
|
return 3;
|
|
13302
13311
|
}
|
|
13303
13312
|
tab = resolved.tab;
|
|
@@ -14300,8 +14309,8 @@ ${url.href}
|
|
|
14300
14309
|
await platform2.write(2, "mcpcat: sign-in cancelled\n");
|
|
14301
14310
|
return 1;
|
|
14302
14311
|
}
|
|
14303
|
-
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
14304
|
-
`);
|
|
14312
|
+
await platform2.write(2, sanitizeTerminal(`mcpcat: login failed: ${err.message}
|
|
14313
|
+
`));
|
|
14305
14314
|
return 4;
|
|
14306
14315
|
}
|
|
14307
14316
|
}
|
|
@@ -14934,8 +14943,8 @@ async function runAdd(platform2, args2, probe) {
|
|
|
14934
14943
|
if (!flags.noProbe && probe) {
|
|
14935
14944
|
const reason = await probe(name, config, platform2);
|
|
14936
14945
|
if (reason) {
|
|
14937
|
-
await platform2.write(2, `mcpcat add: probe failed: ${reason}
|
|
14938
|
-
`);
|
|
14946
|
+
await platform2.write(2, sanitizeTerminal(`mcpcat add: probe failed: ${reason}
|
|
14947
|
+
`));
|
|
14939
14948
|
return 3;
|
|
14940
14949
|
}
|
|
14941
14950
|
}
|
|
@@ -15316,7 +15325,7 @@ function redact(text, secrets) {
|
|
|
15316
15325
|
function createVerboseLogger(platform2, enabled) {
|
|
15317
15326
|
const secrets = /* @__PURE__ */ new Set();
|
|
15318
15327
|
function write(line) {
|
|
15319
|
-
const safe = redact(line, secrets);
|
|
15328
|
+
const safe = sanitizeTerminal(redact(line, secrets));
|
|
15320
15329
|
platform2.write(2, `${safe}
|
|
15321
15330
|
`).catch(() => {
|
|
15322
15331
|
});
|
|
@@ -15444,6 +15453,7 @@ Server and code verbs:
|
|
|
15444
15453
|
discover <url> Find MCP servers at a URL
|
|
15445
15454
|
inbox [--json] List discovery candidates
|
|
15446
15455
|
help [verb] Show help
|
|
15456
|
+
help --skill Print the agent skill (SKILL.md) for this version
|
|
15447
15457
|
|
|
15448
15458
|
Options:
|
|
15449
15459
|
--json JSON output
|
|
@@ -16135,8 +16145,11 @@ async function connectTargets(platform2, names, config, parsed, version) {
|
|
|
16135
16145
|
const info = await conn.info();
|
|
16136
16146
|
targets.push({ name, conn, instructions: info.instructions });
|
|
16137
16147
|
} catch (err) {
|
|
16138
|
-
await platform2.write(
|
|
16139
|
-
|
|
16148
|
+
await platform2.write(
|
|
16149
|
+
2,
|
|
16150
|
+
sanitizeTerminal(`mcpcat: connection to ${name} failed: ${err.message}
|
|
16151
|
+
`)
|
|
16152
|
+
);
|
|
16140
16153
|
}
|
|
16141
16154
|
}
|
|
16142
16155
|
return {
|
|
@@ -16290,6 +16303,19 @@ async function run(platform2, version, args2) {
|
|
|
16290
16303
|
throw err;
|
|
16291
16304
|
}
|
|
16292
16305
|
}
|
|
16306
|
+
async function help(platform2, version, args2) {
|
|
16307
|
+
if (args2[0] !== "help" || !args2.includes("--skill")) {
|
|
16308
|
+
await platform2.write(1, helpText(version));
|
|
16309
|
+
return 0;
|
|
16310
|
+
}
|
|
16311
|
+
const skill = true ? "---\nname: mcpcat\ndescription: Call Model Context Protocol (MCP) servers from the shell with mcpcat, a curl for MCP. List and call tools, read resources and prompts, keep servers in mcp.json, and send secrets as ${VAR} placeholders that the vault fills in, never as values. Use when a task needs a tool from an MCP server over HTTP or a stdio command.\n---\n\n# mcpcat\n\n`mcpcat` talks to MCP servers over Streamable HTTP or stdio, one shell command per request. Options go before the target.\n\n## Targets\n\n- A URL: `https://example.com/mcp`.\n- A stdio command: `stdio:<command> [args\u2026]`, as one argument (`'stdio:my-server --flag'`).\n- A name from `mcp.json`, the same `~/.pi/agent/mcp.json` the agent reads.\n\n## Discover and call\n\n```bash\nmcpcat info https://example.com/mcp\nmcpcat tools https://example.com/mcp\nmcpcat describe https://example.com/mcp search\nmcpcat call https://example.com/mcp search --q oauth --limit 5\nmcpcat call docs search -d '{\"q\":\"oauth\",\"limit\":5}'\nmcpcat --json call docs search --q oauth\nmcpcat grep issue\n```\n\n- Tool arguments are `--name value` flags, coerced to the schema's types, or one JSON object with `-d` (`-d @file`, `-d @-` for stdin).\n- `describe` prints a tool's schema and help; `--schema` prints the raw JSON Schema, `--ts` TypeScript declarations.\n- `--json` prints deterministic JSON for scripts; human output otherwise. `-m <s>` sets the timeout (default 60 s).\n- `code -e '<js>'` runs a short script over configured servers (`tools.mcp__<server>__<tool>(args)`), with `-s <server>` to pick servers.\n\n## Configure servers\n\n```bash\nmcpcat add docs https://example.com/mcp\nmcpcat add docs https://example.com/mcp -H 'Authorization: Bearer ${DOCS_TOKEN}'\nmcpcat list\nmcpcat rm docs\nmcpcat import ~/.cursor/mcp.json\nmcpcat login docs\n```\n\n`login` runs the OAuth sign-in for a server that needs one; `logout` removes its tokens.\n\n## Secrets: never values, always placeholders\n\n- **Never print, echo, log or paste a secret's value**, not in a command, a file, a message or your answer. The value must never reach the conversation.\n- **Use `${NAME}` placeholders in single quotes**: `-H 'Authorization: Bearer ${GH_TOKEN}'`. mcpcat expands them itself, so the value stays out of argv. In seven, `NAME` holds the vault's placeholder, and the vault fills in the value where a secret rule allows that host.\n- **Never put a token into `mcp.json`.** Write `${NAME}` there too; `mcpcat add` refuses a literal `Bearer \u2026` value.\n- `-v` traces show expanded headers as `[REDACTED]`. Don't try to work around redaction.\n- If a request needs a secret that isn't there, ask the user to add it to the vault; don't ask them to paste it.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | Success |\n| 1 | The tool reported an error (`isError`), or `grep` found nothing |\n| 2 | Usage: bad flags, unknown tool, invalid arguments or config |\n| 3 | Connection, transport or protocol failure |\n| 4 | Authentication required or failed: run the `mcpcat login` the message names |\n| 5 | JSON-RPC error from the server |\n| 6 | The server asked for input and there is no terminal |\n| 124 | Timeout |\n| 130 | Interrupted |\n\n## In seven\n\n- Host servers on the user's machine are `http://host.slicc.internal:<port>`, through a linked slicc CLI.\n- Stdio servers must be kernel commands; `npx` needs the optional Node.js package.\n- The agent's own MCP tools come from `mcp.json`; `mcpcat add` is how to add a server to it.\n" : void 0;
|
|
16312
|
+
if (!skill) {
|
|
16313
|
+
await platform2.write(2, "mcpcat help: this build has no skill\n");
|
|
16314
|
+
return EXIT_USAGE;
|
|
16315
|
+
}
|
|
16316
|
+
await platform2.write(1, skill);
|
|
16317
|
+
return 0;
|
|
16318
|
+
}
|
|
16293
16319
|
async function runInner(platform2, version, args2) {
|
|
16294
16320
|
if (args2.includes("--version") || args2.includes("-V")) {
|
|
16295
16321
|
await platform2.write(1, `${version}
|
|
@@ -16297,8 +16323,7 @@ async function runInner(platform2, version, args2) {
|
|
|
16297
16323
|
return 0;
|
|
16298
16324
|
}
|
|
16299
16325
|
if (args2.includes("--help") || args2.includes("-h") || args2[0] === "help") {
|
|
16300
|
-
|
|
16301
|
-
return 0;
|
|
16326
|
+
return help(platform2, version, args2);
|
|
16302
16327
|
}
|
|
16303
16328
|
if (args2.length === 0) {
|
|
16304
16329
|
await platform2.write(1, helpText(version));
|
|
@@ -16555,8 +16580,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16555
16580
|
if (!showProgress) return;
|
|
16556
16581
|
const pct = total ? ` ${Math.round(progress / total * 100)}%` : "";
|
|
16557
16582
|
const msg = message ? ` ${message}` : "";
|
|
16558
|
-
platform2.write(2, `* progress:${pct}${msg}
|
|
16559
|
-
`).catch(() => {
|
|
16583
|
+
platform2.write(2, sanitizeTerminal(`* progress:${pct}${msg}
|
|
16584
|
+
`)).catch(() => {
|
|
16560
16585
|
});
|
|
16561
16586
|
};
|
|
16562
16587
|
const config = await MergedConfigSource.load(platform2);
|
|
@@ -16684,13 +16709,13 @@ async function closeAndKillStdio(conn) {
|
|
|
16684
16709
|
}
|
|
16685
16710
|
async function writeErr(p, json, kind, msg) {
|
|
16686
16711
|
if (json) await renderErrorJson(p, kind, msg);
|
|
16687
|
-
else await p.write(2, `mcpcat: ${msg}
|
|
16688
|
-
`);
|
|
16712
|
+
else await p.write(2, sanitizeTerminal(`mcpcat: ${msg}
|
|
16713
|
+
`));
|
|
16689
16714
|
}
|
|
16690
16715
|
async function writeErrRedacted(p, json, kind, msg, redact2) {
|
|
16691
16716
|
if (json) await renderErrorJson(p, kind, msg, void 0, void 0, redact2);
|
|
16692
|
-
else await p.write(2, `mcpcat: ${redact2(msg)}
|
|
16693
|
-
`);
|
|
16717
|
+
else await p.write(2, sanitizeTerminal(`mcpcat: ${redact2(msg)}
|
|
16718
|
+
`));
|
|
16694
16719
|
}
|
|
16695
16720
|
function collectConfigSecrets(headers, env) {
|
|
16696
16721
|
if (!headers) return [];
|
|
@@ -16963,5 +16988,5 @@ try {
|
|
|
16963
16988
|
});
|
|
16964
16989
|
} catch {
|
|
16965
16990
|
}
|
|
16966
|
-
var code = await run(platform, "0.
|
|
16991
|
+
var code = await run(platform, "0.17.0", args);
|
|
16967
16992
|
process.exit(code);
|
package/dist/kernel/mcpcat.mjs
CHANGED
|
@@ -8734,6 +8734,12 @@ async function connect(platform2, options) {
|
|
|
8734
8734
|
}
|
|
8735
8735
|
}
|
|
8736
8736
|
|
|
8737
|
+
// src/render/sanitize.ts
|
|
8738
|
+
var CONTROL_RE = /[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g;
|
|
8739
|
+
function sanitizeTerminal(text) {
|
|
8740
|
+
return text.replace(CONTROL_RE, (c) => `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`);
|
|
8741
|
+
}
|
|
8742
|
+
|
|
8737
8743
|
// src/client/elicit.ts
|
|
8738
8744
|
function readLine(platform2) {
|
|
8739
8745
|
return new Promise((resolve2, reject) => {
|
|
@@ -8785,7 +8791,7 @@ async function promptForSchema(platform2, schema) {
|
|
|
8785
8791
|
const desc = prop.description ? ` - ${prop.description}` : "";
|
|
8786
8792
|
const def = prop.default !== void 0 ? ` [${String(prop.default)}]` : "";
|
|
8787
8793
|
const choices = prop.enum ? ` (${prop.enum.join("|")})` : "";
|
|
8788
|
-
await platform2.write(2, `${key}${req}${desc}${choices}${def}: `);
|
|
8794
|
+
await platform2.write(2, sanitizeTerminal(`${key}${req}${desc}${choices}${def}: `));
|
|
8789
8795
|
const line = await readLine(platform2);
|
|
8790
8796
|
const value = line.trim();
|
|
8791
8797
|
if (value === "" && prop.default !== void 0) {
|
|
@@ -8807,8 +8813,8 @@ function parseInputRequest(result) {
|
|
|
8807
8813
|
async function handleElicitation(platform2, inputRequest, isTty, respondData) {
|
|
8808
8814
|
if (inputRequest.url) {
|
|
8809
8815
|
if (isTty) {
|
|
8810
|
-
await platform2.write(2, `Open this URL to continue: ${inputRequest.url}
|
|
8811
|
-
`);
|
|
8816
|
+
await platform2.write(2, sanitizeTerminal(`Open this URL to continue: ${inputRequest.url}
|
|
8817
|
+
`));
|
|
8812
8818
|
await platform2.write(2, "Press Enter when done...");
|
|
8813
8819
|
await readLine(platform2);
|
|
8814
8820
|
return { answers: {} };
|
|
@@ -8823,8 +8829,8 @@ async function handleElicitation(platform2, inputRequest, isTty, respondData) {
|
|
|
8823
8829
|
}
|
|
8824
8830
|
const schema = inputRequest.schema ?? { type: "object", properties: {} };
|
|
8825
8831
|
if (inputRequest.message) {
|
|
8826
|
-
await platform2.write(2, `${inputRequest.message}
|
|
8827
|
-
`);
|
|
8832
|
+
await platform2.write(2, sanitizeTerminal(`${inputRequest.message}
|
|
8833
|
+
`));
|
|
8828
8834
|
}
|
|
8829
8835
|
const answers = await promptForSchema(platform2, schema);
|
|
8830
8836
|
return { answers };
|
|
@@ -9345,8 +9351,11 @@ async function formatOutput(platform2, result, json) {
|
|
|
9345
9351
|
`);
|
|
9346
9352
|
}
|
|
9347
9353
|
if (!result.ok) {
|
|
9348
|
-
await platform2.write(
|
|
9349
|
-
|
|
9354
|
+
await platform2.write(
|
|
9355
|
+
2,
|
|
9356
|
+
sanitizeTerminal(`mcpcat: script failed: ${result.error.message}
|
|
9357
|
+
`)
|
|
9358
|
+
);
|
|
9350
9359
|
}
|
|
9351
9360
|
}
|
|
9352
9361
|
const isTimeout = !result.ok && result.error && result.error.kind === "timeout";
|
|
@@ -10808,8 +10817,8 @@ async function renderCallResult(platform2, result, options) {
|
|
|
10808
10817
|
}
|
|
10809
10818
|
const text = renderCallResultText(result, options);
|
|
10810
10819
|
if (result.isError) {
|
|
10811
|
-
await platform2.write(2, r(`${text || "(tool reported error)"}
|
|
10812
|
-
`));
|
|
10820
|
+
await platform2.write(2, sanitizeTerminal(r(`${text || "(tool reported error)"}
|
|
10821
|
+
`)));
|
|
10813
10822
|
return 1;
|
|
10814
10823
|
}
|
|
10815
10824
|
if (text) {
|
|
@@ -10860,17 +10869,17 @@ function sanitizeName(raw) {
|
|
|
10860
10869
|
const cleaned = raw.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
10861
10870
|
return cleaned || "cli";
|
|
10862
10871
|
}
|
|
10863
|
-
function cliPrefix(
|
|
10872
|
+
function cliPrefix(help2, filePath, explicit) {
|
|
10864
10873
|
if (explicit) return sanitizeName(explicit);
|
|
10865
|
-
const fromUsage = usageProgramName(
|
|
10874
|
+
const fromUsage = usageProgramName(help2);
|
|
10866
10875
|
if (fromUsage) return sanitizeName(fromUsage);
|
|
10867
10876
|
const base = filePath.split("/").pop() ?? filePath;
|
|
10868
10877
|
return sanitizeName(base.replace(/\.jsh$/i, ""));
|
|
10869
10878
|
}
|
|
10870
|
-
function parseHelp(
|
|
10871
|
-
const block = commandsFromBlock(
|
|
10872
|
-
const program = usageProgramName(
|
|
10873
|
-
const commands = mergeCommandLists(block.commands, usageCommands(
|
|
10879
|
+
function parseHelp(help2) {
|
|
10880
|
+
const block = commandsFromBlock(help2);
|
|
10881
|
+
const program = usageProgramName(help2);
|
|
10882
|
+
const commands = mergeCommandLists(block.commands, usageCommands(help2, program));
|
|
10874
10883
|
return {
|
|
10875
10884
|
...program ? { program } : {},
|
|
10876
10885
|
commands: applyScopedFlags(commands, block.scoped),
|
|
@@ -11014,12 +11023,12 @@ function encodeFlag(flag, value) {
|
|
|
11014
11023
|
}
|
|
11015
11024
|
return { argv: [`--${flag.name}`, String(value)] };
|
|
11016
11025
|
}
|
|
11017
|
-
function commandsFromBlock(
|
|
11026
|
+
function commandsFromBlock(help2) {
|
|
11018
11027
|
let section = "none";
|
|
11019
11028
|
const commands = [];
|
|
11020
11029
|
const globalFlags = [];
|
|
11021
11030
|
const scoped = [];
|
|
11022
|
-
for (const rawLine of
|
|
11031
|
+
for (const rawLine of help2.split("\n")) {
|
|
11023
11032
|
const line = rawLine.replace(/\t/g, " ");
|
|
11024
11033
|
const header = classifyHeader(line);
|
|
11025
11034
|
if (header) {
|
|
@@ -11067,8 +11076,8 @@ function parseCommandLine(line) {
|
|
|
11067
11076
|
flags: spec.flags
|
|
11068
11077
|
};
|
|
11069
11078
|
}
|
|
11070
|
-
function usageProgramName(
|
|
11071
|
-
for (const raw of
|
|
11079
|
+
function usageProgramName(help2) {
|
|
11080
|
+
for (const raw of help2.split("\n")) {
|
|
11072
11081
|
if (!/^[\s>*]*usage:/i.test(raw)) continue;
|
|
11073
11082
|
const tokens = tokenize2(raw);
|
|
11074
11083
|
const name = tokens[0];
|
|
@@ -11076,9 +11085,9 @@ function usageProgramName(help) {
|
|
|
11076
11085
|
}
|
|
11077
11086
|
return void 0;
|
|
11078
11087
|
}
|
|
11079
|
-
function usageCommands(
|
|
11088
|
+
function usageCommands(help2, program) {
|
|
11080
11089
|
const commands = [];
|
|
11081
|
-
for (const raw of
|
|
11090
|
+
for (const raw of help2.split("\n")) {
|
|
11082
11091
|
const command = usageCommand(raw, program);
|
|
11083
11092
|
if (command) commands.push(command);
|
|
11084
11093
|
}
|
|
@@ -12917,8 +12926,8 @@ async function runWebMcpServe(platform2, options, version, resolvedBearer, gener
|
|
|
12917
12926
|
return 3;
|
|
12918
12927
|
}
|
|
12919
12928
|
if (!resolved.tab) {
|
|
12920
|
-
await platform2.write(2, `mcpcat serve --webmcp: ${resolved.error}
|
|
12921
|
-
`);
|
|
12929
|
+
await platform2.write(2, sanitizeTerminal(`mcpcat serve --webmcp: ${resolved.error}
|
|
12930
|
+
`));
|
|
12922
12931
|
return 3;
|
|
12923
12932
|
}
|
|
12924
12933
|
tab = resolved.tab;
|
|
@@ -13921,8 +13930,8 @@ ${url.href}
|
|
|
13921
13930
|
await platform2.write(2, "mcpcat: sign-in cancelled\n");
|
|
13922
13931
|
return 1;
|
|
13923
13932
|
}
|
|
13924
|
-
await platform2.write(2, `mcpcat: login failed: ${err.message}
|
|
13925
|
-
`);
|
|
13933
|
+
await platform2.write(2, sanitizeTerminal(`mcpcat: login failed: ${err.message}
|
|
13934
|
+
`));
|
|
13926
13935
|
return 4;
|
|
13927
13936
|
}
|
|
13928
13937
|
}
|
|
@@ -14555,8 +14564,8 @@ async function runAdd(platform2, args, probe) {
|
|
|
14555
14564
|
if (!flags.noProbe && probe) {
|
|
14556
14565
|
const reason = await probe(name, config, platform2);
|
|
14557
14566
|
if (reason) {
|
|
14558
|
-
await platform2.write(2, `mcpcat add: probe failed: ${reason}
|
|
14559
|
-
`);
|
|
14567
|
+
await platform2.write(2, sanitizeTerminal(`mcpcat add: probe failed: ${reason}
|
|
14568
|
+
`));
|
|
14560
14569
|
return 3;
|
|
14561
14570
|
}
|
|
14562
14571
|
}
|
|
@@ -14937,7 +14946,7 @@ function redact(text, secrets) {
|
|
|
14937
14946
|
function createVerboseLogger(platform2, enabled) {
|
|
14938
14947
|
const secrets = /* @__PURE__ */ new Set();
|
|
14939
14948
|
function write(line) {
|
|
14940
|
-
const safe = redact(line, secrets);
|
|
14949
|
+
const safe = sanitizeTerminal(redact(line, secrets));
|
|
14941
14950
|
platform2.write(2, `${safe}
|
|
14942
14951
|
`).catch(() => {
|
|
14943
14952
|
});
|
|
@@ -15065,6 +15074,7 @@ Server and code verbs:
|
|
|
15065
15074
|
discover <url> Find MCP servers at a URL
|
|
15066
15075
|
inbox [--json] List discovery candidates
|
|
15067
15076
|
help [verb] Show help
|
|
15077
|
+
help --skill Print the agent skill (SKILL.md) for this version
|
|
15068
15078
|
|
|
15069
15079
|
Options:
|
|
15070
15080
|
--json JSON output
|
|
@@ -15756,8 +15766,11 @@ async function connectTargets(platform2, names, config, parsed, version) {
|
|
|
15756
15766
|
const info = await conn.info();
|
|
15757
15767
|
targets.push({ name, conn, instructions: info.instructions });
|
|
15758
15768
|
} catch (err) {
|
|
15759
|
-
await platform2.write(
|
|
15760
|
-
|
|
15769
|
+
await platform2.write(
|
|
15770
|
+
2,
|
|
15771
|
+
sanitizeTerminal(`mcpcat: connection to ${name} failed: ${err.message}
|
|
15772
|
+
`)
|
|
15773
|
+
);
|
|
15761
15774
|
}
|
|
15762
15775
|
}
|
|
15763
15776
|
return {
|
|
@@ -15911,6 +15924,19 @@ async function run(platform2, version, args) {
|
|
|
15911
15924
|
throw err;
|
|
15912
15925
|
}
|
|
15913
15926
|
}
|
|
15927
|
+
async function help(platform2, version, args) {
|
|
15928
|
+
if (args[0] !== "help" || !args.includes("--skill")) {
|
|
15929
|
+
await platform2.write(1, helpText(version));
|
|
15930
|
+
return 0;
|
|
15931
|
+
}
|
|
15932
|
+
const skill = true ? "---\nname: mcpcat\ndescription: Call Model Context Protocol (MCP) servers from the shell with mcpcat, a curl for MCP. List and call tools, read resources and prompts, keep servers in mcp.json, and send secrets as ${VAR} placeholders that the vault fills in, never as values. Use when a task needs a tool from an MCP server over HTTP or a stdio command.\n---\n\n# mcpcat\n\n`mcpcat` talks to MCP servers over Streamable HTTP or stdio, one shell command per request. Options go before the target.\n\n## Targets\n\n- A URL: `https://example.com/mcp`.\n- A stdio command: `stdio:<command> [args\u2026]`, as one argument (`'stdio:my-server --flag'`).\n- A name from `mcp.json`, the same `~/.pi/agent/mcp.json` the agent reads.\n\n## Discover and call\n\n```bash\nmcpcat info https://example.com/mcp\nmcpcat tools https://example.com/mcp\nmcpcat describe https://example.com/mcp search\nmcpcat call https://example.com/mcp search --q oauth --limit 5\nmcpcat call docs search -d '{\"q\":\"oauth\",\"limit\":5}'\nmcpcat --json call docs search --q oauth\nmcpcat grep issue\n```\n\n- Tool arguments are `--name value` flags, coerced to the schema's types, or one JSON object with `-d` (`-d @file`, `-d @-` for stdin).\n- `describe` prints a tool's schema and help; `--schema` prints the raw JSON Schema, `--ts` TypeScript declarations.\n- `--json` prints deterministic JSON for scripts; human output otherwise. `-m <s>` sets the timeout (default 60 s).\n- `code -e '<js>'` runs a short script over configured servers (`tools.mcp__<server>__<tool>(args)`), with `-s <server>` to pick servers.\n\n## Configure servers\n\n```bash\nmcpcat add docs https://example.com/mcp\nmcpcat add docs https://example.com/mcp -H 'Authorization: Bearer ${DOCS_TOKEN}'\nmcpcat list\nmcpcat rm docs\nmcpcat import ~/.cursor/mcp.json\nmcpcat login docs\n```\n\n`login` runs the OAuth sign-in for a server that needs one; `logout` removes its tokens.\n\n## Secrets: never values, always placeholders\n\n- **Never print, echo, log or paste a secret's value**, not in a command, a file, a message or your answer. The value must never reach the conversation.\n- **Use `${NAME}` placeholders in single quotes**: `-H 'Authorization: Bearer ${GH_TOKEN}'`. mcpcat expands them itself, so the value stays out of argv. In seven, `NAME` holds the vault's placeholder, and the vault fills in the value where a secret rule allows that host.\n- **Never put a token into `mcp.json`.** Write `${NAME}` there too; `mcpcat add` refuses a literal `Bearer \u2026` value.\n- `-v` traces show expanded headers as `[REDACTED]`. Don't try to work around redaction.\n- If a request needs a secret that isn't there, ask the user to add it to the vault; don't ask them to paste it.\n\n## Exit codes\n\n| Code | Meaning |\n|---|---|\n| 0 | Success |\n| 1 | The tool reported an error (`isError`), or `grep` found nothing |\n| 2 | Usage: bad flags, unknown tool, invalid arguments or config |\n| 3 | Connection, transport or protocol failure |\n| 4 | Authentication required or failed: run the `mcpcat login` the message names |\n| 5 | JSON-RPC error from the server |\n| 6 | The server asked for input and there is no terminal |\n| 124 | Timeout |\n| 130 | Interrupted |\n\n## In seven\n\n- Host servers on the user's machine are `http://host.slicc.internal:<port>`, through a linked slicc CLI.\n- Stdio servers must be kernel commands; `npx` needs the optional Node.js package.\n- The agent's own MCP tools come from `mcp.json`; `mcpcat add` is how to add a server to it.\n" : void 0;
|
|
15933
|
+
if (!skill) {
|
|
15934
|
+
await platform2.write(2, "mcpcat help: this build has no skill\n");
|
|
15935
|
+
return EXIT_USAGE;
|
|
15936
|
+
}
|
|
15937
|
+
await platform2.write(1, skill);
|
|
15938
|
+
return 0;
|
|
15939
|
+
}
|
|
15914
15940
|
async function runInner(platform2, version, args) {
|
|
15915
15941
|
if (args.includes("--version") || args.includes("-V")) {
|
|
15916
15942
|
await platform2.write(1, `${version}
|
|
@@ -15918,8 +15944,7 @@ async function runInner(platform2, version, args) {
|
|
|
15918
15944
|
return 0;
|
|
15919
15945
|
}
|
|
15920
15946
|
if (args.includes("--help") || args.includes("-h") || args[0] === "help") {
|
|
15921
|
-
|
|
15922
|
-
return 0;
|
|
15947
|
+
return help(platform2, version, args);
|
|
15923
15948
|
}
|
|
15924
15949
|
if (args.length === 0) {
|
|
15925
15950
|
await platform2.write(1, helpText(version));
|
|
@@ -16176,8 +16201,8 @@ async function runClientVerb(platform2, version, parsed) {
|
|
|
16176
16201
|
if (!showProgress) return;
|
|
16177
16202
|
const pct = total ? ` ${Math.round(progress / total * 100)}%` : "";
|
|
16178
16203
|
const msg = message ? ` ${message}` : "";
|
|
16179
|
-
platform2.write(2, `* progress:${pct}${msg}
|
|
16180
|
-
`).catch(() => {
|
|
16204
|
+
platform2.write(2, sanitizeTerminal(`* progress:${pct}${msg}
|
|
16205
|
+
`)).catch(() => {
|
|
16181
16206
|
});
|
|
16182
16207
|
};
|
|
16183
16208
|
const config = await MergedConfigSource.load(platform2);
|
|
@@ -16305,13 +16330,13 @@ async function closeAndKillStdio(conn) {
|
|
|
16305
16330
|
}
|
|
16306
16331
|
async function writeErr(p, json, kind, msg) {
|
|
16307
16332
|
if (json) await renderErrorJson(p, kind, msg);
|
|
16308
|
-
else await p.write(2, `mcpcat: ${msg}
|
|
16309
|
-
`);
|
|
16333
|
+
else await p.write(2, sanitizeTerminal(`mcpcat: ${msg}
|
|
16334
|
+
`));
|
|
16310
16335
|
}
|
|
16311
16336
|
async function writeErrRedacted(p, json, kind, msg, redact2) {
|
|
16312
16337
|
if (json) await renderErrorJson(p, kind, msg, void 0, void 0, redact2);
|
|
16313
|
-
else await p.write(2, `mcpcat: ${redact2(msg)}
|
|
16314
|
-
`);
|
|
16338
|
+
else await p.write(2, sanitizeTerminal(`mcpcat: ${redact2(msg)}
|
|
16339
|
+
`));
|
|
16315
16340
|
}
|
|
16316
16341
|
function collectConfigSecrets(headers, env2) {
|
|
16317
16342
|
if (!headers) return [];
|
|
@@ -16402,11 +16427,12 @@ async function kernelSpawn(ctx, options) {
|
|
|
16402
16427
|
}
|
|
16403
16428
|
}
|
|
16404
16429
|
const useGroupKill = groupSpawnSupported;
|
|
16430
|
+
let waited;
|
|
16405
16431
|
return {
|
|
16406
16432
|
stdin: result.stdin ?? new WritableStream(),
|
|
16407
16433
|
stdout: result.stdout,
|
|
16408
16434
|
stderr: result.stderr,
|
|
16409
|
-
wait: () => result.wait(),
|
|
16435
|
+
wait: () => waited ??= result.wait(),
|
|
16410
16436
|
kill: async (signal) => {
|
|
16411
16437
|
if (useGroupKill) {
|
|
16412
16438
|
try {
|
|
@@ -16684,7 +16710,7 @@ async function main(ctx) {
|
|
|
16684
16710
|
packageDir: "/node_modules/@ai-ecoverse/mcpcat"
|
|
16685
16711
|
});
|
|
16686
16712
|
const args = ctx.argv.slice(1);
|
|
16687
|
-
return run(platform2, "0.
|
|
16713
|
+
return run(platform2, "0.17.0", args);
|
|
16688
16714
|
}
|
|
16689
16715
|
export {
|
|
16690
16716
|
main as default
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mcpcat
|
|
3
|
+
description: Call Model Context Protocol (MCP) servers from the shell with mcpcat, a curl for MCP. List and call tools, read resources and prompts, keep servers in mcp.json, and send secrets as ${VAR} placeholders that the vault fills in, never as values. Use when a task needs a tool from an MCP server over HTTP or a stdio command.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# mcpcat
|
|
7
|
+
|
|
8
|
+
`mcpcat` talks to MCP servers over Streamable HTTP or stdio, one shell command per request. Options go before the target.
|
|
9
|
+
|
|
10
|
+
## Targets
|
|
11
|
+
|
|
12
|
+
- A URL: `https://example.com/mcp`.
|
|
13
|
+
- A stdio command: `stdio:<command> [args…]`, as one argument (`'stdio:my-server --flag'`).
|
|
14
|
+
- A name from `mcp.json`, the same `~/.pi/agent/mcp.json` the agent reads.
|
|
15
|
+
|
|
16
|
+
## Discover and call
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
mcpcat info https://example.com/mcp
|
|
20
|
+
mcpcat tools https://example.com/mcp
|
|
21
|
+
mcpcat describe https://example.com/mcp search
|
|
22
|
+
mcpcat call https://example.com/mcp search --q oauth --limit 5
|
|
23
|
+
mcpcat call docs search -d '{"q":"oauth","limit":5}'
|
|
24
|
+
mcpcat --json call docs search --q oauth
|
|
25
|
+
mcpcat grep issue
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
- Tool arguments are `--name value` flags, coerced to the schema's types, or one JSON object with `-d` (`-d @file`, `-d @-` for stdin).
|
|
29
|
+
- `describe` prints a tool's schema and help; `--schema` prints the raw JSON Schema, `--ts` TypeScript declarations.
|
|
30
|
+
- `--json` prints deterministic JSON for scripts; human output otherwise. `-m <s>` sets the timeout (default 60 s).
|
|
31
|
+
- `code -e '<js>'` runs a short script over configured servers (`tools.mcp__<server>__<tool>(args)`), with `-s <server>` to pick servers.
|
|
32
|
+
|
|
33
|
+
## Configure servers
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
mcpcat add docs https://example.com/mcp
|
|
37
|
+
mcpcat add docs https://example.com/mcp -H 'Authorization: Bearer ${DOCS_TOKEN}'
|
|
38
|
+
mcpcat list
|
|
39
|
+
mcpcat rm docs
|
|
40
|
+
mcpcat import ~/.cursor/mcp.json
|
|
41
|
+
mcpcat login docs
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`login` runs the OAuth sign-in for a server that needs one; `logout` removes its tokens.
|
|
45
|
+
|
|
46
|
+
## Secrets: never values, always placeholders
|
|
47
|
+
|
|
48
|
+
- **Never print, echo, log or paste a secret's value**, not in a command, a file, a message or your answer. The value must never reach the conversation.
|
|
49
|
+
- **Use `${NAME}` placeholders in single quotes**: `-H 'Authorization: Bearer ${GH_TOKEN}'`. mcpcat expands them itself, so the value stays out of argv. In seven, `NAME` holds the vault's placeholder, and the vault fills in the value where a secret rule allows that host.
|
|
50
|
+
- **Never put a token into `mcp.json`.** Write `${NAME}` there too; `mcpcat add` refuses a literal `Bearer …` value.
|
|
51
|
+
- `-v` traces show expanded headers as `[REDACTED]`. Don't try to work around redaction.
|
|
52
|
+
- If a request needs a secret that isn't there, ask the user to add it to the vault; don't ask them to paste it.
|
|
53
|
+
|
|
54
|
+
## Exit codes
|
|
55
|
+
|
|
56
|
+
| Code | Meaning |
|
|
57
|
+
|---|---|
|
|
58
|
+
| 0 | Success |
|
|
59
|
+
| 1 | The tool reported an error (`isError`), or `grep` found nothing |
|
|
60
|
+
| 2 | Usage: bad flags, unknown tool, invalid arguments or config |
|
|
61
|
+
| 3 | Connection, transport or protocol failure |
|
|
62
|
+
| 4 | Authentication required or failed: run the `mcpcat login` the message names |
|
|
63
|
+
| 5 | JSON-RPC error from the server |
|
|
64
|
+
| 6 | The server asked for input and there is no terminal |
|
|
65
|
+
| 124 | Timeout |
|
|
66
|
+
| 130 | Interrupted |
|
|
67
|
+
|
|
68
|
+
## In seven
|
|
69
|
+
|
|
70
|
+
- Host servers on the user's machine are `http://host.slicc.internal:<port>`, through a linked slicc CLI.
|
|
71
|
+
- Stdio servers must be kernel commands; `npx` needs the optional Node.js package.
|
|
72
|
+
- The agent's own MCP tools come from `mcp.json`; `mcpcat add` is how to add a server to it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ai-ecoverse/mcpcat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "A curl for the Model Context Protocol",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
"README.md",
|
|
35
35
|
"dist"
|
|
36
36
|
],
|
|
37
|
+
"pi": {
|
|
38
|
+
"skills": [
|
|
39
|
+
"dist/skills"
|
|
40
|
+
]
|
|
41
|
+
},
|
|
37
42
|
"slicc": {
|
|
38
43
|
"commands": {
|
|
39
44
|
"mcpcat": {
|
|
@@ -56,15 +61,15 @@
|
|
|
56
61
|
"trust:dry": "npx fledgling sync --dry-run -y --skip-publish"
|
|
57
62
|
},
|
|
58
63
|
"devDependencies": {
|
|
59
|
-
"@ai-ecoverse/slicc-bios": "1.
|
|
60
|
-
"@ai-ecoverse/slicc-kernel": "1.
|
|
61
|
-
"@ai-ecoverse/slicc-shared-web": "1.
|
|
64
|
+
"@ai-ecoverse/slicc-bios": "1.20.0",
|
|
65
|
+
"@ai-ecoverse/slicc-kernel": "1.127.1",
|
|
66
|
+
"@ai-ecoverse/slicc-shared-web": "1.23.2",
|
|
62
67
|
"@biomejs/biome": "2.5.15",
|
|
63
68
|
"@earendil-works/pi-codemode": "1.2.0",
|
|
64
69
|
"@earendil-works/pi-mcp": "1.2.0",
|
|
65
70
|
"@types/node": "26.6.5",
|
|
66
71
|
"esbuild": "0.28.2",
|
|
67
|
-
"kernel-cert-new": "npm:@ai-ecoverse/slicc-kernel@1.
|
|
72
|
+
"kernel-cert-new": "npm:@ai-ecoverse/slicc-kernel@1.127.1",
|
|
68
73
|
"monocart-coverage-reports": "2.13.2",
|
|
69
74
|
"proper-lockfile": "4.1.2",
|
|
70
75
|
"quickjs-wasi": "3.6.2",
|