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.phpandstyles.cssare copies of the OAuth tool's versions rather than shared includes, so you can movemcp/anywhere without anything breaking. Keep the internal folder structure intact.
Click any image to view the full size.
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.
Put the folder where your web server serves it from, or run PHP's built-in server:
php -S localhost:9100 -t mcp- Connect — endpoint URL, protocol version, request headers, TLS policy. The
initializehandshake runs immediately. - Connection — the negotiated protocol version, the
Mcp-Session-Id(if the server issued one),serverInfo, the declared capabilities, and anyinstructionsthe server wants added to a model's system prompt. - Tools — every tool across all pagination pages, with its behaviour hints as pills
(
destructiveshows red before you click Call). Pick one and you get a form generated from itsinputSchema— 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, embeddedresource), and if the tool declares anoutputSchemaitsstructuredContentis validated against it. - Resources — concrete resources and URI templates (fill the variables to build a URI),
rendering
textandblobcontents. - Prompts — argument form, then the expanded
messages[]as a role-labelled transcript. - 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 →202with no body, a missingMCP-Protocol-Versionheader, and whether every declared capability is actually backed by a working method. None of them invokes a real tool. - Network transcript — every request the client made, on every page, with headers, timing and copy-as-curl. Exportable as HAR or JSON.
- SSE-framed replies. A Streamable HTTP response may be
application/jsonortext/event-stream, per request. Frames are parsed, the reply is matched by JSON-RPCid, and anything else the server streamed (progress, log records) is shown as "extra messages". - Session expiry. A 404 on a request carrying
Mcp-Session-Idmeans 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
errorobject (the protocol failed) and a result withisError: 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.
- Only the Streamable HTTP transport is supported. The deprecated 2024-11-05 HTTP+SSE transport
and local
stdioservers 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_tlsexists because local MCP servers often have self-signed certificates. Turning it off means anyone on the path can read the bearer token you send.


