charmbracelet/crush · error
failed to decode MCP pending auth: %w
Error message
failed to decode MCP pending auth: %w
What it means
After a successful 200 response from /workspaces/{id}/mcp/pending-auth, the client decodes the body into []proto.MCPPendingAuthServer with json.NewDecoder. If the body is not valid JSON or does not match the expected shape, this error wraps the json.Decoder failure. It indicates a client/server contract mismatch rather than an HTTP problem.
Source
Thrown at internal/client/proto.go:344
return nil, fmt.Errorf("failed to decode MCP states: %w", err)
}
return states, nil
}
// MCPPendingAuth retrieves the MCP servers awaiting OAuth authentication
// for a workspace.
func (c *Client) MCPPendingAuth(ctx context.Context, id string) ([]proto.MCPPendingAuthServer, error) {
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/mcp/pending-auth", id), nil, nil)
if err != nil {
return nil, fmt.Errorf("failed to get MCP pending auth: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("failed to get MCP pending auth: status code %d", rsp.StatusCode)
}
var pending []proto.MCPPendingAuthServer
if err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {
return nil, fmt.Errorf("failed to decode MCP pending auth: %w", err)
}
return pending, nil
}
// MCPAuthURL retrieves the current OAuth authorization URL for a named MCP
// server, if a flow is in progress.
func (c *Client) MCPAuthURL(ctx context.Context, id, name string) (string, error) {
q := url.Values{"name": []string{name}}
rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/mcp/auth-url", id), q, nil)
if err != nil {
return "", fmt.Errorf("failed to get MCP auth URL: %w", err)
}
defer rsp.Body.Close()
if rsp.StatusCode != http.StatusOK {
return "", fmt.Errorf("failed to get MCP auth URL: status code %d", rsp.StatusCode)
}
var resp proto.MCPAuthResponse
if err := json.NewDecoder(rsp.Body).Decode(&resp); err != nil {View on GitHub (pinned to 7944b8e522)
Solutions
- Log rsp.Body (or a tee'd copy) on failure to see what the server actually returned.
- Confirm the endpoint is the real API server, not a proxy/captive portal returning HTML — check the Content-Type header is application/json.
- Align client and server versions so the MCPPendingAuthServer schema matches the running server.
- If you control the server, fix the handler to always emit a JSON array (use [] not null/empty body) for this route.
Example fix
// before
var pending []proto.MCPPendingAuthServer
if err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {
return nil, fmt.Errorf("failed to decode MCP pending auth: %w", err)
}
// after (capture body for diagnosis and verify content type)
if ct := rsp.Header.Get("Content-Type"); !strings.Contains(ct, "application/json") {
body, _ := io.ReadAll(rsp.Body)
return nil, fmt.Errorf("unexpected content type %q: %s", ct, body)
}
var pending []proto.MCPPendingAuthServer
if err := json.NewDecoder(rsp.Body).Decode(&pending); err != nil {
return nil, fmt.Errorf("failed to decode MCP pending auth: %w", err)
} Defensive patterns
Strategy: validation
Validate before calling
// Verify the endpoint speaks JSON before trusting the decode.
// If you own the transport, check headers on the response:
// if !strings.Contains(rsp.Header.Get("Content-Type"), "application/json") { ... bail ... }
// Caller-side sanity check of the decoded result:
servers, err := client.MCPPendingAuth(ctx, id)
if err != nil {
return err
}
if servers == nil {
return errors.New("server returned null pending-auth payload") Type guard
func validPendingAuth(v []proto.MCPPendingAuthServer) bool {
for _, s := range v {
if s.Name == "" {
return false
}
}
return true
} Try / catch
servers, err := client.MCPPendingAuth(ctx, id)
if err != nil {
if strings.Contains(err.Error(), "failed to decode MCP pending auth") {
// contract mismatch: log server env/version, fall back to empty list
log.Warn("pending-auth payload unusable; assuming no pending servers", "err", err)
return nil, nil
}
return err
} Prevention
- Ensure no proxy sits between client and server that can inject HTML with a 200 status.
- Keep client and server binaries on matching versions so the MCPPendingAuthServer schema agrees.
- Test against the real server, not stubs returning empty 200 bodies.
- When you control the server, always emit a JSON array (never null or empty body) for this route.
When it happens
Trigger: Calling MCPPendingAuth when the server returns 200 with a non-JSON body (HTML error page from a misconfigured proxy), truncated JSON, or JSON whose structure cannot unmarshal into []proto.MCPPendingAuthServer (e.g. object instead of array, wrong field types).
Common situations: Hitting the wrong port or a dev proxy that injects an HTML login page with status 200; server and client versions out of sync so the payload schema changed; an empty body returned by a stub/mock endpoint.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to decode MCP auth URL: %w
- failed to decode MCP prompt response: %w
- failed to decode workspaces: %w
- failed to decode response: %w
- error parsing parameters: %s
AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29).
Data as JSON: /api/errors/ab026ad09aa89c40.
Report an issue: GitHub.