chaos-mcp

A fault-injection playground for debugging MCP clients & servers.

Baseline

healthyHealthy server (control)Everything works. Use this to confirm your client is set up correctly before testing failures.

Transport & framing

malformed-jsonTruncated / malformed JSONClient errors with 'Unexpected end of JSON input' or a parse error on every call.not-json-rpcValid JSON, not JSON-RPCClient says 'invalid response' / missing jsonrpc field, though the body parses as JSON.wrong-content-typeWrong Content-TypeStrict clients reject the response; some hang waiting for a stream.html-login-wallHTML login wall instead of JSONClient reports 'Unexpected token < in JSON' — the body is HTML.empty-bodyEmpty 200 responseClient hangs or errors with 'empty response' — status is 200 but there are zero bytes.bom-garbageLeading garbage before JSONSome parsers fail; others silently skip the first tool. Intermittent and confusing.

Status & reliability

slow-responseVery slow responseClient times out. Add ?delay=ms to tune (default 35000ms).controlled-delayToken-gated delay you set live401 until your client sends a bearer token on every request; then whatever delay you dial in at /control — instant now, 10s on the next attempt, or no answer at all.hang-foreverConnection hangs foreverRequest never completes and never errors — the socket just stays open.always-500Always 500Every request returns HTTP 500 with a stack-trace-like body.flaky-500Flaky (50% errors)Roughly half of requests fail with 500; retries sometimes succeed.rate-limitedRate limited (429)Client gets HTTP 429 with Retry-After; tools never run.broken-gzipLies about gzip encodingClient errors with 'invalid gzip header' / decompression failure.

Auth & OAuth

401-no-www-authenticate401 without WWW-AuthenticateClient gets 401 but can't start OAuth — it has nowhere to discover the auth server.oauth-dead-discoveryOAuth discovery dead-endClient begins OAuth, fetches the advertised metadata URL, and gets 404. Auth can't proceed.token-always-rejectedToken always rejectedEven right after authenticating, every call returns 401 — the client loops re-authing.oauth-client-credentialsOAuth with a client id and secret you supplyClient discovers the auth server, then stalls: registration returns 404 and it has no client_id of its own. Paste the published client id and secret and everything works.

CORS & headers

no-corsNo CORS headersBrowser-based clients fail with a CORS error; curl/CLI clients work fine.no-session-idMissing Mcp-Session-IdSession can't be resumed; some clients re-initialize on every call or drop the stream.

Protocol negotiation

bad-protocol-versionBogus protocol versionClient aborts the handshake citing an unsupported/unknown protocol version.missing-capabilitiesMissing capabilitiesClient thinks the server supports nothing; tools/list is never attempted.bad-server-infoMalformed serverInfoClient errors validating the initialize result; serverInfo has the wrong shape.init-errorinitialize returns an errorHandshake fails immediately with a JSON-RPC error; nothing else is reachable.

Protocol versioning

version-downgradeAlways negotiates the oldest versionYou request a modern protocol version (e.g. 2025-06-18) but initialize always answers 2024-11-05, so newer features (the version header, structured tool output, elicitation) are silently off.version-too-newNegotiates a version from the futureinitialize returns a protocolVersion newer than any your client knows (here 2026-06-18). Spec-compliant clients disconnect; lax ones proceed and then mishandle newer message shapes.requires-version-headerRequires MCP-Protocol-Version headerinitialize succeeds, but every following request (tools/list, tools/call) fails with HTTP 400 unless an MCP-Protocol-Version header is sent. Clients that negotiated 2024-11-05 never send it and break.rejects-version-headerRejects the MCP-Protocol-Version headerModern clients fail immediately: any request carrying an MCP-Protocol-Version header is rejected with HTTP 400. Older clients that omit it work fine.version-header-mismatchValidates header against the wrong versionThe handshake negotiates one version, but later requests 400 with 'protocol version mismatch' even when you send the version you just negotiated.

Tools — listing

huge-tool-listHundreds of toolstools/list is huge; the client's context balloons and tool selection degrades.duplicate-tool-namesDuplicate tool namesTwo tools share a name; the client calls one but gets the other's behaviour.bad-tool-schemaInvalid inputSchemaClients that validate schemas reject the tool; others can't build a valid call.huge-descriptionEnormous tool descriptionA single tool eats thousands of tokens; context fills before any work happens.prompt-injectionPrompt injection in tool metadataThe model's behaviour changes after listing/calling the tool — it follows hidden instructions.

Tools — execution

huge-payloadMulti-MB tool resultOne tool call returns megabytes of text and blows the context window / times out.tool-always-errorsTool returns isErrortools/call succeeds at the protocol level but the result is flagged isError every time.tool-rpc-errortools/call returns JSON-RPC errorInstead of a result with isError, the whole call fails with a JSON-RPC error object.tool-slowOne slow toolHandshake and tools/list are instant, but this one tool takes ~40s and times out.tool-never-returnsTool never returnstools/call hangs forever; the rest of the server is responsive.control-charactersControl chars & bidi in resultOutput renders weirdly, corrupts the terminal, or hides/spoofs text via bidi overrides.malformed-contentMalformed content blocksClient fails to render the result; content blocks are missing 'type' or use an unknown type.null-resulttools/call result is nullClient errors dereferencing result.content — the result is null.wrong-result-shapeResult is a bare stringClient can't find content[]; the result is a plain string, not the expected object.