charmbracelet/crush · error

failed to get MCP states: %w

Error message

failed to get MCP states: %w

What it means

MCPGetStates issues GET /workspaces/{id}/mcp/states and wraps any transport failure from c.get. The HTTP request never completed — no status code exists. This reports connectivity, not MCP configuration problems.

Source

Thrown at internal/client/proto.go:318

	if err != nil {
		return nil, fmt.Errorf("failed to get LSPs: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get LSPs: status code %d", rsp.StatusCode)
	}
	var lsps map[string]proto.LSPClientInfo
	if err := json.NewDecoder(rsp.Body).Decode(&lsps); err != nil {
		return nil, fmt.Errorf("failed to decode LSPs: %w", err)
	}
	return lsps, nil
}

// MCPGetStates retrieves the MCP client states for a workspace.
func (c *Client) MCPGetStates(ctx context.Context, id string) (map[string]proto.MCPClientInfo, error) {
	rsp, err := c.get(ctx, fmt.Sprintf("/workspaces/%s/mcp/states", id), nil, nil)
	if err != nil {
		return nil, fmt.Errorf("failed to get MCP states: %w", err)
	}
	defer rsp.Body.Close()
	if rsp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("failed to get MCP states: status code %d", rsp.StatusCode)
	}
	var states map[string]proto.MCPClientInfo
	if err := json.NewDecoder(rsp.Body).Decode(&states); err != nil {
		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)

View on GitHub (pinned to 7944b8e522)

Solutions

  1. Read the wrapped cause (errors.Unwrap) and fix it — usually restart the server or correct the address.
  2. Verify server reachability with a simple request (curl the base URL).
  3. Check workspace id validity in the path.
  4. Use a longer-lived context or add retry with backoff for transient failures.

Example fix

// before
states, err := client.MCPGetStates(ctx, wsID)
if err != nil { return err }
// after
states, err := client.MCPGetStates(ctx, wsID)
if err != nil {
    if isConnRefused(err) { return fmt.Errorf("crush server not reachable; start it before polling MCP states") }
    return err
}
Defensive patterns

Strategy: retry

Validate before calling

resp, err := http.Get(baseURL + "/health")
if err != nil || resp.StatusCode != 200 { return errors.New("crush server not reachable before MCPGetStates") }

Type guard

func isConnRefused(err error) bool {
	return errors.Is(err, syscall.ECONNREFUSED) || strings.Contains(err.Error(), "connection refused")
}

Try / catch

states, err := client.MCPGetStates(ctx, wsID)
if err != nil {
	if isConnRefused(err) { return nil, ErrServerDown } // upstream: restart server and retry
	return nil, err
}

Prevention

When it happens

Trigger: Calling MCPGetStates(ctx, id) when the server socket is closed, the base URL is misconfigured, the connection resets, or ctx is canceled/expired during the call.

Common situations: Crush daemon stopped while a client polls MCP states; wrong host/port config; firewall blocking localhost or remote server; context deadline too tight for a busy server.

Understand the failure class

Background: 'Something went wrong' / 'Request failed (500)' / 'HTTP error! status: 404' — what failed HTTP requests actually mean and how to find the real cause — this error's family across 28 libraries.

Related errors


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