modelcontextprotocol / modelcontextprotocol/go-sdk

Proposal: Provide better error handling for HTTP errors when using a Streamable Client

Open
#579 10 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

needs community feedback
Dominant language
Go
Stars
5.1k
Forks
543
Avg merge
1d 17h
Merged PRs (30d)
37

Description

Is your feature request related to a problem? Please describe.

When an MCP server responds to an initialize request with an error status (e.g., 401 Unauthorized), the current error handling in client.Connect() only provides the HTTP status code as part of the error message itself.
Important context like response headers (e.g., WWW-Authenticate), response body, and other HTTP metadata are not accessible to the caller.

This makes it difficult to implement proper authentication flows or provide detailed error messages to users, as critical information needed to handle the error appropriately is lost.

Describe the solution you'd like

Expose HTTP response details in connection errors, potentially through:

  1. A structured error type that includes:

    • HTTP status code
    • Response headers
    • Response body
    • Original error
  2. Example API:

type HTTPError struct {
    StatusCode int
    Headers    http.Header
    Body       []byte
    Err        error
}

func (e *HTTPError) Error() string { ... }
func (e *HTTPError) Unwrap() error { return e.Err }

This would allow clients to handle authentication challenges and other HTTP-level errors appropriately:

session, err := client.Connect(ctx, transport, nil)
if err != nil {
    var httpErr *mcp.HTTPError
    if errors.As(err, &httpErr) {
        if httpErr.StatusCode == 401 {
        // Access WWW-Authenticate header
        authHeader := httpErr.Headers.Get("WWW-Authenticate")
        // Handle authentication flow
        }
    }
}

Describe alternatives you've considered

Wrapping the HTTP client in Transport options:
While this works, it adds significant complexity:

  • Requires manual synchronization (mutexes) to safely capture response data when the session is used concurrently
  • Couples transport-level concerns with business logic
  • Makes the code harder to maintain and test

Additional context

Minimal reproduction example:

package main

import (
	"context"
	"log"
	"net/http"

	"github.com/modelcontextprotocol/go-sdk/mcp"
)

func main() {
	go runServer("localhost:8000")
	runClient("http://localhost:8000")
}

func runServer(url string) {
	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
		w.WriteHeader(http.StatusUnauthorized)
	})

	log.Printf("[Server] MCP server listening on %s", url)

	if err := http.ListenAndServe(url, handler); err != nil {
		log.Fatalf("[Server] Server failed: %v", err)
	}
}

func runClient(url string) {
	ctx := context.Background()

	log.Printf("[Client] Connecting to MCP server at %s", url)

	client := mcp.NewClient(&mcp.Implementation{}, nil)

	session, err := client.Connect(ctx, &mcp.StreamableClientTransport{Endpoint: url}, nil)
	if err != nil {
		// Currently: no access to WWW-Authenticate header or response body
		log.Fatalf("[Client] Failed to connect: %v (type: %T)", err, err)
	}
	defer session.Close()

	log.Println("[Client] Session acquired successfully")
}

Output:

2025/10/14 16:41:25 [Client] Connecting to MCP server at http://localhost:8000
2025/10/14 16:41:25 [Server] MCP server listening on localhost:8000
2025/10/14 16:41:25 [Client] Failed to connect: calling "initialize": broken session: 401 Unauthorized (type: *fmt.wrapError)
exit status 1

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at client.Connect() and StreamableClientTransport, then trace how an initialize HTTP failure becomes the current broken-session error. Done means callers can access the HTTP status, response headers, response body, and original error through a structured connection error, including the WWW-Authenticate header shown in the example.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, networking
Issue type
Feature
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.