Skip to content

Commit f383442

Browse files
committed
handle tool errors and protocol errors distinctly.
1 parent 12eb2ba commit f383442

File tree

2 files changed

+47
-22
lines changed

2 files changed

+47
-22
lines changed

lib/mcp/server.rb

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -258,30 +258,46 @@ def list_tools(request)
258258

259259
def call_tool(request)
260260
tool_name = request[:name]
261+
arguments = request[:arguments] || {}
262+
261263
tool = tools[tool_name]
262264
unless tool
263-
add_instrumentation_data(error: :tool_not_found)
264-
raise RequestHandlerError.new("Tool not found #{tool_name}", request, error_type: :tool_not_found)
265+
add_instrumentation_data(tool_name:, error: :tool_not_found)
266+
return Tool::Response.new(
267+
[{
268+
type: "text",
269+
text: "Tool not found: #{tool_name}",
270+
}],
271+
error: true,
272+
).to_h
265273
end
266274

267-
arguments = request[:arguments] || {}
268275
add_instrumentation_data(tool_name:)
269276

270277
if tool.input_schema&.missing_required_arguments?(arguments)
271278
add_instrumentation_data(error: :missing_required_arguments)
272-
raise RequestHandlerError.new(
273-
"Missing required arguments: #{tool.input_schema.missing_required_arguments(arguments).join(", ")}",
274-
request,
275-
error_type: :missing_required_arguments,
276-
)
279+
missing = tool.input_schema.missing_required_arguments(arguments).join(", ")
280+
return Tool::Response.new(
281+
[{
282+
type: "text",
283+
text: "Missing required arguments: #{missing}",
284+
}],
285+
error: true,
286+
).to_h
277287
end
278288

279289
if configuration.validate_tool_call_arguments && tool.input_schema
280290
begin
281291
tool.input_schema.validate_arguments(arguments)
282292
rescue Tool::InputSchema::ValidationError => e
283293
add_instrumentation_data(error: :invalid_schema)
284-
raise RequestHandlerError.new(e.message, request, error_type: :invalid_schema)
294+
return Tool::Response.new(
295+
[{
296+
type: "text",
297+
text: e.message,
298+
}],
299+
error: true,
300+
).to_h
285301
end
286302
end
287303

test/mcp/server_test.rb

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ class ServerTest < ActiveSupport::TestCase
255255
assert_instrumentation_data({ method: "tools/call", tool_name: })
256256
end
257257

258-
test "#handle tools/call returns error if required tool arguments are missing" do
258+
test "#handle tools/call returns error response with isError true if required tool arguments are missing" do
259259
tool_with_required_argument = Tool.define(
260260
name: "test_tool",
261261
title: "Test tool",
@@ -279,8 +279,10 @@ class ServerTest < ActiveSupport::TestCase
279279

280280
response = server.handle(request)
281281

282-
assert_equal "Missing required arguments: message", response[:error][:data]
283-
assert_equal "Internal error", response[:error][:message]
282+
assert_nil response[:error], "Expected no JSON-RPC error"
283+
assert response[:result][:isError]
284+
assert_equal "text", response[:result][:content][0][:type]
285+
assert_equal "Missing required arguments: message", response[:result][:content][0][:text]
284286
end
285287

286288
test "#handle_json tools/call executes tool and returns result" do
@@ -389,7 +391,7 @@ def call(message:, server_context: nil)
389391
assert_instrumentation_data({ method: "tools/call", tool_name: "tool_that_raises" })
390392
end
391393

392-
test "#handle tools/call returns error for unknown tool" do
394+
test "#handle tools/call returns error response with isError true for unknown tool" do
393395
request = {
394396
jsonrpc: "2.0",
395397
method: "tools/call",
@@ -401,12 +403,14 @@ def call(message:, server_context: nil)
401403
}
402404

403405
response = @server.handle(request)
404-
assert_equal "Internal error", response[:error][:message]
405-
assert_equal "Tool not found unknown_tool", response[:error][:data]
406-
assert_instrumentation_data({ method: "tools/call", error: :tool_not_found })
406+
assert_nil response[:error], "Expected no JSON-RPC error"
407+
assert response[:result][:isError]
408+
assert_equal "text", response[:result][:content][0][:type]
409+
assert_equal "Tool not found: unknown_tool", response[:result][:content][0][:text]
410+
assert_instrumentation_data({ method: "tools/call", tool_name: "unknown_tool", error: :tool_not_found })
407411
end
408412

409-
test "#handle_json returns error for unknown tool" do
413+
test "#handle_json returns error response with isError true for unknown tool" do
410414
request = JSON.generate({
411415
jsonrpc: "2.0",
412416
method: "tools/call",
@@ -418,7 +422,10 @@ def call(message:, server_context: nil)
418422
})
419423

420424
response = JSON.parse(@server.handle_json(request), symbolize_names: true)
421-
assert_equal "Internal error", response[:error][:message]
425+
assert_nil response[:error], "Expected no JSON-RPC error"
426+
assert response[:result][:isError]
427+
assert_equal "text", response[:result][:content][0][:type]
428+
assert_equal "Tool not found: unknown_tool", response[:result][:content][0][:text]
422429
end
423430

424431
test "#tools_call_handler sets the tools/call handler" do
@@ -944,8 +951,9 @@ def call(message:, server_context: nil)
944951

945952
assert_equal "2.0", response[:jsonrpc]
946953
assert_equal 1, response[:id]
947-
assert_equal(-32603, response[:error][:code])
948-
assert_includes response[:error][:data], "Missing required arguments"
954+
assert_nil response[:error], "Expected no JSON-RPC error"
955+
assert response[:result][:isError]
956+
assert_includes response[:result][:content][0][:text], "Missing required arguments"
949957
end
950958

951959
test "tools/call validates arguments against input schema when validate_tool_call_arguments is true" do
@@ -968,8 +976,9 @@ def call(message:, server_context: nil)
968976

969977
assert_equal "2.0", response[:jsonrpc]
970978
assert_equal 1, response[:id]
971-
assert_equal(-32603, response[:error][:code])
972-
assert_includes response[:error][:data], "Invalid arguments"
979+
assert_nil response[:error], "Expected no JSON-RPC error"
980+
assert response[:result][:isError]
981+
assert_includes response[:result][:content][0][:text], "Invalid arguments"
973982
end
974983

975984
test "tools/call skips argument validation when validate_tool_call_arguments is false" do

0 commit comments

Comments
 (0)