{
  "markdown": "<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"./assets/banner-dark.svg\">\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"./assets/banner-light.svg\">\n    <img alt=\"mcpm - runtime guard for MCP\" src=\"./assets/banner-light.svg\" width=\"680\">\n  </picture>\n</p>\n\n# mcpm\n\n**A runtime security guard for your AI's tools -- and the package manager to install them.** Blocks prompt injection, tool poisoning, and credential exfiltration in live MCP traffic. Local-first, deterministic, no LLM in the enforcement path.\n\n[![npm version](https://img.shields.io/npm/v/@getmcpm/cli)](https://www.npmjs.com/package/@getmcpm/cli)\n[![license](https://img.shields.io/github/license/getmcpm/cli)](./LICENSE)\n[![tests](https://img.shields.io/github/actions/workflow/status/getmcpm/cli/ci.yml?label=tests)](https://github.com/getmcpm/cli/actions)\n[![Known Vulnerabilities](https://snyk.io/advisor/npm-package/@getmcpm/cli/badge.svg)](https://snyk.io/advisor/npm-package/@getmcpm/cli)\n\n---\n\nThe risky part of an MCP server doesn't show up at install -- it shows up while your agent is running: prompt injection hidden in a tool's output, a server that quietly rewrites its tools after you approved them, a sampling request that smuggles instructions into your model. Being listed in a registry is not a safety signal -- in 2026 a proof-of-concept poisoned server was [accepted by 9 of 11 public registries and marketplaces](https://www.ox.security/blog/mcp-supply-chain-advisory-rce-vulnerabilities-across-the-ai-ecosystem/). So mcpm scores every install for hardcoded secrets, prompt injection, and typosquatting -- then runs a live guard between your AI client and each server, pinning tool definitions against rug-pulls and blocking injection before it reaches the model.\n\n**You don't have to take our word for any of that.** Guards are easy to claim and hard to check, so the measuring stick is public: [**mcp-guardbench**](https://github.com/getmcpm/mcp-guardbench) is a guard-agnostic benchmark -- versioned attack and benign cases, an open schema, and a runner that scores *any* MCP guard through its own published CLI. mcpm is scored the same way as everyone else, by shelling out to `mcpm guard inspect`, never by importing its own engine.\n\nmcpm currently scores 100% recall at a 0% false-positive rate on that corpus -- which is **by construction**, since the corpus was extracted from mcpm's own test fixtures. That is a baseline, not a boast. It gets interesting when someone scores a second guard, or contributes a case mcpm misses.\n\n<p align=\"center\">\n  <img src=\"./assets/demo.gif\" alt=\"mcpm demo\" width=\"680\">\n</p>\n\n## Quick start\n\nInstall with the package manager you already use:\n\n| Method | Command |\n|---|---|\n| **npm** (global) | `npm install -g @getmcpm/cli` |\n| **npx** (no install) | `npx @getmcpm/cli <command>` |\n| **pnpm** | `pnpm add -g @getmcpm/cli` |\n| **mise** | `mise use -g npm:@getmcpm/cli` |\n\n**Requires Node `^22.22.2 || ^24.15.0 || >=26.0.0`** — that is **22.22.2+, 24.15.0+ or\n26+**, the intersection of what every runtime dependency itself supports. Everything\nelse is excluded, 23.x and 25.x included. npm warns `EBADENGINE` and fails outright\nunder `--engine-strict`; pnpm installs silently and exits 0 unless you set\n`engine-strict=true`, so there an unsupported Node surfaces as a runtime error rather\nthan an install one.\n\nThe binary is `mcpm`. **Heads up:** the `mcpm` Homebrew formula is a *different,\nunrelated* project ([mcpm.sh](https://mcpm.sh)) — install this mcpm via\nnpm/npx/pnpm/mise above (all resolve the scoped `@getmcpm/cli` package, so there's\nno name collision). A dedicated Homebrew tap is deferred; see\n[`docs/DISTRIBUTION.md`](docs/DISTRIBUTION.md).\n\n```bash\nmcpm search filesystem\nmcpm info io.github.domdomegg/filesystem-mcp\nmcpm install io.github.domdomegg/filesystem-mcp\n```\n\n## Features\n\n### Search the MCP registry\n\nQuery the official MCP Registry and see results with trust indicators.\n\n```\n$ mcpm search filesystem\n\n  Name                                    Description                   Version  Transport  Status\n  io.github.domdomegg/filesystem-mcp       File system access via MCP    1.4.0    stdio      active\n  io.github.Digital-Defiance/mcp-filesystem Read-only filesystem server  0.9.2    stdio      active\n  ...\n```\n\nSearch shows registry lifecycle status, not a trust score -- it is a fast discovery list and does not run the scanner per result. Computed trust lives in `mcpm why`, `info`, `install`, and `audit`.\n\n### Install with trust assessment\n\nEvery install runs a metadata-based trust assessment before writing config.\n\n```\n$ mcpm install io.github.domdomegg/filesystem-mcp\n\n  ███████████████░░░░░ 57/80 CAUTION\n    ├─ Health check: not yet run\n    ├─ Tool descriptions: score 32/40\n    ├─ Package: publisher verification passed\n    └─ External scan: not available (set MCPM_EXTERNAL_SCANNER for deeper analysis)\n\n  Install to Claude Desktop? (Y/n)\n```\n\n### Audit installed servers\n\nScan everything you have installed. Get a trust report.\n\n```\n$ mcpm audit\n\n  Server                                   Score  Level    Findings\n  servers-filesystem                        72/80  safe     0\n  servers-github                            52/80  caution  2\n  some-sketchy-server                       24/80  risky    5\n```\n\n### Cross-IDE support\n\nOne tool for all your AI clients. mcpm reads and writes the correct config format for each.\n\n```\n$ mcpm list\n\n  Client            Server Name                  Status     Command/URL\n  Claude Desktop    servers-filesystem           active     npx -y servers-filesystem\n  Claude Desktop    servers-github               active     npx -y servers-github\n  Cursor            servers-fetch                disabled   npx -y servers-fetch\n```\n\n### Doctor: check your MCP setup health\n\nFind misconfigurations, missing runtimes, broken servers, and plaintext secrets pasted into client config.\n\n```\n$ mcpm doctor\n\n  Checking MCP setup...\n  [pass] Claude Desktop config found\n  [pass] npx runtime available\n  [warn] Cursor config not found\n  [pass] 3 servers installed, 0 with errors\n  [warn] plaintext secret (advisory): github-mcp · env 'GITHUB_TOKEN' — GitHub token\n         Move it to the encrypted store: mcpm secrets set <server> <KEY>\n```\n\nThe plaintext-secret scan reports the key name and label only — never the value — and skips values already stored as `mcpm:keychain:` placeholders. It's advisory (never fails `doctor`).\n\n### Stack files: docker-compose for MCP\n\nDeclare your project's MCP servers in `mcpm.yaml`, lock versions with trust snapshots, and let every team member replicate the setup with one command.\n\n```bash\nmcpm export > mcpm.yaml          # dump current setup\nmcpm lock                        # resolve versions + trust snapshot\nmcpm up                          # install everything from mcpm.yaml\nmcpm diff                        # compare installed vs declared state\n```\n\nStack files include a trust policy. If a server's trust score drops below the threshold, `mcpm up` blocks it.\n\n```yaml\nversion: \"1\"\npolicy:\n  minTrustScore: 60\n  blockOnScoreDrop: true\nservers:\n  io.github.domdomegg/filesystem-mcp:\n    version: \"^1.0.0\"\n  io.github.modelcontextprotocol/servers-github:\n    version: \"1.2.3\"\n    env:\n      GITHUB_TOKEN: { required: true, secret: true }\n```\n\n### Scaffold a stack file\n\nStart a new project's MCP setup in one command. `mcpm init` writes a starter `mcpm.yaml` you fill in with servers from `mcpm search`.\n\n```\n$ mcpm init\n\n  Created mcpm.yaml.\n\n  Next steps:\n    mcpm search <query>   find MCP servers in the registry\n    edit mcpm.yaml        add them under `servers:`\n    mcpm lock             resolve and lock versions\n    mcpm up               install from the stack file\n```\n\nIt refuses to clobber an existing `mcpm.yaml` (pass `--force` to overwrite). mcpm deliberately doesn't ship curated packs — blessing specific community servers is a trust decision a security tool shouldn't bake in.\n\n## Trust score\n\nThe trust score is a 0-100 assessment based on publicly available metadata. It is **not** a source code audit.\n\nWhat it checks:\n\n| Component | Points | What it measures |\n|---|---|---|\n| Health check | 0-30 | Can the server start and respond to `list_tools`? |\n| Static scan | 0-40 | Regex-based detection of hardcoded secrets, prompt injection patterns in tool descriptions, typosquatting in package names, suspicious argument schemas |\n| External scanner | 0-20 | Results from a third-party scanner you have installed, opt-in via `MCPM_EXTERNAL_SCANNER` (off by default) |\n| Registry metadata | 0-10 | Verified publisher, publish date, download count (capped to 0 when critical findings present) |\n\nLevels are a **ratio** of the points available, not absolute: **safe** at 80% of `maxPossible` or better, **caution** at 50-79%, **risky** below 50%. With no external scanner (`maxPossible` 80) that puts safe at 64 points, not 80.\n\n`mcpm audit` shows **`clean · not run`** where the server cleared every check that actually ran and the only unmeasured component is the health check — audit never executes servers, so that bucket scores a flat 15/30 and would otherwise drag every flawless server into **caution**. It is a statement about what mcpm did (found nothing, ran nothing), deliberately weaker than **safe**, which is reserved for servers whose health check really ran.\n\nWithout an external scanner, the maximum possible score is 80/100 and the bucket is dropped from the total rather than counted as a failure. The static scan catches common patterns but cannot detect all vulnerabilities. Treat the score as a signal, not a guarantee.\n\n**External scanning is opt-in and mcpm never downloads a scanner.** Set `MCPM_EXTERNAL_SCANNER` to the path or name of a scanner you have already installed. mcpm probes it with `<scanner> --version` and, if that exits 0, scans each server with `<scanner> --json <server-name>`, expecting `{\"findings\": [...]}` on stdout. A scanner whose output cannot be read is treated as **absent** rather than as a clean pass, so the bucket leaves the total instead of silently earning 20/20 — otherwise any binary that exits 0 would raise trust scores.\n\nPackage runners (`npx`, `uvx`, `pipx`, `docker`, shells, …) are refused, including via symlink or a runner's `-cli.js` entrypoint. Be clear about what that is worth: it is a **footgun guard**, not a security boundary. Anyone who can set this variable can usually set `PATH` or drop a file too. What it buys is that a pasted `npx …` recipe — or a future mcpm default drifting back toward one — cannot quietly re-create the fetch-and-execute vector this seam was rebuilt to remove.\n\nNo scanner is recommended here because none evaluated so far fits: `snyk/agent-scan` (the actively maintained successor to Invariant Labs' mcp-scan) scans local agent config files and machine-wide installs — including starting each MCP server to inspect it live — rather than accepting a single registry server name, and its JSON output isn't the flat `{\"findings\": [...]}` shape above. See `src/scanner/tier2.ts`'s header comment for the full evaluation.\n\nThose 20 points **inform the score but cannot clear a safety floor.** The MCP server surface (`mcpm_install`, `mcpm_up`) enforces a hard trust floor of 25 that no caller-supplied value may lower — and since `MCPM_EXTERNAL_SCANNER` names an arbitrary executable, a two-line script printing `{\"findings\": []}` is caller-supplied input too. So the floor is compared against mcpm's own evidence only: health check + static scan + registry metadata, out of 80. The exclusion is one-directional — a scanner reporting a critical finding still drags a server *down* through the floor (via the registry-metadata cap), it just can't push one up through it. Your own `--min-trust` threshold, a stack file's `policy.minTrustScore`, and `mcpm audit --fix` are unaffected: there the same person picks both the threshold and the scanner. `audit --fix` is also the one score gate that *deletes* rather than refuses, so subtracting the bucket there would remove more servers, not fewer.\n\n## Commands\n\n| Command | Description |\n|---|---|\n| `mcpm search <query>` | Search the MCP registry for servers |\n| `mcpm install <name>` | Install an MCP server from the registry |\n| `mcpm info <name>` | Show full details for an MCP server |\n| `mcpm list` | List all installed MCP servers across detected AI clients |\n| `mcpm remove <name>` | Remove an MCP server from client config(s) |\n| `mcpm audit` | Scan all installed servers and produce a trust report (`--json`, `--sarif` for GitHub code-scanning) |\n| `mcpm update` | Check for newer versions and update installed servers |\n| `mcpm outdated` | Show version drift for installed servers (use `mcpm audit` for current security findings) |\n| `mcpm secrets` | Manage MCP server credentials (AES-GCM encrypted at rest; key held in the OS keychain — macOS Keychain / libsecret / Windows DPAPI — so a copied store can't be decrypted off-machine, with a machine-derived-key fallback where no keychain is available). `mcpm secrets migrate` upgrades older entries |\n| `mcpm publish scaffold` | Create a .mcpm-publish.yaml manifest interactively |\n| `mcpm publish check` | Dry-run: show trust score and what would be submitted |\n| `mcpm publish` | Submit to the official MCP registry (requires GITHUB_TOKEN) |\n| `mcpm doctor` | Check MCP setup health and report issues (`--json` structured model, `--report` redacted paste-for-bug-reports snapshot) |\n| `mcpm init` | Scaffold a starter `mcpm.yaml` stack file in the current directory |\n| `mcpm disable <name>` | Disable an MCP server without removing it from config |\n| `mcpm enable <name>` | Re-enable a previously disabled MCP server |\n| `mcpm import` | Import existing MCP servers from client config files |\n| `mcpm alias` | Create short aliases for long MCP server names |\n| `mcpm export` | Export installed servers as an mcpm.yaml stack file |\n| `mcpm lock` | Resolve versions and create mcpm-lock.yaml with trust snapshots (+ npm provenance identity, WARNs on drift) |\n| `mcpm up` | Install all servers from mcpm.yaml with trust verification |\n| `mcpm verify` | Repo-only CI gate: verify lockfile integrity + re-verify Sigstore provenance vs npm's published record (`--json`) |\n| `mcpm diff` | Compare installed servers against mcpm.yaml and lock file |\n| `mcpm sync` | Show cross-client config drift across all detected clients (`--check` gates CI with exit 2, `--json`) |\n| `mcpm completions <shell>` | Generate shell completion scripts (bash, zsh, fish) |\n| `mcpm why <name>` | Explain a server's trust score (breakdown of all components) |\n| `mcpm serve` | Start mcpm as an MCP server (stdio transport) |\n| `mcpm guard enable` | Wrap detected client configs with the inspection relay |\n| `mcpm guard disable` | Restore original client configs |\n| `mcpm guard status` | Show what's wrapped and the per-server pin state |\n| `mcpm guard demo` | Run the synthetic prompt-injection scenario (visible block) |\n| `mcpm guard accept-drift <server>` | Re-pin a tool's schema after a legitimate upgrade |\n| `mcpm guard mute <signature-id>` | Disable a signature with optional `--for <duration>` |\n| `mcpm guard unmute <signature-id>` | Re-enable a muted signature |\n| `mcpm guard pause` | Pause all guard inspection (debugging escape hatch) |\n| `mcpm guard cleanup` | Prune pin entries for uninstalled servers |\n| `mcpm guard inspect [file]` | Run the signature catalog over MCP JSON-RPC frame(s), offline — no relay, no server |\n| `mcpm guard list-signatures` | Show the shipped OWASP MCP Top 10 signature catalog |\n| `mcpm guard reset-integrity` | Regenerate the pins.json or guard-policy.yaml integrity sidecar |\n\nRun `mcpm <command> --help` for options and flags.\n\n## CI: verify your lockfile\n\n`mcpm verify` is a repo-only, **client-free** gate: it checks your committed\n`mcpm-lock.yaml` against npm's **published** `dist.integrity` record and exits\nnon-zero on integrity drift, an unverifiable record, a format mismatch, or a\nsuspicious missing baseline. Because it needs no AI clients installed, it runs on a\nhosted CI runner (where `mcpm up` cannot).\n\nIt also checks that the lock **covers** what `mcpm.yaml` declares. The other gates\nread only the lock, so they pass over a lock that is missing servers — the coverage\ncheck is what stops a truncated lock from verifying green while enforcing less than\nyou asked for. A lock holding no servers at all never passes unless an `mcpm.yaml`\nbeside it confirms nothing was declared. Correspondingly, `mcpm lock` is\nall-or-nothing: if any server fails to resolve it reports every failure and writes\n**nothing**, leaving the previous lock intact.\n\nIt **also re-verifies Sigstore provenance** for every npm server whose lock recorded\na cryptographically `verified` baseline: it re-runs the offline crypto verification\nagainst npm's current record and fails closed if the attestation regressed — the\nsigner identity changed, it no longer verifies, or it can't be re-checked. This is\nevidence-gated, so a lock with no `verified` baselines (the common case) is\nunaffected. `mcpm up --frozen` runs the same integrity + provenance freeze before\ninstalling.\n\n`mcpm lock` also records each npm server's **published provenance identity** (the\nsource repo + immutable GitHub repo/owner ids behind the build) and WARNs when it\ndrifts across versions — a repo/owner change or a signed→unsigned drop, the shape\nof a hijacked-publish (Postmark) attack.\n\n`mcpm lock` and `mcpm why` additionally **cryptographically verify** the provenance\n**offline** — the attestation's Sigstore bundle is checked against a vendored trust\nroot (no network at verify time), and the attested subject digest is bound to the\npackage's `dist.integrity`, so a valid attestation for a *different* tarball can't\npass. When it holds, the record reads `verified`; otherwise `attested` — an\n*unverified* registry record — or `unsigned` (neutral). Report-only, and honest\nabout scope: `verified` means the **build identity** is cryptographically attested\nby the CI's OIDC token — **not** that the code is safe (a same-repo CI compromise\nmints a valid attestation).\n\n```yaml\n# .github/workflows/mcpm.yml\njobs:\n  verify:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: getmcpm/cli/.github/actions/mcpm-verify@v0.26.0   # or: run: npx @getmcpm/cli verify\n```\n\nThe Action writes a job step summary from `--json`; the same verb works as a\npre-commit hook. See [`.github/actions/mcpm-verify`](.github/actions/mcpm-verify).\n\n**Code scanning:** `mcpm audit --sarif` emits a SARIF 2.1.0 report (one rule per\nfinding type, findings anchored file-level to `mcpm.yaml`) that GitHub ingests as\ncode-scanning alerts:\n\n```yaml\n      - run: npx @getmcpm/cli audit --sarif > mcpm.sarif\n        continue-on-error: true   # audit exits 1 when a server is risky\n      - uses: github/codeql-action/upload-sarif@v3\n        if: always()              # upload even if the audit flagged a risk\n        with:\n          sarif_file: mcpm.sarif\n```\n\n`mcpm audit` exits `1` when a server is risky and `2` when the invocation itself\ncannot be satisfied — an impossible `--min-trust`, or a flag combination mcpm\nrefuses (`--sarif` is report-only and cannot be combined with `--fix`). A script\nthat treats every non-zero exit as \"a server is risky\" will misread the second.\n\n> Honesty boundary: a failure means npm's *published record* diverged from your\n> lock — not that mcpm caught malicious bytes; npx/uvx fetch the artifact\n> independently at server launch.\n\n## Runtime defense (mcpm-guard)\n\nInstall-time trust scoring catches most poisoned servers before they ship. But what about **rug-pulls** — a server that changes its tool definitions after you've already approved them? Or **prompt-injection in tool responses** — adversarial text embedded in a Slack message, web page, or calendar invite that the agent reads through your trusted MCP server?\n\n`mcpm guard` adds a runtime inspection layer. It wraps every installed MCP server with a stdio relay, scans tool descriptions / responses / arguments for OWASP MCP Top 10 attack patterns, pins each tool's schema at install time, and blocks calls when the live response drifts from the pin (rug-pull defense).\n\n### What happens on every tool call\n\nThe guard sits inline on the stdio channel between your AI client and each MCP server, so it sees **both halves of every tool call** and inspects them as they pass:\n\n- **The request your agent sends** — the tool name and arguments, checked for sensitive-path exfil and injection smuggled into call parameters.\n- **The response the server returns** — checked for instruction injection hidden in the tool's output (the Slack message, web page, or calendar invite your agent is about to read).\n- **The tool's own definition** — `tools/list` descriptions, schemas, and annotations, checked against the schema pinned at approval time, so a server can't quietly rewrite a tool you already trusted.\n\nWhen a frame trips a signature, drift check, or policy rule, the guard **drops it and hands your agent a JSON-RPC error in its place** — carrying the signature id and a `remediation` string — so the poisoned content never reaches your model. Clean calls pass straight through (p99 ~0.065 ms on small frames). Server-initiated `sampling` / `elicitation` requests are inspected the same way, with the error routed back to the server rather than to your agent.\n\n### Quick start\n\n```bash\nnpm install -g @getmcpm/cli@latest\n\nmcpm guard enable           # wrap detected client configs (Claude Desktop / Claude Code / Cursor / VS Code / Windsurf / Gemini CLI)\n# → restart your IDE so it re-spawns the wrapped server processes\nmcpm guard demo             # synthetic prompt-injection scenario — see a live block in your terminal\nmcpm guard status           # what's protected, what's still in first-session-pin mode\n```\n\nThe `demo` command boots an in-process synthetic malicious server that returns a canned prompt-injection payload; the relay blocks it. Total time from `npm install` to a screenshot-worthy block: ~5 minutes (most of which is the IDE restart).\n\n### What it catches\n\n| Category | Attack class | Action |\n|---|---|---|\n| OWASP-MCP-1 | Tool-description injection (poisoning) | block |\n| OWASP-MCP-1 | Schema / annotation drift since install (rug-pull) | block |\n| OWASP-MCP-1 | Description-only drift (cosmetic tier) | warn |\n| OWASP-MCP-1 | Injection in `initialize` instructions | block |\n| OWASP-MCP-2 | Instruction injection in tool responses | block |\n| OWASP-MCP-2 | Instruction injection in resource / prompt content | warn (forward) |\n| OWASP-MCP-7 | Sensitive-path exfil in tool arguments | warn (promote to block via policy) |\n| OWASP-MCP-1 | Exfil-named parameter in a tool's input schema (`_system_prompt_`, …) | block (list-time) |\n| Credential phishing | Server solicits a wallet seed phrase / private key / card CVV / SSN / PIN | block (to the server) |\n| Credential egress | High-confidence secret returned in a tool response | warn (secret redacted in the log) |\n| Hidden chars | Zero-width / bidi / non-printable / Unicode TAG block in tool metadata | high (warn) |\n| Unicode TAG block | Payload concealed in U+E0000–U+E007F on any carrier (\"ASCII smuggling\") | decoded and re-scanned — the recovered signature decides |\n| Sampling | Injection in a server-initiated `sampling` prompt | block (to the server) |\n\nDetection is regex + structural; NFKC + zero-width-char stripping defeats the common Unicode evasions, and a separate hidden-character *presence* check flags evasion carriers before they're normalized away. [\"ASCII smuggling\"](https://arxiv.org/abs/2607.05744) -- hiding a payload in the Unicode TAG block (U+E0000-U+E007F), which renders as nothing but is readable by a model -- gets two dedicated passes, because that stripping ERASES a fully encoded payload rather than revealing it. The guard decodes TAG runs back to ASCII and re-runs the carrier's own signatures, so a concealed payload is judged by what it says: a TAG-encoded wallet-seed solicitation is blocked by the credential-phishing signature, not merely noted as suspicious. Beneath that sits a presence floor for payloads that are concealed but match nothing. Emoji subdivision flags are built from the same codepoints, so the three a client actually renders (England, Scotland, Wales) are carved out by whole-sequence validation; another well-formed subdivision flag still warns. Base64 / base64url payloads inside server responses are also decoded and re-scanned, so an injection or credential hidden behind an encoding can't slip past the regex floor (base64-decoded hits warn, never hard-block; TAG-decoded hits keep their native severity, since concealment on that plane is not something benign content does). See `mcpm guard list-signatures` for the current shipped set.\n\n\n### Confinement (opt-in enforcement)\n\nEverything above is *detection* — the relay reasons about the JSON-RPC bytes and warns or blocks. But a server that decides to read `~/.ssh` or write a `~/Library/LaunchAgents` persistence hook never expresses that through inspectable traffic. `mcpm guard enable --confine` complements detection with *enforcement*: it wraps each relayed stdio server in an OS sandbox that physically denies reads of a secret-file denylist and writes outside caches/scratch, so the server can't exfil credentials or persist regardless of the JSON-RPC it emits. `mcpm guard doctor-confine` reports backend availability and which servers are enrolled. **macOS only** for now (Seatbelt/`sandbox-exec`); on other platforms it warns and runs unconfined rather than giving a false sense of protection. See `docs/GUARD.md` for the tier details and caveats.\n\n### Day-1 commands\n\n```bash\nmcpm guard enable [--client <name>] [--server <name>] [--dry-run]    # wrap detected configs\nmcpm guard disable [--client <name>] [--server <name>]               # unwrap\nmcpm guard status                                                    # what's wrapped + pin state\nmcpm guard demo                                                      # synthetic attack-block demo\nmcpm guard list-signatures [--json]                                  # show shipped signatures\nmcpm guard inspect frames.ndjson                                     # verdicts for captured traffic, offline\nmcpm guard enable --confine                                          # also OS-sandbox wrapped stdio servers (macOS)\nmcpm guard doctor-confine [--json]                                   # confine backend availability + enrolled servers\n```\n\n### Inspect frames without a server\n\n`mcpm guard inspect` runs the same signature catalog the relay uses over MCP JSON-RPC frames you already have — a captured response, a suspicious `tools/list`, a payload from a writeup — with no wrapped server, no relay, and no network:\n\n```bash\n# one frame, human-readable\nmcpm guard inspect suspicious-response.json\n\n# a capture, machine-readable — one verdict line per input frame, in input order\ncat frames.ndjson | mcpm guard inspect --json\n```\n\nExit status makes it a CI gate over recorded traffic: `0` all clear, `1` something warns (or a frame wouldn't parse), `2` something would be blocked.\n\nVerdicts are the signature catalog's default actions, including the warn-only carrier clamp — so an injection in a `resources/read` body reports `warn` here exactly as it would inline. Your local policy overrides (mutes, `log_only`) are deliberately *not* applied: this answers \"what do the signatures see\", not \"what would my config do\".\n\nIt is also the seam [mcp-guardbench](https://github.com/getmcpm/mcp-guardbench) uses to score mcpm's guard — through this published binary, never by importing the engine, so mcpm is measured on exactly the same footing as any other guard. [**mcp-guardbench**](https://github.com/getmcpm/mcp-guardbench) is the reference consumer: a guard-agnostic corpus + runner that scores any MCP guard this way.\n\n### When a block fires\n\nThe relay returns a JSON-RPC error response to your IDE with the signature id + a `remediation` string telling you exactly which command to run. Two typical cases:\n\n```bash\n# False positive on a legitimate signature\nmcpm guard mute owasp-mcp-2-instruction-injection-in-response --for 5m\n\n# Schema drift on a legitimate server upgrade\nmcpm guard accept-drift slack-mcp --tool send_message --new-hash sha256:abc... --yes\n```\n\n### Audit the log\n\nEvery block / warn is appended to `~/.mcpm/guard-events.jsonl`. Inspect with `jq`:\n\n```bash\n# Last hour's blocks\ntail -n 1000 ~/.mcpm/guard-events.jsonl | jq 'select(.action == \"block\")'\n\n# Group by signature id\njq -s 'group_by(.findings[0].signature_id) | map({sig: .[0].findings[0].signature_id, n: length})' \\\n   < ~/.mcpm/guard-events.jsonl\n\n# Top-N most-blocked servers\njq -s 'group_by(.server_name) | map({server: .[0].server_name, n: length}) | sort_by(-.n) | .[:10]' \\\n   < ~/.mcpm/guard-events.jsonl\n```\n\n### When you're debugging and need to turn it off briefly\n\n```bash\nmcpm guard pause --for 10m     # disables all inspection for 10 minutes\nmcpm guard pause --off         # cancel an active pause\n```\n\n### Why this exists\n\nIndependent 2026 evidence for each thing the guard does, so you can check the premise rather than trust the pitch:\n\n- **[NSA AI Security Center, \"MCP: Security Design Considerations\"](https://media.defense.gov/2026/Jun/02/2003943289/-1/-1/0/CSI_MCP_SECURITY.PDF)** — recommends filtering outbound proxies, data-loss prevention, sandboxing, and local MCP scanning. That is the guard relay, the credential-egress detectors, `--confine`, and `mcpm audit`, in one government document.\n- **[OX Security, \"Mother of All AI Supply Chains\"](https://www.ox.security/blog/mcp-supply-chain-advisory-rce-vulnerabilities-across-the-ai-ecosystem/)** (2026-04-15) — 10 assigned critical/high CVEs from the config-to-process-spawn design, and a proof-of-concept poisoned server accepted by 9 of 11 public registries and marketplaces. Registry listing is not a safety signal.\n- **Microsoft's tool-poisoning warning** (2026-06-30, [reported here](https://thehackernews.com/2026/06/microsoft-warns-poisoned-mcp-tool.html)) — poisoned tool descriptions steer an agent about as effectively as rewriting its system prompt; the recommended mitigation is code-review-style diffing of description changes. Schema pinning plus drift detection covers the detection half: mcpm tells you a description changed since you approved it, and blocks on schema or annotation drift. It does not render a before/after diff of the text.\n- **[SmartLoader](https://www.straiker.ai/blog/smartloader-clones-oura-ring-mcp-to-deploy-supply-chain-attack)** (disclosed Feb 2026) — a trojanized Oura Ring MCP server, backed by fake GitHub accounts with manufactured social proof, seeded into legitimate registries to drop an infostealer. Stars and listings are forgeable, which is why `mcpm lock`, `why` and `verify` check build provenance instead (it is reported and gated there, not folded into the trust score). Note the honest limit: provenance attests *who built a package*, not that the code is safe — an attacker publishing their own trojanized package from their own CI gets valid provenance. It raises the cost of impersonating someone else; it would not by itself have stopped SmartLoader.\n- **[The official registry's own moderation policy](https://modelcontextprotocol.io/registry/moderation-policy)** — consumers \"should assume minimal-to-no moderation\", with security scanning explicitly delegated to package registries and downstream subregistries. mcpm is one of those downstream layers.\n\n### Read more\n\n- `docs/GUARD.md` — full command reference\n- `docs/SIGNATURES.md` — signature catalog + how to contribute new ones\n- `docs/POLICY.md` — `~/.mcpm/guard-policy.yaml` reference\n- `docs/owasp-mcp-mapping.md` — every mcpm mechanism mapped to an OWASP MCP Top 10 (beta) category, with an honest gap list for what's not covered\n- `docs/VISION.md` — where the project is going (thesis, horizons, doctrine)\n\n## Agent mode\n\nmcpm can run as an MCP server itself, letting AI agents search, install, and audit MCP servers programmatically.\n\n```json\n{\n  \"mcpServers\": {\n    \"mcpm\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@getmcpm/cli\", \"serve\"]\n    }\n  }\n}\n```\n\nThis exposes 9 tools: `mcpm_search`, `mcpm_install`, `mcpm_info`, `mcpm_list`, `mcpm_remove`, `mcpm_audit`, `mcpm_doctor`, `mcpm_setup`, and `mcpm_up`.\n\nThe `mcpm_setup` tool takes a natural language description like \"filesystem and GitHub\" and handles everything: search, trust scoring, install. One tool call to assemble a working MCP toolchain.\n\n**Try it** -- add the config above to your MCP client, restart, then ask your agent:\n\n> You have mcpm tools available (from @getmcpm/cli, the MCP package manager, not the Minecraft one). Use them to find MCP servers for filesystem access and GitHub. Check their trust scores and install anything above 60.\n\n## Supported clients\n\n| Client | Config path (macOS) |\n|---|---|\n| Claude Desktop | `~/Library/Application Support/Claude/claude_desktop_config.json` |\n| Claude Code | `~/.claude.json` (user-global `mcpServers`) |\n| Cursor | `~/.cursor/mcp.json` |\n| VS Code | `~/Library/Application Support/Code/User/mcp.json` |\n| Windsurf | `~/.codeium/windsurf/mcp_config.json` |\n| Gemini CLI | `~/.gemini/settings.json` (user-global `mcpServers`) |\n\nLinux and Windows paths are also supported. See `mcpm doctor` to verify which clients are detected on your system.\n\n## How it works\n\nmcpm is a local-first CLI. There is no mcpm backend or account system.\n\n```mermaid\nflowchart TD\nsubgraph user[\"User / Terminal\"]\n    CLI[\"mcpm CLI<br/>Commander entry point\"]\nend\n\nsubgraph commands[\"Commands (src/commands/)\"]\n    SEARCH[\"search\"]\n    INSTALL[\"install\"]\n    AUDIT[\"audit\"]\n    GUARD[\"guard<br/>enable/disable/status\"]\nend\n\nsubgraph registry[\"Registry API<br/>(Only Remote)\"]\n    REGAPI[\"https://registry.<br/>modelcontextprotocol.io<br/>v0.1\"]\nend\n\nsubgraph scanning[\"Local Scanning & Trust<br/>(src/scanner/)\"]\n    HEALTH[\"Health Check<br/>(0-30): spawn +<br/>verify response\"]\n    TIER1[\"Tier 1: Static Patterns<br/>(0-40): secrets, injection,<br/>typosquatting, exfil\"]\n    TIER2[\"Tier 2: External Scan<br/>(0-20): opt-in via<br/>MCPM_EXTERNAL_SCANNER\"]\n    META[\"Registry Metadata<br/>(0-10): publisher,<br/>age, downloads\"]\n    SCORE[\"Trust Score<br/>(max 80; 100 with<br/>external scan)\"]\nend\n\nsubgraph config[\"Config Management<br/>(src/config/adapters/)\"]\n    DETECT[\"Detect AI clients<br/>Claude Desktop / Claude Code / Cursor<br/>VS Code / Windsurf / Gemini CLI\"]\n    ATOMIC[\"Atomic writes<br/>0o600 + symlink-safe<br/>.tmp/.bak\"]\nend\n\nsubgraph guard_runtime[\"Guard Runtime<br/>(src/guard/)\"]\n    WRAP[\"Config entry wrap<br/>via run --inner\"]\n    RELAY[\"Stdio MITM Relay<br/>per-server\"]\n    PATTERNS[\"Pattern Engine<br/>OWASP MCP Top 10\"]\n    PINS[\"Schema Pins<br/>+ Drift Detection\"]\n    FAILCLOSED[\"Fail-closed on<br/>pins.json error\"]\n    EVENTS[\"Event Log<br/>guard-events.jsonl\"]\nend\n\nsubgraph local_state[\"Local State<br/>(~/.mcpm/)\"]\n    SERVERS[\"servers.json\"]\n    CACHE[\"cache/\"]\n    PINS_STORE[\"pins.json\"]\n    POLICY[\"guard-policy.yaml\"]\nend\n\nsubgraph clients[\"Native AI Clients\"]\n    CD[\"Claude Desktop\"]\n    CC[\"Claude Code\"]\n    CURSOR[\"Cursor\"]\n    VSCODE[\"VS Code\"]\n    WINDSURF[\"Windsurf\"]\n    GEMINI[\"Gemini CLI\"]\nend\n\nCLI --> commands\ncommands -->|searchServers| REGAPI\ncommands -->|scan| HEALTH\ncommands -->|scan| TIER1\ncommands -->|if available| TIER2\ncommands -->|registry meta| META\nHEALTH --> SCORE\nTIER1 --> SCORE\nTIER2 --> SCORE\nMETA --> SCORE\ncommands -->|detect| DETECT\ncommands -->|merge & write| ATOMIC\nDETECT -->|config paths| clients\nATOMIC -->|config| clients\nGUARD -->|wrap| WRAP\nWRAP -->|modifies config<br/>to invoke| clients\nWRAP -->|setup| RELAY\nRELAY -->|parse frames<br/>inspect msg| PATTERNS\nPATTERNS -->|check pins| PINS\nPINS -->|read| PINS_STORE\nPATTERNS -->|read policy| POLICY\nPINS -->|fail-closed| FAILCLOSED\nRELAY -->|record| EVENTS\ncommands -->|store| SERVERS\ncommands -->|cache| CACHE\n```\n\n1. **Search and install** query the [official MCP Registry API](https://registry.modelcontextprotocol.io) (v0.1) maintained by the Model Context Protocol project.\n2. **Trust assessment** runs locally using built-in scanners (regex-based pattern detection), and can additionally shell out to a third-party scanner you have installed and named via `MCPM_EXTERNAL_SCANNER`.\n3. **Config management** reads and writes the native config file for each AI client. All writes use atomic file operations with restricted permissions (0o600 files, 0o700 directories).\n4. **Local state** lives in `~/.mcpm/` (installed server registry, scan results, response cache).\n\nNo telemetry. No analytics. No account required.\n\n## Contributing\n\nContributions are welcome.\n\n```bash\ngit clone https://github.com/getmcpm/cli.git\ncd cli\npnpm install\npnpm test\npnpm build\n```\n\nBefore submitting a PR:\n\n- Run `pnpm test` and ensure all tests pass\n- Run `pnpm lint` to check types\n- Keep commits focused -- one change per commit\n- Follow [conventional commit](https://www.conventionalcommits.org/) format\n\nThis project is MIT licensed. See [LICENSE](./LICENSE).\n\n## Security\n\nIf you discover a security vulnerability, please use [GitHub's private vulnerability reporting](https://github.com/getmcpm/cli/security/advisories/new) instead of opening a public issue. We will respond within 48 hours.\n\nFor trust assessment issues (false positives/negatives in the scanner), regular GitHub issues are fine.\n\n## License\n\n[MIT](./LICENSE)\n",
  "bytes": 37608,
  "sha": "054c2b9d9ded881c74c01d14925eb8f3ccbed200f609cb7bae704bd165d89b15",
  "repo_slug": "getmcpm/cli",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_getmcpm_cli_b414d7ac/readme"
}