VirtuProbe Studio
Get the app
← All posts
Build log 13 July 2026 8 min read

Why testing an MCP server didn't need a new protocol.

Everyone is standing up MCP servers, and the first question is the same one you'd ask of any new endpoint: does it work, and what happens when I send it something it didn't expect? "Add MCP support" sounds like a whole new protocol to build. It isn't. MCP is JSON-RPC over HTTP, with a session header and an optional event stream, which means most of testing one is just testing HTTP, and the interesting work was the small part that isn't.

The Model Context Protocol is how AI tools talk to the servers that give them their tools and data. It's spreading fast, and the servers going up around it are ordinary software with the ordinary problem: the happy path gets a demo, the unhappy path gets shipped untested. A workbench that already speaks HTTP, chains requests and fuzzes fields ought to be able to point straight at one. The question was how much of it we'd have to build.

What MCP actually is

Strip away the framing and an MCP call is a JSON-RPC 2.0 message in an HTTP request body:

POST /mcp Content-Type: application/json Accept: application/json, text/event-stream { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "search", "arguments": { "q": "kerberos" } } }

Three things make it more than a plain POST. The server can answer with application/json or with text/event-stream, a live stream of events ending in the result. It's stateful: an initialize call returns an Mcp-Session-Id that every later call has to echo back as a header. And there are two transports in the wild: the current Streamable HTTP (one endpoint, from the 2025-03-26 spec) and the older HTTP + SSE two-endpoint style. That's the whole surface. None of it is a new protocol in the sense that DNS or LDAP is. There are no new bytes on the socket that HTTP doesn't already carry.

So it's a body mode, not a module

We'd made this call once already. When GraphQL landed, the tempting design was a GraphQL "protocol"; the honest one was to notice GraphQL is a POST with a particular body shape, and add a mode to the HTTP probe's Body tab that builds that shape for you. MCP is the same situation, one step more structured, so it got the same treatment: a third option next to Raw and GraphQL.

Switch the toggle to MCP and the body editor becomes a small form: pick the transport, pick the method (initialize, tools/list, tools/call, resources/read…), fill the JSON params, and VirtuProbe serialises the JSON-RPC envelope, sets Content-Type and the streaming Accept header, and drops the id for notifications the way the spec requires. Reopen a probe whose body is already a JSON-RPC envelope and it detects that and opens the form for you. Flip back to Raw at any point and the envelope is right there to hand-edit.

Building a parallel "MCP client" would have meant a second HTTP stack to keep in step with the first. The discipline was to resist it.

The payoff of not building a module is that MCP inherits everything the HTTP probe already has, for free: the credential store and OAuth2, history and diffing, import and export, and {{variables}} in every field. There was no model migration and no database reset. An MCP body is just a string in the same field a Raw body lives in.

The session is just a chain

Statefulness is where a lesser design bolts on a session manager. We didn't need one, because VirtuProbe already threads data between steps. That's what chains are. The MCP handshake is a four-step chain and nothing more:

# 1 initialize → extract MCP_SESSION_ID into {{sid}} # 2 notifications/initialized → send header Mcp-Session-Id: {{sid}} # 3 tools/list → extract MCP_TOOL_NAMES (newline-joined) # 4 tools/call → read the answer with MCP_RESULT

The session id is extracted once and replayed as a header, exactly the way you'd thread a bearer token or a CSRF value through any other flow. MCP_TOOL_NAMES comes back newline-joined so it drops straight into an ITERATE step, so you can call every advertised tool in a loop if that's the test. MCP_ERROR_CODE gives you the JSON-RPC error code when a call is rejected. No new stateful machine; the machine was already there.

The part we did have to build: streaming

Here's the one place MCP asked for something genuinely new. Until now every send in the HTTP probe was synchronous: fire the request, wait, render the response. But an MCP server answering over text/event-stream emits a run of events, progress notifications and partial results, before the final answer, and showing that after the fact defeats the point of a stream.

So the server opens the SSE connection to the target, parses events as they arrive, and relays each one to the UI over a WebSocket, live, while the request is still in flight. A stream card fills up event by event and then condenses to the final JSON-RPC result in the normal response panel below. We didn't invent new plumbing for it. The MITM proxy already feeds live traffic to the interface over a socket, and the MCP stream reuses that pattern with a per-request channel instead of a broadcast one. It's a small amount of code, but it's the real engineering in the feature: the first thing in the probe that streams rather than blocks.

The half a connect-and-call tool doesn't have

The reference MCP dev tooling will connect, list the tools and call one, and so will we, from a schema-aware form. But "does it respond" is the shallow end. A workbench is for the rest:

  • Tool poisoning. Every MCP response is scanned for prompt-injection, the class of attack where a tool's description or result carries instructions meant to hijack the agent reading it. A warning surfaces above the body when something in the payload looks like it's talking to the model instead of to you.
  • Fuzzing the envelope. Because an MCP request is a request like any other, the same mutation engine that malforms an HTTP message can malform a JSON-RPC one, with wrong types, missing ids, oversized params or a method that doesn't exist, and watch how the server holds up.
  • Chaining and proof. An MCP call can sit in the middle of a cross-protocol chain: authenticate over HTTP, call a tool, verify the side effect it claimed to cause in a database or a mailbox. The AI writes the happy path; you prove the rest against the real server.

Where it sits

The MCP editor, both transports, chaining and the extractors are part of the Engineering tier, the "does my server work" half of the job. Fuzzing the JSON-RPC and intercepting MCP traffic through the proxy are Security. Designing and saving an MCP probe is free on any tier; only sending one needs an entitlement, so the AI assistant can still offer to build you the chain regardless of what you've paid for.

None of this is a new protocol module, and that's the point worth keeping. MCP is HTTP wearing a JSON-RPC coat and, sometimes, an event stream. Treat it as what it is and most of the testing you already know how to do simply applies, with one new card that fills up live while the server thinks.

The HTTP probe is free forever, the whole thing. Raw, GraphQL and the MCP form, request chaining and the built-in AI assistant, with no account, no cloud and no expiry, on macOS, Linux and Windows. Sending MCP requests, the credential-backed auth, the wider integration protocols and the MITM proxy are part of the Engineering tier; fuzzing is Security.

Join our Discord