---
name: mcp-observability
description: >-
  MCP server structured logging, stderr discipline for stdio transport, log message notifications, request tracing,
  debugging with MCP Inspector, and performance monitoring
topics:
  - mcp
  - observability
  - logging
  - tracing
  - debugging
  - monitoring
volatility: evolving
last-reviewed: 2026-06-14
version-pin: null
sources:
  - url: https://modelcontextprotocol.io/docs/tools/debugging
    hash: sha256:3f9e2e6a4dc66bec775eadc473478d9c8475164c4f61e99315349faeadf17b68
    retrieved: 2026-06-14
  - url: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/logging
    hash: sha256:21e45cf3257b8859a79082615d6e83e37bd26e82d27a2bbf7284feff5755c950
    retrieved: 2026-06-14
---

Good observability in an MCP server means you can diagnose problems without attaching a debugger. The fundamental constraint is the stdio transport's stdout-is-protocol-only rule, which makes naive logging dangerous. Structured logging to the right channel is non-negotiable.

## Summary

For **stdio servers**, write all logging to **stderr** — stdout is reserved exclusively for JSON-RPC protocol messages. Any stray write to stdout corrupts the protocol stream. For **HTTP servers**, use standard structured logging to files, stdout, or an aggregator — stderr is not captured by clients. MCP also provides a protocol-level logging mechanism via `notifications/message` (usable on any transport). Always include a correlation ID per request. Use the MCP Inspector as your first debugging tool.

## Deep Guidance

### Stderr discipline for stdio transport

The stdio transport reserves stdout entirely for JSON-RPC protocol messages. Even a single line of non-JSON output to stdout — a startup banner, a debug print, a library that logs to stdout by default — will corrupt the message stream and cause parse errors in the client. This is the most common cause of "MCP server not responding" problems.

**TypeScript/Node.js**: use `process.stderr.write(...)` or a logging library configured to stderr:
```typescript
import { createWriteStream } from 'node:fs'
// write to stderr or a log file — never process.stdout
const log = (msg: string) => process.stderr.write(`[server] ${msg}\n`)
```

**Python**: use `print(..., file=sys.stderr)` or configure `logging` to use stderr:
```python
import logging, sys
logging.basicConfig(
    level=logging.INFO,
    stream=sys.stderr,  # critical: must be stderr, not stdout
    format='%(asctime)s %(levelname)s %(name)s: %(message)s'
)
```

The Python SDK's FastMCP does this correctly by default. Watch for third-party libraries that configure their own logging handlers pointing at stdout.

**Go**: `log.Println` writes to stderr by default — safe for stdio servers. Avoid `fmt.Println` which goes to stdout.

### MCP protocol-level logging (all transports)

For any transport, the server can send log messages to the client via the `notifications/message` notification. The server must declare `logging: {}` in its capabilities during initialization.

Log levels follow RFC 5424: `debug`, `info`, `notice`, `warning`, `error`, `critical`, `alert`, `emergency`. Clients can set the minimum level via `logging/setLevel`.

TypeScript SDK:
```typescript
await server.sendLoggingMessage({ level: 'info', data: 'Tool execution started', logger: 'weather-tool' })
```

Python FastMCP:
```python
await ctx.session.send_log_message(level="info", data="Processing request", logger="my-server")
```

Protocol-level logging is visible to the MCP client (and to the Inspector's Notifications pane), making it useful for surfacing server state to the host application without relying on stderr capture.

### Structured logging format

Use structured logs (JSON or key=value) rather than unstructured strings. Include:
- `timestamp`: ISO 8601 or epoch milliseconds
- `level`: debug/info/warning/error
- `request_id` or `correlation_id`: unique per MCP request (use the JSON-RPC `id` field value)
- `method`: the JSON-RPC method being handled
- `tool_name` / `resource_uri` / `prompt_name`: the specific capability invoked
- `duration_ms`: time taken (for completed requests)
- `error`: error type and message (for failures)

Example structured log entry:
```json
{
  "timestamp": "2025-06-18T14:32:01.234Z",
  "level": "info",
  "request_id": "42",
  "method": "tools/call",
  "tool_name": "get_weather",
  "duration_ms": 234,
  "location": "New York"
}
```

### Request tracing

Assign a correlation ID to each incoming MCP request using the JSON-RPC `id` field (for requests) or a generated UUID (for notifications). Thread this ID through all log entries, downstream API calls, and database queries generated by that request. This makes it possible to reconstruct the full execution path for a single tool call when debugging a failure.

For HTTP transport, also include the `Mcp-Session-Id` in logs to correlate all requests within a session.

### Debugging with MCP Inspector

The MCP Inspector is the most effective debugging tool for local development:

```bash
# Launch your server with the Inspector
npx @modelcontextprotocol/inspector node dist/server.js
```

Inspector debugging workflow:
1. Check the Notifications pane for server log messages and error notifications.
2. Invoke a failing tool and inspect the raw request/response in the tool result pane.
3. Verify capability negotiation — check that the capabilities shown in the Inspector match what your server declares.
4. Test error paths explicitly: call tools with invalid inputs and verify `isError: true` responses display correctly.

For Claude Desktop specifically, tail the MCP log files:
```bash
# macOS
tail -F ~/Library/Logs/Claude/mcp*.log
```

### Performance monitoring

Log operation timing for all tool calls and resource reads. Track:
- **p50/p95/p99 latency** per tool: identify slow tools before users complain.
- **Error rate** per tool: high error rates indicate API instability or logic bugs.
- **Payload size**: large tool results consume LLM context; log result byte sizes to catch runaway responses.
- **Downstream API latency**: separately log time spent waiting for external APIs vs. server-side processing.

For HTTP-transport servers, standard HTTP middleware (Morgan, Express's req timing) captures request-level metrics. For stdio servers, implement timing within each tool handler and write to stderr/log file.

Set alerts on: error rate above 5%, tool latency p99 above 5 seconds, and any `SIGTERM` not followed by clean exit within 30 seconds (hung shutdown).
