SDK Client: `ConnectAsync` discover→initialize fallback unreachable when `AutoDetect` transport fails with `HttpRequestException`
Nobody has claimed this yet.
Assessment
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Newbie friendliness
- 55/100
- Issue type
- Bug
- Clarity
- Mostly clear
- Activity status
- Active
- Tech stack
- csharp
- Domain
- api, networking
Research direction
Start by locating ConnectAsync, AutoDetectingClientSessionTransport, TryReadJsonRpcErrorAsync, and ReceiveUnsolicitedMessagesAsync, then trace the discover request through transport fallback and standalone GET handling. Done means older POST-only servers can fall back to initialize, valid JSON-RPC errors are recognized despite non-JSON content types, and a failed optional GET stream does not tear down a working POST connection.
Written by the indexing model from the issue text.
Description
Summary
SDK v2.0 defaults to probing servers with server/discover (a 2026-07-28 method). When a server on an older protocol version rejects this probe with a non-2xx response, the SDK should fall back to the initialize handshake. This fallback is unreachable for servers that return non-application/json error responses and don't support GET/SSE, because:
TryReadJsonRpcErrorAsyncrequiresContent-Type: application/json— servers returningtext/plain(or other types) with valid JSON-RPC error bodies are not recognized- AutoDetect falls through to SSE (GET), which also fails for POST-only servers
- The combined
HttpRequestExceptionis not caught byConnectAsync— onlyMcpProtocolExceptionandOperationCanceledExceptiontriggerfallbackToInitialize
The connection fails without ever attempting initialize, which would have succeeded via POST.
Note: In SDK v1.4.0, this didn't happen because v1.4.0 sent initialize directly (no server/discover probe). The entire discover→initialize fallback path is new in v2.0.
Root cause analysis
Two layers are involved
Layer 1 — ConnectAsync (protocol negotiation): With ProtocolVersion = null, sends server/discover. If it fails, certain exceptions trigger fallbackToInitialize = true → PerformInitializeHandshakeAsync.
Layer 2 — AutoDetectingClientSessionTransport (transport negotiation): Delivers the first message (happens to be server/discover) by trying POST, then falling back to GET/SSE if POST fails.
The failure chain
ConnectAsync sends server/discover
│
└→ AutoDetect.InitializeAsync receives it as the first message
│
├─ POST server/discover → server returns 400 with text/plain JSON-RPC body
│
├─ TryReadJsonRpcErrorAsync checks Content-Type:
│ Content-Type: text/plain ≠ application/json → returns null
│ (valid JSON-RPC body is never parsed)
│
├─ Falls to else branch → SSE fallback
│ GET → server returns 405 (POST-only, no SSE support)
│
├─ InitializeSseTransportAsync wraps both errors:
│ new HttpRequestException(postError.Message, sseError, postError.StatusCode)
│
└→ HttpRequestException propagates to ConnectAsync
│
├─ catch (McpProtocolException) → NOT MATCHED
├─ catch (OperationCanceledException) → NOT MATCHED
└─ No HttpRequestException catch exists → ESCAPES ❌
initialize POST is never attempted. If it were, it would succeed (the server supports initialize just fine — it only rejects the unknown server/discover method).
What works correctly (for contrast)
When a server returns errors with Content-Type: application/json:
POST server/discover → 400 with application/json JSON-RPC body
→ TryReadJsonRpcErrorAsync succeeds → McpProtocolException
→ ConnectAsync catches McpProtocolException → fallbackToInitialize = true
→ PerformInitializeHandshakeAsync → POST initialize → 200 ✅
This path works because the SDK recognizes the JSON-RPC error and throws McpProtocolException instead of HttpRequestException.
Example: gitmcp.io
gitmcp.io is a public MCP server providing GitHub repository documentation as MCP resources. It is a POST-only Streamable HTTP server on protocol version 2025-03-26.
Server behavior (verified via curl):
| Request | Response |
|---|---|
POST initialize |
200 OK (text/event-stream, returns Mcp-Session-Id) |
POST server/discover (without session) |
400 Bad Request, Content-Type: text/plain, body: {"jsonrpc":"2.0","error":{"code":-32000,...}} |
GET (any) |
405 Method Not Allowed, body: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed"}} |
Key detail: The 400 error response has Content-Type: text/plain;charset=UTF-8 despite the body being valid JSON-RPC. This is the trigger for the bug — TryReadJsonRpcErrorAsync returns null because of the content-type check.
Observed SDK v2.0.0 behavior:
- SDK sends
server/discovervia POST - gitmcp returns 400 with
Content-Type: text/plain+ JSON-RPC error body TryReadJsonRpcErrorAsync→ null (content-type is notapplication/json)- AutoDetect falls to SSE → GET → 405
- Combined
HttpRequestException(400)escapesConnectAsync - Connection fails —
initializePOST is never attempted
Error output:
System.Net.Http.HttpRequestException: Response status code does not indicate success: 400 (Bad Request).
Response body: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Bad Request: Mcp-Session-Id header is required"},"id":null}
---> System.Net.Http.HttpRequestException: Response status code does not indicate success: 405 (Method Not Allowed).
Response body: {"jsonrpc":"2.0","error":{"code":-32000,"message":"Method not allowed"},"id":null}
Expected behavior: SDK should fall back to initialize POST, which succeeds.
Proposed fixes
Fix 1: Catch HttpRequestException in ConnectAsync discover probe
ConnectAsync currently catches McpProtocolException and OperationCanceledException from the discover probe, both setting fallbackToInitialize = true. Add HttpRequestException to this list:
// Existing catches:
catch (McpProtocolException) { fallbackToInitialize = true; }
catch (OperationCanceledException) when (...) { fallbackToInitialize = true; }
// Proposed addition:
catch (HttpRequestException) { fallbackToInitialize = true; }
Rationale: When the discover probe fails at the transport layer (both POST and GET/SSE fail), this is strong evidence the server doesn't support server/discover. Falling back to initialize is the correct behavior — same as when a McpProtocolException indicates an unknown method.
Fix 2: Relax TryReadJsonRpcErrorAsync content-type check
TryReadJsonRpcErrorAsync currently requires exactly application/json:
if (response.Content.Headers.ContentType?.MediaType != "application/json")
return null;
Many servers return valid JSON-RPC error bodies with text/plain or other content types. Consider:
- Attempting to parse JSON-RPC from any text-based content type
- Or at minimum, accepting
text/plainalongsideapplication/json
This would allow the SDK to correctly identify the server as Streamable HTTP and throw McpProtocolException (which ConnectAsync already catches), preventing the unnecessary SSE fallback.
Fix 3: These are independent and complementary
Fix 1 is a safety net — catches all transport failures regardless of content-type. Fix 2 is more precise — correctly identifies Streamable HTTP servers that use non-standard content types. Both should be applied.
Separate issue: Standalone GET stream failure treated as fatal for POST-only servers
Summary: After a successful initialize POST handshake, the Streamable HTTP transport opens a standalone GET SSE stream for unsolicited server notifications (EnableStandaloneGetStream defaults to true). If the server rejects GET (e.g., 405), the SDK treats this as a fatal connection error — even though initialize succeeded and the MCP spec treats the GET notification stream as optional.
Example: gitmcp.io accepts POST (initialize → 200, tools/list → 200) but rejects GET with 405. The connection fails despite a fully working POST channel.
Expected behavior: A failed GET notification stream should degrade gracefully — log a warning, skip unsolicited notifications, and continue operating via POST request/response. The connection should not be torn down when the core POST channel is functional.
Impact: Any POST-only Streamable HTTP server (serverless deployments, simple implementations, servers behind proxies that don't support long-lived GET) will fail to connect with default SDK settings.
Proposed fix: When the standalone GET stream fails with 405 or similar, catch the error in ReceiveUnsolicitedMessagesAsync, log it, and mark the stream as unavailable rather than propagating as a fatal connection error.
Impact
This is not gitmcp-specific. Any MCP server that meets ALL three conditions will fail:
- Returns non-
application/jsoncontent-type on JSON-RPC error responses (e.g.,text/plain) - Does not support GET/SSE (POST-only Streamable HTTP)
- Is on an older protocol version that doesn't recognize
server/discover
As the SDK defaults to probing with server/discover (a 2026-07-28 method), the number of servers hitting this failure will grow — many servers in the ecosystem are still on 2025-03-26 or 2025-11-25.
Environment
- SDK version: ModelContextProtocol 2.0.0 (C# SDK)
- Server: gitmcp.io (POST-only Streamable HTTP,
2025-03-26protocol) - Transport:
AutoDetectingClientSessionTransport(default,ProtocolVersion = null) - OS: Linux / Windows (not platform-specific)
- Note: In SDK v1.4.0, this scenario didn't arise because v1.4.0 sent
initializedirectly (noserver/discoverprobe). The discover→initialize fallback path is new in v2.0.
- Dominant language
- C#
- Stars
- 4.5k
- Forks
- 814
- Avg merge
- 9d 19h
- Merged PRs (30d)
- 4
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from modelcontextprotocol/csharp-sdk
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
modelcontextprotocol/csharp-sdk#1867 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 88/100
modelcontextprotocol/csharp-sdk#1840 · 1 comment ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
modelcontextprotocol/csharp-sdk#1836 ·
-
enhancement needs confirmation
Difficulty 2/5 1-3 hours Newbie friendliness 64/100
modelcontextprotocol/csharp-sdk#678 · 1 comment ·
-
enhancement needs confirmation P3 ready for work
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
modelcontextprotocol/csharp-sdk#515 · 6 comments · 3 reactions ·
All issues in modelcontextprotocol/csharp-sdk
Similar issues
-
type/automation type/tech-debt
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
-
t/bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
ci-failure-cause test-failure
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
-
area:auth FE mvp P3
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
klasolsson81/jobbliggaren#1788 ·