charmbracelet/crush · error

failed to decode MCP resource: %w

Error message

failed to decode MCP resource: %w

What it means

After a successful 200 response, ReadMCPResource decodes the body into []MCPResourceContents. 'failed to decode MCP resource: %w' means the body was not the expected JSON array — a client/server API mismatch or a proxy HTML error page.

Source

Thrown at internal/client/config.go:319

	return nil
}

// ReadMCPResource reads a resource from a named MCP server.
func (c *Client) ReadMCPResource(ctx context.Context, id, name, uri string) ([]MCPResourceContents, error) {
	rsp, err := c.post(ctx, fmt.Sprintf("/workspaces/%s/mcp/read-resource", id), nil, jsonBody(struct {
		Name string `json:"name"`
		URI  string `json:"uri"`
	}{Name: name, URI: uri}), http.Header{"Content-Type": []string{"application/json"}})
	if err != nil {
		return nil, fmt.Errorf("failed to read MCP resource: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to read MCP resource: status code %d", rsp.StatusCode)
	}
	var contents []MCPResourceContents
	if err := json.NewDecoder(rsp.Body).Decode(&contents); err != nil {
		return nil, fmt.Errorf("failed to decode MCP resource: %w", err)
	}
	return contents, nil
}

func (c *Client) ListMCPPrompts(ctx context.Context, id string) ([]proto.MCPPrompt, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/mcp/prompts", id), nil, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to list MCP prompts: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to list MCP prompts: status code %d", rsp.StatusCode)
	}
	var prompts []proto.MCPPrompt
	if err := json.NewDecoder(rsp.Body).Decode(&prompts); err != nil {
		return nil, fmt.Errorf("failed to decode MCP prompts: %w", err)
	}
	return prompts, nil

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Check client/server version compatibility and align versions.
  2. Dump the raw response body (curl the endpoint) to see what was actually returned.
  3. Confirm no proxy is mangling responses (look for HTML in the error).
  4. Retry; transient truncation can also cause decode failures.

Example fix

// before
contents, err := client.ReadMCPResource(ctx, id, name, uri)
// after
contents, err := client.ReadMCPResource(ctx, id, name, uri)
if err != nil && strings.Contains(err.Error(), "failed to decode MCP resource") {
    return nil, fmt.Errorf("incompatible server response for resource %q — verify client/server versions: %w", uri, err)
}
Defensive patterns

Strategy: type-guard

Validate before calling

// sanity-check client/server compatibility at startup
ver, err := client.BuildInfo(ctx)
if err != nil || !compatibleVersions(clientVersion, ver.ExternalURL) {
    return errors.New("coder client and server versions are incompatible")
}

Type guard

func isDecodeError(err error) bool {
    return err != nil && strings.Contains(err.Error(), "failed to decode MCP resource")
}
// usage: if isDecodeError(err) { /* treat as schema/proxy mismatch, not transient */ }

Try / catch

contents, err := client.ReadMCPResource(ctx, id, name, uri)
if isDecodeError(err) {
    // non-retryable: inspect raw response, check versions, rule out proxies
    return nil, fmt.Errorf("unexpected response shape from server: %w", err)
}
if err != nil { return nil, err }

Prevention

When it happens

Trigger: json.Decode fails: server returned a different schema (version skew), an HTML error page from a reverse proxy, truncated body, or empty response.

Common situations: Client and Coder server versions out of sync; an auth proxy or load balancer intercepting and returning HTML; resource contents shape changed in a newer server release.

Understand the failure class

Related errors


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