How do you monitor and observe an MCP server in production?

5 minadvancedmcpobservabilitymonitoringproduction

Quick Answer

Treat an MCP server like any other production service: emit structured logs for every tools/call/resources/read (tool name, arguments with secrets redacted, latency, success/failure, isError status) to your standard logging pipeline; track metrics like per-tool call counts, latency percentiles, and error rates, since a single tool silently failing or timing out can degrade the whole conversational experience without an obvious crash; and add tracing (correlating a single tool call with the broader request, especially useful for a remote HTTP server handling concurrent sessions) so you can reconstruct what happened during a specific session after the fact. Beyond generic service observability, MCP-specific things worth watching include: rate of isError: true results per tool (a spike often means an upstream dependency broke or a schema mismatch appeared), how often clients disconnect mid-session, and (for HTTP servers) authorization failures, which can indicate either a misconfigured client or an attempted abuse pattern.

Detailed Answer

An MCP server is still a regular backend service under the hood. The same observability fundamentals apply, plus a few signals specific to how MCP calls actually get triggered.

Structured logging per call

import time, logging

logger = logging.getLogger("mcp-server")

async def handle_tool_call(name, arguments):
    start = time.time()
    try:
        result = await execute_tool(name, arguments)
        logger.info("tool_call", extra={
            "tool": name, "duration_ms": (time.time() - start) * 1000,
            "is_error": result.get("isError", False)
        })
        return result
    except Exception as e:
        logger.error("tool_call_failed", extra={"tool": name, "error": str(e)})
        raise

Always redact secrets and potentially sensitive user data before logging arguments. A logged api_key argument — which shouldn't exist per the security topic's guidance, but might slip in — or a logged customer record is a real data-exposure risk in your own logging pipeline.

Metrics worth tracking

  • Per-tool invocation count and latency (p50/p95/p99). A slow tool degrades the whole conversation's responsiveness, even without ever technically failing.
  • isError: true rate, per tool. A sudden spike usually means an upstream API changed shape, credentials expired, or a new client is sending malformed arguments your validation didn't anticipate.
  • Session duration and disconnect patterns (for HTTP servers). An unusual rate of abrupt disconnects can indicate a client-side bug or a server-side crash under specific conditions.
  • Authorization failure rate (HTTP servers). Spikes can indicate either a misconfigured client rollout or a genuine abuse/probing attempt worth investigating.

Tracing for multi-session HTTP servers

Trace ID: abc-123
  -> initialize (session xyz)
  -> tools/call: search_orders (120ms)
  -> tools/call: get_order (45ms)
  -> resources/read: order-invoice-42.pdf (300ms)

Correlating every request within one session under a shared trace ID makes debugging a specific user's reported issue far faster than grepping raw logs.

Why this matters more than it might seem

Tool calls are triggered by model decisions, not a fixed, predictable call pattern a developer wrote. Production issues often show up as "the assistant behaved oddly" rather than a clean stack trace. Good observability is frequently the only way to reconstruct which tool call, with which arguments, actually caused a confusing downstream result.

Related Resources