Skip to content

Conversation

@KKonstantinov
Copy link
Contributor

Currently, a few scenarios return a MCP protocol level error as opposed to a CallToolResult with isError: true. This leads to MCP hosts (e.g. Claude and others) not being able to read the error message and attempt self-correct.

Affected scenarios:

  • Non-existent tool call
  • Disabled tool call
  • InputSchema validation not passing

Motivation and Context

MCP hosts should be able to attempt self-correct after getting a validation error, or calling a non-existent or disabled tool.

Specification non-conformance

The spec confirms that a CallToolResult should be returned:

Any errors that originate from the tool SHOULD be reported inside the result object, with isError set to true, not as an MCP protocol-level error response. Otherwise, the LLM would not be able to see that an error occurred and self-correct.

https://modelcontextprotocol.io/specification/2025-06-18/schema#calltoolresult

(thanks @cliffhall for finding the right spot in the spec)

Examples of other SDKs not having this issue and following spec

Python SDK is an example that returns a CallToolResult for these scenarios. Code for inputSchema validation returning CallToolResult (isError: true) can be observed at:

https://github.com/modelcontextprotocol/python-sdk/blob/main/src%2Fmcp%2Fserver%2Flowlevel%2Fserver.py#L499

https://github.com/modelcontextprotocol/python-sdk/blob/6c26d087df3406c7da1f85b8ae3c3caeabf7b002/src/mcp/server/lowlevel/server.py#L440

How Has This Been Tested?

Tested the modified server. Unit tests applied/modified to that effect.

Breaking Changes

None. It's fixing a current bug.

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

…rn CallToolResult instead of a protocol-level error
@KKonstantinov KKonstantinov requested a review from a team as a code owner October 24, 2025 18:48
@KKonstantinov
Copy link
Contributor Author

KKonstantinov commented Oct 25, 2025

A screenshot of running the "everything server" (https://github.com/modelcontextprotocol/example-remote-server) and doing a test against it.

image

Edit: The everything server seems to achieve the same end effect, but by going a different route. It itself uses the Server class directly as opposed to the McpServer, and then throws a ZodError directly at https://github.com/modelcontextprotocol/example-remote-server/blob/a02624c8b558d42e0fbccf710e7e2dbd581e1fc2/src/modules/mcp/services/mcp.ts#L473 .

It is then caught by the Protocol class general catch block at

return capturedTransport?.send({
.


TL;DR the everything server likely needs an issue fix on its own since it's not using McpServer.

@KKonstantinov
Copy link
Contributor Author

Cc @pcarleton

Was thinking - the conformance suite you raised few weeks back would immediately catch exactly things like this and this issue underwrites the need for such a suite even more.

Copy link
Contributor

@felixweinberger felixweinberger left a comment

Choose a reason for hiding this comment

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

Thanks for this, makes sense to me - just think we should also move the output validation into the code block (+ tests covering that) into the try-catch block while we're at it for SDK consistency.

@felixweinberger felixweinberger merged commit 7387c44 into modelcontextprotocol:main Oct 28, 2025
2 checks passed
devcrocod added a commit to modelcontextprotocol/kotlin-sdk that referenced this pull request Oct 30, 2025
…th MCP spec (#354)

Update server-side tool error handling to return `CallToolResult` with
`isError: true` instead of throwing protocol-level exceptions,
conforming with MCP specification

## Motivation and Context
[The MCP specification
states](https://modelcontextprotocol.io/specification/2025-06-18/schema#calltoolresult):
> Any errors that originate from the tool SHOULD be reported inside the
result object, with isError set to true, not as an MCP protocol-level
error response. Otherwise, the LLM would not be able
  to see that an error occurred and self-correct.

This PR updates the kotlin sdk to match this behavior

## How Has This Been Tested?
All tests pass successfully with the new behavior

## Breaking Changes
None

## Types of changes
- [x] 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
- [x] I have read the [MCP
Documentation](https://modelcontextprotocol.io)
- [x] My code follows the repository's style guidelines
- [x] New and existing tests pass locally
- [x] I have added appropriate error handling
- [x] I have added or updated documentation as needed

## Additional context

The TypeScript SDK was recently updated
[#1044](modelcontextprotocol/typescript-sdk#1044)
to conform with the MCP specification regarding tool error handling.
That’s why we got exceptions in the integration tests
abloomston pushed a commit to abloomston/typescript-sdk that referenced this pull request Oct 30, 2025
…col#1044 pattern

Change strict validation errors to return CallToolResult with isError: true
instead of throwing protocol-level errors, aligning with the pattern established
in PR modelcontextprotocol#1044 (commit 7387c44) where all validation errors return error responses
rather than throwing.

This ensures consistent error handling across all validation scenarios and
enables LLMs to see validation errors and attempt self-correction, as specified
in the MCP protocol specification.

Changes:
- Remove conditional throw for strict validation input errors
- Update test expectations to check for error responses instead of thrown errors
- Keep strictInputSchemaValidation field in RegisteredTool for future use

The strictInputSchemaValidation field is retained as it still serves to apply
.strict() to the Zod schema, rejecting unknown parameters. The difference is
now the rejection returns an error response visible to LLMs rather than a
protocol-level error.

Changes linted and tested.

<em>🤖 Created with Claude Code via Airchat</em>
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