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/oauth.rb,
lib/mcp/client/stdio.rb,
lib/mcp/client/oauth/flow.rb,
lib/mcp/client/oauth/pkce.rb,
lib/mcp/client/elicitation.rb,
lib/mcp/client/oauth/provider.rb,
lib/mcp/client/oauth/discovery.rb,
lib/mcp/client/paginated_result.rb,
lib/mcp/client/oauth/in_memory_storage.rb,
lib/mcp/client/oauth/jwt_client_assertion.rb,
lib/mcp/client/oauth/id_jag_token_exchange.rb,
lib/mcp/client/oauth/storage_backed_provider.rb,
lib/mcp/client/oauth/cross_app_access_provider.rb,
lib/mcp/client/oauth/client_credentials_provider.rb

Defined Under Namespace

Modules: Elicitation, OAuth Classes: HTTP, InputRequiredError, ListPromptsResult, ListResourceTemplatesResult, ListResourcesResult, ListToolsResult, RequestHandlerError, ServerError, ServerRequestError, 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.



90
91
92
# File 'lib/mcp/client.rb', line 90

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



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

def transport
  @transport
end

Instance Method Details

#call_tool(name: nil, tool: nil, arguments: nil, progress_token: nil, meta: nil, cancellation: 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")

Cancellable call

cancellation = MCP::Cancellation.new
Thread.new do
  client.call_tool(name: "slow_tool", arguments: {}, cancellation: cancellation)
rescue MCP::CancelledError
  # cleanup
end
cancellation.cancel(reason: "user pressed cancel")

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.

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

    Additional _meta entries to send with the request, e.g. the W3C Trace Context keys reserved by SEP-414 (MCP::TraceContext::TRACEPARENT_META_KEY, tracestate, baggage). progress_token takes precedence over a progressToken entry in meta.

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token. Cancelling it from another thread sends notifications/cancelled to the server and raises MCP::CancelledError from this call.

Returns:

  • (Hash)

    The full JSON-RPC response from the transport.

Raises:

  • (ArgumentError)


342
343
344
345
346
347
348
349
350
351
352
353
354
355
# File 'lib/mcp/client.rb', line 342

def call_tool(name: nil, tool: nil, arguments: nil, progress_token: nil, meta: nil, cancellation: nil)
  tool_name = name || tool&.name
  raise ArgumentError, "Either `name:` or `tool:` must be provided." unless tool_name

  params = { name: tool_name, arguments: arguments }
  meta_entries = meta ? meta.dup : {}
  if progress_token
    meta_entries.delete("progressToken")
    meta_entries[:progressToken] = progress_token
  end
  params[:_meta] = meta_entries unless meta_entries.empty?

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

#complete(ref:, argument:, context: nil, meta: nil, cancellation: 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.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

Returns:

  • (Hash)

    The completion result with "values", "hasMore", and optionally "total".



393
394
395
396
397
398
399
400
# File 'lib/mcp/client.rb', line 393

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

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

  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.

https://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. May include an extensions member per SEP-2133, keyed by reverse-DNS extension identifiers, e.g. { extensions: { "com.example/feature" => {} } }.

Returns:

  • (Hash, nil)

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



121
122
123
124
125
126
127
128
129
# File 'lib/mcp/client.rb', line 121

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)


134
135
136
137
138
# File 'lib/mcp/client.rb', line 134

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

  true
end

#get_prompt(name:, meta: nil, cancellation: nil) ⇒ Hash

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

Parameters:

  • name (String)

    The name of the prompt to get.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

Returns:

  • (Hash)

    A hash containing the prompt details.



377
378
379
380
381
# File 'lib/mcp/client.rb', line 377

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

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

#list_prompts(cursor: nil, meta: nil, cancellation: 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.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

Returns:



280
281
282
283
284
285
286
287
288
289
290
291
292
# File 'lib/mcp/client.rb', line 280

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

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

#list_resource_templates(cursor: nil, meta: nil, cancellation: 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.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

Returns:



245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/mcp/client.rb', line 245

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

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

#list_resources(cursor: nil, meta: nil, cancellation: 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.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

Returns:



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

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

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

#list_tools(cursor: nil, meta: nil, cancellation: 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.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional token; cancelling it sends notifications/cancelled to the server and raises MCP::CancelledError from this call.

Returns:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/mcp/client.rb', line 158

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

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

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

#on_elicitation(&handler) ⇒ Object

Registers a handler for elicitation/create requests the server sends while one of this client's requests is in flight. The handler receives the request params (message and requestedSchema, string keys) and must return an ElicitResult-shaped Hash: { action: "accept" | "decline" | "cancel", content: { ... } }.

Requires a transport that supports server-to-client requests (e.g. MCP::Client::HTTP); pass capabilities: { elicitation: {} } to connect so the server knows it may send them.

https://modelcontextprotocol.io/specification/2025-11-25/client/elicitation

Examples:

Accept with schema defaults applied (SEP-1034)

client.on_elicitation do |params|
  {
    action: "accept",
    content: MCP::Client::Elicitation.apply_defaults(params["requestedSchema"]),
  }
end


418
419
420
421
422
423
424
# File 'lib/mcp/client.rb', line 418

def on_elicitation(&handler)
  unless transport.respond_to?(:on_server_request)
    raise ArgumentError, "The transport does not support server-to-client requests"
  end

  transport.on_server_request(Methods::ELICITATION_CREATE, &handler)
end

#on_sampling(&handler) ⇒ Object

Deprecated.

MCP Sampling (sampling/createMessage) is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). Register this handler only to interoperate with servers that still send sampling requests during the deprecation window; new servers should call LLM provider APIs directly.

Registers a handler for sampling/createMessage requests the server sends while one of this client's requests is in flight. The handler receives the request params (messages, maxTokens, optionally systemPrompt, modelPreferences, tools, toolChoice, ...; string keys) and must return a CreateMessageResult-shaped Hash: { role: "assistant", content: { type: "text", text: "..." }, model: "...", stopReason: "..." }.

For trust and safety, the spec recommends a human in the loop able to review, edit, or reject the request and the generated response before it is returned to the server. To reject, raise ServerRequestError with the spec's user-rejection code -1.

Requires a transport that supports server-to-client requests (e.g. MCP::Client::HTTP); pass capabilities: { sampling: {} } to connect (or { sampling: { tools: {} } } to receive tool-enabled sampling requests) so the server knows it may send them.

https://modelcontextprotocol.io/specification/2025-11-25/client/sampling

Examples:

Forward the request to an LLM and return its completion


client.on_sampling do |params|
  raise MCP::Client::ServerRequestError.new("User rejected sampling request", code: -1) unless approved?(params)

  completion = my_llm.complete(params["messages"], max_tokens: params["maxTokens"])
  {
    role: "assistant",
    content: { type: "text", text: completion.text },
    model: completion.model,
    stopReason: "endTurn",
  }
end


456
457
458
459
460
461
462
# File 'lib/mcp/client.rb', line 456

def on_sampling(&handler)
  unless transport.respond_to?(:on_server_request)
    raise ArgumentError, "The transport does not support server-to-client requests"
  end

  transport.on_server_request(Methods::SAMPLING_CREATE_MESSAGE, &handler)
end

#ping(meta: nil, cancellation: nil) ⇒ Hash

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 # => {}

Parameters:

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

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:



478
479
480
481
482
483
# File 'lib/mcp/client.rb', line 478

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

  result
end

#prompts(cancellation: nil) ⇒ Array<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.

Parameters:

Returns:

  • (Array<Hash>)

    An array of available prompts.



302
303
304
305
# File 'lib/mcp/client.rb', line 302

def prompts(cancellation: nil)
  # TODO: consider renaming to `list_all_prompts`.
  fetch_all_pages { |cursor| list_prompts(cursor: cursor, cancellation: cancellation) }.flat_map(&:prompts)
end

#read_resource(uri:, meta: nil, cancellation: nil) ⇒ Array<Hash>

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

Parameters:

  • uri (String)

    The URI of the resource to read.

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

    Additional _meta entries to send with the request, e.g. SEP-414 trace context (see TraceContext).

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token.

Returns:

  • (Array<Hash>)

    An array of resource contents (text or blob).



364
365
366
367
368
# File 'lib/mcp/client.rb', line 364

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

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

#resource_templates(cancellation: nil) ⇒ Array<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.

Parameters:

Returns:

  • (Array<Hash>)

    An array of available resource templates.



267
268
269
270
# File 'lib/mcp/client.rb', line 267

def resource_templates(cancellation: nil)
  # TODO: consider renaming to `list_all_resource_templates`.
  fetch_all_pages { |cursor| list_resource_templates(cursor: cursor, cancellation: cancellation) }.flat_map(&:resource_templates)
end

#resources(cancellation: nil) ⇒ Array<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.

Parameters:

Returns:

  • (Array<Hash>)

    An array of available resources.



232
233
234
235
# File 'lib/mcp/client.rb', line 232

def resources(cancellation: nil)
  # TODO: consider renaming to `list_all_resources`.
  fetch_all_pages { |cursor| list_resources(cursor: cursor, cancellation: cancellation) }.flat_map(&: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.



102
103
104
# File 'lib/mcp/client.rb', line 102

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

#tools(cancellation: nil) ⇒ Array<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

Parameters:

  • cancellation (MCP::Cancellation, nil) (defaults to: nil)

    Optional cancellation token. Cancelling it aborts whichever page is currently in flight; pages already returned are kept, but the call raises MCP::CancelledError instead of returning the partial set.

Returns:



197
198
199
200
# File 'lib/mcp/client.rb', line 197

def tools(cancellation: nil)
  # TODO: consider renaming to `list_all_tools`.
  fetch_all_pages { |cursor| list_tools(cursor: cursor, cancellation: cancellation) }.flat_map(&:tools)
end