Drop .jsonl traces
one file opens · two files diff · more merge by timestamp
mcp-replay trace viewer for agents by Craig Merry
How it works Views Compare Start to finish Live mode Format Docs
replaying demo Open demo trace →
mcpreplay.dev mcp-tape 0.6.0 · MIT LIVE MODE

See what your
agent actually did.
Then send someone the link.

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.

MCP npx mcp-tape install --target=claude-code
SESSION npx mcp-tape cc --latest
LLM npx mcp-tape llm -- your-agent
Pick one. The first wraps your MCP servers, the second imports your last Claude Code session, the third records whatever your agent sends to an LLM API. Then mcp-tape share <trace>.jsonl prints a link like mcpreplay.dev/?trace=8dae0b5d… that expires in 30 days.
Format
One JSON object per line · open spec
This page
Static · your file never uploads
Share links
Optional · redacted · expire in 30 days
Open a demo session → Demo: Claude Code session → Get mcp-tape ↗

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.

Live demo A real trace, replayed right here. Recorded by mcp-tape.
Demo trace
2026·05·12 · 15:00
§ 01 · How it works

A proxy, a text file,
and a static page.

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.

⊙ STAGE 01 · YOUR CLIENT

Your client

Claude Code · Desktop · Antigravity · Gemini

Whatever makes the calls. It sees a normal MCP server and needs no changes.

stdin · stdout (JSON-RPC 2.0)
in · request▶
▲ STAGE 02 · THE RECORDER

mcp-tape

Reads the pipe, writes the file

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.

./mcp-traces/<session>.jsonl + ws://127.0.0.1:<port>
out · response◀
⊙ STAGE 03 · THE SERVER

The MCP server

filesystem · github · sqlite · anything

Runs unmodified as a child process. Nothing is patched or wrapped in code.

spawned via <command> after --
Why bother.  When an agent does something you did not expect, a slow tool call, a write to the wrong path, an answer that came from nowhere, the trace is the only record of what actually happened. This page makes it readable.
§ 02 · The views

One trace,
read it whichever way you need.

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.

VIEW 01 Timeline · what happened, in order 13 frames · 4 tool-calls · 15.5s session
ALL IN OUT ERR 13 frames · 4 tool-calls
TIMESTAMPDIRMETHOD · ARGSFRAME #
15:00:00.045► INinitializeid 1 · protocolVersion=2024-11-05001
15:00:00.123◄ OUTinitializeid 1 · serverInfo=filesystem +0.08s002
15:00:00.131► INnotifications/initializednotification · no reply expected003
15:00:00.140► INtools/listid 2004
15:00:00.198◄ OUTtools/listid 2 · 3 tools · +0.06s005
15:00:02.500► INtools/callid 3 · list_directory /home/me/proj/src006
15:00:02.580◄ OUTtools/callid 3 · 3 entries · 142 b · +0.08s007
15:00:05.100► INtools/callid 4 · read_text_file index.ts008
15:00:05.220◄ OUTtools/callid 4 · 48 b read · +0.12s009
15:00:08.400► INtools/callid 5 · read_text_file /etc/passwd010
15:00:08.420◄ OUTtools/callid 5 · ERR -32602 Access denied · +0.02s011
15:00:12.000► INtools/callid 6 · write_file notes.md (30 b)012
15:00:12.090◄ OUTtools/callid 6 · wrote 30 b · +0.09s013
BEGIN · 2026·05·12 15:00:00.000Z END · 2026·05·12 15:00:15.500Z · exit 0 · 15.5s
VIEW 02 Tools · which tools, how slow, how often wrong aggregate readings per instrument · sorted by use
5 INSTRUMENTS · 30 READINGS · 1 ERR (3.3%)
SORT · CALL COUNT ▾
INSTRUMENT (TOOL)CALLSPROFILEMEANp95ERR
⊙read_text_file12
95%
118 ms280 ms1 (8.3%)
⊙list_directory8
64%
84 ms180 msn/a
⊙write_file6
48%
102 ms220 msn/a
⊙search_files3
24%
412 ms740 msn/a
⊙move_file1
8%
74 ms74 msn/a
Reading.  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.
VIEW 03 Calls · what it asked, what came back in / out shown as facing pages · errors flagged on the spine
5 CALLS · SORTED · BY ID
SHOT
03
80 ms
►IN · CLIENT → SERVER
tools/call · list_directory
{
  "path": "/home/me/proj/src"
}
◄OUT · result
[FILE] index.ts
[FILE] util.ts
[DIR] components
SHOT
04
120 ms
►IN · CLIENT → SERVER
tools/call · read_text_file
{
  "path": "/home/me/proj/src/index.ts"
}
◄OUT · result
export function main() {
  console.log("hello");
}
SHOT
05
20 ms
►IN · CLIENT → SERVER
tools/call · read_text_file
{
  "path": "/etc/passwd"
}
◄OUT · error -32602
Access denied: /etc/passwd is outside allowed directories
VIEW 04 Raw · the file itself in ink · out highway · err oxblood · meta + end faded
14 LINES · 2682 BYTES · UTF-8 NDJSON
demo/demo-trace.jsonl
01{"v":1,"type":"meta","startedAt":"2026-05-12T15:00:00.000Z","label":"fs-demo",
02 "command":["npx","-y","@modelcontextprotocol/server-filesystem","/home/me/proj"]}
03{"t":"2026-05-12T15:00:00.045Z","dir":"in","raw":{"jsonrpc":"2.0","id":1,
04 "method":"initialize","params":{"protocolVersion":"2024-11-05",
05 "clientInfo":{"name":"claude-code","version":"2.0.0"}}}}
06{"t":"2026-05-12T15:00:00.123Z","dir":"out","raw":{"jsonrpc":"2.0","id":1,
07 "result":{"protocolVersion":"2024-11-05","serverInfo":{...}}}}
08{"t":"2026-05-12T15:00:02.500Z","dir":"in","raw":{"jsonrpc":"2.0","id":3,
09 "method":"tools/call","params":{"name":"list_directory",
10 "arguments":{"path":"/home/me/proj/src"}}}}
11{"t":"2026-05-12T15:00:02.580Z","dir":"out","raw":{"jsonrpc":"2.0","id":3,
12 "result":{"content":[{"type":"text","text":"[FILE] index.ts ..."}]}}}
13{"t":"2026-05-12T15:00:08.420Z","dir":"out","raw":{"jsonrpc":"2.0","id":5,
14 "error":{"code":-32602,"message":"Access denied: /etc/passwd ..."}}}
15{"t":"2026-05-12T15:00:15.500Z","type":"end","exitCode":0,"durationMs":15500}
► IN client→server   ◄ OUT server→client   ✕ ERROR SPEC v1 →
VIEW 05 Diff · what changed between two runs ═ same · ≠ changed · + added · − removed
DISCREPANCY · 2 TRACES ALIGNED BY ORDINAL
3 SAME · 1 CHANGED · 1 ADDED · 1 REMOVED
TRACE A · session-2026-05-12-15:00.jsonl
⇄
TRACE B · session-2026-05-13-09:14.jsonl
list_directory(/proj/src)80ms
═
list_directory(/proj/src)76ms
read_text_file(index.ts)120ms
═
read_text_file(index.ts)132ms
read_text_file(/etc/passwd)20ms
≠
read_text_file(util.ts)88ms
n/a
+
list_directory(/proj/lib)92ms
write_file(notes.md)90ms
═
write_file(notes.md)88ms
move_file(notes.md)74ms
-
n/a
How to read.  Calls are paired by ordinal (1st of A vs 1st of B). ═ same · ≠ changed args/result · + added in B · - removed from B.
§ 02b · Thinking traces

Claude Code sessions,
turn by turn.

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.

VIEW · TURNS

See the thinking. Then decide what to share.

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.

Open the Turns demo →

◑ Thinking traces contain verbatim prompts · review before sharing
◑ Thinking token counts are chars/4 estimates, labeled est.
> 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
VIEW · INSIGHTS

Any model, any agent. Recorded from the wire.

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.

Open the Insights demo →

◑ API keys are never written to a trace · headers are allowlisted
◑ Costs are list-price estimates from an editable table · never billing data
> 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)
VIEW · SESSION

Hours long? Start with what happened.

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.

Open the Session demo →

◑ Short traces still open on their rows · nine rows are their own summary
◑ Every estimate says est. · nothing here is billing data
> 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
§ 03 · How it compares

Good tools exist.
Here is where this one differs.

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.
Checked.  Every claim above comes from each project's own documentation, read on 2026-07-15. If something here is wrong or out of date, file an issue and it gets fixed.
§ 04 · Start to finish

Install to shareable link
in five steps.

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.

STEP 01 · wrap

Wrap your MCP servers

npx mcp-tape install --target=claude-code

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.

STEP 02 · use

Restart your client and use it

(no command)

Work normally. Every server writes its own trace to ./mcp-traces/, one file per session, named by timestamp.

STEP 03 · open

Open the trace

drag-and-drop

Drag the .jsonl onto this page, or use Open your own file… above. The file is read in your browser and goes nowhere else.

STEP 04 · share

Make a link (optional)

mcp-tape share <trace>.jsonl

Redacts again, uploads, and prints mcpreplay.dev/?trace=<id>. The link expires in 30 days and --delete kills it sooner.

STEP 05 · replay

Send it

?trace=<uuid>

It opens right here for whoever you send it to. No account, no install, nothing to build.

OR · stay in the terminal

Read it without a browser

mcp-tape stats <trace>.jsonl

Cost, tokens, cache hit rate, tool latencies, and every error, printed. --json makes it a CI check.

AND · keep the disk tidy

Your traces, your files

mcp-tape ls · mcp-tape prune

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.

AND · let the agent read it

Ask about its own session

claude mcp add tape -- npx mcp-tape mcp

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.

§ 05 · Live mode

Or watch it happen
while it happens.

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.

SURVEY IN PROGRESS

No file needed. Just watch the frames arrive.

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.

◑ Reconnects automatically · resumes with ?since=<lastSeq>
◑ Port-conflict fallback · always reads the actual port from stderr
◑ --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
§ 06 · The format · v1

One JSON object
per line.

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 typeRequired?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.
Redaction. Producers may replace sensitive substrings with "[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.
Full format spec ↗
mcp-replay
TRACE VIEWER · MIT

A record of what your agent actually did. Readable, shareable, and yours.

Made by Craig Merry A solo developer. This tool is free and MIT licensed, with no account and nothing to upgrade.
CRAIGMERRY.COM →

Related

  • mcp-tape (the recorder)↗
  • Claude Code↗
  • MCP spec↗

Project

  • The map & the trace, launch essay↗
  • Source↗
  • README↗
  • Documentation
  • Format spec
  • Format extensions
  • Live mode guide
  • Insights metrics
  • File an issue↗

URL grammar

  • ?trace=<url>
  • ?trace=<a>;<b> · merge
  • ?diff=<a>;<b> · discrepancy
  • ?live=ws://127.0.0.1:<port>
  • ?since=<lastSeq> · resume
SOURCE · github.com/craigm26/mcp-replay
HOSTED · mcpreplay.dev · Cloudflare Pages
BUILT BY · Craig Merry
MIT LICENSE · 2026
◑ mcp-replay · mcpreplay.dev ※ set in your system fonts · nothing fetched from a CDN · free and MIT
mcp-replay
connecting…
Shared traces auto-delete after 30 days. Report this trace

Couldn't load that trace.

Live-mode setup checklist

  1. Confirm mcp-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.
  2. Confirm the port in your URL matches the port from that banner. Port fallback may have moved it: look for port N was unavailable, bound M instead.
  3. Live mode only accepts ws:// addresses on localhost or your own network (192.168.…, 10.…, .local). Public hosts are refused intentionally.
  4. Watching from a phone? Run 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.
  5. Security software or strict firewall rules may block loopback websockets. Try connecting from curl or another browser tab to isolate.

Full guide: docs/live-mode.md.

← back

Keyboard