Skip to content

Stdio MCP server stderr is always logged as error, contrary to MCP spec (stderr carries all log levels) #321

Description

@bobbyhyam

Summary

For STDIO MCP servers, MetaMCP logs every line a child process writes to stderr at level error. Per the MCP specification, stderr is the designated channel for all logging from a stdio server (not just errors), so well-behaved servers routinely emit info/debug lines there. The result is a flood of false [ERROR] log entries for perfectly healthy servers.

What the spec says

From the MCP specification (2025-11-25), Transports → stdio:

Servers may use standard error for logging purposes, which clients may choose to capture or ignore.

And the changelog for that revision clarifies this exact point under "Minor changes":

Servers using stdio transport are permitted to use stderr for all types of logging, not exclusively for error messages.

This is by design: for a stdio server, stdout is reserved exclusively for newline-delimited JSON-RPC messages (a stray log line there corrupts the stream), so stderr is the only place a server can log — at any level. A server emitting INFO ... on stderr is therefore spec-compliant, and a client should not assume those lines are errors.

Current behavior

apps/backend/src/lib/metamcp/client.ts, the stderr data handler (lines ~67–73) hardcodes the level to error:

stderrStream?.on("data", (chunk: Buffer) => {
  metamcpLogStore.addLog(
    serverParams.name,
    "error",            // <-- every stderr line tagged as error
    chunk.toString().trim(),
  );
});

stderr is an unstructured byte stream with no severity attached, so tagging all of it error misrepresents the severity the spec deliberately leaves open.

Reproduction

  1. Configure any stdio MCP server that logs at info level to stderr (e.g. a Python server using the standard SDK / logging, such as uvx some-mcp-server).
  2. Watch MetaMCP's logs.
  3. Observe routine info lines surfaced as [ERROR], e.g.:
    [ERROR] ... [MetaMCP][some-mcp] INFO  Processing request of type ...
    
    These are healthy request traces, not errors.

Suggested fix

Log captured stderr data at a non-error level (info is the minimum correct choice; MetaMcpLogEntry.level is "error" | "info" | "warn"):

stderrStream?.on("data", (chunk: Buffer) => {
  metamcpLogStore.addLog(serverParams.name, "info", chunk.toString().trim());
});

Note: the adjacent stream error event handler (lines ~75–82) should stay at error — that's a genuine failure of the stderr stream itself, not server log output.

(A fancier version could parse common level prefixes like INFO/WARN/ERROR from the line, but defaulting captured stderr to info/neutral is the spec-correct baseline.)

🤖 Filed with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions