# Test your MCP server for regressions by replaying a golden run

The mental model is one line: put Reelier in front of your MCP server, record one healthy pass through the tools you ship, and freeze it as a golden run. Every replay after re-issues those exact tool calls against the live server and fails the moment a result stops matching — a regression test for your server that you get by recording, not by hand-writing request/response fixtures.

This guide runs the whole loop with the reelier CLI against a stdio MCP server: wrap it with the recorder, compile the golden run to a `SKILL.md` baseline, replay it, and diff two runs so a regression becomes an exit code you can gate CI on. Every command below is real and every output is verbatim from an actual run.

Markdown twin of https://www.reelier.com/guides/test-mcp-servers — a step-by-step Reelier how-to. Every command below is a real Reelier command and every output block is verbatim, never a mockup.

## Before you start

- Reelier installed — `npm i -g reelier` (or use `npx -y reelier@latest` below).
- The MCP server you want to guard, startable from the command line over stdio — the thing under test.
- A client or agent that can drive the golden run's tool calls once while the recorder is up — any MCP client works.
- Nothing else. No account, no API key: deterministic replay never calls a model.

## 01 · Put the recorder in front of your MCP server

`reelier mcp --wrap` is the recorder: it launches your MCP server as a downstream and re-exposes every one of its tools through a proxy, plus three control tools — `reelier_start_recording`, `reelier_note`, `reelier_stop_recording`. Point your agent or client at the proxy instead of the server directly, and it sees the same tools it always did.

This command runs as a stdio MCP server and prints nothing on its own — it sits between the client and your server, capturing calls. What recording captures on each call is covered by the glossary; this guide stays on the how-to. Swap `node weather-mcp.mjs` for whatever launches your server (`npx -y @your/mcp-server`, `python -m your_server`, and so on).

```bash
reelier mcp --wrap "node weather-mcp.mjs"
```

## 02 · Record one golden run and compile the baseline

With the recorder up, have your client call `reelier_start_recording`, drive the exact tool calls that represent a healthy server — the sequence you want to guard — then call `reelier_stop_recording`. Each call lands in a trace `.jsonl` under `.reelier/traces/`. In the run below, one read-only `get_forecast` call was recorded to `.reelier/traces/mcp-forecast-golden-1.jsonl`.

`reelier compile` turns that trace into a `SKILL.md` — a plain markdown file, checked into your repo, that IS the golden baseline. The compiler writes minimal assertions: a step whose call recorded a clean result gets exactly one assert — `status == 200` — and no brittle value-pins, so a fresh response body on each honest run never trips a false failure. It also classifies the effect from the tool's `readOnlyHint`, so a read-only tool is marked `read` and is safe to replay repeatedly.

```bash
reelier compile .reelier/traces/mcp-forecast-golden-1.jsonl -o mcp-forecast-golden.skill.md
```

```
Wrote mcp-forecast-golden.skill.md
  steps:   1
  asserts: 1
  binds:   0
  effects: read=1 idempotent-write=0 destructive=0

Open questions: (none)
```

## 03 · Replay the golden run against the server

`reelier run` re-executes the recorded tool calls against a freshly launched copy of your server and checks each assertion — pass the same `--wrap` so they hit a live server, not a stub. No model is in the loop: at the default `--max-level 0` an LLM client is never even constructed, so the replay costs 0 tokens and tests the deterministic core of your server, nothing re-reasoned.

Replay is read-only by default: a recorded write step does not re-fire unless you pass `--allow-writes` (idempotent writes only; a destructive step still needs `--yes`). A read-only tool like this one is safe to run on every push. Every run appends a receipt to `.reelier/runs/<skill>.jsonl` — the append-only record the next step diffs against.

```bash
reelier run mcp-forecast-golden.skill.md --wrap "node weather-mcp.mjs"
```

```
✓ Step 1 — get the forecast for Denver [passed] 2ms

PASSED: 1/1 steps ok, 0 failed, 2ms total
```

## 04 · Diff two runs — a regression becomes an exit code

Replay the skill a second time, then `reelier diff <name>` compares the last two runs in that record, step by step. On a healthy server it reports `SAME` and exits 0 — every step's outcome and escalation level match the baseline. Wire that exit code into CI and the job stays green as long as your server keeps answering the golden run the way it did the day you recorded it.

The moment a tool regresses — it starts erroring, or a step that passed now fails its `status == 200` assertion — the same command flips to `DRIFTED` and exits 1, blocking the merge. The receipt below is that catch, verbatim: the golden run passes against the healthy server, then fails against a regressed build, and the diff exits 1. Run it in a GitHub Actions job and that is the whole regression gate.

```bash
reelier diff mcp-forecast-golden
```

```
  = Step 1 "get the forecast for Denver": passed, unchanged.

✓ SAME — 1 step(s), every outcome and escalation level identical to the baseline.
```

## The receipt

```
$ reelier run mcp-forecast-golden.skill.md --wrap "node weather-mcp.mjs"
✓ Step 1 — get the forecast for Denver [passed] 2ms

PASSED: 1/1 steps ok, 0 failed, 2ms total

$ reelier run mcp-forecast-golden.skill.md --wrap "node weather-mcp-broken.mjs"
✗ Step 1 — get the forecast for Denver [failed] 2ms
    - status == 200 failed: got 500

FAILED: 0/1 steps ok, 1 failed, 2ms total

$ reelier diff mcp-forecast-golden
  ✗ Step 1 "get the forecast for Denver": passed → failed — status == 200 failed: got 500.

⚠ DRIFTED — 1 outcome/structure change(s): #1 outcome-changed.
```

The regression caught, verbatim: the `mcp-forecast-golden` skill replayed against the healthy server (passed), then against a deliberately-broken build whose `get_forecast` returns an error — standing in for the day your real server breaks. Reelier maps the tool error to status 500, the `status == 200` assertion fails, and `reelier diff` exits 1 (DRIFTED). Produced on 2026-07-22 with `reelier` v0.14.0; the golden run was recorded by `reelier mcp --wrap` fronting a minimal example stdio MCP server (one read-only `get_forecast` tool). No model was constructed at any point.

## FAQ

**How is this different from writing request/response fixtures by hand?**

You never write the expected values. Reelier records a real healthy call and freezes its actual result as the baseline, then replays that call against the live server on each run. There is no hand-maintained mock to drift out of sync — the golden run IS the fixture, and the server itself produced it.

**My server's tool returns data that changes every call. Will that show as a regression?**

No. The compiler writes minimal assertions — one `status == 200` per clean step, no value-pins — so a fresh response body on each honest run updates the receipt without failing the diff. A step only drifts when its outcome flips (passed to failed), a step is added or removed, or it needed a different escalation level to pass.

**Can I replay a server whose tools write data?**

Replay is read-only by default, so a recorded write step does not re-fire unless you pass `--allow-writes` (idempotent writes only; a destructive step still needs `--yes`). For a scheduled regression test, record a read-only path, or gate the writes deliberately — the compile output states each step's effect so you know what is safe to replay.

## Related

- [MCP recording — what the recorder captures on each tool call, defined](https://www.reelier.com/learn/mcp-recording)
- [Agent regression testing — the pattern this guide runs, defined](https://www.reelier.com/learn/agent-regression-testing)
- [Reelier for GitHub Actions — run this replay as a CI gate on every push](https://www.reelier.com/for/github-actions)
- [Guide: record MCP tool calls with the wrap recorder](https://www.reelier.com/guides/record-mcp-tool-calls)
- [Guide: diff two agent runs and fail CI on drift](https://www.reelier.com/guides/diff-two-agent-runs)

## Sources

- [seldonframe/reelier — the open-source (AGPL-3.0) CLI every command here runs](https://github.com/seldonframe/reelier)
- [Model Context Protocol — the spec that defines MCP servers and tools](https://modelcontextprotocol.io)
- [modelcontextprotocol/servers — reference MCP servers you can wrap and replay](https://github.com/modelcontextprotocol/servers)

Other guides: [How to snapshot-test a Claude Code workflow](https://www.reelier.com/guides/snapshot-test-claude-code-workflows) · [How to record MCP tool calls with a wrap proxy](https://www.reelier.com/guides/record-mcp-tool-calls) · [How to replay an agent workflow at 0 LLM tokens](https://www.reelier.com/guides/replay-agent-workflow-zero-tokens) · [How to gate CI on agent drift](https://www.reelier.com/guides/ci-gate-for-agent-drift) · [Replay an agent skill on every PR with the Reelier GitHub Action](https://www.reelier.com/guides/github-action-agent-replay) · [Run a deterministic cron agent by scheduling a recorded replay](https://www.reelier.com/guides/deterministic-cron-agents) · [Build an agent audit trail from Reelier run receipts](https://www.reelier.com/guides/agent-audit-trail-receipts) · [Configure BYOK escalation levels: L0, L1, and L2](https://www.reelier.com/guides/byok-escalation-levels) · [Compile a skill from an existing session with reelier from-session](https://www.reelier.com/guides/from-session-compile) · [Diff two agent runs with reelier diff (exit 1 on drift)](https://www.reelier.com/guides/diff-two-agent-runs) · [Convert an LLM agent skill into a deterministic, assertion-checked skill](https://www.reelier.com/guides/convert-agent-skill-deterministic)
