{"record":{"id":"295433d4241b370c","repo":"charmbracelet/crush","slug":"failed-to-get-mcp-auth-url-w","errorCode":null,"errorMessage":"failed to get MCP auth URL: %w","messagePattern":"failed to get MCP auth URL: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/proto.go","lineNumber":355,"sourceCode":"\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"failed to get MCP pending auth: status code %d\", rsp.StatusCode)\n\t}\n\tvar pending []proto.MCPPendingAuthServer\n\tif err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode MCP pending auth: %w\", err)\n\t}\n\treturn pending, nil\n}\n\n// MCPAuthURL retrieves the current OAuth authorization URL for a named MCP\n// server, if a flow is in progress.\nfunc (c *Client) MCPAuthURL(ctx context.Context, id, name string) (string, error) {\n\tq := url.Values{\"name\": []string{name}}\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/mcp/auth-url\", id), q, nil)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to get MCP auth URL: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn \"\", fmt.Errorf(\"failed to get MCP auth URL: status code %d\", rsp.StatusCode)\n\t}\n\tvar resp proto.MCPAuthResponse\n\tif err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {\n\t\treturn \"\", fmt.Errorf(\"failed to decode MCP auth URL: %w\", err)\n\t}\n\treturn resp.AuthURL, nil\n}\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 {","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/proto.go#L337-L373","documentation":"MCPAuthURL performs GET /workspaces/{id}/mcp/auth-url?name=... via the client's transport helper c.get. If the HTTP request itself fails — DNS failure, connection refused, TLS error, context cancellation — the underlying error is wrapped with this message. This happens before any status-code or body checks, so it always indicates a transport-level problem.","triggerScenarios":"Calling MCPAuthURL(ctx, id, name) when the server host is unreachable (connection refused/timeout), DNS does not resolve, TLS handshake fails, the request context is canceled/expired, or c.get fails to build the request.","commonSituations":"Server not running or wrong base URL/port configured, offline or behind a VPN that blocks the host, firewall dropping the connection, or the caller cancels the context (user aborts, deadline exceeded) mid-request.","solutions":["Print the wrapped error chain (%w) — it names the root cause (dial tcp ...: connection refused, no such host, context canceled).","Verify the client's server URL/host and that the server is running and reachable (curl the endpoint directly).","Check network path: DNS, VPN, proxy, and firewall rules for the host/port being dialed.","If the cause is context deadline exceeded, increase the timeout or check why the server is slow; never retry on context.Canceled without a new context."],"exampleFix":"// before\nctx := context.Background()\nurl, err := client.MCPAuthURL(ctx, id, \"github\") // no deadline; hangs then fails on some networks\n\n// after\nctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)\ndefer cancel()\nurl, err := client.MCPAuthURL(ctx, id, \"github\")\nif err != nil {\n    if errors.Is(err, context.DeadlineExceeded) {\n        // retry or surface \"server unreachable\"\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Preflight connectivity before calling MCPAuthURL.\nif err := ctx.Err(); err != nil {\n    return fmt.Errorf(\"context already done: %w\", err)\n}\nreq, err := http.NewRequestWithContext(ctx, http.MethodGet, serverBaseURL, nil)\nif err != nil {\n    return fmt.Errorf(\"bad server URL: %w\", err)\n}\nif _, err := http.DefaultClient.Do(req); err != nil {\n    return fmt.Errorf(\"server unreachable: %w\", err)","typeGuard":"func isTransportError(err error) bool {\n    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n        return true\n    }\n    var ne net.Error\n    return errors.As(err, &ne) || errors.Is(err, syscall.ECONNREFUSED)\n}","tryCatchPattern":"url, err := client.MCPAuthURL(ctx, id, name)\nif err != nil {\n    switch {\n    case errors.Is(err, context.DeadlineExceeded):\n        return retryWithBackoff(ctx, func() (string, error) { return client.MCPAuthURL(ctx, id, name) })\n    case errors.Is(err, context.Canceled):\n        return \"\", err // do not retry user cancellation\n    default:\n        return \"\", fmt.Errorf(\"transport failure reaching auth-url endpoint: %w\", err)\n    }\n}","preventionTips":["Always pass a context with a sane timeout; never use context.Background() unadorned.","Validate the server base URL and port at client construction time.","Check server reachability (health endpoint) before OAuth flows that need multiple round-trips.","Unwrap the error chain with errors.Is to distinguish canceled vs refused vs DNS failures before retrying."],"tags":["network","http","mcp","client","transport"],"backgroundTag":"http-request-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}