feat(record): capture MCP call streams to NDJSON and replay deterministically#175
feat(record): capture MCP call streams to NDJSON and replay deterministically#175mvanhorn wants to merge 2 commits into
Conversation
…stically mcporter record <session> wraps the runtime transport and appends every JSON-RPC request, response, and notification to a per-session NDJSON file under ~/.mcporter/recordings/. mcporter replay <session> reconstructs an in-memory transport from the recording and matches requests by method + deep-equal params, returning the recorded response without contacting the live server. Use cases: - Reproduce MCP-backed agent bugs offline (no live Linear quota, no Vercel API rate limits) - Build test fixtures from real call sequences - Share a session for a postmortem without sharing credentials The format is plain JSON-RPC over NDJSON with a small _meta field (direction, server, timestamp). No proprietary blob. Env-var passthrough (MCPORTER_RECORD=<name>, MCPORTER_REPLAY=<name>) lets the existing runtime constructor wrap any transport when set.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e2460611af
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| this.expectedSends.shift(); | ||
| if (expected.response) { | ||
| queueMicrotask(() => this.onmessage?.(expected.response as JSONRPCMessage)); |
There was a problem hiding this comment.
Rewrite replayed response ids to the active request
When the replayed client’s JSON-RPC id counter differs from the original recording (for example, a fixture captured after earlier daemon traffic or replayed from a different call sequence), this emits the recorded response id instead of the id from the request that just matched. JSON-RPC clients correlate responses by id, so the client will ignore the message and the call will hang or time out even though method/params matched; clone the recorded response with the current request id before delivering it.
Useful? React with 👍 / 👎.
|
Hmmm - not fully sold. What will you use that for? As a tool to build/debug MCP servers? |
|
Three things, in order of value:
Use cases #2 and #3 are the ones I personally lean on; #1 is the one most maintainers will reach for first. Happy to drop any of these if the scope feels off for mcporter. |
|
Hmmmm. It would shift mcporter from sth that is "helps call mcps for agents" more towards a test/debugger tool. I haven't had that need yet so not fully sold. Will think about it. |
|
Codex review: needs real behavior proof before merge. Latest ClawSweeper review: 2026-05-22 06:12 UTC / May 22, 2026, 2:12 AM ET. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. for the blocking replay bug: the PR source and test show a request with id PR rating Rank-up moves:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. Real behavior proof Risk before merge
Maintainer options:
Next step before merge Security Review findings
Review detailsBest possible solution: If maintainers want record/replay in core, land a narrower version that rewrites response ids, warns about raw payload sensitivity, and includes redacted real CLI proof; otherwise pause or close it as outside the current product direction. Do we have a high-confidence way to reproduce the issue? Yes for the blocking replay bug: the PR source and test show a request with id Is this the best way to solve the issue? No: the direction may be useful, but this implementation should not merge until replay rewrites response ids, raw-recording sensitivity is documented, and a maintainer accepts the new record/replay product surface. Label changes:
Label justifications:
Full review comments:
Overall correctness: patch is incorrect Security concerns:
Acceptance criteria:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 0c36a6d3f833. |
|
ClawSweeper PR egg 🎁 Pass real behavior proof to wake the egg and unlock a hatchable treat. Where did the egg go?
|
Summary
mcporter record <session>andmcporter replay <session>. Record wraps the runtime transport and appends every JSON-RPC request, response, and notification to a per-session NDJSON file under~/.mcporter/recordings/<session>.ndjson. Replay reconstructs an in-memory transport from the recording and matches requests by method + deep-equal params, returning the recorded response without contacting the live server._metafield (direction, server name, ISO timestamp). No proprietary blob, no streaming archive library, no new runtime deps.MCPORTER_RECORD=<name>andMCPORTER_REPLAY=<name>let any existingmcporterinvocation participate (the runtime constructor wraps each server's transport when set).recvin the recording fails with a clear error naming the request and the next expectedrecv. No fuzzy matching, no auto-fallback to live — replay is for reproducing exact runs.Why this matters
When an MCP-backed workflow breaks in production, reproducing the bug means re-running the live MCP server with the same inputs — which is often expensive (Linear quota, Vercel API rate limits) or impossible (the server's state has changed). Today mcporter exposes MCP servers as TypeScript APIs and CLIs but has no way to capture what an agent actually called and what came back.
Three concrete use cases this unlocks:
mcporter record session-foo→ commitsession-foo.ndjsonto your test suite. Replay it in CI without network.Sibling project
openclaw/acpxalready has a flow trace replay (docs/2026-03-26-acpx-flow-trace-replay.md); this PR brings the same shape to MCP transport.Demo
Simulated demo:
The demo shows the full loop: record a Linear MCP call, then replay it deterministically even after the live server becomes unreachable. The NDJSON envelopes carry the
_metadirection + server fields the replay transport matches on.Testing
corepack pnpm typecheckcorepack pnpm lint(oxlint clean, oxfmt clean)corepack pnpm test— 646 tests pass; newtests/record-replay.test.tscovers:_meta.dirand_meta.serverpopulatedrecv_meta.server