Skip to content

Return tool argument validation failures as tool execution errors#333

Open
koic wants to merge 1 commit intomodelcontextprotocol:mainfrom
koic:tool_validation_error
Open

Return tool argument validation failures as tool execution errors#333
koic wants to merge 1 commit intomodelcontextprotocol:mainfrom
koic:tool_validation_error

Conversation

@koic
Copy link
Copy Markdown
Member

@koic koic commented Apr 30, 2026

Motivation and Context

MCP 2025-11-25 (SEP-1303) clarifies that input validation errors for tools/call should be returned as Tool Execution Errors ({ content: [...], isError: true }) rather than as JSON-RPC -32602 protocol errors, so models can observe the validation message and self-correct on a follow-up call.
modelcontextprotocol/modelcontextprotocol#1303

This is a clarification rather than a brand-new requirement: the 2024-11-05, 2025-03-26, and 2025-06-18 specifications all defined two error categories with overlapping language ("Invalid arguments" listed under Protocol Errors and "Invalid input data" listed under Tool Execution Errors), leaving the routing of JSON-Schema validation failures ambiguous. The Ruby SDK had selected the "Protocol Errors / Invalid arguments" interpretation, while the TypeScript SDK (packages/server/src/server/mcp.ts, which wraps validateToolInput errors via createToolError) and Python SDK's FastMCP (which routes through the generic is_error=True path) had selected the other. SEP-1303 in 2025-11-25 disambiguates this by replacing the bullets with "Malformed requests (requests that fail to satisfy CallToolRequest schema)" under Protocol Errors and "Input validation errors (e.g., date in wrong format, value out of range)" under Tool Execution Errors, and explicitly states that the latter "contain actionable feedback that language models can use to self-correct".

tool_not_found continues to be returned as a JSON-RPC -32602 protocol error since the spec change only covers input validation.

How Has This Been Tested?

Updated existing tests that previously asserted -32602 and "Invalid arguments" / "Missing required arguments" in the JSON-RPC error data to instead assert result[:isError] == true with the same text in the content block. Added new regression tests covering:

  • Instrumentation still records :missing_required_arguments and :invalid_schema on the new non-raising path
  • Nested schema validation failure (deep arrays of objects with required fields) returns a tool execution error rather than a protocol error
  • tool_not_found continues to return JSON-RPC -32602 (regression guard against accidentally widening the change)

bundle exec rake test and bundle exec rake rubocop both pass.

Breaking Changes

Yes. Clients that detect tool argument validation errors via error.code == -32602 will need to switch to inspecting result.isError == true and reading result.content[].text.

Note that the Ruby SDK's previous behavior was a defensible reading of the 2024-11-05 / 2025-03-26 / 2025-06-18 spec wording. The 2025-11-25 disambiguation is what makes the previous behavior non-conforming; the TypeScript and Python SDKs already shipped the new behavior.

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Checklist

  • I have read the MCP Documentation
  • My code follows the repository's style guidelines
  • New and existing tests pass locally
  • I have added appropriate error handling
  • I have added or updated documentation as needed

## Motivation and Context

MCP 2025-11-25 (SEP-1303) clarifies that input validation errors for `tools/call`
should be returned as Tool Execution Errors (`{ content: [...], isError: true }`)
rather than as JSON-RPC `-32602` protocol errors, so models can observe
the validation message and self-correct on a follow-up call.
modelcontextprotocol/modelcontextprotocol#1303

This is a clarification rather than a brand-new requirement: the 2024-11-05, 2025-03-26,
and 2025-06-18 specifications all defined two error categories with overlapping language
("Invalid arguments" listed under Protocol Errors *and* "Invalid input data" listed under
Tool Execution Errors), leaving the routing of JSON-Schema validation failures ambiguous.
The Ruby SDK had selected the "Protocol Errors / Invalid arguments" interpretation,
while the TypeScript SDK (`packages/server/src/server/mcp.ts`, which wraps
`validateToolInput` errors via `createToolError`) and Python SDK's FastMCP
(which routes through the generic `is_error=True` path) had selected the other.
SEP-1303 in 2025-11-25 disambiguates this by replacing the bullets with
"Malformed requests (requests that fail to satisfy CallToolRequest schema)"
under Protocol Errors and "Input validation errors (e.g., date in wrong format,
value out of range)" under Tool Execution Errors, and explicitly states that the latter
"contain actionable feedback that language models can use to self-correct".

`tool_not_found` continues to be returned as a JSON-RPC `-32602` protocol error since
the spec change only covers input validation.

## How Has This Been Tested?

Updated existing tests that previously asserted `-32602` and "Invalid arguments" /
"Missing required arguments" in the JSON-RPC error data to instead
assert `result[:isError] == true` with the same text in the `content` block.
Added new regression tests covering:

- Instrumentation still records `:missing_required_arguments` and
  `:invalid_schema` on the new non-raising path
- Nested schema validation failure (deep arrays of objects with
  required fields) returns a tool execution error rather than a
  protocol error
- `tool_not_found` continues to return JSON-RPC `-32602` (regression
  guard against accidentally widening the change)

`bundle exec rake test` and `bundle exec rake rubocop` both pass.

## Breaking Changes

Yes. Clients that detect tool argument validation errors via `error.code == -32602`
will need to switch to inspecting `result.isError == true` and reading `result.content[].text`.

Note that the Ruby SDK's previous behavior was a defensible reading of the 2024-11-05 /
2025-03-26 / 2025-06-18 spec wording. The 2025-11-25 disambiguation is what makes
the previous behavior non-conforming; the TypeScript and Python SDKs already shipped
the new behavior.
@koic koic force-pushed the tool_validation_error branch from 6e76890 to 48d4fb9 Compare April 30, 2026 18:18
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.

2 participants