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
29 changes: 29 additions & 0 deletions lib/mcp/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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<Hash>] 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.
Expand All @@ -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<Hash>] 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
Expand Down
84 changes: 84 additions & 0 deletions test/mcp/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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