feat(mcp): add MCP-023, tool prints to stdout, corrupting a stdio transport - #82
feat(mcp): add MCP-023, tool prints to stdout, corrupting a stdio transport#82bradAGI wants to merge 1 commit into
Conversation
…nsport MCP had no observability rule. OpenAI ships OAI-010 and ADK ships ADK-009 for the same pattern, both at low severity as a lost-diagnostic problem. It is not a lost-diagnostic problem here, which is why this ships at medium rather than low. On a stdio server — the FastMCP default, and the usual way an editor or desktop client launches a server — stdout is the protocol channel, so a print interleaves with the newline-delimited JSON-RPC frames the client is parsing. The client hits a parse error on a line that is not JSON and drops the response or tears down the connection, and the symptom does not resemble the cause: a tool call that returns nothing, or a mid-session disconnect, traced back to a print that reads like harmless debugging. Confidence 0.7 rather than higher because a server run over HTTP or SSE escapes the corruption, though it still loses the diagnostic. The fix names stderr specifically, which the transport leaves alone.
|
Heads-up on an ID overlap, and it's already resolved on my side — no action needed from you. This PR and my #80 both shipped as Your stack claims 023 through 028 (#82, #85, #88, #91), so yielding was one edit for me versus five for you. I've renumbered mine to While I was checking the ranges: #52 and #81 both claim Separately, #55's note that |
MCP had no observability rule. OpenAI ships OAI-010 and ADK ships ADK-009 for the same
print()pattern — both atlowseverity, framed as a lost-diagnostic problem.It isn't a lost-diagnostic problem here, which is why this ships at
mediuminstead. On a stdio server — the FastMCP default, and the usual way an editor or desktop client launches a server — stdout is the protocol channel. The client reads newline-delimited JSON-RPC messages from it, so a looseprintinterleaves with those frames: the client hits a parse error on a line that isn't JSON and, depending on the client, drops the response, logs a protocol error, or tears down the connection.The reason it's worth flagging rather than leaving to review is that the failure doesn't look like its cause. The symptom is a tool call that returns nothing, or a server that disconnects mid-session; the
printthat broke it reads like harmless debugging and sits nowhere near the error.OAI-010's explanation already anticipates exactly this — it warns that a tool "ever exposed over an MCP stdio server" will corrupt the stream. This rule is that case as the primary reading rather than the footnote.
Confidence is 0.7 rather than higher because a server run over HTTP or SSE escapes the corruption — though it still loses the diagnostic, since the model only ever sees the return value. The fix names stderr specifically, which the transport leaves alone and clients typically capture as server logs, plus the caveat about nothing in the process reconfiguring logging onto stdout.
Verification — engine built at
main:Fire (
print(f"looking up {order_id}")in a FastMCP tool handler):MCP-023Silent (
logger.info(...)withbasicConfig(stream=sys.stderr)): no findingshas_print_callmatches a bareprintcallee, sopprintand other attribute calls don't false-positive.No new predicates, so no
schema_versionbump.