# How to Monitor a Remote MCP Server: Health Checks, Metrics and Alerts

> Monitor a remote MCP server with health checks, synthetic probes, per-tool metrics and alert rules that work across replicas and AI clients.

Source: https://flowlines.ai/blog/remote-mcp-server-monitoring · Updated 2026-10-22 · By Flowlines
Index of all Flowlines pages for AI agents: https://flowlines.ai/llms.txt

To monitor a remote MCP server, run a health endpoint and an external MCP probe, record per-tool call counts and latency, and alert on sustained error and latency changes per tool. Under the 2026-07-28 Streamable HTTP transport every message is its own HTTP POST with no protocol session, which makes requests easier to measure and replicas easier to scale.

A server can return HTTP 200 on every request and still fail users, because a JSON-RPC error or a tool result with `isError: true` travels inside a successful HTTP response. This guide covers what to measure at each layer, example alert rules and how to run behind load balancers and multiple AI clients.

## What does the Streamable HTTP transport mean for monitoring?

In the 2026-07-28 revision, the server exposes one MCP endpoint, such as `https://example.com/mcp`, that accepts POST. Each JSON-RPC request is its own POST, and the server answers with either a single JSON object or a Server-Sent Events stream scoped to that request.

The [changelog](https://modelcontextprotocol.io/specification/2026-07-28/changelog) lists the changes that matter most for operations:

- **No protocol sessions.** The `Mcp-Session-Id` header is gone. Servers that need state across calls return explicit handles as tool arguments.
- **No initialize handshake.** Every request carries its protocol version and client capabilities in `_meta`, and clients should add their client info.
- **No GET stream.** Long-lived change notifications use a `subscriptions/listen` request whose response stream stays open.
- **No `ping`.** It was removed, so do not build health checks on it.
- **New `server/discover` method**, which servers must implement to advertise versions, capabilities and identity.

POST requests now carry headers that mirror the body: `MCP-Protocol-Version`, `Mcp-Method` on every request, and `Mcp-Name` for `tools/call`, `resources/read` and `prompts/get`. Gateways and load balancers can label traffic by method and tool without parsing JSON. The server must reject mismatches.

Status codes worth counting at the edge:

| Response | Meaning in 2026-07-28 | Error class |
|---|---|---|
| `202 Accepted` | Notification accepted | Success |
| `400` with `-32020 HeaderMismatch` | Headers missing or not matching the body | Protocol |
| `400` with `UnsupportedProtocolVersionError` | Client asked for a version you do not serve | Protocol |
| `401` / `403` | Missing or invalid token / insufficient scope | Auth |
| `404` with `-32601` | Method not found | Protocol |
| `405` | GET or DELETE from an older client | Protocol (legacy traffic) |
| `5xx`, reset, timeout | Server or network failure | Transport |

On Streamable HTTP, a client cancels by closing the request's response stream, and streams are not resumable. Count closed streams as cancellations, not errors.

## How do I set up liveness and readiness checks?

Use three checks, each on its own path:

| Check | Path | What it tests |
|---|---|---|
| Liveness | `/healthz` | The process responds. No dependency calls. |
| Readiness | `/readyz` | Database, upstream APIs and the auth provider are reachable. |
| MCP probe | `/mcp` | A real `tools/list` or read-only tool call succeeds end to end. |

Keep health routes off the MCP endpoint so they never need MCP headers or tokens, and make sure they reveal nothing sensitive. Fail readiness to take a replica out of rotation; restart only on repeated liveness failures.

The MCP probe sends what a client would send. A `tools/list` request under 2026-07-28 looks like this:

```sh
curl -sS https://mcp.example.com/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2026-07-28' \
  -H 'Mcp-Method: tools/list' \
  -H "Authorization: Bearer $PROBE_TOKEN" \
  -d '{"jsonrpc":"2.0","id":"probe-1","method":"tools/list","params":{"_meta":{
       "io.modelcontextprotocol/protocolVersion":"2026-07-28",
       "io.modelcontextprotocol/clientInfo":{"name":"uptime-probe","version":"1.0.0"},
       "io.modelcontextprotocol/clientCapabilities":{}}}}'
```

Check that the response contains the tools you expect, not only that it returned 200. Then call one read-only tool against a synthetic account. The [MCP Inspector CLI](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/cli) also works as a probe: it exits `3` when authentication is required, `4` when the server is unreachable and `5` when a tool returns `isError: true`.

Give the probe its own client name and identity, and exclude it from user analytics.

## Which metrics should I collect?

| Metric | What to record | Why it matters |
|---|---|---|
| Request rate | Calls per second by server, environment, tool and client | Traffic drops are often the first sign of a broken client or route |
| Error ratio | Failed classified attempts / all classified attempts, by class | Separates broken infrastructure from tools that report failures |
| Latency | p50, p95 and p99 per tool, in seconds | Averages hide the slow tail that models time out on |
| Saturation | In-flight requests vs concurrency limit, queue time, pool use, open listen streams, upstream 429s | Predicts failures before they happen |
| Auth failures | 401 and 403 counts at the auth boundary | Expired tokens, scope changes or an auth server outage |
| Payload size | Request and response body size histograms | Oversized results slow clients and fill model context |
| Probe success | Successful probes / attempted probes | Availability that does not depend on user traffic |

For tool calls, the metric names in the Flowlines examples are a good default. `mcp_tool_calls_total` is a counter with the labels `service_name`, `environment`, `tool` and `outcome` (`success` or `error`, with cancellations and unknowns kept separate). `mcp_tool_duration_seconds` is a histogram in seconds with the same dimensions. These are example names, not official OpenTelemetry metric names. Count attempts before trace sampling.

To split errors by class, add a low-cardinality `error_type` label with values such as `transport`, `protocol`, `auth` and `tool_error`. The spec separates protocol errors, returned as JSON-RPC errors, from tool execution errors, returned as results with `isError: true` so the model can self-correct. A rise in `tool_error` often means bad inputs or an upstream API problem. A rise in `protocol` often means a client or schema mismatch.

Never put user IDs, emails or arguments in metric labels. Keep them in access-controlled traces or analytics.

## What alert rules should I start with?

Start with a few alerts that each have an owner and a runbook. The error and latency rules below come from the [Flowlines example alerts file](https://flowlines.ai/examples/mcp-monitoring/alerts.yml):

```yaml
- alert: MCPToolErrorRateHigh
  expr: |
    (
      sum by (service_name, environment, tool) (rate(mcp_tool_calls_total{outcome="error"}[5m]))
      /
      sum by (service_name, environment, tool) (rate(mcp_tool_calls_total{outcome=~"success|error"}[5m]))
    ) > 0.05
    and
    sum by (service_name, environment, tool) (increase(mcp_tool_calls_total{outcome=~"success|error"}[5m])) >= 100
  for: 5m
- alert: MCPToolLatencyHigh
  expr: |
    histogram_quantile(0.95,
      sum by (service_name, environment, tool, le) (rate(mcp_tool_duration_seconds_bucket[5m]))
    ) > 2
    and
    sum by (service_name, environment, tool) (increase(mcp_tool_duration_seconds_count[5m])) >= 100
  for: 5m
```

The volume condition stops a handful of calls from paging someone at night. Then add:

| Alert | Example condition | Severity |
|---|---|---|
| Probe failing | 3 consecutive failed probes from 2 or more regions | Page |
| Auth failures rising | 401 + 403 rate more than 3x the same hour last week, for 10 minutes | Ticket, page if probes also fail |
| Traffic missing | No tool calls for 15 minutes during a normally busy window | Page |
| Saturation | In-flight requests above 80% of the limit for 10 minutes | Ticket |
| Telemetry missing | No metrics received from a server for 10 minutes | Ticket |

Thresholds are starting points to tune, not standards. More detail is in [MCP error spike alerts](https://flowlines.ai/questions/mcp-error-spike-alerts) and [tracking MCP tool latency](https://flowlines.ai/questions/mcp-tool-latency). The [example Grafana dashboard](https://flowlines.ai/examples/mcp-monitoring/grafana-dashboard.json) uses the same metric names, with panels for calls, error rate and p50, p95 and p99 latency by tool, filtered by server and environment. Import it and adapt the labels to yours.

## How do I run behind a load balancer with several replicas?

Because the 2026-07-28 revision has no protocol session, any replica can serve any request and the protocol does not need sticky sessions. Three things still need care:

- **Tool state.** If tools return handles, such as a basket or job ID, store the state behind them where every replica can read it.
- **Long-lived streams.** `subscriptions/listen` responses stay open. Set idle timeouts above your keep-alive interval, send the SSE comment keep-alives the spec suggests, and set `X-Accel-Buffering: no` so proxies do not buffer events. Track open streams per replica.
- **Older clients.** Revisions 2025-03-26 through 2025-11-25 used `Mcp-Session-Id`. If you still serve them, those sessions need affinity or a shared store.

Aggregate metrics across replicas by summing counters and histogram buckets, then compute percentiles. Never average p95 values from separate replicas. Label each replica so one bad instance stands out.

## How do I monitor the server across several AI clients?

Each request can carry `io.modelcontextprotocol/clientInfo` in `_meta`, and the spec says clients should send it. Record the client name as a label, keep an `unknown` bucket and normalize versions to avoid label explosion. Client names are self-reported, so treat them as a hint, not an identity.

Compare the same tool across clients. One client may retry on `isError: true` while another gives up, so the same server change can look different in each. If you run several servers, give each a distinct `service_name`; see [monitoring multiple MCP servers](https://flowlines.ai/questions/monitor-multiple-mcp-servers).

Flowlines adds who was affected: the users, AI clients and sessions behind a spike.

## Why run uptime checks from outside the network?

Internal checks miss failures in DNS, TLS certificates, CDNs, firewalls and the authorization server, which is exactly what remote clients hit first. Run the MCP probe from at least two regions outside your cloud, through the same public hostname clients use, with a real token. Alert only when several locations agree, to avoid paging on one probe's network.

## Checklist

- [ ] `/healthz` and `/readyz` separate from the MCP endpoint
- [ ] External `tools/list` and read-only tool probe from 2+ regions
- [ ] `mcp_tool_calls_total` and `mcp_tool_duration_seconds` with consistent labels
- [ ] Errors split into transport, protocol, auth and `tool_error`
- [ ] 401 and 403 counted at the auth boundary
- [ ] Error, latency, probe and missing-traffic alerts with owners
- [ ] Shared storage for tool state handles across replicas
- [ ] Client name label with an `unknown` bucket

For the wider setup, read the [MCP observability guide](https://flowlines.ai/blog/mcp-observability-guide), the [OpenTelemetry instrumentation guide](https://flowlines.ai/blog/mcp-server-observability-guide) and the overview of [MCP observability](https://flowlines.ai/mcp-observability).

## Primary sources

- [MCP specification 2026-07-28: Streamable HTTP transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)
- [MCP specification 2026-07-28: Key changes](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP specification 2026-07-28: Tools](https://modelcontextprotocol.io/specification/2026-07-28/server/tools)
- [MCP specification 2026-07-28: Authorization](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)
- [MCP Inspector CLI client](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/cli)

## Sources

- [MCP specification 2026-07-28: Streamable HTTP transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/streamable-http)
- [MCP specification 2026-07-28: Key changes](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP specification 2026-07-28: Tools and error handling](https://modelcontextprotocol.io/specification/2026-07-28/server/tools)
- [MCP specification 2026-07-28: Authorization error handling](https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization)
- [MCP Inspector CLI client](https://modelcontextprotocol.io/docs/2026-07-28/tools/inspector/cli)
- [Flowlines example Prometheus alert rules](https://flowlines.ai/examples/mcp-monitoring/alerts.yml)
- [Flowlines example Grafana dashboard](https://flowlines.ai/examples/mcp-monitoring/grafana-dashboard.json)
## Frequently asked questions

### How do I health check a remote MCP server?

Expose a liveness route and a readiness route separate from the MCP endpoint, then run a synthetic MCP request such as tools/list or a read-only tool call from outside your network. Each answers a different question, so keep all three.

### Does a stateless MCP server need sticky sessions?

Not for the protocol. The 2026-07-28 revision removed protocol-level sessions and the Mcp-Session-Id header, so any replica can serve any request. You still need shared storage for any state handles your tools create, and affinity if you keep serving older session-based clients.

### Which metrics matter most for a remote MCP server?

Track request rate, error ratio split by error class, p50, p95 and p99 latency per tool, saturation, 401 and 403 responses, and request and response size. Add probe success as its own metric so availability does not depend on user traffic.

### Is a tool result with isError: true a server error?

Not at the transport level. The HTTP request and JSON-RPC envelope succeeded, but the tool reported a failure such as invalid input or an upstream API error. Count it as a separate error class so you can tell a broken server from a tool that needs a better message.

### How do I monitor one MCP server across several AI clients?

Record the client name from the clientInfo each request carries, keep it as a low-cardinality label and keep an unknown bucket. Compare error ratio and latency per client for the same tool, since each client calls tools and handles errors differently.
