Skip to content

Commit c510600

Browse files
authored
Merge pull request #160 from modelcontextprotocol/client-add-resource-support
add `resources/list` and `resources/read` support to client
2 parents b10a469 + 2d1a1da commit c510600

File tree

2 files changed

+113
-0
lines changed

2 files changed

+113
-0
lines changed

lib/mcp/client.rb

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,20 @@ def tools
4444
end || []
4545
end
4646

47+
# Returns the list of resources available from the server.
48+
# Each call will make a new request – the result is not cached.
49+
#
50+
# @return [Array<Hash>] An array of available resources.
51+
def resources
52+
response = transport.send_request(request: {
53+
jsonrpc: JsonRpcHandler::Version::V2_0,
54+
id: request_id,
55+
method: "resources/list",
56+
})
57+
58+
response.dig("result", "resources") || []
59+
end
60+
4761
# Calls a tool via the transport layer and returns the full response from the server.
4862
#
4963
# @param tool [MCP::Client::Tool] The tool to be called.
@@ -67,6 +81,21 @@ def call_tool(tool:, arguments: nil)
6781
})
6882
end
6983

84+
# Reads a resource from the server by URI and returns the contents.
85+
#
86+
# @param uri [String] The URI of the resource to read.
87+
# @return [Array<Hash>] An array of resource contents (text or blob).
88+
def read_resource(uri:)
89+
response = transport.send_request(request: {
90+
jsonrpc: JsonRpcHandler::Version::V2_0,
91+
id: request_id,
92+
method: "resources/read",
93+
params: { uri: uri },
94+
})
95+
96+
response.dig("result", "contents") || []
97+
end
98+
7099
private
71100

72101
def request_id

test/mcp/client_test.rb

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,5 +57,89 @@ def test_call_tool_sends_request_to_transport_and_returns_content
5757

5858
assert_equal("result", content)
5959
end
60+
61+
def test_resources_sends_request_to_transport_and_returns_resources_array
62+
transport = mock
63+
mock_response = {
64+
"result" => {
65+
"resources" => [
66+
{ "name" => "resource1", "uri" => "file:///path/to/resource1", "description" => "First resource" },
67+
{ "name" => "resource2", "uri" => "file:///path/to/resource2", "description" => "Second resource" },
68+
],
69+
},
70+
}
71+
72+
# Only checking for the essential parts of the request
73+
transport.expects(:send_request).with do |args|
74+
args in { request: { method: "resources/list", jsonrpc: "2.0" } }
75+
end.returns(mock_response).once
76+
77+
client = Client.new(transport: transport)
78+
resources = client.resources
79+
80+
assert_equal(2, resources.size)
81+
assert_equal("resource1", resources.first["name"])
82+
assert_equal("file:///path/to/resource1", resources.first["uri"])
83+
assert_equal("resource2", resources.last["name"])
84+
assert_equal("file:///path/to/resource2", resources.last["uri"])
85+
end
86+
87+
def test_read_resource_sends_request_to_transport_and_returns_contents
88+
transport = mock
89+
uri = "file:///path/to/resource.txt"
90+
mock_response = {
91+
"result" => {
92+
"contents" => [
93+
{ "uri" => uri, "mimeType" => "text/plain", "text" => "Hello, world!" },
94+
],
95+
},
96+
}
97+
98+
# Only checking for the essential parts of the request
99+
transport.expects(:send_request).with do |args|
100+
args in {
101+
request: {
102+
method: "resources/read",
103+
jsonrpc: "2.0",
104+
params: {
105+
uri: uri,
106+
},
107+
},
108+
}
109+
end.returns(mock_response).once
110+
111+
client = Client.new(transport: transport)
112+
contents = client.read_resource(uri: uri)
113+
114+
assert_equal(1, contents.size)
115+
assert_equal(uri, contents.first["uri"])
116+
assert_equal("text/plain", contents.first["mimeType"])
117+
assert_equal("Hello, world!", contents.first["text"])
118+
end
119+
120+
def test_read_resource_returns_empty_array_when_no_contents
121+
transport = mock
122+
uri = "file:///path/to/nonexistent.txt"
123+
mock_response = { "result" => {} }
124+
125+
transport.expects(:send_request).returns(mock_response).once
126+
127+
client = Client.new(transport: transport)
128+
contents = client.read_resource(uri: uri)
129+
130+
assert_empty(contents)
131+
end
132+
133+
def test_resources_returns_empty_array_when_no_resources
134+
transport = mock
135+
mock_response = { "result" => {} }
136+
137+
transport.expects(:send_request).returns(mock_response).once
138+
139+
client = Client.new(transport: transport)
140+
resources = client.resources
141+
142+
assert_empty(resources)
143+
end
60144
end
61145
end

0 commit comments

Comments
 (0)