How to Health-Check a Remote MCP Server (HTTP 200 Is Not Enough)
If you point a conventional uptime monitor at an MCP endpoint, it will GET the URL, see a response, and report the server healthy. That report is close to meaningless. An MCP server is not a web page — it is a JSON-RPC service that AI agents open sessions against and call tools on. It can return an HTTP status to a GET while being completely broken for every agent that tries to use it: the handshake can error, tools/list can throw, a tool's input schema can change shape overnight and silently break every client that calls it. In fact, a correct Streamable HTTP server that only does request/response is allowed by the spec to answer GET with 405 — so a naive GET probe can mark a perfectly healthy server down, and a 200 from a CDN error page can mark a dead one up.
"Up," for an MCP server, is a protocol-level claim. This post walks through what a real health check verifies, gives you a probe you can run with curl, and lists what is actually worth alerting on.
What "Up" Actually Means
A health check that speaks the protocol verifies five things, in order:
1. The initialize handshake completes. The client POSTs a JSON-RPC 2.0 initialize request; the server must answer with a result carrying protocolVersion, serverInfo, and its capabilities. A JSON-RPC error here, or a response body that is not parseable JSON-RPC at all, means agents cannot start a session — the server is down for its actual consumers regardless of the HTTP status code. (There is a growing class of exceptions: servers on the 2026-07-28 stateless spec revision have no initialize at all — more on that below.)
2. The protocol version is sane. A spec-compliant server echoes the negotiated protocolVersion in its initialize result. A missing version is a mild interop risk — some clients reject a handshake without it — and a version you did not expect (or one that changes between checks) is a sign the server was upgraded under you.
3. tools/list returns. The handshake proves the server is alive; the capability inventory proves it is useful. If the server advertises the tools capability, a follow-up tools/list call should return the actual tool list. A server that shakes hands but fails to enumerate its tools will break any agent that starts by asking what it can do — which is most of them.
4. The schema has not drifted. This is the failure mode one-shot checks cannot see. Take a digest of each tool's contract — name, description, and inputSchema — and compare it against the previous run. A tool that disappears, appears, or changes its input schema between checks is a silent breaking change: agents built against the old contract start failing mid-flight, and nothing in the HTTP layer ever looked unhealthy. Protocol-version changes and a stateful-to-stateless spec migration belong in the same diff.
5. The auth posture is what you expect. A 401 or 403 from an MCP endpoint is not a failure — the server is up, just gated. But the posture should be stable: a server that required auth yesterday and answers anonymously today has a problem worth paging about, and the reverse (suddenly auth-gated) will lock out every agent holding no credential.
A Probe You Can Run With curl
The handshake is just an HTTP POST, so you can run the first two verifications from a shell. Note the Accept header: Streamable HTTP servers may answer with plain JSON or with an SSE-framed response, and the spec expects clients to accept both.
curl -sS -X POST https://example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "health-probe", "version": "1.0.0" }
}
}'
A healthy stateful server answers with something like:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"serverInfo": { "name": "example-server", "version": "1.4.2" },
"capabilities": { "tools": {} }
}
}
If the response arrives as text/event-stream, the same JSON is inside a data: line — still a pass. Check the response headers for Mcp-Session-Id; if present, replay it on the follow-ups. Then send the notifications/initialized notification (a strict server expects it before further calls) and the inventory call:
curl -sS -X POST https://example.com/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-H 'MCP-Protocol-Version: 2025-06-18' \
-H 'Mcp-Session-Id: <id from the initialize response>' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'
A usable result.tools array means an agent can actually work with this server. If instead you get a JSON-RPC error, an empty body, or HTML, the diagnosis matters — why MCP initialize fails walks through each failure signature.
Two special cases to classify correctly:
- 401/403 — up but auth-gated. Degraded from a monitoring perspective (you cannot see behind the gate without a token), not down.
initializerejected but the server is otherwise reachable — before declaring it down, probetools/listdirectly. The 2026-07-28 spec revision removed theinitializehandshake entirely (client info and protocol version travel per-request in_meta), so a migrated server rejects your handshake while being perfectly healthy for agents. A monitor that does not know this false-alarms on every migrated server.
Measure latency on the handshake round-trip while you are at it. A server that answers in four seconds is up, but every agent session against it will feel slow to start; a sensible budget to warn at is around three seconds.
What to Alert On
Not every observation deserves a page. A workable severity split:
Alert as down:
- Endpoint unreachable (network error, DNS failure).
initializereturns a JSON-RPC error, or the response is not parseable JSON-RPC — and the statelesstools/listfallback also fails.- A 5xx or an unexpected non-auth HTTP status.
Alert as degraded:
- The handshake succeeds but
tools/listfails while thetoolscapability is advertised. - Handshake latency over your budget.
- The server flipped to auth-gated (or a stored token stopped working).
Alert as drift (the one only continuous checks can catch):
- A tool was removed, or its input schema or description changed — breaking for agents already calling it.
- The advertised capability set shrank.
- The protocol version changed, or the server crossed the stateful/stateless spec boundary — clients pinned to the old transport can break the moment that happens. Which transport a server speaks, and how to tell, is its own topic: see Streamable HTTP vs SSE.
New tools appearing is informational — worth a notification, not a page.
Run One Now
You can run this whole sequence by hand, but the point of a health check is that it runs when you are not looking. Merlonix's free MCP health checker performs exactly the probe described here against any URL you give it — the real initialize handshake, the protocol-version and transport read, tools/list, latency, and an A–F security-posture grade — with no signup. The same check runs continuously as a monitored check type, with drift detection between runs, on the paid plans. And if you operate MCP servers as part of your job, the MCP server developers page collects the rest of the toolchain in one place.