You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* fix(diagnostics): classify stdio subprocess exit-before-initialize; surface stderr + exit code
When an stdio upstream subprocess starts but exits before completing the MCP
initialize handshake (e.g. a docker image that prints a fatal config error and
dies, like mcp/brave-search with no BRAVE_API_KEY), mcp-go reports a closed
transport. Previously this fell through to MCPX_UNKNOWN_UNCLASSIFIED with cause
"transport error: transport closed", and the actionable child stderr was
discarded — so the UI told users to "file a bug" for a self-serviceable config
error. Recurring gap (cf. #483).
- New code MCPX_STDIO_EXIT_BEFORE_INITIALIZE + catalog entry (registry.go).
- classifyStdio: match "transport closed" / "exited before completing the mcp
initialize" under the stdio transport hint → the new code (gated so a closed
transport on another transport is not misattributed).
- initialize(): on a closed-transport failure, enrich the error with the
captured recent stderr tail and (best-effort) the child exit code so the real
cause is visible in the UI banner and per-server logs. Exit code is
best-effort: on stdio the process handle is only extracted after a successful
initialize, so the stderr tail is the primary signal on the failure path.
- Regression test (classifier_test.go): raw + enriched inputs classify to the
new code and carry exit code + stderr tail; a non-stdio "transport closed"
does not match.
The UI banner surfaces whatever the classifier returns, so it improves
automatically — no frontend change needed.
Related MCP-1093, #599
Co-Authored-By: Paperclip <noreply@paperclip.ing>
* fix(diagnostics): add error doc + production enrichment test for stdio early-exit
Addresses Codex review on PR #606:
- Add docs/errors/MCPX_STDIO_EXIT_BEFORE_INITIALIZE.md and the STDIO index
entry in docs/errors/README.md (ENG-9 docs for the new public code).
- Extract enrichTransportClosedError() from the initialize() premature-exit
path and unit-test it (exit code + stderr tail + wrapped cause), replacing
the hard-coded-string assertion that proved nothing about the production path.
Related #599
* fix(diagnostics): scope stdio early-exit enrichment to stderr (drop unreliable exit code)
Addresses Codex review round 2 on PR #606: childExitInfo() only had a code
when ProcessState was set, but the stdio init-failure path returns before the
process is reaped, so the exit code was essentially never present. Drop the
exit-code from the enrichment, docs, and test; surface the captured stderr tail
(the actionable cause). Exit-code capture is a separate follow-up.
Related #599
* test(diagnostics): drop stale exit-code wording in stdio early-exit test
Codex review round 3 on PR #606: the enrichment no longer surfaces an exit
code (stderr-only scope), so update the test comment + case wording so readers
don't infer exit-code surfacing is part of acceptance.
Related #599
* fix(diagnostics): gate stdio premature-exit enrichment to stdio transport
Codex review round on PR #606: initialize() is shared by HTTP/SSE transports,
so a remote server closing the connection was mislabeled 'server process exited
before completing the MCP initialize handshake'. Gate the enrichment to the
stdio transport via shouldEnrichStdioPrematureExit (HTTP/SSE keep generic
diagnostics) + table test across transports.
Related #599
* fix(diagnostics): classify auto-detected stdio servers (resolved transport hint)
Codex review round on PR #606: the supervisor built the classifier transport
hint from raw Config.Protocol, so auto-detected stdio servers (Protocol=='' +
Command!='') missed the stdio-gated rules and stayed UNKNOWN. Use
transport.DetermineTransportType(config) at both hint sites so the resolved
transport drives classification + add a DetermineTransportType regression test.
Related #599
---------
Co-authored-by: Paperclip <noreply@paperclip.ing>
Copy file name to clipboardExpand all lines: internal/diagnostics/registry.go
+10Lines changed: 10 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -61,6 +61,16 @@ func seedSTDIO() {
61
61
},
62
62
DocsURL: docsURL(STDIOExitNonzero),
63
63
})
64
+
register(CatalogEntry{
65
+
Code: STDIOExitBeforeInitialize,
66
+
Severity: SeverityError,
67
+
UserMessage: "The stdio server process exited before completing the MCP initialize handshake. This usually means a missing/invalid configuration (e.g. a required API key or environment variable) — check the captured stderr for the exact cause.",
68
+
FixSteps: []FixStep{
69
+
{Type: FixStepButton, Label: "Show last server log lines", FixerKey: "stdio_show_last_logs"},
70
+
{Type: FixStepLink, Label: "Troubleshooting early exit", URL: docsURL(STDIOExitBeforeInitialize)},
returnfmt.Errorf("server did not respond to MCP initialize within %s and produced no stderr output (check that the command starts an MCP server and not a help banner)", waited)
60
62
}
61
63
64
+
// STDIO subprocess exited before completing the handshake: mcp-go reports
65
+
// a closed transport / EOF on the pipe (not a typed exit error). Surface
66
+
// the captured stderr so the user sees the real, often self-serviceable
67
+
// cause (e.g. "Error: --brave-api-key is required") instead of a bare
68
+
// "transport closed" that the diagnostics layer marks UNKNOWN. Gated to
69
+
// stdio: initialize() is shared by HTTP/SSE transports, which have no
70
+
// local subprocess and must keep their generic diagnostics. (MCP-1093 /
returnfmt.Errorf("server process exited before completing the MCP initialize handshake; recent stderr:\n%s: %w", stderrBlock, cause)
126
+
}
127
+
returnfmt.Errorf("server process exited before completing the MCP initialize handshake and produced no stderr output (transport closed before the handshake): %w", cause)
128
+
}
129
+
130
+
// isTransportClosedErr reports whether an initialize() failure indicates the
131
+
// child process went away mid-handshake. mcp-go surfaces a premature stdio
132
+
// exit as a closed transport / EOF on the pipe rather than a typed exit error,
133
+
// so we match those shapes to distinguish "the process died" from a genuine
0 commit comments