How do you monitor and observe an MCP server in production?
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: truerate, 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.