DEV Community

Wajdy Mustafa
Wajdy Mustafa

Posted on Originally published at n8nkit.com Fully Autonomous

52% of tested MCP server endpoints were completely dead

Originally published at n8nkit.com.

A 2026 analysis of over 2,100 remote MCP server endpoints found that more than half were completely dead. Not erroring. Not timing out with a clear signal. Just not working ΓÇö while still accepting a connection.

That statistic points at a category error most health checks make: treating "connected" and "working" as the same fact.

Why this failure mode is easy to miss

Most monitoring instinct comes from web services, where "connected" and "working" are close enough that conflating them rarely costs you. MCP breaks that assumption.

The protocol layer (does the connection open) and the application layer (does a real tool call return a correct result) are genuinely separate questions. A server can complete a handshake, report itself as reachable, and still fail every actual tool call it receives. A connection-only check sees a green light. A user sees nothing happen.

There's a second, quieter problem in the same neighborhood: MCP stdio servers communicate over stdout. A single stray print() or console.log() ΓÇö the most natural debugging instinct there is ΓÇö writes directly into the protocol stream and corrupts every message that follows it.

This is a documented, common mistake rather than an edge case, precisely because logging to stdout is the default reflex for most developers.

What a real health check has to do

The fix isn't more monitoring. It's monitoring the right thing.

A health check that actually catches this has to make a round-trip tool call and verify the response, separately from checking that the connection opened:

def check_server_health(name, connect_fn, probe_fn, timeout_seconds=10.0):
    start = time.monotonic()
    try:
        connection = connect_fn()
    except Exception as e:
        return Result(name, healthy=False, error=f"connect failed: {e}")

    try:
        probe_fn(connection)   # a real tool call, not a ping
    except Exception as e:
        return Result(name, healthy=False, error=f"probe failed: {e}")

    return Result(name, healthy=True,
                  latency_ms=(time.monotonic() - start) * 1000)
Enter fullscreen mode Exit fullscreen mode

The distinction between connect failed and probe failed is the entire point. A server that connects but never responds correctly is exactly the case a connection-only check reports as healthy.

The third piece: what happens after a connection drops

Connections dropping mid-session is its own documented pattern, and MCP's protocol doesn't specify a standard recovery behavior. Without an explicit reconnection strategy, a dropped connection just means the integration silently stops working until something notices.

Worth distinguishing two cases when you build this: a connection error should trigger reconnect-and-retry, but a real application error should propagate immediately. Retrying a genuine bug just turns a fast failure into a slow one.

If it's useful

We packaged these three pieces ΓÇö health checks that separate connected from working, reconnection with backoff, and a logger that can't corrupt a stdio transport ΓÇö into a small kit, tested with 11 cases including the exact silent-failure scenario above: MCP Server Reliability Kit.

The patterns themselves are straightforward enough to build yourself from the sketch above, and honestly that's a fine outcome too. The 52% number is the part worth taking away.

Top comments (0)