mcp-tape
v0.3.0
Published
Stdio proxy for MCP servers that logs all JSON-RPC traffic to a replayable trace file.
Maintainers
Readme
mcp-tape
Stdio proxy for Model Context Protocol servers. Sits between any MCP client (Claude Code, Cursor, any future host) and any MCP server, forwards JSON-RPC byte-for-byte in both directions, and writes every message to a replayable .jsonl trace file.
Pairs with the mcpreplay.dev renderer to give you time-travel debugging for agent tool calls. The trace format is open — anything can produce or consume it.
Strategic context
mcp-tape (this) and mcp-replay are the free, open-source halves of an MCP-observability product family. The JSONL trace format is deliberately open and stable — anyone can write a producer or consumer; we don't have to be the only ones. Internal strategy + team-product roadmap lives in a private companion document; the Roadmap section below tracks what's shipped and what's planned on the open side.
Install
npm i -g mcp-tapeFirst stable release is
0.1.0. Plainnpm i -g mcp-tapeinstalls@latest. Use-g(notnpx) so the wrapped path in your config survives upgrades.
Requires Node.js 20 or newer.
Try it end-to-end (live validation)
The fastest path to "is this useful to me?" is recording and replaying a real Claude Code session in five steps.
1. Install everywhere (one command):
mcp-tape installThis walks every detected MCP client config and wraps every entry in mcpServers. Supported targets, with the file each one lives in:
| Target | Config file |
|---|---|
| claude-code | ~/.claude.json |
| claude-desktop (per-OS — see below) | Windows: %APPDATA%\Claude\claude_desktop_config.json · macOS: ~/Library/Application Support/Claude/claude_desktop_config.json · Linux: ~/.config/Claude/claude_desktop_config.json |
| antigravity (Google Antigravity IDE) | ~/.gemini/antigravity/mcp_config.json |
| gemini-cli (Google Gemini CLI) | ~/.gemini/settings.json |
Pre-1.0 mcp-tape installs on Linux wrote to
~/.config/claude/(lowercase).installanduninstallstill detect that path if it exists, so older configs aren't orphaned by the upgrade.
install auto-discovers whichever of these files already exist on disk. Your config is backed up to <file>.mcp-tape.bak before any change. The command is idempotent — running it again is a no-op unless the binary path changes (after an upgrade). Add --dry-run to preview, --target=antigravity,gemini-cli to scope to specific clients, --force to refresh the backup with the current state.
On Windows,
~isC:\Users\<you>—mcp-tape installresolves it natively, no path adjustment needed. Antigravity / Gemini CLI write their MCP config lazily, so if you haven't added a server through their UI yet, the file may not exist and that target will simply be skipped.
2. Restart your MCP client (Claude Code / Claude Desktop). The wrapped servers will be invoked through mcp-tape instead of directly.
3. Use the client normally. Every MCP server you talk to writes a JSONL trace to ./mcp-traces/ (relative to wherever the server's working dir is). One file per session per server, named like 2026-05-13T15-30-00-000Z-<label>.jsonl.
4. Open a trace: go to mcpreplay.dev, click Open local trace… and pick the .jsonl file. (Or drag-and-drop it onto the page.) The trace never leaves your browser. Drag multiple .jsonl files at once to merge sessions across servers (e.g. robot-md-gateway + robot-md-mcp interleaved by timestamp).
5. Share a trace: send the .jsonl file to a collaborator. They drag it into mcpreplay.dev. Done — no public URL, no server staging.
When to consider Tier 1 validated: you've recorded ≥3 real multi-hour sessions, shared at least one trace with a collaborator, and neither of you had to hand-edit ~/.claude.json or hand-scrub secrets. When that lands, the daily-driver loop works.
Roll back when done:
mcp-tape uninstallPer-entry, in place. Any unwrapped entries you added manually after install are preserved. The .mcp-tape.bak is removed after a successful restore.
Usage (single server, no auto-install)
If you don't want to wrap your whole config — for one-off experiments or non-Claude clients — call mcp-tape directly with -- separating its flags from the server command:
mcp-tape -- npx -y @modelcontextprotocol/server-filesystem /home/me
mcp-tape --out ~/.mcp-traces --label fs -- node my-server.jsThe proxy is transparent — JSON-RPC traffic is forwarded byte-for-byte. The trace file is the only side-effect.
Long sessions: rotation + size caps
Trace files rotate when they exceed --max-bytes (default 50 MB). The newest is <name>.jsonl, then .1, .2, … up to --max-files (default 4). Oldest is evicted. A multi-hour session caps at roughly 200 MB total.
mcp-tape --max-bytes 104857600 --max-files 8 -- node my-server.js # 100 MB × 8Env vars MCP_TAPE_MAX_BYTES and MCP_TAPE_MAX_FILES work the same way; the CLI flag overrides the env var. To merge rotated files in the viewer, drag the active file plus its .1/.2/… companions onto mcpreplay.dev together.
Live mode (Tier 2)
Stream a session to mcpreplay.dev while it's still running:
mcp-tape --serve -- npx -y @modelcontextprotocol/server-filesystem /home/meThis starts a localhost websocket on port 7777 (override with --serve 9001 or
MCP_TAPE_SERVE=9001). In POSIX shells the -- separates mcp-tape's flags
from the server command; PowerShell's native-command shim strips -- from
argv, so on Windows you can drop it (mcp-tape --serve npx -y …) — mcp-tape
will see the bare npx and start the wrapped command from there.
Open the URL printed on stderr:
mcp-tape: live mode on ws://127.0.0.1:7777/ (open https://mcpreplay.dev/?live=ws://127.0.0.1:7777)If the requested port is already in use — a previous Claude session that didn't shut down cleanly, another wrapped server holding it — mcp-tape scans a small range above the requested port and then asks the OS for any free port. The stderr line tells you which port it actually bound:
mcp-tape: port 7777 was unavailable — bound 7778 instead
mcp-tape: live mode on ws://127.0.0.1:7778/ (open https://mcpreplay.dev/?live=ws://127.0.0.1:7778)New subscribers get a snapshot of everything-so-far on connect, then live
appends as each frame is written. Reconnect-resume is automatic via
?since=<lastSeq> so a flaky network or refreshed browser tab doesn't lose
frames.
The websocket is bound to 127.0.0.1 only — there is no auth, no TLS, and no
remote-access path. mcp-replay refuses to connect to any host other than
localhost / 127.0.0.1.
--no-file: serve-only mode
For short debug sessions where you don't want a .jsonl file to clean up
afterwards, add --no-file:
mcp-tape --serve --no-file -- node my-server.jsThis streams every frame to the websocket but writes nothing to disk.
--no-file requires --serve. If the websocket fails to bind entirely
(extremely rare), mcp-tape automatically reverts to writing a .jsonl so the
session isn't silently lost.
Full live-mode walkthrough lives in the renderer repo at mcp-replay/docs/live-mode.md.
Secret redaction
mcp-tape redacts common secret shapes from the logged trace by default. It does not alter messages forwarded between client and server. Even with redaction on, treat traces as sensitive — no regex is exhaustive.
Default field-name redaction (case-insensitive substring match on the JSON key):
password, secret, token, apiKey / api_key, authorization, bearer, privateKey / private_key, accessKey / access_key.
Default value patterns:
- AWS access key id (
AKIA…) sk-*API keys (OpenAI/Anthropic shape)- GitHub tokens (
ghp_,gho_,ghu_,ghs_,ghr_) - JWTs (three base64url segments separated by
.) Authorization:/Bearer …header values appearing in any string- File paths matching
id_rsa,id_ed25519,id_ecdsa,id_dsa, or.env*
Inspect or add to these:
mcp-tape --redact-defaults # print the built-in list
mcp-tape --redact 'MYCO_[A-Z0-9]{20}' -- node my-server.js
mcp-tape --no-redact-defaults --redact '...' -- node my-server.jsCustom redaction config
For project-specific rules — especially nested fields — create ~/.config/mcp-tape/redact.json:
{
"extends": "default",
"rules": [
{ "type": "path", "path": "$.params.arguments.api_key" },
{ "type": "path", "path": "$..customer_id" },
{ "type": "regex", "pattern": "MYCO-[A-Z0-9]{20}" }
]
}The file is auto-loaded at startup. extends: "default" merges with built-in rules; set it to null to replace them entirely. Path rules use a JSONPath subset ($, .field, [i], [*], ..field). Override the path with --redact-file PATH or env MCP_TAPE_REDACT.
Trace format
One JSON object per line. First line is a meta header, last line is an end marker, everything in between is one protocol message per line.
{"v":1,"type":"meta","startedAt":"2026-05-12T23:00:00.000Z","label":"fs","command":["npx","-y","@modelcontextprotocol/server-filesystem","/home/me"],"mcpTapVersion":"0.1.0"}
{"t":"2026-05-12T23:00:00.123Z","dir":"in","raw":{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}}
{"t":"2026-05-12T23:00:00.245Z","dir":"out","raw":{"jsonrpc":"2.0","id":1,"result":{}}}
{"t":"2026-05-12T23:00:30.000Z","type":"end","exitCode":0,"durationMs":30000}dir: "in"— client → server (data the proxy received on stdin)dir: "out"— server → client (data the proxy received from the server's stdout)raw— verbatim JSON-RPC message, post-redactiont— ISO-8601 with millisecond precision
The full spec lives at mcpreplay.dev/docs/format.
Roadmap
Shipped (v0.2.0):
- PlatAtlas upload —
mcp-tape login --subdomain <slug>runs a GitHub device-flow auth against PlatAtlas,mcp-tape upload <file>posts a trace to<subdomain>.platatlas.com/api/traces, and--upload-on-exitauto-uploads the final trace after the wrapped server exits. Session stored XDG-style at~/.config/mcp-tape/session.json.
Shipped (v0.3.0):
mcp-tape upload --publicand--upload-on-exit --publicflag the uploaded trace as world-readable by UUID. The CLI echoes a ready-to-pastemcpreplay.dev/?trace=…URL so you can hand the link to anyone with the UUID. Private remains the default. See--helpfor the full caveat.
Shipped (v0.1.0 — stable):
- Stdio proxy + JSONL trace + signal forwarding
install/uninstallsubcommands for Claude Code, Claude Desktop, Antigravity, and Gemini CLI configs (per-OS path resolution for Claude Desktop)- Trace rotation with
--max-bytes/--max-files(andMCP_TAPE_MAX_*env vars) - File-based redaction config at
~/.config/mcp-tape/redact.jsonwith JSONPath + regex rules, layered on the legacy defaults - Live mode —
mcp-tape --serve <port>exposes a localhost websocket with snapshot + append + reconnect-resume; renderer connects via?live=ws://127.0.0.1:7777. Port-conflict fallback if 7777 is busy.--no-filefor serve-only sessions. - In-trace search by tool-call name + argument pattern (in the renderer)
- Trace diff view in the renderer (
mcpreplay.dev/?diff=a.jsonl;b.jsonl)
Planned:
- HTTP / SSE transport (currently stdio-only)
- Frame drop-count surfaced to subscribers when the broadcaster's ring buffer evicts
License
MIT
