What is CallToolResult?
CallToolResult is the envelope the MCP server returns to the client after executing a tool call. It sits at the far end of the tools/call round-trip: the client sends a CallToolRequest, the server performs the work, and wraps every piece of output into a CallToolResult before handing it back over JSON-RPC. Because tools are the primary way an LLM-driven host acquires external data or triggers side effects, this type is one of the highest-traffic structures in any production MCP deployment. The content array holds one or more ContentBlock values — text, image, audio, an embedded resource, or a resource link. This heterogeneous list lets a single tool invocation return, say, a plaintext summary alongside a rendered PNG chart without requiring separate round-trips. The array is required and must contain at least one element; returning an empty array is technically valid JSON but will confuse most host implementations that iterate the blocks to build context for the model. The isError flag is a deliberate design choice that keeps error semantics inside the application layer rather than surfacing them as JSON-RPC faults. When set to true the JSON-RPC call itself still succeeds (2xx equivalent), meaning the LLM receives the error text as normal content and can decide whether to retry, ask for clarification, or surface the failure to the user. The optional _meta field is reserved for protocol extensions and should be treated as an opaque passthrough by application code.
When to use
Always — every tools/call must return a CallToolResult or a JSON-RPC error.
When NOT to use
Never bypass the content structure — clients won't render raw strings.
Notes
isError vs JSON-RPC fault semantics
Returning isError: true keeps the error payload inside the LLM's context window, which allows the model to reason about the failure and self-correct. Throwing a JSON-RPC error instead drops the message entirely from the model's view. Reserve actual JSON-RPC faults for transport-level or authentication failures, not for domain errors your tool encountered during execution.
Content block ordering matters
Most MCP host implementations append content blocks to the conversation context in array order. Put the most semantically dense block first — typically a text summary — so that token-budget truncation strategies preserve the highest-signal content. Image and audio blocks are expensive in token terms; include them only when the calling model is multimodal and the host advertises that capability in the server's capability negotiation.
Large payloads and context window pressure
There is no protocol-level size limit on the content array, so a runaway tool can exhaust the host's context window in a single call. In production, apply server-side guards: truncate text blocks to a configurable character ceiling, return resource links instead of embedding large binary blobs, and document expected maximum sizes in the tool's inputSchema description so the LLM can pre-emptively request paginated results.
SDK defaults differ across languages
The official TypeScript SDK's server.tool() helper wraps your handler's return value and injects isError: false if you do not set it explicitly. The Python SDK (mcp library) requires you to return a CallToolResult dataclass directly and will raise a validation error at runtime if content is omitted. Always check SDK release notes when upgrading, as the helper signatures for building ContentBlock arrays have changed between minor versions.
Distinguishing from ReadResourceResult
ReadResourceResult also returns content to the client but is scoped to the resources/read endpoint and uses a ResourceContents union rather than ContentBlock. Do not conflate the two: tool results are routed through the sampling loop and become model context, while resource results are typically stored or displayed by the host application without necessarily entering the LLM's context window.
Fields
| Field | Type | Required | Purpose |
|---|---|---|---|
| content | ContentBlock[] | yes | Array of TextContent, ImageContent, AudioContent, EmbeddedResource, or ResourceLink. |
| isError | boolean? | no | True if the tool failed; the content describes the error. |
| _meta | object? | no | Optional metadata. |
Examples
Successful text result
{
"content": [
{ "type": "text", "text": "Found 3 matching documents." }
]
}
Validation error
{
"isError": true,
"content": [
{ "type": "text", "text": "query must be at least 3 characters." }
]
}
Common mistakes
❌ Returning a plain string as result
✅ Wrap in content array with type 'text'.