Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions lib/mcp/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -92,21 +92,29 @@ 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")
#
# @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
Expand Down
26 changes: 26 additions & 0 deletions test/mcp/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down