{"record":{"id":"6da226d860cae3b7","repo":"charmbracelet/crush","slug":"failed-to-authenticate-mcp-s","errorCode":null,"errorMessage":"failed to authenticate MCP: %s","messagePattern":"failed to authenticate MCP: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":384,"sourceCode":"}\n\n// 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 {","sourceCodeStart":366,"sourceCodeEnd":402,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L366-L402","documentation":"This error is returned by Client.MCPAuthenticate when the server responds with a non-200 status code AND the response body successfully decodes into proto.Error with a non-empty Message. The server's error message is surfaced verbatim: \"failed to authenticate MCP: <server message>\". It indicates the request reached the server but the server rejected the MCP authentication request for an application-level reason.","triggerScenarios":"Calling MCPAuthenticate(ctx, id, name) and receiving e.g. 400 Bad Request (unknown MCP name or malformed request), 401/403 (missing/invalid credentials or insufficient permissions on the workspace), or 404 (workspace id or named MCP client not found), with the server including a JSON error body {\"message\": \"...\"}.","commonSituations":"MCP client name not registered on the workspace; user token lacking admin rights to authenticate MCP servers; workspace id pointing to a deleted or foreign workspace; stale API token after rotation.","solutions":["Read the server message after the colon — it states the exact application-level rejection reason","Verify the MCP name matches a server configured on that workspace and the workspace id is valid","Check/refresh the client's authentication credentials and permissions for the workspace","Retry after correcting the configuration; if it is a 401/403, re-authenticate or request access"],"exampleFix":"// before\nname := \"filesytem\" // typo, MCP not registered on workspace\nerr := client.MCPAuthenticate(ctx, workspaceID, name)\n\n// after\nname := \"filesystem\" // must match the MCP server name registered on the workspace\nerr := client.MCPAuthenticate(ctx, workspaceID, name)","handlingStrategy":"validation","validationCode":"// Validate inputs and session before calling MCPAuthenticate\nfunc canAuthenticate(id, name, token string) error {\n    if id == \"\" { return errors.New(\"workspace id is required\") }\n    if name == \"\" { return errors.New(\"mcp name is required\") }\n    if token == \"\" { return errors.New(\"api token missing; log in first\") }\n    if expiry, err := tokenExpiry(token); err == nil && time.Now().After(expiry) {\n        return errors.New(\"api token expired; re-authenticate\")\n    }\n    return nil\n}","typeGuard":"// Distinguish a server-rejected auth from other failures by message shape\nfunc isServerAuthRejection(err error) bool {\n    if err == nil { return false }\n    msg := err.Error()\n    if !strings.HasPrefix(msg, \"failed to authenticate MCP: \") { return false }\n    rest := strings.TrimPrefix(msg, \"failed to authenticate MCP: \")\n    return !strings.HasPrefix(rest, \"status code \") && !strings.Contains(rest, \": \")\n}","tryCatchPattern":"// Inspect the surfaced server message and branch on the cause\nif err := client.MCPAuthenticate(ctx, id, name); err != nil {\n    msg := err.Error()\n    switch {\n    case strings.Contains(msg, \"401\") || strings.Contains(strings.ToLower(msg), \"unauthorized\"):\n        return reauthenticateAndRetry(ctx, id, name)\n    case strings.Contains(strings.ToLower(msg), \"not found\"):\n        return fmt.Errorf(\"mcp %q not registered on workspace %s: %w\", name, id, err)\n    default:\n        return err\n    }\n}","preventionTips":["Confirm the MCP name matches exactly a server registered on the workspace (watch for typos/case)","Log in or refresh the API token before batch MCP operations","Verify workspace ids against a listing call instead of hardcoding them","Check workspace permissions for the authenticated user before attempting MCP auth"],"tags":["http","mcp","authentication","api-error"],"backgroundTag":"http-4xx-rejected","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}