-
Notifications
You must be signed in to change notification settings - Fork 664
Description
Is your feature request related to a problem? Please describe.
When an MCP server process exits during McpClient.CreateAsync (e.g., the server fails validation and terminates with a non-zero exit code before completing the initialization handshake), the caller cannot access StdioClientCompletionDetails (ExitCode, ProcessId, StandardErrorTail) through any public API.
The McpClient.Completion API introduced in #1368 works perfectly for post-connection scenarios — when a server crashes after CreateAsync succeeds. However, for servers that exit during initialization (e.g., permission checks, feature gates, configuration errors), the McpClient instance is created and disposed internally by the static CreateAsync method and never returned to the caller. The only way to get the exit code is to parse the IOException message string or use reflection on the internal TransportClosedException type — neither of which is reliable or maintainable.
This is a problem for the Windows On-Device Registry (ODR), which returns specific exit codes during startup (42=Feature Disabled, 43=Agent Switch Disabled, 44=User Not Supported, 45=LAF Unavailable). Clients need to programmatically distinguish these to provide actionable error messages and recovery flows.
Describe the solution you'd like
When CreateAsync fails because the stdio server process exited, the caller should be able to access StdioClientCompletionDetails (ExitCode, ProcessId, StandardErrorTail) through a public API — the same typed completion details that McpClient.Completion provides for post-connection failures. For example, making TransportClosedException public so callers can catch (TransportClosedException ex) and access ex.Details, or exposing ClientCompletionDetails as a property on the IOException thrown by CreateAsync.
Describe alternatives you've considered
- Parsing
IOException.Message— The SDK's exception message contains "Exit code: N" text, but string parsing is fragile and not a reliable API contract. - Reflection on internal
TransportClosedException— The transport'sMessageReader.Completionfaults with this internal exception which carriesClientCompletionDetails, but accessing itsDetailsproperty requires reflection, is not AOT-safe, and couples to internal SDK implementation details. - Manual
ConnectAsync+PreConnectedTransportwrapper — CallingStdioClientTransport.ConnectAsync()manually to retain theITransportreference, then wrapping it forCreateAsync. This gives access toMessageReader.Completionon failure, but still requires reflection on the internal exception type to extractClientCompletionDetails.
Additional context
This is the launch-failure counterpart to #1332 (resolved by #1368). The Completion API solved the post-connection case; this request is about closing the gap for the pre-connection/initialization failure case. SDK version: 1.1.0.