Field note progress0%
← All field notes

MCP engineering / 8 min

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.

On this page

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.

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 ↗. 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.

FieldExampleWhy it matters
Timestamp2026-10-22T09:14:03.120ZOrdering and time windows
SeverityINFO, ERRORFiltering and alerting
service.namebilling-mcpSeparates servers with tools of the same name
deployment.environment.nameproductionKeeps test traffic out of production numbers
service.version2026.10.22Release comparisons
mcp.method.nametools/callSeparates tool calls from list and read requests
gen_ai.tool.namecreate_invoicePer-tool error rates and latency
Outcomeok, tool_error, protocol_errorDistinguishes isError: true results from JSON-RPC errors
error.typeValidationError, upstream_timeoutGroups failures without logging messages that may contain data
Duration in ms412Latency per tool and per client
Client name and versionExampleClient 1.0.0Client-specific bugs; self-reported, see below
mcp.protocol.version2026-07-28Spots clients on legacy revisions
Pseudonymous user and account IDsu_7f3a9c, acct_91c2e4Affected users and repeat use, without raw identity
jsonrpc.request.id plus your own request ID42, req_8k2m4pJoins records for one request
trace_id, span_idfrom the active spanJoins logs to traces

The attribute names in code font follow the OpenTelemetry MCP semantic conventions ↗, 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 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 logWhyInstead
Access and refresh tokens, Authorization headersA logged token is a usable credentialLog token presence, scopes and expiry; drop the header in middleware
The whole auth objectSDK auth objects can include the raw token; the TypeScript SDK's AuthInfo doesCopy only the fields you need
API keys, passwords, connection strings, environment dumpsSecrets in logs spread to every log consumerLog the key's ID or last four characters, never the value
Full tool arguments and resultsThey contain user content, documents and query textLog argument names, sizes and validation outcomes
Personal data such as emails, names and IP addressesLogs rarely have deletion workflowsPseudonymous IDs; truncated or dropped IPs
Secrets inside argumentsUsers paste keys into prompts, and models pass them onPattern-based redaction before any write
Raw upstream error messagesThey often echo the request payload or internal hostnamesLog 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. 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.

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 for that view and the production monitoring 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

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.

100 MCP sessions a month, free

See what people do with your MCP.