mcptape
mcptape is a tape recorder for the Model Context Protocol: wrap any MCP server, record a session once (REC ⏺), replay it offline forever (PLAY ▶), and diff exactly where the tool behavior drifted (⏩).
Your agent talks to MCP servers — filesystem, browser, search, your own tools. Those servers change, their tools change, their results change, and your agent breaks in ways you cannot reproduce without paying for a dozen live runs. mcptape records the whole session — every
tools/call, every result, every notification — as local JSONL. From then on, replay serves the recorded results offline for free and byte-identically, and one command diffs two recordings to the first drifted call.
Zero dependencies: pure Python standard library. Nothing leaves your machine — a trace is a local file.
(The screenshot is rendered from real command output by docs/make_demo_svg.py — not staged.)
中文文档:README.zh-CN.md
How it works
flowchart LR
A["MCP client<br/>(your agent)"] -->|"stdio"| B["mcptape rec<br/>transparent proxy ⏺"]
B -->|"stdio"| C["real MCP server"]
B --> D[".mcptape/runs/<br/>trace.jsonl + meta.json"]
A -->|"stdio"| E["mcptape play<br/>served from tape ▶"]
D -.->|"byte-for-byte"| E
F["second recording"] --> G["mcptape diff<br/>first drifted call"]
E -.-> G
G -->|"exit 0 / 1 / 2"| H["CI ✅ ❌"]
In rec mode mcptape is a transparent proxy: it spawns the real server, forwards every JSON-RPC line both ways unchanged, and files each message to the tape. Your agent cannot tell it is being recorded.
In play mode mcptape is the server: it answers initialize, tools/list and tools/call from the tape — no child process, no network, identical results.
30 seconds
pip install mcptape
# 1. record a session: put mcptape between your client and any MCP server
python examples/demo_client.py -- mcptape rec --name demo-stable -- python examples/demo_server.py
# 2. replay it offline -- same results, no server process, $0.00
python examples/demo_client.py -- mcptape play demo-stable
# 3. the server changes; record again and diff
MCPTAPE_DEMO_DRIFT=1 python examples/demo_client.py -- mcptape rec --name demo-drift -- python examples/demo_server.py
mcptape diff demo-stable demo-drift
Real output (paste the commands and you get this verbatim):
=== replay ===
server: demo-tools
tools: echo, add
echo: tapedeck, but for MCP
add(2, 3): 5
=== diff demo-stable demo-drift ===
run A: demo-stable (11 events)
run B: demo-drift (11 events)
event 10 (s2c response):
result changed
A: {"result": {"content": [{"type": "text", "text": "5"}]}}
B: {"result": {"content": [{"type": "text", "text": "6"}]}}
context (events …i):
c2s/request tools/call
s2c/notification notifications/message
diff exit codes are designed for CI: 0 identical, 1 output drift, 2 structural drift.
In your agent config
rec is one config edit away from any stdio-based MCP client (Claude Desktop, Claude Code, Cursor, ...):
{
"mcpServers": {
"my-tools": {
"command": "mcptape",
"args": ["rec", "--name", "daily-run", "--", "python", "-m", "my_tools"]
}
}
}
Flip "rec" to "play", "daily-run" and the agent runs against the tape — full regression pass with zero live calls. The client cannot tell the difference: same tools, same results, same notifications.
Why matching is by content, not position
tapedeck aligns calls by position; MCP clients pipeline and run tool calls in parallel, so positional alignment would false-positive on every concurrent session. mcptape matches each incoming call against the earliest unconsumed recorded exchange with the same method and JSON-equal arguments. Identical repeated calls consume identical recorded entries in order; reordered or parallel calls match freely; a call whose arguments changed matches nothing — that is the drift, reported with the exact arguments.
initialize is matched leniently on parameters (client name/version/protocol bumps are not behavior drift) and its recorded capabilities answer is served verbatim.
Why not LangSmith / LangFuse / OTel platforms
| Cloud observability | mcptape | |
|---|---|---|
| Position | watch runs (dashboard) | reproduce runs (recorder) |
| Replay | re-calls the tools (state changed, money spent) | replays recorded results (free, deterministic) |
| Data | uploaded to a third party | local files only |
| Dependencies | SDK + reporting config | zero, two commands |
Complementary, not competing: platforms show trends; mcptape does forensics and regression. The same role tapedeck plays for model calls, one layer down.
CLI
mcptape rec --name RUN -- <server command> # proxy + record a live session
mcptape play RUN [--lenient] # serve a recording as an MCP server
mcptape diff A B # first drift + context; exit 0/1/2
mcptape ls # list recorded runs
mcptape report RUN # per-message table
Trace format
One directory per run, two plain files:
.mcptape/runs/20261031-011921-demo-stable/
meta.json # server command, protocol version, counts
trace.jsonl # one JSON-RPC message per line, with request echoes
{"v": 1, "i": 10, "dir": "s2c", "kind": "response", "id": 4,
"payload": {"result": {"content": [{"type": "text", "text": "5"}]}},
"for_method": "tools/call", "for_params": {"name": "add", "arguments": {"a": 2, "b": 3}},
"wall_ms": 0.9}
Every response carries an echo of the request it answers, so each line is self-contained — replay and diff never need to join across lines.
Honest boundaries
- stdio transport only (v0.1). HTTP/SSE-based MCP servers are not wrapped yet; streamable-HTTP support is on the roadmap.
- Sessions with server-initiated requests cannot be replayed. If the server asked the client to sample or elicit, the tape would have to impersonate the client —
playrefuses such recordings up front (exit 2);diffstill works on them. - Replay is deterministic only up to the tape. Client-side logic that reads the clock, uses randomness, or branches on anything outside the session may take a different path than during recording — that is true of every record/replay tool and is exactly what
diffbetween two real recordings is for. - Server-side state is frozen at record time. Replaying a
tools/callthat wrote a file serves the recorded result without writing again; replay never touches the real world. If you need the side effects re-applied, that is a live run, not a replay. - No monkeypatching.
recis a proxy process; the client config opts in explicitly.
Roadmap
- streamable-HTTP transport wrapping
-
--subsetreplay (serve only named tools from the tape, live for the rest) - trace signing (tamper-evident hash chain)
- OTel GenAI span import/export
The tape suite
The same "record once, replay forever" idea, at three layers:
- tapedeck — model & tool calls inside your agent (Python, PyPI)
- mcptape — the MCP tool-protocol boundary (this repo)
- mocktape — HTTP traffic behind your frontend (TypeScript, npm)
Design trade-offs live in the source docstrings; every image in this README is rendered from real command output.
Metadata
Release files for mcptape 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| mcptape-0.1.0.tar.gz | 21.9 kB | With provenance | Details for mcptape-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| mcptape-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for mcptape-0.1.0-py3-none-any.whl |
Total release size: 40.1 kB
Release files / mcptape-0.1.0.tar.gz
| Download URL | mcptape-0.1.0.tar.gz |
|---|---|
| Size | 21.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
11aae19587480a31f5e6b6f6b47b772720e0b004bf2dd2744a66ef9b5d3d91a1
|
|
BLAKE2b-256 checksum How to use checksums |
cdef74fa0ae833314fd19fcf7d16c547196675daca4f28f7ed73d2f62087c842
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logRelease files / mcptape-0.1.0-py3-none-any.whl
| Download URL | mcptape-0.1.0-py3-none-any.whl |
|---|---|
| Size | 18.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
17d4d27967d44f7f9e8e7bacb2002a49fce4887a338f341e4657e7a629147db0
|
|
BLAKE2b-256 checksum How to use checksums |
1393875cf6c10453a799066f20759453cef475dfd0727eba9b8426c0cb61dc65
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logSecurity for mcptape 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for mcptape-0.1.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.