DEV Community

Ahmed Hassan
Ahmed Hassan

Posted on

I found a confirmed bug in the official MCP SDK while building a red-team tool for it

Model Context Protocol (MCP) is having a rough year. In the last fourteen months: a CVSS 9.4 RCE in Anthropic's own MCP Inspector, a prompt-injection exfiltration bug in GitHub's MCP server, a supply-chain disclosure in April 2026 that touched the official SDKs across four languages, and — just this June — RufRoot: an unauthenticated MCP bridge in an open-source agent platform with 67,000+ GitHub stars, giving full remote code execution from a single HTTP request.

Existing red-teaming tools (garak, PyRIT, promptfoo) are built for prompt injection and jailbreaks against a model. None of them connect to a live MCP server and test the protocol-level failure modes above — the ones that don't care what the model says, only what the server actually lets happen.

So I built mcp-redteam: a small tool that connects to a real MCP server — over stdio or Streamable HTTP — and runs six adversarial scenarios, each modeled on a real, disclosed incident or a currently-open issue on a funded organization's own project. Not hypotheticals.

The bug I didn't expect to find

Building the HTTP test fixtures, I hit something strange: the second request to a stateless StreamableHTTPServerTransport instance always failed with a bare 500 and an empty body — no exception, no stack trace, nothing reaching my error handlers. The first request (an initialize call) always worked. Every request after it, on the same transport instance, didn't.

I reproduced it two independent ways — raw node:http and Express — before finding modelcontextprotocol/typescript-sdk#1994: a confirmed, still-open regression. The 1.25.0 rewrite bridges Node's HTTP objects through @hono/node-server's getRequestListener, and an exception thrown inside that bridge becomes a generic Hono 500 — bypassing the SDK's own onerror callback and JSON-RPC error formatting entirely. It's a real trap: the SDK's own documented "stateless mode" example doesn't reuse a transport across requests, but nothing stops you from doing it, and when you do, the failure is nearly undiagnosable from the outside.

I posted an independent confirmation on the issue — reproduced on the current published SDK version, confirmed it wasn't specific to one HTTP framework — and the fix (construct a fresh transport per request) is now baked into mcp-redteam's own test fixtures with a comment explaining exactly why.

What the tool actually checks

Six scenarios, each with a real citation, not a vibe:

  1. Tool description/schema stability — does a tool change what it does after being inspected once ("rug pull")
  2. Unannotated destructive tools — destructive-sounding tools with no destructiveHint annotation
  3. Oversized-payload handling (opt-in) — does a "read-only" tool bound a large argument or hang — echoes a still-open finding on a real litellm PR
  4. Unauthenticated tool exposure — modeled directly on RufRoot (CVE-2026-59726, CVSS 10.0)
  5. Token audience validation — does the server actually enforce the MCP spec's own MUST-validate-audience requirement, or just check that some Authorization header exists
  6. tools/call authorization bypass — is tools/call gated as strictly as tools/list, modeled on the same pattern showing up independently in wso2/api-platform#2869 and two separate litellm issues (#31977, #36358)

Testing it against something real

I didn't want to ship this against only my own fixtures, so I ran it against LangFlow (153K+ GitHub stars) — a platform with a real, currently-relevant CVE (CVE-2026-33017, an unauthenticated RCE via its MCP adapter, still exploitable in the version the ecosystem widely believed was patched, per JFrog's research). The result was a clean negative: LangFlow's actual MCP protocol endpoint correctly requires authentication across every scenario. Worth stating plainly — that's not a miss, it's the tool telling the truth. The real CVE lives in a different layer (LangFlow's own REST API session-bootstrap logic, not its MCP server's protocol handling), which is outside what a pure MCP-protocol tester like this one checks — and I'd rather say that clearly than force a positive result to make the story better.

Why this, and not another jailbreak scanner

Prompt injection tooling is already a crowded, well-funded lane (OpenAI acquired Promptfoo for this exact space in March 2026). Protocol-level MCP testing — does the server actually enforce what its own spec requires — is not. Every scenario above traces to something a real, funded organization is dealing with right now, not a hypothetical I invented to have something to build.

Repo: https://github.com/AAH20/mcp-redteam — 18/18 tests passing, CI green on Node 20/22/24, Apache-2.0.

Top comments (1)

Collapse
 
deanlee profile image
Dean Lee

The protocol-level point is the useful bit here. A lot of agent safety writing still treats the model as the whole attack surface, but MCP moves part of the risk into ordinary server behavior, transport state, and auth boundaries. A 500 on the second request is exactly the kind of boring bug that becomes expensive once tools are allowed to take real actions.