Skip to content

MCP debug client

PHP License: AGPL v3 Transport Status

A tiny standalone page that connects to an MCP server over Streamable HTTP, runs the initialize handshake, lists everything the server exposes, and lets you invoke any of it by hand — showing the raw JSON-RPC request and response at every step.

Same shape as the OAuth debug client next door: no Composer, no framework, no build step — just PHP + ext-curl + sessions, split into a handful of plain required files:

index.php     # front controller: load config, decide the route, dispatch
config.php    # the knobs (copy from config.sample.php)
lib/          # util, transcript, http, config_store   (shared plumbing)
              # mcp          — JSON-RPC framing + the Streamable HTTP transport
              # schema       — inputSchema → form, form → typed arguments
              # auth         — 401 authorization-chain diagnosis
              # conformance  — spec grading and active probes
views/        # layout.php (page shell) + blocks.php (payload renderers) + styles.css
routes/       # connect, home, tools, resources, prompts, conformance,
              #   settings, transcript

This folder is self-contained. util.php, transcript.php and styles.css are copies of the OAuth tool's versions rather than shared includes, so you can move mcp/ anywhere without anything breaking. Keep the internal folder structure intact.

Project docs

Screenshots

Click any image to view the full size.

Connected screen Tool call screen Tool response screen


1. Configure

Edit config.php, or open ?settings in the browser and let it write config.local.php:

'server_url'       => 'http://localhost:3000/mcp',   // the Streamable HTTP endpoint
'protocol_version' => '2025-06-18',
'headers'          => '',                            // "Authorization: Bearer …" goes here
'verify_tls'       => true,

The home screen's connect form can point at a different server, with different headers, for one session without saving anything.

2. Serve it

Put the folder where your web server serves it from, or run PHP's built-in server:

php -S localhost:9100 -t mcp

What you'll see

  1. Connect — endpoint URL, protocol version, request headers, TLS policy. The initialize handshake runs immediately.
  2. Connection — the negotiated protocol version, the Mcp-Session-Id (if the server issued one), serverInfo, the declared capabilities, and any instructions the server wants added to a model's system prompt.
  3. Tools — every tool across all pagination pages, with its behaviour hints as pills (destructive shows red before you click Call). Pick one and you get a form generated from its inputSchema — a real field per property, enums as dropdowns, required marked — with a Raw JSON toggle for schemas too gnarly to render. Results render every content block type (text, image, audio, resource_link, embedded resource), and if the tool declares an outputSchema its structuredContent is validated against it.
  4. Resources — concrete resources and URI templates (fill the variables to build a URI), rendering text and blob contents.
  5. Prompts — argument form, then the expanded messages[] as a role-labelled transcript.
  6. Conformance — grades the server against the spec. Some rows come from the handshake; the rest are active probes that deliberately send bad input to check the answer: unknown method → -32601, malformed JSON → -32700, invalid cursor → -32602, a notification → 202 with no body, a missing MCP-Protocol-Version header, and whether every declared capability is actually backed by a working method. None of them invokes a real tool.
  7. Network transcript — every request the client made, on every page, with headers, timing and copy-as-curl. Exportable as HAR or JSON.

Things it handles that are easy to get wrong

  • SSE-framed replies. A Streamable HTTP response may be application/json or text/event-stream, per request. Frames are parsed, the reply is matched by JSON-RPC id, and anything else the server streamed (progress, log records) is shown as "extra messages".
  • Session expiry. A 404 on a request carrying Mcp-Session-Id means the server dropped the session — the client re-initializes and replays the call instead of reporting a bare 404.
  • Protocol-vs-tool failure. A JSON-RPC error object (the protocol failed) and a result with isError: true (the tool failed, the protocol worked) are rendered differently and labelled.
  • 401s. The tool follows the chain: WWW-Authenticate → RFC 9728 protected-resource metadata → each authorization server's RFC 8414 / OIDC metadata, reporting whether PKCE S256 and dynamic client registration are available, then deep-links you into the OAuth debug tool to get a token.

Notes

  • Only the Streamable HTTP transport is supported. The deprecated 2024-11-05 HTTP+SSE transport and local stdio servers are out of scope.
  • Credential-looking request headers (Authorization, and any header named like *token*, *secret*, *key*, *auth*, *password*) are masked in the transcript and in exports. Response bodies are recorded verbatim — treat an export as sensitive.
  • A token saved through the Settings screen is stored in plain text in config.local.php.
  • verify_tls exists because local MCP servers often have self-signed certificates. Turning it off means anyone on the path can read the bearer token you send.

About

A tiny standalone page that connects to an MCP server over Streamable HTTP, runs the initialize handshake, lists everything the server exposes, and lets you invoke any of it by hand — showing the raw JSON-RPC request and response at every step.

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Contributors

Languages