Class: MCP::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp/client.rb,
lib/mcp/client/http.rb,
lib/mcp/client/tool.rb,
lib/mcp/client/stdio.rb,
lib/mcp/client/paginated_result.rb

Defined Under Namespace

Classes: HTTP, ListPromptsResult, ListResourceTemplatesResult, ListResourcesResult, ListToolsResult, RequestHandlerError, ServerError, SessionExpiredError, Stdio, Tool, ValidationError

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transport:) ⇒ Client

Initializes a new MCP::Client instance.

Examples:

transport = MCP::Client::HTTP.new(url: "http://localhost:3000")
client = MCP::Client.new(transport: transport)

Parameters:

  • transport (Object)

    The transport object to use for communication with the server. The transport should be a duck type that responds to ‘send_request`. See the README for more details.



54
55
56
# File 'lib/mcp/client.rb', line 54

def initialize(transport:)
  @transport = transport
end

Instance Attribute Details

#transportObject (readonly)

The user may want to access additional transport-specific methods/attributes So keeping it public



60
61
62
# File 'lib/mcp/client.rb', line 60

def transport
  @transport
end

Instance Method Details

#call_tool(name: nil, tool: nil, arguments: nil, progress_token: nil) ⇒ Hash

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.

Calls a tool via the transport layer and returns the full response from the server.

Examples:

Call by name

response = client.call_tool(name: "my_tool", arguments: { foo: "bar" })
content = response.dig("result", "content")

Call with a tool object

tool = client.tools.first
response = client.call_tool(tool: tool, arguments: { foo: "bar" })
structured_content = response.dig("result", "structuredContent")

Parameters:

  • name (String) (defaults to: nil)

    The name of the tool to call.

  • tool (MCP::Client::Tool) (defaults to: nil)

    The tool to be called.

  • arguments (Object, nil) (defaults to: nil)

    The arguments to pass to the tool.

  • progress_token (String, Integer, nil) (defaults to: nil)

    A token to request progress notifications from the server during tool execution.

Returns:

  • (Hash)

    The full JSON-RPC response from the transport.

Raises:

  • (ArgumentError)


313
314
315
316
317
318
319
320
321
322
323
# File 'lib/mcp/client.rb', line 313

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

  request(method: "tools/call", params: params)
end

#complete(ref:, argument:, context: nil) ⇒ Hash

Requests completion suggestions from the server for a prompt argument or resource template URI.

Parameters:

  • ref (Hash)

    The reference, e.g. ‘{ type: “ref/prompt”, name: “my_prompt” }` or `{ type: “ref/resource”, uri: “file:///path” }`.

  • argument (Hash)

    The argument being completed, e.g. ‘{ name: “language”, value: “py” }`.

  • context (Hash, nil) (defaults to: nil)

    Optional context with previously resolved arguments.

Returns:

  • (Hash)

    The completion result with ‘“values”`, `“hasMore”`, and optionally `“total”`.



352
353
354
355
356
357
358
359
# File 'lib/mcp/client.rb', line 352

def complete(ref:, argument:, context: nil)
  params = { ref: ref, argument: argument }
  params[:context] = context if context

  response = request(method: "completion/complete", params: params)

  response.dig("result", "completion") || { "values" => [], "hasMore" => false }
end

#connect(client_info: nil, protocol_version: nil, capabilities: {}) ⇒ Hash?

Performs the MCP ‘initialize` handshake by delegating to the transport (e.g. `MCP::Client::HTTP`, `MCP::Client::Stdio`). Returns the server’s ‘InitializeResult`.

When the transport does not respond to ‘:connect`, this is a no-op and returns `nil`.

modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization

Parameters:

  • client_info (Hash, nil) (defaults to: nil)

    ‘{ name:, version: }` identifying the client.

  • protocol_version (String, nil) (defaults to: nil)

    Protocol version to offer.

  • capabilities (Hash) (defaults to: {})

    Capabilities advertised by the client.

Returns:

  • (Hash, nil)

    The server’s ‘InitializeResult`, or `nil` when the transport does not expose an explicit handshake.



83
84
85
86
87
88
89
90
91
# File 'lib/mcp/client.rb', line 83

def connect(client_info: nil, protocol_version: nil, capabilities: {})
  return unless transport.respond_to?(:connect)

  transport.connect(
    client_info: client_info,
    protocol_version: protocol_version,
    capabilities: capabilities,
  )
end

#connected?Boolean

Returns true once ‘connect` has completed the handshake on the underlying transport. Transports that do not expose connection state are assumed connected and return `true`.

Returns:

  • (Boolean)


96
97
98
99
100
# File 'lib/mcp/client.rb', line 96

def connected?
  return transport.connected? if transport.respond_to?(:connected?)

  true
end

#get_prompt(name:) ⇒ Hash

Gets a prompt from the server by name and returns its details.

Parameters:

  • name (String)

    The name of the prompt to get.

Returns:

  • (Hash)

    A hash containing the prompt details.



339
340
341
342
343
# File 'lib/mcp/client.rb', line 339

def get_prompt(name:)
  response = request(method: "prompts/get", params: { name: name })

  response.fetch("result", {})
end

#list_prompts(cursor: nil) ⇒ MCP::Client::ListPromptsResult

Returns a single page of prompts from the server.

Parameters:

  • cursor (String, nil) (defaults to: nil)

    Cursor from a previous page response.

Returns:



255
256
257
258
259
260
261
262
263
264
265
# File 'lib/mcp/client.rb', line 255

def list_prompts(cursor: nil)
  params = cursor ? { cursor: cursor } : nil
  response = request(method: "prompts/list", params: params)
  result = response["result"] || {}

  ListPromptsResult.new(
    prompts: result["prompts"] || [],
    next_cursor: result["nextCursor"],
    meta: result["_meta"],
  )
end

#list_resource_templates(cursor: nil) ⇒ MCP::Client::ListResourceTemplatesResult

Returns a single page of resource templates from the server.

Parameters:

  • cursor (String, nil) (defaults to: nil)

    Cursor from a previous page response.

Returns:



212
213
214
215
216
217
218
219
220
221
222
# File 'lib/mcp/client.rb', line 212

def list_resource_templates(cursor: nil)
  params = cursor ? { cursor: cursor } : nil
  response = request(method: "resources/templates/list", params: params)
  result = response["result"] || {}

  ListResourceTemplatesResult.new(
    resource_templates: result["resourceTemplates"] || [],
    next_cursor: result["nextCursor"],
    meta: result["_meta"],
  )
end

#list_resources(cursor: nil) ⇒ MCP::Client::ListResourcesResult

Returns a single page of resources from the server.

Parameters:

  • cursor (String, nil) (defaults to: nil)

    Cursor from a previous page response.

Returns:



169
170
171
172
173
174
175
176
177
178
179
# File 'lib/mcp/client.rb', line 169

def list_resources(cursor: nil)
  params = cursor ? { cursor: cursor } : nil
  response = request(method: "resources/list", params: params)
  result = response["result"] || {}

  ListResourcesResult.new(
    resources: result["resources"] || [],
    next_cursor: result["nextCursor"],
    meta: result["_meta"],
  )
end

#list_tools(cursor: nil) ⇒ MCP::Client::ListToolsResult

Returns a single page of tools from the server.

Examples:

Iterate all pages

cursor = nil
loop do
  page = client.list_tools(cursor: cursor)
  page.tools.each { |tool| puts tool.name }
  cursor = page.next_cursor
  break unless cursor
end

Parameters:

  • cursor (String, nil) (defaults to: nil)

    Cursor from a previous page response.

Returns:



116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/mcp/client.rb', line 116

def list_tools(cursor: nil)
  params = cursor ? { cursor: cursor } : nil
  response = request(method: "tools/list", params: params)
  result = response["result"] || {}

  tools = (result["tools"] || []).map do |tool|
    Tool.new(
      name: tool["name"],
      description: tool["description"],
      input_schema: tool["inputSchema"],
    )
  end

  ListToolsResult.new(tools: tools, next_cursor: result["nextCursor"], meta: result["_meta"])
end

#pingHash

Sends a ‘ping` request to the server to verify the connection is alive. Per the MCP spec, the server responds with an empty result.

Examples:

client.ping # => {}

Returns:

  • (Hash)

    An empty hash on success.

Raises:

  • (ServerError)

    If the server returns a JSON-RPC error.

  • (ValidationError)

    If the response ‘result` is missing or not a Hash.

See Also:



372
373
374
375
376
377
# File 'lib/mcp/client.rb', line 372

def ping
  result = request(method: Methods::PING)["result"]
  raise ValidationError, "Response validation failed: missing or invalid `result`" unless result.is_a?(Hash)

  result
end

#promptsArray<Hash>

Returns every prompt available on the server. Iterates through all pages automatically when the server paginates, so the full collection is returned regardless of the server’s ‘page_size` setting. Use #list_prompts when you need fine-grained cursor control.

Each call will make a new request - the result is not cached.

Returns:

  • (Array<Hash>)

    An array of available prompts.



274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/mcp/client.rb', line 274

def prompts
  # TODO: consider renaming to `list_all_prompts`.
  all_prompts = []
  seen = Set.new
  cursor = nil

  loop do
    page = list_prompts(cursor: cursor)
    all_prompts.concat(page.prompts)
    next_cursor = page.next_cursor
    break if next_cursor.nil? || seen.include?(next_cursor)

    seen << next_cursor
    cursor = next_cursor
  end

  all_prompts
end

#read_resource(uri:) ⇒ Array<Hash>

Reads a resource from the server by URI and returns the contents.

Parameters:

  • uri (String)

    The URI of the resource to read.

Returns:

  • (Array<Hash>)

    An array of resource contents (text or blob).



329
330
331
332
333
# File 'lib/mcp/client.rb', line 329

def read_resource(uri:)
  response = request(method: "resources/read", params: { uri: uri })

  response.dig("result", "contents") || []
end

#resource_templatesArray<Hash>

Returns every resource template available on the server. Iterates through all pages automatically when the server paginates, so the full collection is returned regardless of the server’s ‘page_size` setting. Use #list_resource_templates when you need fine-grained cursor control.

Each call will make a new request - the result is not cached.

Returns:

  • (Array<Hash>)

    An array of available resource templates.



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/mcp/client.rb', line 231

def resource_templates
  # TODO: consider renaming to `list_all_resource_templates`.
  all_templates = []
  seen = Set.new
  cursor = nil

  loop do
    page = list_resource_templates(cursor: cursor)
    all_templates.concat(page.resource_templates)
    next_cursor = page.next_cursor
    break if next_cursor.nil? || seen.include?(next_cursor)

    seen << next_cursor
    cursor = next_cursor
  end

  all_templates
end

#resourcesArray<Hash>

Returns every resource available on the server. Iterates through all pages automatically when the server paginates, so the full collection is returned regardless of the server’s ‘page_size` setting. Use #list_resources when you need fine-grained cursor control.

Each call will make a new request - the result is not cached.

Returns:

  • (Array<Hash>)

    An array of available resources.



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/mcp/client.rb', line 188

def resources
  # TODO: consider renaming to `list_all_resources`.
  all_resources = []
  seen = Set.new
  cursor = nil

  loop do
    page = list_resources(cursor: cursor)
    all_resources.concat(page.resources)
    next_cursor = page.next_cursor
    break if next_cursor.nil? || seen.include?(next_cursor)

    seen << next_cursor
    cursor = next_cursor
  end

  all_resources
end

#server_infoObject

The server’s ‘InitializeResult` (protocol version, capabilities, server info, instructions), as reported by the transport after a successful `connect`. Returns `nil` before `connect`, after `close`, or when the transport does not expose a cached handshake result.



66
67
68
# File 'lib/mcp/client.rb', line 66

def server_info
  transport.server_info if transport.respond_to?(:server_info)
end

#toolsArray<MCP::Client::Tool>

Returns every tool available on the server. Iterates through all pages automatically when the server paginates, so the full collection is returned regardless of the server’s ‘page_size` setting. Use #list_tools when you need fine-grained cursor control.

Each call will make a new request - the result is not cached.

Examples:

tools = client.tools
tools.each do |tool|
  puts tool.name
end

Returns:



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/mcp/client.rb', line 145

def tools
  # TODO: consider renaming to `list_all_tools`.
  all_tools = []
  seen = Set.new
  cursor = nil

  loop do
    page = list_tools(cursor: cursor)
    all_tools.concat(page.tools)
    next_cursor = page.next_cursor
    break if next_cursor.nil? || seen.include?(next_cursor)

    seen << next_cursor
    cursor = next_cursor
  end

  all_tools
end