Fix: Return CallToolResult with isError for tool errors to conform with 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](https://github.com/modelcontextprotocol/typescript-sdk/pull/1044)
to conform with the MCP specification regarding tool error handling.
That’s why we got exceptions in the integration tests
This commit is contained in:
Pavel Gorgulov 2025-10-30 12:19:23 +01:00 • committed by GitHub
commit 20e4e44509
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -21,6 +21,7 @@ import io.modelcontextprotocol.kotlin.sdk.ReadResourceRequest
import io.modelcontextprotocol.kotlin.sdk.ReadResourceResult
import io.modelcontextprotocol.kotlin.sdk.Resource
import io.modelcontextprotocol.kotlin.sdk.ServerCapabilities
import io.modelcontextprotocol.kotlin.sdk.TextContent
import io.modelcontextprotocol.kotlin.sdk.Tool
import io.modelcontextprotocol.kotlin.sdk.ToolAnnotations
import io.modelcontextprotocol.kotlin.sdk.shared.ProtocolOptions
@ -32,6 +33,7 @@ import kotlinx.collections.immutable.minus
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toPersistentSet
import kotlinx.coroutines.CancellationException
private val logger = KotlinLogging.logger {}
@ -515,13 +517,30 @@ public open class Server(
private suspend fun handleCallTool(request: CallToolRequest): CallToolResult {
logger.debug { "Handling tool call request for tool: ${request.name}" }
// Check if tool exists
val tool = _tools.value[request.name]
?: run {
logger.error { "Tool not found: ${request.name}" }
throw IllegalArgumentException("Tool not found: ${request.name}")
return CallToolResult(
content = listOf(TextContent(text = "Tool ${request.name} not found")),
isError = true,
)
}
logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" }
return tool.handler(request)
// Execute tool handler and catch any errors
return try {
logger.trace { "Executing tool ${request.name} with input: ${request.arguments}" }
tool.handler(request)
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
logger.error(e) { "Error executing tool ${request.name}" }
CallToolResult(
content = listOf(TextContent(text = "Error executing tool ${request.name}: ${e.message}")),
isError = true,
)
}
}
private suspend fun handleListPrompts(): ListPromptsResult {