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

  1. Log rsp.Body (or a tee'd copy) on failure to see what the server actually returned.
  2. Confirm the endpoint is the real API server, not a proxy/captive portal returning HTML — check the Content-Type header is application/json.
  3. Align client and server versions so the MCPPendingAuthServer schema matches the running server.
  4. 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

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

Related errors


AI-assisted analysis of charmbracelet/crush@7944b8e522 (2026-08-29). Data as JSON: /api/errors/ab026ad09aa89c40. Report an issue: GitHub.