Class: McpToolkit::Gateway::Client

Inherits:
Object
  • Object
show all
Defined in:
lib/mcp_toolkit/gateway/client.rb

Overview

Minimal MCP client over Streamable HTTP, used by a GATEWAY to talk to an upstream MCP server when aggregating its tool list and proxying tool calls.

It speaks the same Streamable-HTTP MCP that McpToolkit::Transport serves:

1. POST `initialize`                 -> capture the `Mcp-Session-Id` response header
2. POST `notifications/initialized`  (a notification; no response expected)
3. POST `tools/list` / `tools/call`, echoing the session header

Auth & account selection are pass-through: the caller's bearer token and the account selector (_meta) are forwarded so the upstream can introspect the same token against its authority and resolve the same account.

Transport notes

  • Content negotiation: we POST application/json and Accept both application/json and text/event-stream. If the upstream answers with SSE (one message + EOF, as the toolkit's own transport does) we extract the single JSON payload from the data: line.
  • Every public method may raise McpToolkit::Gateway::Client::Error (timeouts, non-2xx, unparseable bodies, JSON-RPC errors). Callers decide whether to degrade (omit from list) or surface the error (proxied call).

Everything app-specific (server identity, protocol version, timeout, logger) is injected via McpToolkit::Configuration; nothing here names a deployment.

Defined Under Namespace

Classes: Error

Constant Summary collapse

SESSION_HEADER =
"Mcp-Session-Id"
JSONRPC_VERSION =
"2.0"
DEFAULT_PROTOCOL_VERSION =

The protocol version offered on the handshake when the config does not pin one. Sourced from the wrapped mcp SDK's latest-supported constant when available, with a literal fallback so the gem still loads if the SDK moves the constant. config.protocol_version overrides it.

if defined?(MCP::Configuration) && MCP::Configuration.const_defined?(:LATEST_STABLE_PROTOCOL_VERSION)
  MCP::Configuration::LATEST_STABLE_PROTOCOL_VERSION
else
  "2025-06-18"
end
SESSION_LOSS_HTTP_STATUS =

An upstream whose session store is not shared across its pods can answer a NON-initialize request with HTTP 404 (the pod fielding it never saw our initialize), or with a JSON-RPC -32001 "Session not found or expired". Both mean the same thing: our session is gone and a fresh handshake recovers it.

404
SESSION_NOT_FOUND_CODE =
-32_001

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(upstream:, bearer_token: nil, config: McpToolkit.config) ⇒ Client

Returns a new instance of Client.



68
69
70
71
72
73
74
75
# File 'lib/mcp_toolkit/gateway/client.rb', line 68

def initialize(upstream:, bearer_token: nil, config: McpToolkit.config)
  @upstream = upstream
  @bearer_token = bearer_token
  @config = config
  @timeout = config.upstream_timeout
  @session_id = nil
  @initialized = false
end

Instance Attribute Details

#bearer_tokenObject (readonly)

Returns the value of attribute bearer_token.



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

def bearer_token
  @bearer_token
end

#configObject (readonly)

Returns the value of attribute config.



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

def config
  @config
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



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

def timeout
  @timeout
end

#upstreamObject (readonly)

Returns the value of attribute upstream.



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

def upstream
  @upstream
end

Instance Method Details

#tools_call(name:, arguments: {}, meta: nil) ⇒ Object

Proxies a tools/call to the upstream. arguments and meta are forwarded as-is. Returns the upstream's result hash verbatim (typically { "content" => [...] }). A JSON-RPC error from the upstream is raised as an Error carrying jsonrpc_error so a proxy can relay it.



91
92
93
94
95
96
97
# File 'lib/mcp_toolkit/gateway/client.rb', line 91

def tools_call(name:, arguments: {}, meta: nil)
  params = { "name" => name, "arguments" => arguments }
  params["_meta"] = meta if meta.present?
  with_session_recovery("tools/call #{name}") do
    rpc!("tools/call", params)
  end
end

#tools_listObject

Returns the upstream's raw tools array (each a tool definition hash with string keys: "name", "description", "inputSchema"). Bare names — the caller namespaces them.



80
81
82
83
84
85
# File 'lib/mcp_toolkit/gateway/client.rb', line 80

def tools_list
  with_session_recovery("tools/list") do
    result = rpc!("tools/list")
    Array(result["tools"])
  end
end