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/modern_envelope.rb,
lib/mcp/client/oauth/discovery.rb,
lib/mcp/client/paginated_result.rb,
lib/mcp/client/mcp_param_headers.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, McpParamHeaders, ModernEnvelope, OAuth Classes: DiscoverResult, HTTP, InputRequiredError, ListPromptsResult, ListResourceTemplatesResult, ListResourcesResult, ListToolsResult, PaginationLimitError, RequestHandlerError, ServerError, ServerRequestError, SessionExpiredError, Stdio, Tool, ValidationError

Constant Summary collapse

MAX_PAGES =

Upper bound on the number of pages the all-pages methods (tools, resources, resource_templates, prompts) will walk. The cursor guard in fetch_all_pages only stops a server that repeats or cycles cursors; one that returns a fresh nextCursor on every response would otherwise be followed indefinitely, growing the retained pages with it.

1_000
DEFAULT_INPUT_REQUIRED_MAX_ROUNDS =

Rounds the SEP-2322 driver runs before giving up, matching the TypeScript and Python SDK defaults. Every leg counts, including requestState-only retries.

10
STATE_ONLY_BACKOFF_INITIAL_SECONDS =

Backoff for requestState-only (load shedding) legs: exponential from 50ms to a 250ms cap, matching the Python SDK (the TypeScript SDK uses a fixed 250ms).

0.05
STATE_ONLY_BACKOFF_CAP_SECONDS =
0.25

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(transport:, input_required_max_rounds: DEFAULT_INPUT_REQUIRED_MAX_ROUNDS, max_pages: MAX_PAGES) ⇒ Client

Initializes a new MCP::Client instance.

Once a handler is registered through on_elicitation, on_sampling, or on_roots, call_tool, get_prompt, and read_resource resume input_required results automatically; without handlers (or when a requested kind has no handler) they raise InputRequiredError for manual driving, exactly as before.

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.

  • input_required_max_rounds (Integer) (defaults to: DEFAULT_INPUT_REQUIRED_MAX_ROUNDS)

    Cap on SEP-2322 driver rounds.

  • max_pages (Integer) (defaults to: MAX_PAGES)

    Maximum number of pages the all-pages methods (#tools, #resources, #resource_templates, #prompts) will walk before raising PaginationLimitError.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/mcp/client.rb', line 133

def initialize(transport:, input_required_max_rounds: DEFAULT_INPUT_REQUIRED_MAX_ROUNDS, max_pages: MAX_PAGES)
  # `nil` or a non-positive value would make the pagination unbounded and silently
  # disable the protection, so reject it up front.
  unless max_pages.is_a?(Integer) && max_pages > 0
    raise ArgumentError, "max_pages must be a positive Integer"
  end

  @transport = transport
  @max_pages = max_pages
  # Populated by `on_elicitation`, `on_sampling`, and `on_roots`. The same handler answers both ways
  # the server can ask for input: a real server-to-client request, and an embedded request inside
  # a SEP-2322 `input_required` result.
  @input_required_handlers = {}
  @input_required_max_rounds = input_required_max_rounds
end

Instance Attribute Details

#transportObject (readonly)

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



151
152
153
# File 'lib/mcp/client.rb', line 151

def transport
  @transport
end

Instance Method Details

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

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

    SEP-2322 answers to a previous input_required result's inputRequests, keyed identically (manual retry legs).

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

    The opaque requestState echoed back byte-exactly.

Returns:

  • (Hash)

    The full JSON-RPC response from the transport.

Raises:

  • (ArgumentError)


482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/mcp/client.rb', line 482

def call_tool(name: nil, tool: nil, arguments: nil, progress_token: nil, meta: nil, cancellation: nil, input_responses: nil, request_state: 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?
  params[:inputResponses] = input_responses if input_responses
  params[:requestState] = request_state if request_state

  drive_input_required(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".



545
546
547
548
549
550
551
552
# File 'lib/mcp/client.rb', line 545

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: {}, mode: nil) ⇒ 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" => {} } }.

  • mode (Symbol, nil) (defaults to: nil)

    Lifecycle selection (SEP-2575). When omitted, transports whose connect declares mode: (the bundled MCP::Client::HTTP and MCP::Client::Stdio) negotiate with :auto: probe server/discover first and fall back to the legacy handshake when the server does not serve a mutually supported modern version. Transports without mode: keep receiving the historical legacy call shape. :legacy forces the initialize handshake exactly as before; :modern requires the modern lifecycle and fails without a mutual modern version. Passing an explicit protocol_version from a legacy generation (e.g. "2025-11-25") pins the legacy handshake without a probe, so an explicitly requested version is never overridden by the default negotiation.

Returns:

  • (Hash, nil)

    The server's InitializeResult (legacy) or DiscoverResult (modern), or nil when the transport does not expose an explicit handshake. Prefer the era-independent readers over inspecting this Hash directly.



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/mcp/client.rb', line 215

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

  effective_mode = resolve_connect_mode(mode, protocol_version)

  if effective_mode == :legacy
    transport.connect(
      client_info: client_info,
      protocol_version: protocol_version,
      capabilities: capabilities,
    )
  else
    transport.connect(
      client_info: client_info,
      protocol_version: protocol_version,
      capabilities: capabilities,
      mode: effective_mode,
    )
  end
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)


264
265
266
267
268
# File 'lib/mcp/client.rb', line 264

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

  true
end

#discover(meta: nil, cancellation: nil) ⇒ MCP::Client::DiscoverResult

Sends server/discover (MCP 2026-07-28, SEP-2575): sessionless capability discovery that works before (or instead of) connect.

Parameters:

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

    Additional _meta entries to send with the request.

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

    Optional cancellation token.

Returns:

Raises:

  • (ServerError)

    If the server returns a JSON-RPC error.

  • (ValidationError)

    If the response result is missing or not a Hash.



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

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

  DiscoverResult.new(
    supported_versions: result["supportedVersions"],
    capabilities: result["capabilities"],
    # The finalized spec (PR #3002) stamps the server identity into the result `_meta`;
    # the top-level fallback tolerates servers built against the frozen SEP text.
    server_info: result.dig("_meta", RequestEnvelope::SERVER_INFO_META_KEY) || result["serverInfo"],
    instructions: result["instructions"],
    ttl_ms: result["ttlMs"],
    cache_scope: result["cacheScope"],
  )
end

#get_prompt(name:, meta: nil, cancellation: nil, input_responses: nil, request_state: 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.



524
525
526
527
528
529
530
531
532
533
# File 'lib/mcp/client.rb', line 524

def get_prompt(name:, meta: nil, cancellation: nil, input_responses: nil, request_state: nil)
  params = { name: name }
  params = params.merge(_meta: meta) if meta && !meta.empty?
  params[:inputResponses] = input_responses if input_responses
  params[:requestState] = request_state if request_state

  response = drive_input_required(method: "prompts/get", params: params, cancellation: cancellation)

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

#instructionsObject

The server's instructions text, present in both eras when provided.



179
180
181
# File 'lib/mcp/client.rb', line 179

def instructions
  server_info&.dig("instructions")
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:



416
417
418
419
420
421
422
423
424
425
426
427
428
# File 'lib/mcp/client.rb', line 416

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:



380
381
382
383
384
385
386
387
388
389
390
391
392
# File 'lib/mcp/client.rb', line 380

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:



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

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:



288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
# File 'lib/mcp/client.rb', line 288

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"] || []).filter_map do |tool|
    next if exclude_invalid_x_mcp_header?(tool)

    Tool.new(
      name: tool["name"],
      description: tool["description"],
      input_schema: tool["inputSchema"],
      output_schema: tool["outputSchema"],
      annotations: tool["annotations"],
    )
  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: { ... } }.

The same handler answers both ways a server can ask: a real request mid-call, which needs a transport that carries server-to-client requests (e.g. MCP::Client::HTTP); and an elicitation/create embedded in a SEP-2322 input_required result, which needs no server-to-client route, and is how the modern lifecycle asks now that it forbids server-to-client requests. Both routes need capabilities: { elicitation: {} } passed to connect: per SEP-2322, a server MUST NOT embed input requests of a kind the client has not declared.

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


573
574
575
# File 'lib/mcp/client.rb', line 573

def on_elicitation(&handler)
  register_input_handler(Methods::ELICITATION_CREATE, &handler)
end

#on_roots(&handler) ⇒ Object

Deprecated.

MCP Roots (roots/list) is deprecated as of MCP protocol version 2026-07-28 (SEP-2577). Register this handler only to interoperate with servers that still ask for roots.

Registers a handler for roots/list, answering both a server-to-client request on transports that support one and an embedded roots/list inside a SEP-2322 input_required result. The handler receives the request params (nil for roots/list) and must return a ListRootsResult-shaped Hash: { roots: [{ uri: "file:///project", name: "Project" }] }.

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

Examples:

client.on_roots { { roots: [{ uri: "file:///project", name: "Project" }] } }


626
627
628
# File 'lib/mcp/client.rb', line 626

def on_roots(&handler)
  register_input_handler(Methods::ROOTS_LIST, &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.

The same handler answers both ways a server can ask: a real request mid-call, which needs a transport that carries server-to-client requests (e.g. MCP::Client::HTTP); and a sampling/createMessage embedded in a SEP-2322 input_required result, which needs no server-to-client route. Both routes need capabilities: { sampling: {} } passed to connect (or { sampling: { tools: {} } } for tool-enabled requests): per SEP-2322, a server MUST NOT embed input requests of a kind the client has not declared.

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


610
611
612
# File 'lib/mcp/client.rb', line 610

def on_sampling(&handler)
  register_input_handler(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:



644
645
646
647
648
649
# File 'lib/mcp/client.rb', line 644

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.

Raises:



439
440
441
442
# File 'lib/mcp/client.rb', line 439

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

#protocol_versionObject

The protocol version in use on this connection, independent of its era: the version negotiated by initialize (legacy) or adopted via server/discover (modern). Returns nil before connect.



167
168
169
170
171
# File 'lib/mcp/client.rb', line 167

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

  server_info&.dig("protocolVersion")
end

#read_resource(uri:, meta: nil, cancellation: nil, input_responses: nil, request_state: 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).



506
507
508
509
510
511
512
513
514
515
# File 'lib/mcp/client.rb', line 506

def read_resource(uri:, meta: nil, cancellation: nil, input_responses: nil, request_state: nil)
  params = { uri: uri }
  params = params.merge(_meta: meta) if meta && !meta.empty?
  params[:inputResponses] = input_responses if input_responses
  params[:requestState] = request_state if request_state

  response = drive_input_required(method: "resources/read", params: params, 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.

Raises:



403
404
405
406
# File 'lib/mcp/client.rb', line 403

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.

Raises:



367
368
369
370
# File 'lib/mcp/client.rb', line 367

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_capabilitiesObject

The server's capabilities Hash, present in both eras. Returns nil before connect.



174
175
176
# File 'lib/mcp/client.rb', line 174

def server_capabilities
  server_info&.dig("capabilities")
end

#server_implementationObject

The server's identity (name/version), independent of where the era puts it: top-level serverInfo on legacy results, the optional _meta io.modelcontextprotocol/serverInfo stamp on modern results. Returns nil when a modern server does not identify itself.



186
187
188
# File 'lib/mcp/client.rb', line 186

def server_implementation
  server_info&.dig("_meta", RequestEnvelope::SERVER_INFO_META_KEY) || server_info&.dig("serverInfo")
end

#server_infoObject

The raw handshake result exactly as the server returned it, so its shape depends on the connection's era (SEP-2575): after the legacy handshake it is an InitializeResult (protocolVersion, top-level serverInfo), after modern adoption it is a DiscoverResult (supportedVersions, ttlMs/cacheScope, serverInfo optionally under _meta). Code that must work against both eras should prefer the era-independent readers #protocol_version, #server_capabilities, #instructions, and #server_implementation; this raw form remains the window to everything they do not cover (supportedVersions, cache hints, _meta, extension data). Returns nil before connect, after close, or when the transport does not expose a cached handshake result.



160
161
162
# File 'lib/mcp/client.rb', line 160

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:

Raises:



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

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