What is CallToolRequest?
CallToolRequest is the wire message a client sends when it wants to invoke a named tool on a connected MCP server. It travels over JSON-RPC 2.0 as a request with method "tools/call", meaning the server is obligated to send back either a result or a protocol-level error. The two required fields — params.name and the outer method field — form the minimum viable invocation; params.arguments is intentionally optional so that tools with no required inputs need not transmit an empty object. The optional params._meta.progressToken field lets callers subscribe to out-of-band progress notifications for long-running tool calls, decoupling the acknowledgment of a request from the delivery of its result.\n\nIn the MCP message flow, CallToolRequest sits downstream of a tools/list round-trip. The client is expected to have already retrieved the tool's inputSchema via ListToolsResult before constructing arguments, though the protocol does not enforce this ordering. The server's job on receipt is to validate params.arguments against that schema and execute the tool. Critically, argument validation failures should be surfaced as tool execution errors (CallToolResult with isError: true) rather than as JSON-RPC error responses — this preserves the model's ability to inspect the failure and retry with corrected arguments without breaking the conversational loop.\n\nThe split between params.name and params.arguments mirrors the design of JSON Schema's separation of identity from data, and it deliberately mirrors the shape used for prompt invocation (GetPromptRequest) to keep the mental model consistent. No versioning field exists on the request itself; capability negotiation at session initialization (via the tools capability block in InitializeResult) governs whether tools/call is available at all. Implementations should treat an unknown tool name as an isError execution result rather than a protocol fault, giving the model a recoverable signal.
When to use
Whenever the client wants the server to execute a specific tool.
When NOT to use
Don't use to fetch data — use ReadResourceRequest for that.
Notes
Argument validation belongs in the result
Servers must not reply with a JSON-RPC error response (e.g., code -32602) when arguments fail schema validation. Returning an isError: true CallToolResult instead keeps the error visible to the model and allows self-correction. A protocol-level error terminates the tool-call branch entirely, whereas an isError result is just another content block the model can reason about.
progressToken enables streaming progress
When params._meta.progressToken is present the server may emit $/progress notifications referencing that token before the final result arrives. This is purely advisory — clients must not block on receiving any notifications and servers may ignore the token entirely. For tools that stream large outputs (file reads, long computations) wiring up a progressToken dramatically improves perceived responsiveness without changing the result shape.
Unknown tool names should not be protocol errors
If params.name does not match any registered tool the server should return an isError: true CallToolResult with a descriptive message rather than a JSON-RPC -32601 method-not-found error. This aligns with the broader MCP principle that tool-layer problems stay in the tool layer. A hard protocol error propagates further up the stack and may abort the session in strict clients.
Arguments object is schema-typed, not free-form
Although params.arguments is typed as object?, its shape is fully constrained by the inputSchema advertised in ListToolsResult. SDKs like the official TypeScript SDK generate typed handler signatures from that schema, so a mismatch between what a client sends and what the schema describes will surface as a TypeScript compile error at the server side when using the SDK's tool registration helpers. Python SDK users relying on Pydantic models get equivalent runtime validation for free.
No built-in argument size limit in the spec
The MCP specification does not mandate a maximum size for params.arguments, but underlying transports impose their own limits — the stdio transport is bounded by pipe buffer defaults and the HTTP/SSE transport is subject to web server body-size limits (often 1 MB by default in Express or FastAPI). Passing large binary payloads via arguments is an anti-pattern; use resource URIs instead and let the server fetch or stream the data through the resources machinery.
Fields
| Field | Type | Required | Purpose |
|---|---|---|---|
| method | 'tools/call' | yes | Method identifier. |
| params.name | string | yes | Name of the tool to invoke. |
| params.arguments | object? | no | Arguments matching the tool's inputSchema. |
| params._meta.progressToken | string | number? | no | Token for progress updates. |
Examples
Calling search_docs
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_docs",
"arguments": { "query": "MCP transports" }
}
}
Common mistakes
❌ Returning protocol errors for input validation failures
✅ Return isError:true in CallToolResult — lets the model self-correct.