← Blog

Monitoring a Remote Streamable HTTP MCP Server: What Actually Needs Watching

By admin · Apr 18, 2026 · 5 min read

Monitoring a Remote Streamable HTTP MCP Server: What Actually Needs Watching

Most people monitor an HTTP service by pinging it. For an MCP server exposed over Streamable HTTP, a plain ping tells you almost nothing useful — the endpoint can return 200 OK on a bare GET while the actual protocol underneath is completely broken. If you run or depend on a remote MCP server, it's worth understanding what a real health check looks like for this transport, and where things quietly break.

Why a plain HTTP check isn't enough

A Streamable HTTP MCP server exposes a single endpoint (something like https://example.com/mcp) that accepts both POST and GET. A POST without a valid JSON-RPC body, or a request with the wrong Accept header, gets rejected for reasons that have nothing to do with whether the protocol itself is healthy. Conversely, a load balancer or reverse proxy can return 200 from an unrelated route while the real MCP endpoint behind it is down, misrouted, or serving a broken tool list.

The only check that means anything is one that actually speaks MCP: send an initialize request, follow it with tools/list, and inspect what comes back.

What the transport requires you to get right

A few details of the spec matter more than they first appear:

None of this is exotic, but it's exactly the kind of detail that a generic uptime monitor has no reason to know about.

A minimal handshake check

Here's roughly what a correct check looks like, stripped down to the essentials:

import httpx

url = "https://example.com/mcp"
headers = {
    "Content-Type": "application/json",
    "Accept": "application/json, text/event-stream",
}

init_req = {
    "jsonrpc": "2.0", "id": 1, "method": "initialize",
    "params": {
        "protocolVersion": "2025-11-25",
        "capabilities": {},
        "clientInfo": {"name": "health-check", "version": "1.0"},
    },
}
resp = httpx.post(url, json=init_req, headers=headers, timeout=10)
session_id = resp.headers.get("MCP-Session-Id")
if session_id:
    headers["MCP-Session-Id"] = session_id

# required notification before normal requests
httpx.post(url, json={"jsonrpc": "2.0", "method": "notifications/initialized"}, headers=headers)

tools_req = {"jsonrpc": "2.0", "id": 2, "method": "tools/list"}
tools = httpx.post(url, json=tools_req, headers=headers, timeout=10).json()

That's the shape of the check. What you do with the tools response is the interesting part.

What to actually watch for

Latency of the round trip, not just the connection. A server can accept the TCP connection instantly and still take seconds to produce an InitializeResult because it's doing auth lookups or cold-starting a backing service.

Schema drift in tools/list. Each tool in the response has a name, description, an inputSchema (JSON Schema), and optionally an outputSchema. Deployments change these more often than teams expect — a renamed parameter, a field that silently became required, a type that widened or narrowed. None of this trips a normal uptime check, but it breaks every client that built against the old shape. This is the failure mode that's easy to miss because the server is technically "up."

SSE streams that open but never resolve. If the server responds with text/event-stream, the spec says it should eventually deliver a JSON-RPC response for the request on that stream. A check that only confirms the stream opened, without waiting for the actual response event, will report healthy on a server that's hung mid-request.

Session churn. Watch for how often MCP-Session-Id changes across otherwise-identical requests, or for a spike in 404s (the code servers must return once a session has expired) — it usually means something upstream is recycling server instances more aggressively than clients expect.

One thing worth flagging early

A release candidate circulating for the next spec revision proposes removing the initialize handshake and session IDs entirely, making Streamable HTTP fully stateless. It hasn't shipped as final yet, but if you're building your own checks by hand, it's worth writing them so the handshake logic is isolated in one place — you'll want to swap it out without rewriting your whole monitor.

Putting it on a schedule

Doing this by hand once is easy. Doing it every five minutes, from an independent network path, with alerting on both handshake failures and schema changes, is the part that's tedious to build and maintain yourself. That's the specific gap MCP Vitals fills: it runs the real initializetools/list handshake against your server on a schedule, diffs the tool schemas between runs to catch drift before your clients do, and gives you a public status badge you can drop in your README.

If you're currently monitoring an MCP server with a generic uptime tool, the gap above — schema drift, stalled SSE streams, silent session churn — is exactly what it won't catch. Worth checking whether your current setup actually exercises the protocol, not just the port.

Monitor your MCP server — free

Real handshake checks, tool-schema drift detection, and a public health badge in 60 seconds.

Start free