Skip to content

Commit 12eb2ba

Browse files
committed
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
1 parent c510600 commit 12eb2ba

File tree

3 files changed

+25
-18
lines changed

3 files changed

+25
-18
lines changed

lib/mcp/server.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,14 @@ def call_tool(request)
288288
begin
289289
call_tool_with_args(tool, arguments)
290290
rescue => e
291-
raise RequestHandlerError.new("Internal error calling tool #{tool_name}", request, original_error: e)
291+
report_exception(e, { request: request })
292+
Tool::Response.new(
293+
[{
294+
type: "text",
295+
text: "Internal error calling tool #{tool_name}",
296+
}],
297+
error: true,
298+
).to_h
292299
end
293300
end
294301

test/mcp/server_context_test.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,10 @@ def call(message:, server_context:)
152152

153153
response = server_no_context.handle(request)
154154

155-
assert response[:error]
156-
# The error is wrapped as "Internal error calling tool..."
157-
assert_equal "Internal error", response[:error][:message]
155+
assert_nil response[:error], "Expected no JSON-RPC error"
156+
assert response[:result][:isError]
157+
assert_equal "text", response[:result][:content][0][:type]
158+
assert_equal "Internal error calling tool tool_with_required_context", response[:result][:content][0][:text]
158159
end
159160

160161
test "call_tool_with_args correctly detects server_context parameter presence" do

test/mcp/server_test.rb

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -340,17 +340,12 @@ def call(message:, server_context: nil)
340340
assert_equal({ content: [{ type: "text", content: "OK" }], isError: false }, response[:result])
341341
end
342342

343-
test "#handle tools/call returns internal error and reports exception if the tool raises an error" do
343+
test "#handle tools/call returns error response with isError true if the tool raises an error" do
344344
@server.configuration.exception_reporter.expects(:call).with do |exception, server_context|
345345
assert_not_nil exception
346346
assert_equal(
347347
{
348-
request: {
349-
jsonrpc: "2.0",
350-
method: "tools/call",
351-
params: { name: "tool_that_raises", arguments: { message: "test" } },
352-
id: 1,
353-
},
348+
request: { name: "tool_that_raises", arguments: { message: "test" } },
354349
},
355350
server_context,
356351
)
@@ -368,12 +363,14 @@ def call(message:, server_context: nil)
368363

369364
response = @server.handle(request)
370365

371-
assert_equal "Internal error", response[:error][:message]
372-
assert_equal "Internal error calling tool tool_that_raises", response[:error][:data]
373-
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises", error: :internal_error })
366+
assert_nil response[:error], "Expected no JSON-RPC error"
367+
assert response[:result][:isError]
368+
assert_equal "text", response[:result][:content][0][:type]
369+
assert_equal "Internal error calling tool tool_that_raises", response[:result][:content][0][:text]
370+
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises" })
374371
end
375372

376-
test "#handle_json returns internal error and reports exception if the tool raises an error" do
373+
test "#handle_json returns error response with isError true if the tool raises an error" do
377374
request = JSON.generate({
378375
jsonrpc: "2.0",
379376
method: "tools/call",
@@ -385,9 +382,11 @@ def call(message:, server_context: nil)
385382
})
386383

387384
response = JSON.parse(@server.handle_json(request), symbolize_names: true)
388-
assert_equal "Internal error", response[:error][:message]
389-
assert_equal "Internal error calling tool tool_that_raises", response[:error][:data]
390-
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises", error: :internal_error })
385+
assert_nil response[:error], "Expected no JSON-RPC error"
386+
assert response[:result][:isError]
387+
assert_equal "text", response[:result][:content][0][:type]
388+
assert_equal "Internal error calling tool tool_that_raises", response[:result][:content][0][:text]
389+
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises" })
391390
end
392391

393392
test "#handle tools/call returns error for unknown tool" do

0 commit comments

Comments
 (0)