alibaba/open-code-review · error

call MCP tool %q: %w

Error message

call MCP tool %q: %w

What it means

CallTool wraps any error returned by the MCP session's CallTool request with 'call MCP tool %q: %w'. It covers transport/session failures (connection dropped, timeout, context cancelled, protocol-level errors) — tool-level failures reported inside a successful response are NOT this error; they come back as a string with result.IsError.

Source

Thrown at internal/mcp/client.go:166

		resp.Body.Close()
		return nil, fmt.Errorf("remote MCP server %q returned HTTP 403 Forbidden — your credentials may lack required permissions", t.serverName)
	}
	return resp, nil
}

func (c *Client) Name() string       { return c.name }
func (c *Client) Tools() []*mcp.Tool { return c.tools }

// CallTool invokes a tool on the MCP server and returns the text result.
func (c *Client) CallTool(ctx context.Context, name string, args map[string]any) (string, error) {
	params := &mcp.CallToolParams{
		Name:      name,
		Arguments: args,
	}

	result, err := c.session.CallTool(ctx, params)
	if err != nil {
		return "", fmt.Errorf("call MCP tool %q: %w", name, err)
	}

	if result.IsError {
		return fmt.Sprintf("MCP tool %q returned an error: %s", name, contentToText(result.Content)), nil
	}

	return contentToText(result.Content), nil
}

func (c *Client) Close() error {
	return c.session.Close()
}

func contentToText(contents []mcp.Content) string {
	var parts []string
	for _, item := range contents {
		switch v := item.(type) {
		case *mcp.TextContent:

View on GitHub (pinned to 5cf97d0d15)

Solutions

  1. Check the wrapped error (%w chain) to distinguish network/timeout vs protocol errors; re-connect the MCP client if the session is stale
  2. Verify the tool name against Client.Tools() before calling
  3. Increase the context deadline for slow tools, or re-invoke with a fresh context
  4. Confirm the remote MCP server is healthy and reachable

Example fix

// before
res, err := client.CallTool(ctx, "search_docs", args)
// after — guard with a fresh context and pre-check the tool
if !hasTool(client.Tools(), "search_docs") { return fmt.Errorf("tool search_docs unavailable") }
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
defer cancel()
res, err := client.CallTool(ctx, "search_docs", args)
Defensive patterns

Strategy: try-catch

Validate before calling

// pre-check the tool exists on this client
toolNames := map[string]bool{}
for _, t := range client.Tools() { toolNames[t.Name] = true }
if !toolNames["search_docs"] { return fmt.Errorf("tool not offered by server") }

Try / catch

res, err := client.CallTool(ctx, name, args)
if err != nil {
    if errors.Is(err, context.DeadlineExceeded) {
        // retry with a longer deadline or smaller workload
    } else {
        // wrapped session/transport failure: reconnect the MCP client
    }
    // Note: tool-level errors arrive as res == "MCP tool %q returned an error: ...", err == nil
}

Prevention

When it happens

Trigger: c.session.CallTool returns a non-nil error: the remote MCP connection is down, the request timed out or the context was cancelled, the tool name is unknown at the server, or the session was closed.

Common situations: MCP server restarted mid-session leaving a stale connection; long-running tool exceeding the caller's context deadline; typo in the tool name (tool was listed but later removed); network interruption to the remote MCP endpoint.

Related errors


AI-assisted analysis of alibaba/open-code-review@5cf97d0d15 (2026-09-02). Data as JSON: /api/errors/6b25fafa6fda8c40. Report an issue: GitHub.