# MCP Server Logging: What to Log, What Never to Log

> Where MCP server logs go, which fields to record for each tool call, what never to log, and how to tie logs to traces under the 2026-07-28 spec.

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

MCP server logs go to stderr for stdio servers and to your own log pipeline for remote servers, never to stdout. Log one structured record per tool call with the tool, outcome, duration, version, client and a pseudonymous user ID, and keep tokens, secrets, personal data and full payloads out by default.

This guide covers where logs go on each transport, what changed for protocol logging in the 2026-07-28 specification, which fields to record, what to redact, and how to find logs in Claude Desktop, VS Code and Cursor. For metrics and traces beyond logs, see the [OpenTelemetry instrumentation guide](https://flowlines.ai/blog/mcp-server-observability-guide).

## Where do MCP server logs go?

It depends on the transport.

**stdio servers.** The client launches the server as a subprocess and reads MCP messages from its stdout. The stdio transport specification says the server "MUST NOT write anything to its stdout that is not a valid MCP message" and MAY write UTF-8 strings to stderr "for any logging purposes". The client MAY capture, forward or ignore stderr, and should not assume stderr output means an error.

The most common bug is a stray print. In Python, `print()` writes to stdout and corrupts the protocol stream, while the standard `logging` module writes to stderr by default. In Node.js, `console.log` writes to stdout; use `console.error` or a logger configured for stderr. Check that dependencies do not print banners or progress bars to stdout either.

**Remote servers (Streamable HTTP).** The MCP debugging guide notes that stderr is not captured by the client. Use your own server-side log aggregation or OpenTelemetry, as you would for any HTTP service.

## Is MCP protocol logging deprecated?

Yes. The 2026-07-28 revision deprecates the Logging feature, along with Roots and Sampling, in [SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577). The suggested migration is to log to stderr for stdio, or use OpenTelemetry.

What that means in practice:

- The feature still works during the deprecation window, which lasts at least twelve months after the revision's release. New implementations SHOULD NOT adopt it.
- `logging/setLevel` is removed. A client now asks for log messages per request with `io.modelcontextprotocol/logLevel` in `_meta`, and the server MUST NOT emit `notifications/message` for requests without that field.
- These messages go to the client, not to you. They help a user debug a single request in their client; they are not your server's operational log.

The security rules on that page still apply as a baseline for every log you write: log messages MUST NOT contain credentials or secrets, personal identifying information, or internal system details that could aid attacks.

## What should I log for each MCP tool call?

One structured record per tool call, with fixed field names across every server you run. These fields answer most debugging and usage questions without storing content.

| Field | Example | Why it matters |
|---|---|---|
| Timestamp | `2026-10-22T09:14:03.120Z` | Ordering and time windows |
| Severity | `INFO`, `ERROR` | Filtering and alerting |
| `service.name` | `billing-mcp` | Separates servers with tools of the same name |
| `deployment.environment.name` | `production` | Keeps test traffic out of production numbers |
| `service.version` | `2026.10.22` | Release comparisons |
| `mcp.method.name` | `tools/call` | Separates tool calls from list and read requests |
| `gen_ai.tool.name` | `create_invoice` | Per-tool error rates and latency |
| Outcome | `ok`, `tool_error`, `protocol_error` | Distinguishes `isError: true` results from JSON-RPC errors |
| `error.type` | `ValidationError`, `upstream_timeout` | Groups failures without logging messages that may contain data |
| Duration in ms | `412` | Latency per tool and per client |
| Client name and version | `ExampleClient 1.0.0` | Client-specific bugs; self-reported, see below |
| `mcp.protocol.version` | `2026-07-28` | Spots clients on legacy revisions |
| Pseudonymous user and account IDs | `u_7f3a9c`, `acct_91c2e4` | Affected users and repeat use, without raw identity |
| `jsonrpc.request.id` plus your own request ID | `42`, `req_8k2m4p` | Joins records for one request |
| `trace_id`, `span_id` | from the active span | Joins logs to traces |

The attribute names in code font follow the [OpenTelemetry MCP semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/mcp.md), so logs and spans can share them.

A few details matter:

- **Client name** comes from `io.modelcontextprotocol/clientInfo` in each request's `_meta`. It is self-reported and unverified, so treat it as context. See [which AI clients call your MCP server](https://flowlines.ai/blog/identify-mcp-clients-and-users) for the details.
- **Request IDs.** A JSON-RPC `id` only has to be unique among the sender's in-flight requests, so two clients can both send `id: 1`. Generate your own request ID as well.
- **Sessions.** The 2026-07-28 revision has no protocol sessions, and the specification says a connection is not a session. Do not derive a session ID from the connection. If your server mints its own state handles, log a hashed form of the handle.
- **Pseudonymous IDs** should be a keyed hash of the verified token's issuer and subject, computed in the server.

## What should I never log?

Assume logs are read by more people, kept longer and copied to more places than your database. The OWASP Logging Cheat Sheet lists access tokens, session identifiers, passwords, connection strings, encryption keys and sensitive personal data among the things that should usually not be recorded directly.

| Never log | Why | Instead |
|---|---|---|
| Access and refresh tokens, `Authorization` headers | A logged token is a usable credential | Log token presence, scopes and expiry; drop the header in middleware |
| The whole auth object | SDK auth objects can include the raw token; the TypeScript SDK's `AuthInfo` does | Copy only the fields you need |
| API keys, passwords, connection strings, environment dumps | Secrets in logs spread to every log consumer | Log the key's ID or last four characters, never the value |
| Full tool arguments and results | They contain user content, documents and query text | Log argument names, sizes and validation outcomes |
| Personal data such as emails, names and IP addresses | Logs rarely have deletion workflows | Pseudonymous IDs; truncated or dropped IPs |
| Secrets inside arguments | Users paste keys into prompts, and models pass them on | Pattern-based redaction before any write |
| Raw upstream error messages | They often echo the request payload or internal hostnames | Log the error class and status code |

Redact in layers, because one layer always misses something:

1. **Allowlist fields at the source.** Build the log record from named fields rather than serialising request objects. What you never add cannot leak.
2. **Strip headers in middleware.** Drop `Authorization`, cookies and any custom credential headers before the request reaches the logger.
3. **Scrub patterns before export.** Run a redaction processor, in the application or in an OpenTelemetry Collector, for bearer tokens, JWT shapes, common key prefixes and email addresses.
4. **Test with synthetic secrets.** Send a fake token and a fake email address through each tool, then search every log destination for them.

If you need payloads for a specific investigation, capture them behind a short-lived, access-controlled debug flag, not by default. For the full approach, see [MCP monitoring without payloads](https://flowlines.ai/questions/mcp-monitoring-without-payloads). Authentication failures deserve their own care, because they are the moment tokens are most likely to be printed; see [how to debug MCP OAuth failures](https://flowlines.ai/questions/debug-mcp-oauth-failures).

## How do I correlate MCP logs with traces?

Emit logs inside the active span and propagate trace context from the client.

- The 2026-07-28 specification reserves `traceparent`, `tracestate` and `baggage` in `_meta` for OpenTelemetry context propagation, in W3C Trace Context and Baggage formats. Extract them when a request arrives so the server span joins the client's trace.
- The OpenTelemetry logs data model gives each log record `TraceId` and `SpanId` fields. Most logging bridges fill them automatically when a log is written inside an active span. If yours does not, add `trace_id` and `span_id` yourself.
- Use the same `service.name`, environment and version on logs, spans and metrics, so a filter in one view works in the others.

**Flowlines builds MCP usage analytics from the same tool-call spans, so the fields you record for debugging also show which users and clients rely on each tool.** See [MCP observability](https://flowlines.ai/mcp-observability) for that view and the [production monitoring guide](https://flowlines.ai/blog/mcp-observability-guide) for alert rules.

## How long should I keep MCP server logs?

As long as you need them for a stated purpose, and no longer. OWASP's guidance is to keep logs for the required retention period and not beyond it, with periods set by legal, regulatory and contractual obligations. No MCP-specific standard exists. A common pattern:

- Debug-level logs and any payload captures: days, not months.
- Structured tool-call records with pseudonymous IDs: long enough to compare releases and see repeat use.
- Aggregated metrics with no user IDs: longer, because they carry little risk.

Apply the same retention to every copy, including a second export destination and backups.

## Where are MCP logs in Claude Desktop, VS Code and Cursor?

For stdio servers, the client captures stderr. These locations come from each product's documentation:

- **Claude Desktop:** `~/Library/Logs/Claude` on macOS and `%APPDATA%\Claude\logs` on Windows. On macOS, run `tail -n 20 -F ~/Library/Logs/Claude/mcp*.log`.
- **VS Code:** run **MCP: List Servers** from the Command Palette, select the server and choose **Show Output**.
- **Cursor:** open the Output panel (Cmd+Shift+U) and select **MCP Logs** from the dropdown.

Client logs show what the client saw, which helps with startup and connection errors. They do not replace your own logs, and remote servers do not appear there at all.

## Checklist

- [ ] stdio servers write nothing but MCP messages to stdout; all logs go to stderr.
- [ ] Remote servers send logs to your own pipeline or OpenTelemetry.
- [ ] No new code depends on deprecated MCP protocol logging.
- [ ] One structured record per tool call with the fields above.
- [ ] Tokens, auth headers, secrets, personal data and full payloads are excluded, with redaction tested using synthetic secrets.
- [ ] Logs carry trace and span IDs, and `traceparent` is read from `_meta`.
- [ ] Retention is set per log class and applied to every copy.

## Sources

- [MCP 2026-07-28 changelog](https://modelcontextprotocol.io/specification/2026-07-28/changelog)
- [MCP 2026-07-28 stdio transport](https://modelcontextprotocol.io/specification/2026-07-28/basic/transports/stdio)
- [MCP 2026-07-28 Logging (deprecated)](https://modelcontextprotocol.io/specification/2026-07-28/server/utilities/logging)
- [MCP 2026-07-28 base protocol: `_meta`](https://modelcontextprotocol.io/specification/2026-07-28/basic/index)
- [MCP debugging guide](https://modelcontextprotocol.io/docs/tools/debugging)
- [OpenTelemetry MCP semantic conventions](https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/mcp.md)
- [OpenTelemetry logs data model](https://opentelemetry.io/docs/specs/otel/logs/data-model/)
- [OWASP Logging Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html)
- [VS Code: manage and troubleshoot MCP servers](https://code.visualstudio.com/docs/agents/run/manage-mcp-servers)
- [Cursor MCP documentation](https://cursor.com/docs/context/mcp)
## Frequently asked questions

### Where should a stdio MCP server write its logs?

To stderr. The stdio transport reserves stdout for MCP messages, and the server MUST NOT write anything to stdout that is not a valid MCP message.

### Is MCP protocol logging deprecated?

Yes. The 2026-07-28 revision deprecates the Logging feature (SEP-2577). It still works for at least twelve months, but new servers should log to stderr for stdio or use OpenTelemetry instead.

### What should I log for each MCP tool call?

Log the tool name, outcome and error class, duration, server name, environment and version, client name and version, a pseudonymous user or account ID, the request ID and the trace and span IDs. Leave arguments and results out by default.

### What must never appear in MCP server logs?

Access and refresh tokens, Authorization headers, API keys, passwords, connection strings and other secrets, plus personal data and full tool payloads unless you have a specific, approved reason to keep them.

### Where are Claude Desktop MCP logs?

On macOS in `~/Library/Logs/Claude`, and on Windows in the Claude logs folder under `%APPDATA%`. The MCP debugging guide suggests following the MCP log files in that folder with `tail -F` on macOS.

### How do I correlate MCP logs with traces?

Emit logs inside the active span so each record carries the trace ID and span ID, and accept `traceparent` from the request metadata so the server span joins the client's trace.
