{"_id":"@anma-labs/mcpgaze","name":"@anma-labs/mcpgaze","dist-tags":{"latest":"1.0.1"},"versions":{"1.0.1":{"name":"@anma-labs/mcpgaze","version":"1.0.1","description":"A transparent wiretap for MCP servers. See exactly what your AI client sends your server — without breaking the protocol — and catch tool-schema drift in CI.","repository":{"type":"git","url":"git+https://github.com/anma-labs/mcpgaze.git"},"homepage":"https://github.com/anma-labs/mcpgaze#readme","bugs":{"url":"https://github.com/anma-labs/mcpgaze/issues"},"type":"module","bin":{"mcpgaze":"dist/index.js"},"engines":{"node":">=18"},"scripts":{"build":"tsup src/index.ts --format esm --target node18 --clean","typecheck":"tsc --noEmit","test":"node --import tsx --test src/test/*.test.ts","test:fuzz":"node --import tsx --test src/test/fuzz.test.ts","harden":"npm run build && node scripts/diff-proxies.mjs --corpus scripts/corpus --repeat 10 && node scripts/wire-integrity.mjs && node scripts/dogfood.mjs","dev":"tsx src/index.ts","prepack":"npm run build","pretest":"npm run build"},"keywords":["mcp","model-context-protocol","debugging","proxy","observability","schema-drift","cli","ai-agents","llm-tools"],"license":"Apache-2.0","devDependencies":{"@modelcontextprotocol/sdk":"^1.29.0","@types/node":"^20.14.0","tsup":"^8.3.0","tsx":"^4.19.0","typescript":"^5.5.0","zod":"^3.23.0"},"publishConfig":{"access":"public"},"_id":"@anma-labs/mcpgaze@1.0.1","gitHead":"c884429de4335918f33f506fb567090d80b721b9","_nodeVersion":"22.22.3","_npmVersion":"10.9.8","dist":{"integrity":"sha512-OpNFCM7L+FGx+RQpsGwuaFUV2L9znl6D9G9U/Gp+tiOndK0UYDRgLkKO7o87i9ZxlIe+5LH/rVL8pOen7wE/vA==","shasum":"3ec9dc0f2c80713d03eba9c992aae6409f0a1ce0","tarball":"https://registry.npmjs.org/@anma-labs/mcpgaze/-/mcpgaze-1.0.1.tgz","fileCount":6,"unpackedSize":126288,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@anma-labs%2fmcpgaze@1.0.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQD9bTf0JNObtk61GpAkbv7Z6uCkNFmyo/PWu1n7KPGkxwIhAI3smkWDQL2xavRa3NGuWsDntHop0fWu9IFwifKzMNO9"}]},"_npmUser":{"name":"gogetassgk","email":"kellum@anmalabs.dev"},"directories":{},"maintainers":[{"name":"gogetassgk","email":"kellum@anmalabs.dev"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/mcpgaze_1.0.1_1780878086348_0.008480804239562323"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-08T00:21:26.151Z","1.0.1":"2026-06-08T00:21:26.498Z","modified":"2026-06-08T00:21:26.977Z"},"maintainers":[{"name":"gogetassgk","email":"kellum@anmalabs.dev"}],"description":"A transparent wiretap for MCP servers. See exactly what your AI client sends your server — without breaking the protocol — and catch tool-schema drift in CI.","homepage":"https://github.com/anma-labs/mcpgaze#readme","keywords":["mcp","model-context-protocol","debugging","proxy","observability","schema-drift","cli","ai-agents","llm-tools"],"repository":{"type":"git","url":"git+https://github.com/anma-labs/mcpgaze.git"},"bugs":{"url":"https://github.com/anma-labs/mcpgaze/issues"},"license":"Apache-2.0","readme":"<div align=\"center\">\n\n# mcpgaze\n\n**A transparent wiretap for MCP servers.**\nSee exactly what your AI client sends your server — without breaking the protocol — and catch tool-schema drift before it ships.\n\n[![License: Apache-2.0](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](./LICENSE)\n[![Node](https://img.shields.io/badge/node-%E2%89%A518-339933.svg?logo=node.js&logoColor=white)](https://nodejs.org)\n[![Runtime deps](https://img.shields.io/badge/runtime%20deps-0-success.svg)](#zero-dependencies-by-design)\n[![MCP spec](https://img.shields.io/badge/MCP%20spec-2025--11--25-7c3aed.svg)](https://modelcontextprotocol.io/specification)\n[![PRs welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](./CONTRIBUTING.md)\n\n[Quickstart](#quickstart) · [Why mcpgaze](#why-mcpgaze) · [Commands](#commands) · [Docs](./docs) · [Security](./SECURITY.md) · [Contributing](./CONTRIBUTING.md)\n\n</div>\n\n---\n\nAn MCP server's **stdout *is* the protocol wire** — so a single stray `console.log` corrupts every message, and the logs you actually wanted vanish into the void. `mcpgaze` sits transparently between your client and your server, forwarding every byte **untouched** while logging the whole JSON-RPC conversation through a side channel.\n\n```\n   BEFORE                              AFTER\n client ⇄ server            client ⇄ [ mcpgaze ] ⇄ server\n                                          │ side channel (never stdout)\n                                          ▼  log file · latencies · drift\n```\n\nOn top of that live wiretap it adds schema-drift detection for CI, multi-version spec conformance, behavioral (response-shape) drift checks, record/replay, health monitoring, and failure triage — eleven commands, **zero runtime dependencies**, Apache-2.0.\n\n> Context: a 2025 academic crawl of ~17.6k MCP registry entries ([arXiv:2509.25292](https://arxiv.org/abs/2509.25292)) found more than half invalid or low-value — i.e. abandoned or broken-on-install, not live downtime. The MCP ecosystem is young and brittle; `mcpgaze` is the instrument that tells you which half you're running.\n\n## Quickstart\n\n`mcpgaze` isn't published to npm yet, so build it from source (zero runtime deps, ~5 seconds):\n\n```bash\ngit clone https://github.com/anma-labs/mcpgaze && cd mcpgaze\nnpm install && npm run build\nnode dist/index.js --help\n```\n\n> Once published, this becomes `npx @anma-labs/mcpgaze --help` / `npm i -g @anma-labs/mcpgaze`. Until then, substitute `node /abs/path/to/mcpgaze/dist/index.js` everywhere this README writes `mcpgaze`. See [Getting Started](./docs/getting-started.md) for the full setup, including a `mcpgaze` shell alias.\n\n**Watch a live session:**\n\n```bash\nmcpgaze wrap --tui -- node my-server.js     # full-screen live dashboard\n```\n\n**Gate CI on tool-schema drift:**\n\n```bash\nmcpgaze snapshot -- node my-server.js               # writes mcpgaze.baseline.json (commit it)\nmcpgaze diff --fail-on-drift -- node my-server.js    # exit 1 on a breaking change\n```\n\nThat's the whole loop: **see** what happens live, then **lock** the contract so it can't silently change.\n\n## Why mcpgaze\n\nEvery other MCP debugging tool — the official [Inspector](https://github.com/modelcontextprotocol/inspector), MCPJam — acts **as the client**. It can only exercise a server you point it at, with traffic *it* generates. `mcpgaze` is different: it leaves a tap in place while your **real** client (Claude Desktop, Cursor, …) drives, so you see what actually happened — in development and in production.\n\n|  | Inspector / MCPJam | **mcpgaze** |\n|---|---|---|\n| Role | Acts *as* the client | Sits *between* client and server |\n| Traffic seen | What the tool generates | What your real client actually sends |\n| stdio + Streamable HTTP | ✓ / ✓ | ✓ / ✓ |\n| Captures server stderr | — | ✓ (the logs that normally disappear) |\n| Schema-drift gate for CI | — | ✓ `snapshot` / `diff` |\n| Behavioral drift | — | ✓ `verify` |\n| Record / replay (VCR) | — | ✓ `record` / `replay` |\n| Continuous health | — | ✓ `health` |\n| Runtime dependencies | many | **zero** |\n\n### The two invariants\n\n`mcpgaze` is engineered around two guarantees, enforced by generative tests (fuzz, differential, dogfood) on every push — not just hand-written cases:\n\n- **(A) Wire integrity** — on the forward path, **bytes in == bytes out**. `mcpgaze` parses *copies* off the hot path; the protocol stream is never reconstructed, reordered, or re-encoded.\n- **(B) Observer safety** — the observation/logging path **never throws** into the wire. Adversarial bytes (invalid UTF-8, NULs, multi-MB lines, malformed JSON) can disturb a log line, never the protocol.\n\nEverything else in the tool is downstream of these two promises. See [Architecture](./docs/architecture.md) for how they're held.\n\n## Commands\n\nEleven commands, one binary. Full reference with every flag, exit code, and example: **[docs/commands.md](./docs/commands.md)**.\n\n| Command | What it does |\n|---|---|\n| [`wrap`](./docs/commands.md#wrap) | Transparent stdio proxy; logs the live session to a side channel. `--tui`, `--native`. |\n| [`wrap-http`](./docs/commands.md#wrap-http) | Streamable HTTP proxy (JSON + SSE); localhost-bound, Origin-checked, path-routes many upstreams. |\n| [`snapshot`](./docs/commands.md#snapshot) | Probe the server, write a tool-schema baseline you commit to git. |\n| [`diff`](./docs/commands.md#diff) | Diff the live tool surface against the baseline; gate CI with `--fail-on`. |\n| [`conform`](./docs/commands.md#conform) | Spec-conformance suite across protocol versions. |\n| [`verify`](./docs/commands.md#verify) | Behavioral (response-shape) drift vs a recorded cassette. |\n| [`record`](./docs/commands.md#record) | Wrap a server and write a replayable cassette (secrets redacted by default). |\n| [`replay`](./docs/commands.md#replay) | Deterministic mock MCP server from a cassette — no backend. |\n| [`health`](./docs/commands.md#health) | Continuous uptime/latency/drift monitoring, or `--once` as a liveness probe. |\n| [`triage`](./docs/commands.md#triage) | Surface failures from a session log; optional Claude diagnosis with `--ai`. |\n| [`preflight`](./docs/commands.md#preflight) | Find env vars a GUI client won't inherit; statically check a config's `env` block. |\n\n---\n\n### `wrap` — see the live session\n\nWrap your server command in your client config. For `claude_desktop_config.json` (Cursor and others are analogous):\n\n```json\n{\n  \"mcpServers\": {\n    \"my-server\": {\n      \"command\": \"node\",\n      \"args\": [\"/abs/path/to/mcpgaze/dist/index.js\", \"wrap\", \"--\", \"node\", \"/abs/path/server.js\"]\n    }\n  }\n}\n```\n\nThen tail the structured session log, or run standalone with a live view:\n\n```bash\nmcpgaze wrap --print -- node server.js     # standalone; pretty stream to stderr\nmcpgaze wrap --tui   -- node server.js     # full-screen dashboard (zero deps, hand-drawn ANSI)\n# every run also writes .mcpgaze/session-<ts>.jsonl\n```\n\nYou get every JSON-RPC message (both directions), request→response **latency matched by id**, **orphaned requests** that never got a reply, parse errors, and the server's **stderr captured** alongside. The forwarded stream stays byte-exact (invariant A) — an observer error can never disturb the wire (invariant B).\n\n→ [`wrap` reference](./docs/commands.md#wrap) · [`--native` Rust hot-path](./docs/commands.md#wrap---native) · [`--tui`](./docs/commands.md#wrap---tui)\n\n### `snapshot` + `diff` — catch schema drift in CI\n\nTool schemas change silently between versions — a field flips to `required`, an enum loses a value — and agents break with no error. Treat your tool surface like a lockfile:\n\n```bash\nmcpgaze snapshot -- node server.js                  # writes mcpgaze.baseline.json (commit it)\nmcpgaze diff --fail-on-drift -- node server.js       # exit 1 on a breaking change\n```\n\n```yaml\n# .github/workflows/mcp.yml\n- run: node dist/index.js diff --fail-on-drift -- node server.js\n```\n\n**Severity model:** removed property · new required property · type change · enum value removed · optional→required = **breaking**; required→optional = **warning**; additive changes = **info**. `--fail-on <breaking|warning|any>` sets the gate; `--update` accepts intentional drift into the baseline.\n\n→ [`snapshot` / `diff` reference](./docs/commands.md#snapshot) · [CI recipes](./docs/ci.md)\n\n### `conform` — spec conformance across versions\n\nRun a conformance suite against your server for one or more protocol versions. Required checks gate CI; recommended checks warn.\n\n```bash\nmcpgaze conform -- node server.js                 # default spec (2025-06-18)\nmcpgaze conform --spec 2025-11-25 -- node ...     # a specific version\nmcpgaze conform --all -- node server.js           # 2025-06-18, 2025-11-25, 2026-07-28 (RC)\nmcpgaze conform --json -- node server.js | jq     # machine-readable\n```\n\nChecks include: `initialize` returns a valid result with `protocolVersion` and `serverInfo.name`; `tools/list` returns named tools with object input schemas; `required[]` only names declared properties; and an unknown method returns a proper JSON-RPC error (`-32601`) instead of hanging. Exits 1 if any required check fails.\n\n→ [`conform` reference & full check catalog](./docs/commands.md#conform)\n\n### `record` + `replay` — VCR for MCP\n\nRecord a real session into a cassette, then replay it as a deterministic mock server with no backend — for offline client development and regression CI.\n\n```bash\nmcpgaze record --cassette s.json -- node server.js   # capture req/res pairs (secrets redacted by default)\nmcpgaze replay --cassette s.json                     # serve those pairs over stdio\n```\n\nReplay matches by method + params (exact first, then a unique method-only fallback) and returns a clear JSON-RPC error for anything unrecorded instead of hanging. Cassettes are written `0600`, `*.cassette.json` is git-ignored by default, and `record` **redacts credential-shaped values by default** — review before sharing. See [Security](./SECURITY.md).\n\n→ [`record` / `replay` reference](./docs/commands.md#record)\n\n### `verify` — behavioral (response-shape) drift\n\n`diff` compares *declared* schemas; `verify` catches drift the schema can't see — a server can keep an identical tool schema while its responses change shape (a field disappears, a list goes empty, a type flips). It re-issues a cassette's requests against the live server and diffs the **response shapes**.\n\n```bash\nmcpgaze verify --cassette s.json --fail-on warning --allow-tool-calls -- node server.js\n#   WARNING   tools/call.results[] — array is now empty (was populated)\n#   BREAKING  tools/call.total — field removed from response\n```\n\n> **Caveat:** `verify` re-executes recorded requests. Only read-only methods are re-issued unless you pass `--allow-tool-calls` — run that against a disposable instance.\n\n→ [`verify` reference](./docs/commands.md#verify)\n\n### `health` — continuous local monitoring\n\n```bash\nmcpgaze health --interval 30 -- node server.js     # daemon: uptime, latency, schema-drift transitions\nmcpgaze health --once -- node server.js            # cron/CI liveness probe (exit 0 up / 1 down)\n```\n\nProbes `initialize` + `tools/list` on an interval, prints up↔down and drift transitions, and persists status to `.mcpgaze/health.json`.\n\n→ [`health` reference](./docs/commands.md#health)\n\n### `triage` — turn a failed session into a diagnosis\n\n```bash\nmcpgaze triage --log .mcpgaze/session-<ts>.jsonl              # local failure summary\nANTHROPIC_API_KEY=sk-... mcpgaze triage --log s.jsonl --ai --yes   # + Claude root-cause & fix\n```\n\nSurfaces every failure signal — error responses, orphaned requests, parse errors, crash-y stderr — and, with `--ai`, gets a plain-English root cause from Claude. The AI call uses zero extra dependencies (plain `fetch`), **redacts** secrets at the egress boundary, and requires explicit consent (`--yes` or an interactive `y`).\n\n→ [`triage` reference](./docs/commands.md#triage)\n\n### `preflight` — catch the env vars a GUI client won't inherit\n\nGUI apps (Claude Desktop, etc.) do **not** inherit your shell environment, so a server that works in your terminal fails silently in production. `preflight` spawns the server twice — full env vs. the GUI-inherited subset — and names the vars that matter:\n\n```bash\nmcpgaze preflight -- node server.js\nmcpgaze preflight --config claude_desktop_config.json --server my-server\n```\n\n→ [`preflight` reference](./docs/commands.md#preflight)\n\n### `wrap-http` — the Streamable HTTP transport\n\nFor remote/HTTP MCP servers, `mcpgaze` runs as a localhost-bound proxy that forwards to your upstream and observes both plain JSON and SSE. (For HTTP, the spec gives the client no view of server stderr — so the proxy is your *only* window.)\n\n```bash\nmcpgaze wrap-http --upstream http://localhost:3000/mcp --port 7000\n# point your client at http://127.0.0.1:7000/mcp\n```\n\nOne proxy can front several upstreams, routed by path prefix (longest match wins):\n\n```bash\nmcpgaze wrap-http --port 7000 \\\n  --route /github=http://localhost:3001/mcp \\\n  --route /slack=http://localhost:3002/mcp\n```\n\n**Security defaults, baked in:** binds to `127.0.0.1` only, and rejects cross-origin browser requests (DNS-rebinding defense — the bug class behind the Inspector's [CVE-2025-49596](https://nvd.nist.gov/vuln/detail/CVE-2025-49596), CVSS 9.4). Multi-route credential scoping strips `Authorization`/`Cookie` unless a route opts in. Full model: [Security](./SECURITY.md) and [`wrap-http` reference](./docs/commands.md#wrap-http).\n\n## `--native` — the Rust hot-path\n\nA single static binary (`mcpgaze-proxy`, ~450 lines of `std`-only Rust, **no crates**) does the same byte-exact forward + observation as the Node proxy, with no Node runtime required:\n\n```bash\ncd native/mcpgaze-proxy && cargo build --release\nmcpgaze wrap --native -- node server.js     # or set MCPGAZE_PROXY_BIN\n```\n\nIn a 20k-round-trip microbenchmark against a mock server, the Rust proxy runs ~1.7× the Node proxy's throughput and roughly halves added latency. The headline absolute numbers are machine- and runtime-specific and the bench harness isn't committed yet, so treat them as one machine's reading — **the durable result is the relationship**: direct ≫ Rust > Node, all far above any real MCP workload. `--native` earns its place for **single-binary distribution (no Node)** and high-throughput/streaming cases; it stays opt-in.\n\n→ [`--native` details, benchmark, and the classifier trade-off](./docs/commands.md#wrap---native)\n\n## Zero dependencies by design\n\n`package.json` lists **no runtime dependencies** — nothing extra enters your protocol path. The TUI is hand-drawn ANSI, the AI triage call is plain `fetch`, the Rust proxy is `std`-only. Everything in `devDependencies` (TypeScript, tsup, the MCP SDK used only as a test fixture) is build/test-time and never ships in the `dist/` you run.\n\n## Testing & hardening\n\nBecause `mcpgaze` sits in the protocol data path, correctness is enforced by **generative** checks, not just examples:\n\n- **Property/fuzz hunt** (`npm run test:fuzz`) — thousands of seeded-random trials assert both invariants: framing is **invariant to chunk boundaries** (a message split at any byte, including mid-multibyte, yields identical results) and the observer **never throws** on adversarial bytes.\n- **Wire-integrity fuzz** (`scripts/wire-integrity.mjs`) — random *binary* payloads forwarded through the proxy come out **byte-identical**.\n- **Differential oracle** (`scripts/diff-proxies.mjs`) — the Node and Rust proxies run identical traffic; their logs must **agree** on every message, keeping the two implementations honest.\n- **Dogfood** (`scripts/dogfood.mjs`) — `replay` is itself an MCP server, so `mcpgaze`'s own **conformance suite runs against it**.\n- **Real-SDK integration** — the suite probes and conforms a genuine `@modelcontextprotocol/sdk` server, plus a 20-cell TS + Python SDK matrix across the full command surface.\n\n`npm run harden` runs the differential, wire-integrity, and dogfood workflows together. CI runs typecheck/test/build on Node 18/20/22, builds the Rust proxy, and runs all of the above on every push. See [CONTRIBUTING](./CONTRIBUTING.md#testing) and [Architecture](./docs/architecture.md#how-the-invariants-are-tested).\n\n## Documentation\n\n| Doc | For |\n|---|---|\n| [Getting Started](./docs/getting-started.md) | Install, wire into Claude Desktop / Cursor, read your first session log |\n| [Command Reference](./docs/commands.md) | Every command, flag, exit code, and env var |\n| [CI Recipes](./docs/ci.md) | Drop-in GitHub Actions for drift gating, conformance, and liveness |\n| [Architecture](./docs/architecture.md) | The two invariants, framing, the Node/Rust split, module map |\n| [Session Log Format](./docs/session-log.md) | The `.jsonl` event schema, for building on top of `mcpgaze` |\n| [Security Policy](./SECURITY.md) | Threat model, data-at-rest, credential scoping, reporting |\n| [Known Issues](./KNOWN-ISSUES.md) | Accepted, documented limitations for v1.0 |\n| [Changelog](./CHANGELOG.md) | Release history |\n\n## Where this is going (open core)\n\nThe CLI — proxy, local logging, schema snapshot/diff, CI gating — is free and open source (Apache-2.0), forever. A future hosted layer handles what a local CLI can't: **continuous** uptime/health across many servers, drift *history*, alerting, and team workspaces. The line is simple: **one dev, one server, one machine is free; aggregation across servers, time, and teams is the paid layer.**\n\nPost-1.0 roadmap: hosted control plane (cross-server health/drift history, alerting); prebuilt per-platform `mcpgaze-proxy` binaries shipped with the npm package (so `--native` needs no `cargo`).\n\n## Contributing\n\nIssues and PRs are welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) for the dev loop, the invariant rules every change must respect, and the test gates. By participating you agree to the [Code of Conduct](./CODE_OF_CONDUCT.md).\n\n## License\n\n[Apache-2.0](./LICENSE).\n","readmeFilename":"README.md","_rev":"1-65ea656d9446c800deb1ee60a5f9a0e8"}