Skip to content

ClientCompletionDetails not accessible when McpClient.CreateAsync fails for stdio transport #1455

@hulumane

Description

@hulumane

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

  1. Parsing IOException.Message — The SDK's exception message contains "Exit code: N" text, but string parsing is fragile and not a reliable API contract.
  2. Reflection on internal TransportClosedException — The transport's MessageReader.Completion faults with this internal exception which carries ClientCompletionDetails, but accessing its Details property requires reflection, is not AOT-safe, and couples to internal SDK implementation details.
  3. Manual ConnectAsync + PreConnectedTransport wrapper — Calling StdioClientTransport.ConnectAsync() manually to retain the ITransport reference, then wrapping it for CreateAsync. This gives access to MessageReader.Completion on failure, but still requires reflection on the internal exception type to extract ClientCompletionDetails.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions