Skip to content

Commit 38d952a

Browse files
authored
Merge pull request #333 from koic/tool_validation_error
Return tool argument validation failures as tool execution errors
2 parents 2ad3d21 + 48d4fb9 commit 38d952a

2 files changed

Lines changed: 88 additions & 27 deletions

File tree

lib/mcp/server.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,7 +533,7 @@ def call_tool(request, session: nil, related_request_id: nil)
533533
add_instrumentation_data(error: :missing_required_arguments)
534534

535535
missing = tool.input_schema.missing_required_arguments(arguments).join(", ")
536-
raise RequestHandlerError.new("Missing required arguments: #{missing}", request, error_type: :invalid_params)
536+
return error_tool_response("Missing required arguments: #{missing}")
537537
end
538538

539539
if configuration.validate_tool_call_arguments && tool.input_schema
@@ -542,7 +542,7 @@ def call_tool(request, session: nil, related_request_id: nil)
542542
rescue Tool::InputSchema::ValidationError => e
543543
add_instrumentation_data(error: :invalid_schema)
544544

545-
raise RequestHandlerError.new(e.message, request, error_type: :invalid_params)
545+
return error_tool_response(e.message)
546546
end
547547
end
548548

test/mcp/server_test.rb

Lines changed: 86 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ class ServerTest < ActiveSupport::TestCase
312312
assert_instrumentation_data({ method: "tools/call", tool_name: tool_name, tool_arguments: tool_args })
313313
end
314314

315-
test "#handle tools/call returns protocol error in JSON-RPC format if required tool arguments are missing" do
315+
test "#handle tools/call returns tool execution error if required tool arguments are missing" do
316316
tool_with_required_argument = Tool.define(
317317
name: "test_tool",
318318
title: "Test tool",
@@ -336,10 +336,10 @@ class ServerTest < ActiveSupport::TestCase
336336

337337
response = server.handle(request)
338338

339-
assert_nil response[:result]
340-
assert_equal(-32602, response[:error][:code])
341-
assert_equal "Invalid params", response[:error][:message]
342-
assert_includes response[:error][:data], "Missing required arguments: message"
339+
assert_nil response[:error]
340+
assert(response[:result][:isError])
341+
assert_equal "text", response[:result][:content][0][:type]
342+
assert_includes response[:result][:content][0][:text], "Missing required arguments: message"
343343
end
344344

345345
test "#handle_json tools/call executes tool and returns result" do
@@ -1562,11 +1562,10 @@ class Example < Tool
15621562
refute response[:result].key?(:instructions)
15631563
end
15641564

1565-
test "tools/call returns protocol error in JSON-RPC format for missing arguments" do
1566-
server = Server.new(
1567-
tools: [TestTool],
1568-
configuration: Configuration.new(validate_tool_call_arguments: true),
1569-
)
1565+
test "tools/call returns tool execution error for missing arguments" do
1566+
configuration = Configuration.new(validate_tool_call_arguments: true)
1567+
configuration.instrumentation_callback = instrumentation_helper.callback
1568+
server = Server.new(tools: [TestTool], configuration: configuration)
15701569

15711570
response = server.handle(
15721571
{
@@ -1581,17 +1580,22 @@ class Example < Tool
15811580

15821581
assert_equal "2.0", response[:jsonrpc]
15831582
assert_equal 1, response[:id]
1584-
assert_nil response[:result]
1585-
assert_equal(-32602, response[:error][:code])
1586-
assert_equal "Invalid params", response[:error][:message]
1587-
assert_includes response[:error][:data], "Missing required arguments"
1583+
assert_nil response[:error]
1584+
assert(response[:result][:isError])
1585+
assert_equal "text", response[:result][:content][0][:type]
1586+
assert_includes response[:result][:content][0][:text], "Missing required arguments"
1587+
assert_instrumentation_data({
1588+
method: "tools/call",
1589+
tool_name: "test_tool",
1590+
tool_arguments: {},
1591+
error: :missing_required_arguments,
1592+
})
15881593
end
15891594

1590-
test "tools/call returns protocol error in JSON-RPC format for invalid arguments when validate_tool_call_arguments is true" do
1591-
server = Server.new(
1592-
tools: [TestTool],
1593-
configuration: Configuration.new(validate_tool_call_arguments: true),
1594-
)
1595+
test "tools/call returns tool execution error for invalid arguments when validate_tool_call_arguments is true" do
1596+
configuration = Configuration.new(validate_tool_call_arguments: true)
1597+
configuration.instrumentation_callback = instrumentation_helper.callback
1598+
server = Server.new(tools: [TestTool], configuration: configuration)
15951599

15961600
response = server.handle(
15971601
{
@@ -1607,10 +1611,44 @@ class Example < Tool
16071611

16081612
assert_equal "2.0", response[:jsonrpc]
16091613
assert_equal 1, response[:id]
1610-
assert_nil response[:result]
1611-
assert_equal(-32602, response[:error][:code])
1612-
assert_equal "Invalid params", response[:error][:message]
1613-
assert_includes response[:error][:data], "Invalid arguments"
1614+
assert_nil response[:error]
1615+
assert(response[:result][:isError])
1616+
assert_equal "text", response[:result][:content][0][:type]
1617+
assert_includes response[:result][:content][0][:text], "Invalid arguments"
1618+
assert_instrumentation_data({
1619+
method: "tools/call",
1620+
tool_name: "test_tool",
1621+
tool_arguments: { message: 123 },
1622+
error: :invalid_schema,
1623+
})
1624+
end
1625+
1626+
test "tools/call returns tool execution error for nested schema validation failure" do
1627+
server = Server.new(
1628+
tools: [ComplexTypesTool],
1629+
configuration: Configuration.new(validate_tool_call_arguments: true),
1630+
)
1631+
1632+
response = server.handle(
1633+
{
1634+
jsonrpc: "2.0",
1635+
id: 1,
1636+
method: "tools/call",
1637+
params: {
1638+
name: "complex_types_tool",
1639+
arguments: {
1640+
numbers: [1, 2, 3],
1641+
strings: ["a", "b", "c"],
1642+
objects: [{ name: 123 }],
1643+
},
1644+
},
1645+
},
1646+
)
1647+
1648+
assert_nil response[:error]
1649+
assert(response[:result][:isError])
1650+
assert_equal "text", response[:result][:content][0][:type]
1651+
assert_includes response[:result][:content][0][:text], "Invalid arguments"
16141652
end
16151653

16161654
test "tools/call skips argument validation when validate_tool_call_arguments is false" do
@@ -1695,7 +1733,7 @@ class Example < Tool
16951733
assert_equal "OK", response[:result][:content][0][:content]
16961734
end
16971735

1698-
test "tools/call returns protocol error in JSON-RPC format when additionalProperties set to false" do
1736+
test "tools/call returns tool execution error when additionalProperties set to false" do
16991737
server = Server.new(
17001738
tools: [TestToolWithAdditionalPropertiesSetToFalse],
17011739
configuration: Configuration.new(validate_tool_call_arguments: true),
@@ -1718,10 +1756,33 @@ class Example < Tool
17181756

17191757
assert_equal "2.0", response[:jsonrpc]
17201758
assert_equal 1, response[:id]
1759+
assert_nil response[:error]
1760+
assert(response[:result][:isError])
1761+
assert_equal "text", response[:result][:content][0][:type]
1762+
assert_includes response[:result][:content][0][:text], "Invalid arguments"
1763+
end
1764+
1765+
test "tools/call returns JSON-RPC -32602 protocol error when tool is not found" do
1766+
server = Server.new(
1767+
tools: [TestTool],
1768+
)
1769+
1770+
response = server.handle(
1771+
{
1772+
jsonrpc: "2.0",
1773+
id: 1,
1774+
method: "tools/call",
1775+
params: {
1776+
name: "unknown_tool",
1777+
arguments: {},
1778+
},
1779+
},
1780+
)
1781+
17211782
assert_nil response[:result]
17221783
assert_equal(-32602, response[:error][:code])
17231784
assert_equal "Invalid params", response[:error][:message]
1724-
assert_includes response[:error][:data], "Invalid arguments"
1785+
assert_includes response[:error][:data], "Tool not found: unknown_tool"
17251786
end
17261787

17271788
test "#handle completion/complete returns default completion result" do

0 commit comments

Comments
 (0)