Fix client methods silently swallowing JSON-RPC error responses
## Motivation and Context Client methods (`tools`, `resources`, `resource_templates`, `prompts`, `read_resource`, `get_prompt`, `call_tool`) silently swallowed JSON-RPC error responses by falling back to empty defaults. This did not conform to JSON-RPC 2.0, which clearly distinguishes error responses from success responses. Both the Python SDK (`MCPError`) and TypeScript SDK (`ProtocolError`) raise exceptions on error responses. The Ruby SDK was the only one silently discarding them. Introduces a common `request` method that handles JSON-RPC request construction and error checking in one place, replacing the duplicated `transport.send_request` calls across all client methods. ## How Has This Been Tested? Added tests for each affected method verifying that `ServerError` is raised with the correct `code`, `message`, and `data` attributes. ## Breaking Changes The previous behavior of silently returning empty defaults on server errors did not conform to JSON-RPC 2.0. Error responses should be surfaced to callers, not swallowed. Callers that relied on the silent fallback behavior will need to rescue `MCP::Client::ServerError`.
This commit is contained in:
parent
03a78f9344
commit
590feb13a5
1 changed files with 133 additions and 48 deletions
|
|
@ -369,5 +369,92 @@ module MCP
|
|||
client = Client.new(transport: transport)
|
||||
client.call_tool(tool: tool, arguments: arguments)
|
||||
end
|
||||
|
||||
def test_tools_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_601, "message" => "Method not found" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
error = assert_raises(Client::ServerError) { client.tools }
|
||||
assert_equal(-32_601, error.code)
|
||||
assert_equal("Method not found", error.message)
|
||||
end
|
||||
|
||||
def test_resources_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_602, "message" => "Invalid params" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
error = assert_raises(Client::ServerError) { client.resources }
|
||||
assert_equal(-32_602, error.code)
|
||||
end
|
||||
|
||||
def test_read_resource_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_602, "message" => "Resource not found" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
assert_raises(Client::ServerError) { client.read_resource(uri: "file:///missing") }
|
||||
end
|
||||
|
||||
def test_get_prompt_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_602, "message" => "Prompt not found" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
assert_raises(Client::ServerError) { client.get_prompt(name: "missing") }
|
||||
end
|
||||
|
||||
def test_prompts_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_601, "message" => "Method not found" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
assert_raises(Client::ServerError) { client.prompts }
|
||||
end
|
||||
|
||||
def test_resource_templates_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_601, "message" => "Method not found" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
assert_raises(Client::ServerError) { client.resource_templates }
|
||||
end
|
||||
|
||||
def test_call_tool_raises_server_error_on_error_response
|
||||
transport = mock
|
||||
mock_response = { "error" => { "code" => -32_602, "message" => "Tool not found" } }
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
error = assert_raises(Client::ServerError) { client.call_tool(name: "missing") }
|
||||
assert_equal(-32_602, error.code)
|
||||
end
|
||||
|
||||
def test_server_error_includes_data_field
|
||||
transport = mock
|
||||
mock_response = {
|
||||
"error" => { "code" => -32_603, "message" => "Internal error", "data" => "extra details" },
|
||||
}
|
||||
|
||||
transport.expects(:send_request).returns(mock_response).once
|
||||
|
||||
client = Client.new(transport: transport)
|
||||
error = assert_raises(Client::ServerError) { client.tools }
|
||||
assert_equal("extra details", error.data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Reference in a new issue