Skip to content

feat(mcpserver): let ToolError carry content for is_error results#2984

Open
RaidLZ wants to merge 1 commit into
modelcontextprotocol:mainfrom
RaidLZ:fix/348-toolerror-content
Open

feat(mcpserver): let ToolError carry content for is_error results#2984
RaidLZ wants to merge 1 commit into
modelcontextprotocol:mainfrom
RaidLZ:fix/348-toolerror-content

Conversation

@RaidLZ

@RaidLZ RaidLZ commented Jun 26, 2026

Copy link
Copy Markdown

Summary

Closes #348.

Tool authors currently have no way to return a CallToolResult with is_error=True that carries non-text content — raising an exception only surfaces the message as text. This implements the approach @Kludex suggested in the issue: give ToolError an optional content field that is translated to the error result internally.

What changed

  • ToolError accepts an optional keyword content: list[ContentBlock] | None. When set, it becomes the CallToolResult.content; otherwise behavior is unchanged (the message is returned as text).
  • The tool layer (Tool.run) preserves content when it wraps a raised ToolError, so the content survives to the result.
  • _handle_call_tool returns the attached content for an errored result when present.

A plain ToolError("...") behaves exactly as before — this is purely additive and non-breaking.

@mcp.tool()
def render() -> str:
    raise ToolError(
        "rendering failed",
        content=[ImageContent(type="image", data=..., mime_type="image/png")],
    )
# -> CallToolResult(content=[ImageContent(...)], is_error=True)

Tests / checks

  • Added a test covering an image-bearing ToolError.
  • Existing tool-error behavior (including the snapshot test) is unchanged.
  • ./scripts/test passes at 100% coverage; pyright and ruff are clean.

Disclosure

Developed with AI assistance. I've reviewed the change and can explain every line.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Re-trigger cubic

Tool authors can now raise ToolError(content=[...]) to return a
CallToolResult with is_error=True that carries arbitrary content
(e.g. an image or embedded resource) instead of only the error
message as text. A plain ToolError behaves exactly as before.

content is typed as list[Any] rather than list[ContentBlock] because
exceptions.py is imported during mcp package initialization, before
mcp.types is importable - referencing that type would create a
circular import.

Closes modelcontextprotocol#348
@RaidLZ RaidLZ force-pushed the fix/348-toolerror-content branch from 723770d to 9b6b9af Compare June 26, 2026 08:14
@itxaiohanglover

Copy link
Copy Markdown

Good enhancement! Letting ToolError carry structured content (images, embedded resources) is useful for rich error responses. The circular import avoidance note is a smart documentation touch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No way to set isError=True for arbitrary tool result content

2 participants