Overview

A running MCP server is opaque to the client, so its own logs are the main diagnostic surface. The protocol’s logging feature (the logging capability and notifications/message) is deprecated as of 2026-07-28: logging/setLevel is gone, and a server may emit notifications/message only for a request that set io.modelcontextprotocol/logLevel in _meta. The spec’s migration path is stderr for stdio servers and OpenTelemetry for structured observability. Build on those, not on protocol logging.

Emit one structured JSON line per tool call

Record timestamp, tool name, argument summary, result summary, duration in milliseconds, trace ID, and error. Log at the server boundary so the duration includes serialization and the downstream API call, not only the API client.

{"ts":"2026-10-01T10:21:00Z","tool":"search_issues","args":{"q":"is:open label:bug","limit":30},"result_count":17,"duration_ms":412,"trace_id":"0af7651916cd43dd8448eb211c80319c","error":null}

Log result summaries (counts, status, first characters) at INFO and full payloads only at DEBUG, off by default. Redact before logging; log messages must not contain credentials, personal data, or internal details. See mcp-security.

Correlate through trace context

The protocol has no session ID to correlate on. Read traceparent, tracestate, and baggage from _meta (W3C Trace Context and Baggage formats, reserved for OpenTelemetry), log the trace ID on every line, and forward traceparent to downstream APIs so a failure in their logs traces back to the agent run.

Never write logs to stdout on stdio servers

On the stdio transport stdout carries only MCP messages, one per line. A stray print or console.log corrupts the stream and the client sees a broken or silent server. Send logs to stderr, which clients may capture but should not treat as an error.

Track latency per tool

Keep a rolling median and p95 per tool. The median is typical performance; the p95 is the tail the agent hits on about one call in twenty. Alert when a tool’s latency jumps, which usually means a downstream problem, a cache regression, or a schema change that triggered a full scan. Feed the data into evaluation to catch regressions and into mcp-tool-design pruning decisions.

Debug with MCP Inspector before wiring a client

The Inspector (@modelcontextprotocol/inspector, Node 22.19.0 or newer) is the reference tool and ships three clients behind one binary.

npx @modelcontextprotocol/inspector node path/to/server.js        # web UI; prints a URL with a one-time token
npx @modelcontextprotocol/inspector --cli node server.js --method tools/list
npx @modelcontextprotocol/inspector --server-url https://api.example.com/mcp --transport http

Check that tools/list returns the expected schemas, that tools/call with edge-case inputs returns structured errors instead of stack traces, and that raw messages match the spec. --tui gives a terminal UI.

Recognize the five common failure modes

FailureLog signatureFix
Server never startsNo entries, or the process exitsCheck command, binary path, env vars, and stdout pollution
Unstructured errorNon-null error holding a tracebackWrap handlers; return isError: true with a message the model can act on
Downstream auth failureLow duration_ms, “401” or “unauthorized”Check env expansion and token expiry
Downstream rate limitNear-zero duration_ms, “429”Rate limit per caller before the downstream call
Schema driftresult_count of 0 or missing fieldsValidate output against outputSchema and log shape deviations