-
Notifications
You must be signed in to change notification settings - Fork 233
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:
-
A structured error type that includes:
- HTTP status code
- Response headers
- Response body
- Original error
-
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