mcptest docs GitHub

Serve a cassette (mcptest serve)

mcptest serve is the reverse of mcptest record: it takes a recorded server cassette and answers a live MCP client from it, as if it were the real server. Point any client (Claude Code, Cursor, mcp-inspector) at the tape and it completes the handshake, lists tools, and calls them, getting the recorded responses back. No backend runs. No network. The recording is the server.

This is the offline twin of recording. Record a real server once, then replay that capture to a client (or a teammate, or CI) forever, deterministically.

How it works

MCP client  <-- stdio or HTTP -->  mcptest serve  -->  cassette file

Each request the client sends is matched by content against the recorded exchanges (method plus params; the initialize handshake matches on method alone, so a tape survives a client upgrade). The first not-yet-consumed recording that matches wins, its id is rewritten to the live request's id, and it is sent back. Notifications get no response. A request with no matching recording returns a JSON-RPC error rather than hanging, so one unrecorded call fails only itself.

Because the client runs its own initialize, the tape must contain an initialize exchange. mcptest serve warns on startup when it does not, the same caveat mcptest distill reports.

Serving over stdio

The default front speaks newline-delimited JSON-RPC on its own stdin/stdout, exactly like a real stdio MCP server:

mcptest serve --cassette cassettes/session.json

In a client config (.mcp.json, .cursor/mcp.json), point the server entry at the replay. mcptest serve prints this snippet, with your path filled in and rendered absolute, on startup:

{
  "mcpServers": {
    "replayed": {
      "command": "mcptest",
      "args": [
        "serve",
        "--cassette", "/home/you/project/cassettes/session.json"
      ]
    }
  }
}

Everything human-facing (the banner, the snippet) goes to stderr; stdout carries only JSON-RPC frames, so the client never sees a stray banner where an envelope should be.

Serving over HTTP

--front http stands up a streamable-HTTP server the client POSTs to, with an SSE stream for any server-initiated message:

mcptest serve --cassette cassettes/session.json --front http --port 8765

--port 0 binds an ephemeral port and prints the one it chose. The banner prints a mcpServers entry keyed on url for clients that dial HTTP. This is the same front mcptest record --front http and mcptest mock --front http serve.

Matching strategy

--strategy controls how strict the request match is, mirroring mcptest verify:

Turning a tape into an editable mock

A cassette replays verbatim: useful, but frozen. When you want to edit the canned responses (add a field, change a value, trim the catalog), distill the tape into a mock manifest instead:

mcptest distill cassettes/session.json --as-mock -o mock.yaml
mcptest validate --config mock.yaml
mcptest mock --tools-from mock.yaml --front http --port 8765

--as-mock maps each distinct recorded tool to one mock_server.tools[] entry whose response is the recorded result, lifting description and the input/output schemas from a recorded tools/list when present. Duplicate calls of the same tool collapse into one entry. The result is a plain YAML file you own and edit; mcptest mock serves it over stdio or HTTP. Every value is lifted from real traffic after secret redaction, so review before sharing.

Choosing serve or mock

References