mcp-tape is a command line recorder. Point it at
an MCP server, a Claude Code session, or any LLM API call, and it
writes a plain .jsonl file of what actually happened:
thinking, tool calls, arguments, results, tokens, and timing.
mcp-replay is this page. Drop the file in and read
it. Two reasons to bother: accountability, because
the trace is the only honest record of what your agent did, and
the next run, because it shows you where the
context, the time, and the money actually went. Nothing to install,
no account, no backend.
npx mcp-tape install --target=claude-code
npx mcp-tape cc --latest
npx mcp-tape llm -- your-agent
One viewer for any trace. Drag a .jsonl on to open it,
two to diff them, more to merge them by timestamp. MCP frames and
LLM turns from the same session read as one story.
No agent, no daemon, no SDK. mcp-tape sits in the middle of a connection you already have and writes down what crosses it. The result is a text file you own. This page reads that file in your browser. That is the whole design.
Whatever makes the calls. It sees a normal MCP server and needs no changes.
Copies every message in both directions, redacts anything shaped like a secret, and writes one JSON object per line. Add --serve to also stream it live.
Runs unmodified as a child process. Nothing is patched or wrapped in code.
These five are what an MCP trace fills. A recorded session adds three more, further down: Turns, Insights, and Context. A trace only ever offers the tabs it can actually fill, so an MCP trace never grows an empty cost table. Click a plate to see it.
| INSTRUMENT (TOOL) | CALLS | PROFILE | MEAN | p95 | ERR |
|---|---|---|---|---|---|
| ⊙read_text_file | 12 | 95% | 118 ms | 280 ms | 1 (8.3%) |
| ⊙list_directory | 8 | 64% | 84 ms | 180 ms | n/a |
| ⊙write_file | 6 | 48% | 102 ms | 220 ms | n/a |
| ⊙search_files | 3 | 24% | 412 ms | 740 ms | n/a |
| ⊙move_file | 1 | 8% | 74 ms | 74 ms | n/a |
read_text_file is 40% of the calls here and the source of the only error: an attempt at /etc/passwd that the server rejected. Worth asking what prompt produced it.
{
"path": "/home/me/proj/src"
}
[FILE] index.ts [FILE] util.ts [DIR] components
{
"path": "/home/me/proj/src/index.ts"
}
export function main() {
console.log("hello");
}
{
"path": "/etc/passwd"
}
Access denied: /etc/passwd is outside allowed directories
═ same · ≠ changed args/result · + added in B · - removed from B.
Claude Code already writes every session to disk.
npx mcp-tape cc --latest turns the last one into a
trace, and two more tabs light up. The Turns view shows each turn
with its thinking collapsed until you want it. The Insights view
adds up what the session cost, how much of it hit the prompt
cache, and how long it spent thinking. Cost is an estimate from a
price table you can edit, never billing data.
Open a fully synthetic demo session: thinking blocks, tool calls with results, a compaction marker, and per-turn token counts. No real transcript content, ever.
> npx mcp-tape cc --latest
converted 12 turns · 5 tool calls
wrote ./<session>-cc.jsonl (214 KB)
reminder: thinking traces contain
verbatim prompts · review before sharing
npx mcp-tape llm -- your-agent starts a local proxy, points the child process at it, and records what actually crossed the wire: thinking, tool calls, tokens, and timing. This demo is real recorder output from a scripted upstream, so the Insights view has something to chew on: cache hit rate, time to first token, tokens per second, and an estimated cost per model.
> npx mcp-tape llm -- npm run agent
mcp-tape llm: recording proxy on
http://127.0.0.1:7788
ANTHROPIC_BASE_URL, OPENAI_BASE_URL set
recorded 3 exchanges · 2 models
wrote ./<session>-llm.jsonl (7 KB)
A recorded session runs to thousands of turns. The Session view is where a long trace opens: how long it ran, what it reached for, what it cost, what went wrong, and the moments worth jumping to. The Context view answers the question that changes your next prompt: where the context went, and which tool result put 46,000 characters into it. Copy for AI hands the same facts to an assistant as text, because the honest way to ask a model about a session is to give it the numbers.
> npx mcp-tape stats <trace>.jsonl
Session 3.1 days · 3,209 turns
Models claude-opus-4-8 1,583 calls
Cache 99.2% hit rate
Context peak 996,936 tok
Errors 32
The official Inspector, mcpsnoop, and mcpscope are all worth your time, and several of them record real traffic. The difference here is the link you can send someone, plus diffing two traces and merging several in the browser, without giving up local-first defaults.
| Tool | Sees your real traffic | Replay | Share | Notes |
|---|---|---|---|---|
| mcp-tape + mcp-replay | Yes. A transparent proxy sitting in the pipe. | In the browser: timeline, tools, calls, raw, plus diffing two traces and merging several. | Hosted replay links. mcp-tape share prints a URL anyone can open here. Redacted by default, expires in 30 days, deletable. |
Local first. Traces stay on your disk and this page reads them in your browser. MIT. |
| MCP Inspector | No. It connects as its own client, so you see Inspector talking to your server, not your real client. | Interactive testing UI; no trace files to replay | n/a | The official tool. Excellent for exercising a server by hand. |
| mcpsnoop | Yes. A transparent proxy, "Wireshark for MCP". | Live terminal TUI; can re-send a captured call; exports JSON/HTML/text/OTLP | File exports; no hosted replay links | Terminal first. Strong live debugging, with stderr and hung-call views. |
| mcpscope | Yes. A transparent proxy. | Local dashboard with latency histograms; replays traces against your server in CI; schema diff | File exports; no hosted replay links | Aimed at observability and catching regressions in CI. MIT. |
All of it runs on your machine. install can be run
twice safely and uninstall puts everything back.
Nothing is uploaded unless you run share, and that
redacts first.
Finds Claude Desktop, Antigravity, and Gemini CLI too. Omit --target to do all of them. Your config is backed up first, and --dry-run shows the change without making it.
Work normally. Every server writes its own trace to ./mcp-traces/, one file per session, named by timestamp.
Drag the .jsonl onto this page, or use Open your own file… above. The file is read in your browser and goes nowhere else.
Redacts again, uploads, and prints mcpreplay.dev/?trace=<id>. The link expires in 30 days and --delete kills it sooner.
It opens right here for whoever you send it to. No account, no install, nothing to build.
Cost, tokens, cache hit rate, tool latencies, and every error, printed. --json makes it a CI check.
ls shows every trace with its status: ended cleanly, crashed, still running. prune --older-than 30d prints a plan and deletes nothing until you add --yes.
mcp-tape speaks MCP, so your agent can read the record of what it just did. Ask what a session cost or which tools failed and the answer comes from the trace, not from its memory of it. Read only: it never deletes, shares, or uploads anything.
Add --serve and this page streams a running session
instead of opening a finished file. The websocket listens on
127.0.0.1 only. Use --serve-lan if you
want to watch from your phone on the same Wi-Fi.
Run mcp-tape with --serve. It prints a websocket URL. Open that URL here as ?live=… and frames show up as they are written. You get everything so far first, then the rest live.
?since=<lastSeq>--no-file for serve-only (no on-disk trace)
> mcp-tape --serve -- npx -y \ @modelcontextprotocol/server-filesystem ~/projects mcp-tape: live mode on ws://127.0.0.1:7777/ open: mcpreplay.dev/?live=ws://127.0.0.1:7777
The format is open and stable, and it is just text. Anything can
write it and anything can read it. mcp-tape happens to
be the reference writer, but nothing here locks you to it.
| Line type | Required? | Description |
|---|---|---|
| meta | first line | The bookplate. {v, type, startedAt, label, command, mcpTapVersion?}: format version, start timestamp, server label, and the argv used to launch the server. |
| message (in) | repeated | An inbound bearing. {t, dir:"in", raw: <JSON-RPC>}: client → server frame. Request or notification. |
| message (out) | repeated | An outbound reading. {t, dir:"out", raw: <JSON-RPC>}: server → client frame. Response or error. |
| end | last line | The endpiece. {t, type:"end", exitCode, durationMs}: written when the proxy closes. exitCode 0 = clean. |
"[REDACTED]" before writing.
Multi-server. One file per server; merge by timestamp at render time using ?trace=a;b;c.
Versioning. Additive changes don't bump v; renderers ignore unknown fields.
A record of what your agent actually did. Readable, shareable, and yours.
?trace=<url>?trace=<a>;<b> · merge?diff=<a>;<b> · discrepancy?live=ws://127.0.0.1:<port>?since=<lastSeq> · resumemcp-tape is running with --serve (or MCP_TAPE_SERVE set on its environment). The stderr banner shows live mode on ws://127.0.0.1:<port>/ when it's listening.port N was unavailable, bound M instead.ws:// addresses on localhost or your own network (192.168.…, 10.…, .local). Public hosts are refused intentionally.mcp-tape with --serve-lan and paste the printed address into the MCP Replay iOS app. Browsers block LAN ws:// from https pages, so this hosted viewer can only reach localhost.curl or another browser tab to isolate.Full guide: docs/live-mode.md.