From 73070f1fd7e8e69658c5f13e10212d537ffc3799 Mon Sep 17 00:00:00 2001 From: Koichi ITO Date: Sun, 22 Mar 2026 23:52:17 +0900 Subject: [PATCH] Allow `Client#call_tool` to accept a tool name ## Motivation and Context `Client#call_tool` previously required an `MCP::Client::Tool` object to call a tool. This made the API harder to use compared to other MCP SDK implementations, which accept a tool name string directly: - Python SDK: `session.call_tool(name, arguments)` accepts a name string. https://github.com/modelcontextprotocol/python-sdk/blob/v1.26.0/src/mcp/client/session.py#L370 - TypeScript SDK: `client.callTool({ name, arguments })` accepts a name string. https://github.com/modelcontextprotocol/typescript-sdk/blob/ccb78f2/packages/client/src/client/client.ts#L834 ## How Has This Been Tested? Updated existing tests and added new tests. ## Breaking Change No. This change adds a `name:` parameter so callers can pass a tool name string directly without looking up a `Tool` object first. The existing `tool:` parameter continues to work for backward compatibility. --- lib/mcp/client.rb | 14 +++++++++++--- test/mcp/client_test.rb | 26 ++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/lib/mcp/client.rb b/lib/mcp/client.rb index e56366c4..6437dd68 100644 --- a/lib/mcp/client.rb +++ b/lib/mcp/client.rb @@ -92,12 +92,17 @@ def prompts # Calls a tool via the transport layer and returns the full response from the server. # + # @param name [String] The name of the tool to call. # @param tool [MCP::Client::Tool] The tool to be called. # @param arguments [Object, nil] The arguments to pass to the tool. # @param progress_token [String, Integer, nil] A token to request progress notifications from the server during tool execution. # @return [Hash] The full JSON-RPC response from the transport. # - # @example + # @example Call by name + # response = client.call_tool(name: "my_tool", arguments: { foo: "bar" }) + # content = response.dig("result", "content") + # + # @example Call with a tool object # tool = client.tools.first # response = client.call_tool(tool: tool, arguments: { foo: "bar" }) # structured_content = response.dig("result", "structuredContent") @@ -105,8 +110,11 @@ def prompts # @note # The exact requirements for `arguments` are determined by the transport layer in use. # Consult the documentation for your transport (e.g., MCP::Client::HTTP) for details. - def call_tool(tool:, arguments: nil, progress_token: nil) - params = { name: tool.name, arguments: arguments } + def call_tool(name: nil, tool: nil, arguments: nil, progress_token: nil) + tool_name = name || tool&.name + raise ArgumentError, "Either `name:` or `tool:` must be provided." unless tool_name + + params = { name: tool_name, arguments: arguments } if progress_token params[:_meta] = { progressToken: progress_token } end diff --git a/test/mcp/client_test.rb b/test/mcp/client_test.rb index 2806ef1e..bf83542e 100644 --- a/test/mcp/client_test.rb +++ b/test/mcp/client_test.rb @@ -67,6 +67,32 @@ def test_call_tool_sends_request_to_transport_and_returns_content assert_equal([{ type: "text", text: "Hello, world!" }], content) end + def test_call_tool_by_name + transport = mock + arguments = { foo: "bar" } + mock_response = { + "result" => { "content" => [{ "type": "text", "text": "Hello, world!" }] }, + } + + transport.expects(:send_request).with do |args| + args.dig(:request, :params, :name) == "tool1" && + args.dig(:request, :params, :arguments) == arguments + end.returns(mock_response).once + + client = Client.new(transport: transport) + result = client.call_tool(name: "tool1", arguments: arguments) + content = result.dig("result", "content") + + assert_equal([{ type: "text", text: "Hello, world!" }], content) + end + + def test_call_tool_raises_when_no_name_or_tool + client = Client.new(transport: mock) + + error = assert_raises(ArgumentError) { client.call_tool(arguments: { foo: "bar" }) } + assert_equal("Either `name:` or `tool:` must be provided.", error.message) + end + def test_resources_sends_request_to_transport_and_returns_resources_array transport = mock mock_response = {