charmbracelet/crush · error
failed to authenticate MCP: status code %d
Error message
failed to authenticate MCP: status code %d
What it means
This error is returned by Client.MCPAuthenticate when the server responds with a non-200 status code but the body either fails to decode into proto.Error or has an empty Message. The client can only report the raw HTTP status code: "failed to authenticate MCP: status code %d". It means the request reached the server but the server rejected it without a usable JSON error payload.
Source
Thrown at internal/client/proto.go:386
// MCPAuthenticate runs the OAuth flow for a named MCP server. The server's
// local browser is suppressed; the caller is responsible for surfacing the
// authorization URL (via polling [Client.MCPPendingAuth] / state events)
// and opening it on the user's machine. The call blocks until the flow
// completes, fails, or ctx is cancelled.
func (c *Client) MCPAuthenticate(ctx context.Context, id, name string) error {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/mcp/auth", id), nil,
jsonBody(proto.MCPNameRequest{Name: name}),
http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return fmt.Errorf("failed to authenticate MCP: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
var e proto.Error
if err := json.NewDecoder(rsp.Body).Decode(&e); err == nil && e.Message != "" {
return fmt.Errorf("failed to authenticate MCP: %s", e.Message)
}
return fmt.Errorf("failed to authenticate MCP: status code %d", rsp.StatusCode)
}
return nil
}
// MCPRefreshPrompts refreshes prompts for a named MCP client.
func (c *Client) MCPRefreshPrompts(ctx context.Context, id, name string) error {
rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/mcp/refresh-prompts", id), nil,
jsonBody(struct {
Name string `json:"name"`
}{Name: name}),
http.Header{"Content-Type": []string{"application/json"}})
if err != nil {
return fmt.Errorf("failed to refresh MCP prompts: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return fmt.Errorf("failed to refresh MCP prompts: status code %d", rsp.StatusCode)
}View on GitHub (pinned to 7944b8e522)
Solutions
- Note the numeric status code and map it: 4xx = request/credentials problem, 5xx = server-side problem
- Check the server/proxy logs around the request time for the underlying cause
- Confirm the client base URL targets the correct API version that exposes /workspaces/{id}/mcp/auth
- If 5xx or 429, retry with backoff; if 401/403/404, fix credentials, permissions, or endpoint configuration
Example fix
// before
if err := client.MCPAuthenticate(ctx, wsID, name); err != nil {
return err // opaque: only "status code 502" is known
}
// after
if err := client.MCPAuthenticate(ctx, wsID, name); err != nil {
if strings.Contains(err.Error(), "status code 5") || strings.Contains(err.Error(), "status code 429") {
time.Sleep(backoff) // transient upstream error: retry
return client.MCPAuthenticate(ctx, wsID, name)
}
return err
} Defensive patterns
Strategy: retry
Validate before calling
// Ensure the endpoint is the real API before sending credentials
func endpointIsAPI(ctx context.Context, baseURL string) error {
resp, err := http.Get(strings.TrimRight(baseURL, "/") + "/healthz")
if err != nil { return err }
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { return fmt.Errorf("unexpected health status %d", resp.StatusCode) }
return nil
} Type guard
// Detect the raw-status form of this error
func isOpaqueStatusErr(err error) (int, bool) {
if err == nil { return 0, false }
var code int
n, _ := fmt.Sscanf(err.Error(), "failed to authenticate MCP: status code %d", &code)
return code, n == 1
} Try / catch
// Retry transient statuses, fail fast on persistent auth failures
if err := client.MCPAuthenticate(ctx, id, name); err != nil {
if code, ok := isOpaqueStatusErr(err); ok {
if code == http.StatusTooManyRequests || code >= 500 {
return retryWithBackoff(3, func() error { return client.MCPAuthenticate(ctx, id, name) })
}
return fmt.Errorf("persistent auth rejection (HTTP %d), check endpoint/credentials", code)
}
return err
} Prevention
- Confirm the client base URL includes the correct API version prefix
- Use a health/readiness check or deploy gate so calls are not made during server restarts
- Watch for proxies returning HTML error pages (502/503) and alert on them
- Log the full status code and headers to correlate with server/proxy logs
When it happens
Trigger: Calling MCPAuthenticate(ctx, id, name) and receiving a non-200 response with a non-JSON, empty, or HTML body — e.g. 500 from a crashed handler, 502/503/504 from a reverse proxy, 401 from a gateway that returns plain text, or a 404 from a wrong base URL path served by an HTML error page.
Common situations: Reverse proxy (nginx/traefik) returning HTML 502 pages during deploys; server version without the /mcp/auth endpoint (path mismatch producing HTML 404); rate limiter returning empty 429; proxy stripping the response body.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- failed to refresh MCP prompts: status code %d
- failed to refresh MCP tools: status code %d
- failed to read MCP resource: status code %d
- failed to list MCP prompts: status code %d
- failed to get MCP prompt: status code %d
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/4e5741cddbaa3070.
Report an issue: GitHub.