{"record":{"id":"4e5741cddbaa3070","repo":"charmbracelet/crush","slug":"failed-to-authenticate-mcp-status-code-d","errorCode":null,"errorMessage":"failed to authenticate MCP: status code %d","messagePattern":"failed to authenticate MCP: status code (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":386,"sourceCode":"// MCPAuthenticate runs the OAuth flow for a named MCP server. The server's\n// local browser is suppressed; the caller is responsible for surfacing the\n// authorization URL (via polling [Client.MCPPendingAuth] / state events)\n// and opening it on the user's machine. The call blocks until the flow\n// completes, fails, or ctx is cancelled.\nfunc (c *Client) MCPAuthenticate(ctx context.Context, id, name string) error {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/mcp/auth\", id), nil,\n\t\tjsonBody(proto.MCPNameRequest{Name: name}),\n\t\thttp.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to authenticate MCP: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\tvar e proto.Error\n\t\tif err := json.NewDecoder(rsp.Body).Decode(&e); err == nil && e.Message != \"\" {\n\t\t\treturn fmt.Errorf(\"failed to authenticate MCP: %s\", e.Message)\n\t\t}\n\t\treturn fmt.Errorf(\"failed to authenticate MCP: status code %d\", rsp.StatusCode)\n\t}\n\treturn nil\n}\n\n// MCPRefreshPrompts refreshes prompts for a named MCP client.\nfunc (c *Client) MCPRefreshPrompts(ctx context.Context, id, name string) error {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/mcp/refresh-prompts\", id), nil,\n\t\tjsonBody(struct {\n\t\t\tName string `json:\"name\"`\n\t\t}{Name: name}),\n\t\thttp.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to refresh MCP prompts: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"failed to refresh MCP prompts: status code %d\", rsp.StatusCode)\n\t}","sourceCodeStart":368,"sourceCodeEnd":404,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L368-L404","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nif err := client.MCPAuthenticate(ctx, wsID, name); err != nil {\n    return err // opaque: only \"status code 502\" is known\n}\n\n// after\nif err := client.MCPAuthenticate(ctx, wsID, name); err != nil {\n    if strings.Contains(err.Error(), \"status code 5\") || strings.Contains(err.Error(), \"status code 429\") {\n        time.Sleep(backoff) // transient upstream error: retry\n        return client.MCPAuthenticate(ctx, wsID, name)\n    }\n    return err\n}","handlingStrategy":"retry","validationCode":"// Ensure the endpoint is the real API before sending credentials\nfunc endpointIsAPI(ctx context.Context, baseURL string) error {\n    resp, err := http.Get(strings.TrimRight(baseURL, \"/\") + \"/healthz\")\n    if err != nil { return err }\n    defer resp.Body.Close()\n    if resp.StatusCode != http.StatusOK { return fmt.Errorf(\"unexpected health status %d\", resp.StatusCode) }\n    return nil\n}","typeGuard":"// Detect the raw-status form of this error\nfunc isOpaqueStatusErr(err error) (int, bool) {\n    if err == nil { return 0, false }\n    var code int\n    n, _ := fmt.Sscanf(err.Error(), \"failed to authenticate MCP: status code %d\", &code)\n    return code, n == 1\n}","tryCatchPattern":"// Retry transient statuses, fail fast on persistent auth failures\nif err := client.MCPAuthenticate(ctx, id, name); err != nil {\n    if code, ok := isOpaqueStatusErr(err); ok {\n        if code == http.StatusTooManyRequests || code >= 500 {\n            return retryWithBackoff(3, func() error { return client.MCPAuthenticate(ctx, id, name) })\n        }\n        return fmt.Errorf(\"persistent auth rejection (HTTP %d), check endpoint/credentials\", code)\n    }\n    return err\n}","preventionTips":["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"],"tags":["http","mcp","status-code","api-error"],"backgroundTag":"unexpected-http-status","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}