Skip to content

Route logs to stderr in MCP mode - #5

Draft
MadTinker wants to merge 1 commit into
claude/project-roadmap-review-qlb7j0from
claude/mcp-stdout-logging-fix
Draft

Route logs to stderr in MCP mode#5
MadTinker wants to merge 1 commit into
claude/project-roadmap-review-qlb7j0from
claude/mcp-stdout-logging-fix

Conversation

@MadTinker

Copy link
Copy Markdown
Collaborator

Stacked on #4. Base is claude/project-roadmap-review-qlb7j0, so the diff shows only this fix.

The bug

MCP speaks JSON-RPC 2.0 over stdout. The tracing subscriber also defaulted to stdout, so every log line was interleaved into the protocol stream.

A client reading the first line for its initialize response got this:

INFO ThreadId(01) tinker: src/main.rs:90: Starting Tinker Workshop...

instead of JSON, and failed to parse. This affected any MCP client, including Claude Desktop using the configuration in the readme.

Reproduced by discarding stderr and observing that log lines still arrive on stdout:

$ tinker --mcp --url https://example.com < init.json 2>/dev/null
INFO ThreadId(01) tinker: src/main.rs:90: Starting Tinker Workshop...
INFO ThreadId(01) tinker::browser::console: Creating console monitor...
...

Two causes

  1. tracing_subscriber::fmt() writes to stdout unless told otherwise.
  2. Logging was initialized at line 80, before Args::parse() at line 93 — so it could not know whether --mcp was set. The info! on line 89 was the first line to corrupt the stream.

The fix

Parse arguments first, then point the subscriber at stderr when running as an MCP server. Non-MCP output is unchanged — same destination, same format.

The MCP write loop was already correct (writes JSON, flushes) and there are no other println!/print! calls anywhere in src/, so the subscriber was the sole corruption source.

The ignored tests were masking this

tests/mcp_tests.rs had three #[ignore]d tests whose comment read "Ignore by default since it requires building the binary." That wasn't why they were failing — they were failing on the corrupted stream, at the JSON parse on line 51.

They pass now and are re-enabled. They spawn the real binary and speak JSON-RPC end to end, so they guard this exact regression.

Verification

$ tinker --mcp --url https://example.com < init.json 2>/dev/null
{"jsonrpc":"2.0","id":1,"result":{"capabilities":{...},"protocolVersion":"2024-11-05","serverInfo":{"name":"tinker-browser-mcp","version":"0.1.0"}}}

Valid JSON, correct id, correct protocolVersion.

cargo test → 167 passed, 0 failed, 0 ignored

Previously 164 passed with 3 ignored.


Generated by Claude Code

MCP speaks JSON-RPC 2.0 over stdout, but the tracing subscriber defaulted to
stdout too, so every log line was interleaved into the protocol stream. A
client reading the first line for its initialize response got

  INFO ThreadId(01) tinker: src/main.rs:90: Starting Tinker Workshop...

instead of JSON, and failed to parse. This affected any MCP client, including
Claude Desktop using the configuration in the readme.

Logging was also initialized before the arguments were parsed, so it could not
know whether --mcp was set. Parse first, then point the subscriber at stderr
when running as an MCP server. Non-MCP output is unchanged.

The MCP write loop itself was already correct, and there are no other stdout
writers in the tree, so the subscriber was the sole corruption source.

This is why tests/mcp_tests.rs had three #[ignore]d tests. The attribute said
they were skipped because they require building the binary, but they were
failing on the corrupted stream. They pass now and are re-enabled, giving
end-to-end protocol coverage that guards this regression.

cargo test: 167 passed, 0 failed, 0 ignored (was 164 passed, 3 ignored).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pg53xf5xcUV7oc5CkaiCpz
@MadTinker
MadTinker force-pushed the claude/mcp-stdout-logging-fix branch from cbe0af3 to 28f7cf8 Compare August 22, 2026 18:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants