Fix tool error handling to follow MCP spec

Tool execution errors now return successful responses with isError: true
instead of JSON-RPC protocol errors, per the MCP specification.

This allows clients to distinguish between protocol-level errors and
tool execution errors, providing better error context.

Fixes #159
This commit is contained in:
Justin Flick 2025-10-21 18:33:42 -07:00
commit 389cc2e8e6

View file

@ -258,10 +258,17 @@ module MCP
def call_tool(request)
tool_name = request[:name]
tool = tools[tool_name]
unless tool
add_instrumentation_data(error: :tool_not_found)
raise RequestHandlerError.new("Tool not found #{tool_name}", request, error_type: :tool_not_found)
add_instrumentation_data(tool_name:, error: :tool_not_found)
return Tool::Response.new(
[{
type: "text",
text: "Tool not found: #{tool_name}",
}],
error: true,
).to_h
end
arguments = request[:arguments] || {}
@ -269,11 +276,14 @@ module MCP
if tool.input_schema&.missing_required_arguments?(arguments)
add_instrumentation_data(error: :missing_required_arguments)
raise RequestHandlerError.new(
"Missing required arguments: #{tool.input_schema.missing_required_arguments(arguments).join(", ")}",
request,
error_type: :missing_required_arguments,
)
missing = tool.input_schema.missing_required_arguments(arguments).join(", ")
return Tool::Response.new(
[{
type: "text",
text: "Missing required arguments: #{missing}",
}],
error: true,
).to_h
end
if configuration.validate_tool_call_arguments && tool.input_schema
@ -281,14 +291,27 @@ module MCP
tool.input_schema.validate_arguments(arguments)
rescue Tool::InputSchema::ValidationError => e
add_instrumentation_data(error: :invalid_schema)
raise RequestHandlerError.new(e.message, request, error_type: :invalid_schema)
return Tool::Response.new(
[{
type: "text",
text: e.message,
}],
error: true,
).to_h
end
end
begin
call_tool_with_args(tool, arguments)
rescue => e
raise RequestHandlerError.new("Internal error calling tool #{tool_name}", request, original_error: e)
report_exception(e, { request: request })
Tool::Response.new(
[{
type: "text",
text: "Internal error calling tool #{tool_name}: #{e.message}",
}],
error: true,
).to_h
end
end