cli CLAUDE.md

Project instructions for an open-source command-line package manager for MCP servers. MCP servers are small programs that give AI clients access to tools or data; this project lets developers search, install, audit, publish, and update them.

In plain words
What is it for?
Use them when working on the CLI, MCP package registry, server installation and updates, publishing, or audits that detect unsafe commands and arguments.
Why use it?
They explain the product's purpose and current project context to coding agents. This helps agents make changes that fit the registry and its security checks.

Instructions file

Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

agentmods
npx agentmods add instructions/getmcpm/cli/claude-md
Clone the repo
git clone --depth 1 https://github.com/getmcpm/cli
Per session 35,320 This file is loaded in full into every session.
When invoked 35,320 The same file — it is already loaded in full.
Security scan E 3 findings. Scan, not verified.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5 $0.35320 $0.35320
Opus 5 $0.17660 $0.17660
Sonnet 5 $0.07064 $0.07064
Haiku 4.5 $0.03532 $0.03532

Measured yesterday against content hash 1a53a78f304e, method: parsed. Prices are Anthropic first-party input rates as of 2026-08-30, from the pricing page.

Security

Grade E, and why

cli CLAUDE.md scanned grade E with 3 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured yesterday.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Instruction-override phrasingmediumPrompt injection

Text telling the model to disregard its earlier instructions or safety rules is the shape of a prompt injection, whoever wrote it.

| 2026-07-25 | v0.26.0 — SECURITY: guard detection bypass (budget fail-open) + `--json` framing desync (#149) | Two fixes, both found by an ADVERSARIAL REVIEW pass (4 parallel lenses: contract / security / harness / doc-

Downgraded: this mod is about security review, or the phrase is quoted, so it is likely naming the pattern rather than instructing it.

Reaches for credential fileshighPrivilege escalation

SSH keys, cloud credentials, git-credentials, .npmrc, /etc/shadow: reading these is how a config file becomes a credential leak.

| 2026-07-25 | v0.26.0 — SECURITY: guard detection bypass (budget fail-open) + `--json` framing desync (#149) | Two fixes, both found by an ADVERSARIAL REVIEW pass (4 parallel lenses: contract / security / harness / doc-

Reads agent configuration directoriesmediumAgent snooping

.claude/, .codex/, .gemini/ hold keys, settings and other credentials a mod has no legitimate need for.

│ └── Gemini CLI (~/.gemini/settings.json, user-global)
CLAUDE.md · 654 lines

How it starts

The opening of the file, as written. The whole thing — 654 lines — stays where its author put it; the contents beside it link to each section on GitHub.

MCP Registry — Project Context

This file is automatically read by Claude Code on every session. Keep it updated as decisions are made. Last updated: 2026-08-17.


What We're Building

An open-source, CLI-first MCP package manager — "npm for MCP servers".

A registry where developers can search, install, audit, publish, and update MCP servers across all major clients (Claude Desktop, Claude Code, Cursor, VS Code, Windsurf, Gemini CLI) from a single tool.

npm package: @getmcpm/cli (v0.31.0 released — the guard learned two new command/query-injection detectors from real CVEs, mcpm audit stopped calling every server caution, and a pre-release audit caught the first detector hard-blocking ordinary URLs [Detectors: two new tool_call_args catalog entries take the guard from 13 signatures to 15, both KEY-FIRST because tool_call_args carries NO schema context at call time, so a value-only regex would FP on every shell/exec or query-builder tool whose arguments are MEANT to carry that syntax. shell-metachar-in-identifier-arg (#180) closes CVE-2025-53818 (issue_number) and CVE-2026-25546 (projectPath); query-control-syntax-in-identifier-arg (#181) closes CVE-2026-33980 (table_name f-string-interpolated into a live KQL query). Both PoC families scored pass against shipped 0.30.0. They share tool-call-args-walk.ts and canonicalizeKey, so #52 is a third instance, not a third copy. The pre-tag audit earned its keep for the second cycle running: measuring #180 against a benign corpus — rather than reading it — found its bare | pattern HARD-BLOCKING ?family=Roboto|Open+Sans (the Google Fonts URL), ?fields=id|name and ?sort=created|desc under a path-suffixed argument, on a block-capable carrier. #50's own TODO had PREDICTED that FP ("Legitimate arguments can contain | (filter syntax)") and it shipped anyway. Dropped on the same three premises the file already used for &, each measured: gating is evadable, ungated it FPs, and neither CVE needs it — both still block, and deleting it left all 150 guard tests GREEN, so nothing ever pinned it. That is the certifies-nothing shape logged in the v0.27.0/v0.28.0/v0.29.0 rows, now a fourth instance, and the cost (a pipe-ONLY injection now passes) is stated in TODOS #56 rather than hidden. The audit also found the #180 CHANGELOG claiming the "worst action across findings" reduce was already a single shared export when FOUR verbatim copies remained — the same false-statements-about-just-shipped-code class that produced 4 of v0.30.0's 10 findings. Audit label: mcpm audit never runs a health check, so that bucket contributes a flat 15/30 — a constant, not evidence — and measured over 748 live registry servers ALL 748 read caution: a 3-level scale collapsed to one level across the entire public ecosystem. A clean server now reads clean · not run (cyan, deliberately not the green of safe, because "found nothing" is weaker than "verified"). A RELABEL, not a re-score — score/maxPossible/level are untouched because each is load-bearing (lockfile enum, exit code, --min-trust comparand), and TODOS #43's proposed re-base would have moved absolute scores DOWN 15 while moving percentages UP, silently loosening policy.minTrustScore. clean requires the scan found NOTHING, not merely a top-band score: the first cut checked only the band and labelled 743 of 748 clean, 414 of which carry findings the next column prints. Final split: 329 clean · not run / 419 caution (#176). Also dropCheckNativeScore, so a swapped MCPM_EXTERNAL_SCANNER can no longer mask a native regression across two points in time (#179)] — v0.30.0 = an impossible trust threshold now says so instead of blaming every server; the release dogfood stopped reading your real $HOME and can run somewhere that is not your laptop; and a chalk major [Threshold: every score gate in the product runs BEFORE the health check (healthCheckPassed: null) and mcpm never reads a download count, so 18 of the 80 native points are unreachable AT GATE TIME and a flawless server tops out at 62/80. mcpm install --min-trust 63 therefore refused every server in the registry, forever, while the message read Trust score 62/80 is below the required minimum of 63 — blaming the server and sending you to look for a better one. FIVE gates, not four: mcpm_setup deliberately does NOT forward its threshold to the install gate (forwarding would let a caller-supplied 30 LOWER the enforcing gate), so no guard on handleInstall could ever fire from that path — nearly missed, and the worst place to omit, because every keyword reports its best match as "below minimum" and an agent reading a blanket rejection concludes the ECOSYSTEM is unsafe. All five now share maxAchievableBeforeHealthCheck() from scanner/trust-score.ts, replacing audit's private copy of the replayed-inputs literal. UNITS are the trap: --min-trust is ABSOLUTE (ceiling 62), policy.minTrustScore is a PERCENTAGE (ceiling 78toPct rounds, so a flawless 62/80 reports as 78, 78 PASSES and 79 is the first impossible value; TODOS #45's own claim that 78 fails every server was WRONG and only measuring showed it), and where the external bucket is CREDITED both rise to 82 / 82%. Every refusal holding a scored server recommends the OBSERVED score, never the ceiling — recommending 62 is itself unsatisfiable for an all-npm stack, which tops out at 60 — except mcpm_setup, which fires BEFORE the search and has no score to name. MINOR not patch: mcpm_setup now THROWS where it returned a skipped row, a changed outcome on a public MCP surface, the same shape v0.27.0 numbered MINOR for (#170)] [Dogfood: the publish gate sandboxed the install DIRECTORY but then ran the binary with the real $HOME, so mcpm doctor read the maintainer's actual client configs and ~/.mcpm — every mcpm path derives from os.homedir(), which respects $HOME on POSIX. Now a throwaway $HOME, set AFTER build/pack/install because pnpm's store and npm's cache also live under it, and pwd -P-canonicalized because macOS mktemp -d returns a /var symlink to /private/var — the same trap the confine work hit. MCPM_DOGFOOD_SPEC smokes an ALREADY-PUBLISHED version with no build and no pnpm, and a new on-demand Dogfood workflow drives exactly that across Node 22/24/26 + macOS, so a release is verifiable without a conforming Node installed locally. Deliberately the SAME script as the gate: a second copy of the smoke suite would drift and "we dogfooded it" would come to mean two different things. Partly closes TODOS #47 (#171)] [Deps: chalk 5→6 raises engines.node to >=22 (satisfied since #168) and drops the main field, resolving through exports ONLY — exactly the class of change that passes every source test and then fails on a clean install of the packed tarball. CI does not run the release dogfood, only publish.yml does, so that break would have surfaced AT PUBLISH; the packed artifact was therefore dogfooded on BOTH ends of the range, Node 22.23.2 and 26.7.0, before merge. Also @modelcontextprotocol/sdk 1.30.0 and @sigstore/verify 4.1.2 (#172)] Process lesson: the release commit RENAMES ## [Unreleased], so any open branch that wrote under that heading has its entry silently re-filed under a PUBLISHED version — git merges it with no conflict. It happened to #170 and was caught only by re-reading the section after the merge. — v0.29.1 = two Node-support fixes, no runtime behaviour change [engines.node declared >=22.9.0 while four direct runtime deps required more — 20 packages across the transitive closure — so every install on Node 22.9–22.22.1, 24.0–24.14, 23.x or 25.x printed EBADENGINE and hard-failed under engine-strict, from a package manager whose pitch is supply-chain hygiene. It was NEVER correct: v0.23.0 set it to match @sigstore/bundle@4 while that same commit's lockfile already carried commander@15 at >=22.12.0. CI structurally cannot see this class — setup-node resolves the latest minor of each matrix major, so the declared FLOOR is never the Node that runs — so the guard compares the declaration against the dependency TREE (#168). Its review found the guard was itself an instance of the bug: require.resolve("<pkg>/package.json") is gated by exports, so 30 of 120 packages were checked, INCLUDING commander, one of the four deps the test's own comment named. Mutate the DEPENDENCY the guard reads, not the declaration you wrote. Second fix: @types/node is pinned to the engines FLOOR (22), so tsc described Node 22's API on every leg including 24 and 26, and three WebCrypto sites had already drifted — @types/node 25 narrowed BufferSource to reject SharedArrayBuffer-backed views, and a bare Buffer/Uint8Array defaults to ArrayBufferLike. The VALUES were always right (randomBytes() is NonSharedBuffer); only the annotations were wider, so the fix is annotation-only with zero runtime change. CI now re-runs the typecheck per matrix leg against that leg's own typings; measured, the bug is invisible under 22 AND 24 (#169). The pin stays at 22 — raising it trades a silent type gap for a silent runtime one] — v0.29.0 = SECURITY + DATA LOSS: the audit --fix ceiling guard REOPENED the mass delete it was written to close, a committed lockfile could DISARM the blockOnScoreDrop tripwire, and mcpm lock had been overwriting non-.yaml stack files since v0.3.0 [Ceiling: crediting of the external-scanner bucket is decided PER SERVER (a scanner-error is emitted per invocation), so a half-working scanner yields a MIXED run — and reducing per-server ceilings with Math.max let one credited server license a threshold in 63–82 that no uncredited server could reach, whereupon the RAW candidate filter deleted those servers although their evidence was flawless (--fix --json forces --yes and suppresses the candidate list). The guard's own justification — "the servers below it are below it on evidence" — was FALSE for them: computeTrustScore treats a scanner error as the scanner being ABSENT, a statement about the user's scanner, not the server. Now Math.min, refusing the whole run, which deletes nothing (#167). Lockfile: mcpm-lock.yaml lives in the user's repo with NO integrity sidecar, and the new externalScanCredit field was read back with only a lower clamp — 999 recovered a 0% baseline and killed the tripwire outright, -20 recovered 128% and blocked every server, and (found by reviewing the FIRST fix) an in-range 20 added to an untouched {score: 62, maxPossible: 80} lock dropped the baseline 78%→53% and turned a block into a pass. lock cannot write that last combination — it records breakdown.externalScan, which the scorer zeroes when it did not credit the bucket — so rejecting it costs ZERO false positives. Validation lives in policy.ts, deliberately NOT the schema: parseLockFile whole-file safeParses and throws, so a .max() would brick up/verify/diff over an otherwise-fine lock (#167). Lock path: the anchored case-sensitive /\.yaml$/ replace returned any other path UNCHANGED ⇒ lockPath === stackPathmcpm lock -f mcpm.yml wrote the lock OVER the user's own declarations, exit 0 (#166); the first fix then stripped any yaml extension, which was not INJECTIVE — mcpm.yaml/mcpm.yml/mcpm.YAML all mapped onto one mcpm-lock.yaml, so locking one destroyed another's trust snapshots and sticky Sigstore baselines. The extension is now PRESERVED and -lock inserted before it, byte-identical for every existing mcpm.yaml user, injective by invertibility (#167). Also: blockOnScoreDrop now compares mcpm-NATIVE evidence so a fake MCPM_EXTERNAL_SCANNER cannot mask a drop (#166); audit exits 2 when an invocation cannot be satisfied, keeping exit 1 = "a server is risky" meaningful to CI; audit --sarif --fix is refused rather than silently dropping --fix; install --min-trust reports the real denominator (62/80, not 62/100); mcpm outdated no longer claims a trust regression it could never substantiate — the stored number and the fresh one were never comparable, and InstalledServer.trustScore is deleted with both writers (#166). Two process lessons. (1) Mutation-test each CLAUSE of a compound predicate, not the predicate: the first credit guard's fixtures used values EVERY clause rejected, so deleting any single clause left all 2416 tests green — the certifies-nothing shape this project has now shipped three cycles running, reproduced inside the commit fixing an instance of it. (2) A clean npm server scores 60, not 62: every npm package draws one low install-script finding for the npx -y launcher class, so the 62 ceiling is reachable only by a pypi/oci server and --min-trust 61..62 is unsatisfiable for an all-npm stack — found only by pointing a drift guard at the REAL scorer through audit --json] — v0.28.0 = SECURITY: the tier-2 external scanner was DEAD and an unclaimed-name fetch-execute vector; Unicode TAG-block "ASCII smuggling" is now decoded and re-scanned on every carrier; the MCP hard trust floor no longer counts unverifiable external-scanner credit [tier 2 probed npx @invariantlabs/mcp-scan, a package that 404s on npm with the whole scope unregistered — so tier 2 could NEVER run while three docs claimed mcpm "wraps MCP-Scan", and anyone registering that scope would have had mcpm fetch-and-execute their code on every mcpm audit; mcpm now never fetches a scanner (#159). TAG decode: PATTERN_BREAKERS strips U+E0000–U+E007F BEFORE matching, so a fully encoded phrase was ERASED rather than revealed, and presence detection ran on metadata only — every other carrier scored ZERO findings, including the block-tier sampling_prompt path where a TAG-encoded seed-phrase solicitation passed (#160). That fix then took SEVEN adversarial review rounds, rounds 1–6 each finding a HIGH inside the previous round's fix: a decoy phrase disarmed the whole pass (text-keyed suppression is attacker-writable on both sides), a 256-per-pattern counting bound had NO correct interpretation (one reading fabricated a block on a benign injection dataset, the other let 256 decoys suppress a payload), and a greedy /g scan never enumerated a match beginning INSIDE an earlier one — a REGRESSION vs main that the round's own 600k-input sweep missed by comparing two commits of the branch instead of the branch against main. Final design: two views per segment (decoded = tag→ASCII, masked = tag→NUL) with per-KEY occurrence counts; positions were tried twice and are unsound because NFKC composes a decoded char with a following combining mark but not the NUL mask. Verified 8/8 mutations caught, ~332k benign evaluations across 9 carriers with zero action-level FPs (#162). Trust floor: MCPM_EXTERNAL_SCANNER names an arbitrary executable, so a two-line script printing {"findings":[]} lifted a blocked server from 15 to 35 over the floor of 25 and mcpm_up installed it; floors now compare mcpm-native evidence only, one-directionally (#161)] — v0.27.0 = mcpm guard inspect and the relay now AGREE: 3 of the 12 catalog signatures were unreachable through the PUBLIC scoring seam [inspect shipped in v0.25.0 calling inspectMessage alone while the relay composes THREE stateless detectors, so exfil-param-in-schema (detectExfilParams) + both credential-phishing-* (inspectServerInitiated) returned pass/exit-0 on frames the relay BLOCKS as critical — while guard list-signatures advertised all three as installed. The gap was self-concealing across three layers: mcptox.test.ts evaluated fixtures through the SAME incomplete pipeline, so a fixture for any of them would have FAILED the release gate (the corpus was shaped by the hole it was meant to catch), and mcp-guardbench extracts from that corpus so the published benchmark inherited the blind spot and still scored 100%. FIX = new src/guard/inspect-frame.ts holding the ONE stateless composition (inspectFrame) + the pure helpers moved VERBATIM out of run-inner (mergeInspect/withReplyToOrigin/hasToolsList/inspectServerInitiated); three consumers now share it — relay, guard inspect, fixture release-gate. Drift+policy stay in run-inner (relay STATE, not properties of a frame). Relay behaviour UNCHANGED (a server-initiated frame carries method not result, so neither drift branch applies ⇒ merging pass-drift is a no-op). MINOR not patch: published verdicts CHANGE pass→block and external adapters depend on this seam. 2 new invariants — every attack fixture non-pass through the PUBLIC CLI (black-box, NOT a hand-composed mirror that could drift identically) + every catalog signature has a fixture. Found by DOGFOODING the published tarball] (#153); v0.26.3 = MCP tool schemas strict ON THE WIRE — registerTool given .shape drops object-level strict (closes #31) (#152); v0.26.2 = @sigstore majors validated vs LIVE npm attestations not fixtures (#151); v0.26.1 = registered guard-inspection-truncated in the catalog so its own remediation command works, + stale-doc sweep + guard-first README; v0.26.0 = SECURITY: closed a guard DETECTION BYPASS (leaf-walk budget exhaustion failed open) + a --json framing desync [stringLeaves hit its 100k node cap and returned SILENTLY, so every leaf past the budget went uninspected and the frame reported pass — 25,001 junk pad elements (73 KB) hid a critical tool-description injection; hit the LIVE RELAY (run-inner.ts:404/:445 share inspectMessage), not just guard inspect. Now emits guard-inspection-truncated at critical so it rides the carrier policy (BLOCKS on block-capable, clamps to warn on retrieved-data). FP risk MEASURED not assumed: largest corpus frame = 40 nodes vs 100k budget. The old regression test buried a BENIGN leaf and asserted pass — it certified the blind spot. Also: JSON.stringify leaves U+2028/U+2029 raw but Node readline splits on them ⇒ one verdict became two lines ⇒ positional desync that FORGED a pass on a real attack and a block on a benign case; C1 (U+0080–U+009F) also raw, reachable via the parse-error path with no signature match. Fixed losslessly in jsonLine] (#149); v0.25.0 = **mcpm guard inspectmcpm guard inspect — offline one-frame / NDJSON verdicts over MCP JSON-RPC frames, the PUBLIC seam an external benchmark uses to score the guard through the published binary instead of importing src/guard/* [new src/guard/inspect-cli.ts; file arg or stdin; single frame OR NDJSON; --json = one verdict per frame in INPUT ORDER so a harness can zip verdicts to its own ids without mcpm knowing them; unparseable frame ⇒ an explicit error verdict, never a fabricated pass; exit 0/1/2 = pass/warn-or-parse-error/block so it gates CI over captured traffic; process.exitCode not process.exit() so piped verdict lines can't truncate; reports the signature catalog's DEFAULT actions incl. the warn-only carrier clamp, policy overrides deliberately NOT applied] (#147); v0.24.0 = F8 verify-time re-check ("B3"): mcpm verify + up --frozen now RE-VERIFY Sigstore provenance and FAIL CLOSED on regression [new src/stack/frozen-provenance.ts sibling to the integrity gate; evidence-gated to crypto-verified locked baselines → zero FP on the unsigned ecosystem; blocks signer-changed / regression / unverifiable; wired on CLI + mcpm_up MCP surface + both auto-lock paths; a memoized shared integrity fetch across both gates; a sticky-baseline invariant — carry a verified baseline forward across a re-lock UNLESS the fresh read itself verifies, exhaustive-by-construction so a transient blip can't disarm the gate; cross-derivation drift uses a preserved parse-only payloadIdentity, namespace keyed off the unforgeable verification.outcome; hardened through 6 adversarial ultracode rounds — findings 17→10→7→6→4→2, HIGH→LOW, enforcing gate confirmed sound at R5; caught+fixed a could-not-verify carry gap, a cross-namespace FP, an unsigned/404 eviction lever, and a Postmark-warn regression] (#141); v0.23.0 = F8 crypto slice: mcpm lock/why OFFLINE Sigstore-verify npm SLSA provenance [3 audited @sigstore deps, engines→>=22.9; "verified" requires crypto-pass + GitHub-OIDC issuer + subject-binds-to-EVERY-sha512-dist.integrity-token, identity from the UNFORGEABLE Fulcio SAN; report-only, honest "build identity not code-safety"; hardened through 3 adversarial rounds — 2 live-reproduced false-verified CRITICALs fixed] (#136) + mcpm why provenance section (#135) + a whole-repo dead-code/dedup cleanup [net −871 LOC, #137] + a fail-closed pre-publish release-dogfood gate [pack→clean-install→smoke the real binary before pnpm publish, #138]; v0.22.0 = F8 slice 1: mcpm lock npm provenance-identity drift tripwire [parse-only, ZERO new deps, report-only; captures npm's published Sigstore attestation identity — source repo + immutable numeric GitHub repo/owner ids — and WARNs on drift / signed→unsigned = the Postmark shape; honest "attested = unverified record"] (#133); v0.21.0 = F9 PR1: mcpm doctor plaintext-secret scan over client env/header config [read-only advisory, key+label never value, skips keychain placeholders; two detectors — value-shape + a benign-corpus-gated secret-named-key heuristic] (#132); v0.20.1 = patch closing the 3 v0.20.0-review follow-ups [relay buffer-cap crash-loop → no-arg destroy, confine denylist drift-guard test, registry free-text .max() caps] (#131); v0.20.0 = response-side credential DLP [F10 Detector-A + B: warn-and-redact on credentials egressing in tool responses, incl. base64-decoded] + an adversarial security-review hardening pass [6 findings: zero-width sig bypass, confine denylist, deep-nesting blind spot, scanner ReDoS, relay crash, terminal-escape]; Wave-1 developer-reach batch [D4a/D7/D2/D3/D6] shipped in v0.19.0; Claude Code adapter [D1] in v0.18.0; guard --confine in v0.16.0) | bin command: mcpm | repo: github.com/getmcpm/cli | web UI: deferred to V1+

Read the full file on GitHub · 654 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. yesterday First seen · 654 lines · 35,320 tokens per session scan E 1a53a78f304e

Subscribe to this mod's changes

cli CLAUDE.md is an instructions file published in the GitHub repository getmcpm/cli (3 stars, last pushed yesterday), licensed MIT. It adds 35,320 tokens to every session, about $0.1766 per session on Opus 5. A static security scan graded it E with 3 findings (instruction-override phrasing, reaches for credential files, reads agent configuration directories). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.