From 2d1a1da9374171a9a050f2c3643f0236f3d0cafb Mon Sep 17 00:00:00 2001 From: Topher Bullock Date: Fri, 10 Oct 2025 10:44:46 -0400 Subject: [PATCH] add resources/list and resources/read support to client --- lib/mcp/client.rb | 29 ++++++++++++++ test/mcp/client_test.rb | 84 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+) diff --git a/lib/mcp/client.rb b/lib/mcp/client.rb index 0147561..90095d3 100644 --- a/lib/mcp/client.rb +++ b/lib/mcp/client.rb @@ -44,6 +44,20 @@ def tools end || [] end + # Returns the list of resources available from the server. + # Each call will make a new request – the result is not cached. + # + # @return [Array] An array of available resources. + def resources + response = transport.send_request(request: { + jsonrpc: JsonRpcHandler::Version::V2_0, + id: request_id, + method: "resources/list", + }) + + response.dig("result", "resources") || [] + end + # Calls a tool via the transport layer and returns the full response from the server. # # @param tool [MCP::Client::Tool] The tool to be called. @@ -67,6 +81,21 @@ def call_tool(tool:, arguments: nil) }) end + # Reads a resource from the server by URI and returns the contents. + # + # @param uri [String] The URI of the resource to read. + # @return [Array] An array of resource contents (text or blob). + def read_resource(uri:) + response = transport.send_request(request: { + jsonrpc: JsonRpcHandler::Version::V2_0, + id: request_id, + method: "resources/read", + params: { uri: uri }, + }) + + response.dig("result", "contents") || [] + end + private def request_id diff --git a/test/mcp/client_test.rb b/test/mcp/client_test.rb index c226d03..2a364ca 100644 --- a/test/mcp/client_test.rb +++ b/test/mcp/client_test.rb @@ -57,5 +57,89 @@ def test_call_tool_sends_request_to_transport_and_returns_content assert_equal("result", content) end + + def test_resources_sends_request_to_transport_and_returns_resources_array + transport = mock + mock_response = { + "result" => { + "resources" => [ + { "name" => "resource1", "uri" => "file:///path/to/resource1", "description" => "First resource" }, + { "name" => "resource2", "uri" => "file:///path/to/resource2", "description" => "Second resource" }, + ], + }, + } + + # Only checking for the essential parts of the request + transport.expects(:send_request).with do |args| + args in { request: { method: "resources/list", jsonrpc: "2.0" } } + end.returns(mock_response).once + + client = Client.new(transport: transport) + resources = client.resources + + assert_equal(2, resources.size) + assert_equal("resource1", resources.first["name"]) + assert_equal("file:///path/to/resource1", resources.first["uri"]) + assert_equal("resource2", resources.last["name"]) + assert_equal("file:///path/to/resource2", resources.last["uri"]) + end + + def test_read_resource_sends_request_to_transport_and_returns_contents + transport = mock + uri = "file:///path/to/resource.txt" + mock_response = { + "result" => { + "contents" => [ + { "uri" => uri, "mimeType" => "text/plain", "text" => "Hello, world!" }, + ], + }, + } + + # Only checking for the essential parts of the request + transport.expects(:send_request).with do |args| + args in { + request: { + method: "resources/read", + jsonrpc: "2.0", + params: { + uri: uri, + }, + }, + } + end.returns(mock_response).once + + client = Client.new(transport: transport) + contents = client.read_resource(uri: uri) + + assert_equal(1, contents.size) + assert_equal(uri, contents.first["uri"]) + assert_equal("text/plain", contents.first["mimeType"]) + assert_equal("Hello, world!", contents.first["text"]) + end + + def test_read_resource_returns_empty_array_when_no_contents + transport = mock + uri = "file:///path/to/nonexistent.txt" + mock_response = { "result" => {} } + + transport.expects(:send_request).returns(mock_response).once + + client = Client.new(transport: transport) + contents = client.read_resource(uri: uri) + + assert_empty(contents) + end + + def test_resources_returns_empty_array_when_no_resources + transport = mock + mock_response = { "result" => {} } + + transport.expects(:send_request).returns(mock_response).once + + client = Client.new(transport: transport) + resources = client.resources + + assert_empty(resources) + end end end