Capturing an MCP exchange is easy. Creating a fixture you can safely commit and reliably replay is the actual task.
A raw traffic dump contains dynamic request IDs, timestamps, paths, and possibly credentials. A useful fixture preserves protocol shape while removing values that should never become test data.
Start with one tools/call
request:
{
"jsonrpc": "2.0",
"id": "<REQUEST_ID>",
"method": "tools/call",
"params": {
"name": "lookup_issue",
"arguments": { "owner": "demo", "repo": "sample", "number": 42 }
}
}
Store the expected response beside it, but replace the response ID with the same placeholder.
A recorder should:
The final JSONL can be tiny:
{"direction":"client->server","message":{"jsonrpc":"2.0","id":"<REQUEST_ID>","method":"tools/call","params":{"name":"lookup_issue","arguments":{"owner":"demo","repo":"sample","number":42}}}}
{"direction":"server->client","message":{"jsonrpc":"2.0","id":"<REQUEST_ID>","result":{"content":[{"type":"text","text":"Issue 42: example"}]}}}
Do not replay the original network connection. Feed the normalized client message into the server transport used by tests, capture its reply, normalize that reply, and compare structures.
Fail separately on:
For tools that write data, use a fake implementation or disposable account. A replay suite must not repeat yesterday's external action.
I used to reach for a full transcript recorder. That immediately created questions about every dynamic field. One representative call gives you the canonicalization rules first. Add fixtures only when they cover a new protocol behavior.
The fixture is not proof that the tool result is factually correct. It verifies the contract between client, transport, and server. Keep domain assertions in a separate test so a protocol update does not erase the business expectation.
A good replay file is boring: small, redacted, deterministic, and useful when the SDK or server changes. If it contains enough context to impersonate a user, it is not a fixture—it is an incident waiting for a commit.