Class: Mistri::MCP::Client
- Inherits:
-
Object
- Object
- Mistri::MCP::Client
- Defined in:
- lib/mistri/mcp/client.rb
Overview
A Model Context Protocol client: the initialize handshake, tools/list with pagination, and tools/call, over one of two wires. url: speaks Streamable HTTP on the same persistent transport the providers use; command: spawns a local stdio server with credentials in its environment.
Mistri::MCP::Client.new(url: "https://mcp.linear.app/mcp",
token: -> { connection.bearer_token })
Mistri::MCP::Client.new(command: ["npx", "-y", "some-mcp-server"],
env: { "API_KEY" => key })
HTTP auth is a headers hash or token: a string or a callable. A callable resolves per request, and a 401 retries once after re-resolving, so a host's refresh logic lives in one lambda. A session the server expires (404 with a session attached) transparently re-initializes, per spec.
One client serializes its calls; parallel tool calls against one server queue rather than interleave.
Constant Summary collapse
- PROTOCOL_VERSION =
"2025-11-25"- SUPPORTED_VERSIONS =
%w[2025-11-25 2025-06-18 2025-03-26 2024-11-05].freeze
- LOOPBACK =
%w[localhost 127.0.0.1 ::1].freeze
Instance Attribute Summary collapse
-
#server_info ⇒ Object
readonly
Returns the value of attribute server_info.
Instance Method Summary collapse
- #call_tool(name, arguments = {}) ⇒ Object
- #close ⇒ Object
- #connect ⇒ Object
-
#initialize(url: nil, command: nil, env: {}, token: nil, headers: {}, client_name: "mistri", open_timeout: 15, read_timeout: 120) ⇒ Client
constructor
A new instance of Client.
-
#tools(refresh: false) ⇒ Object
The server's tools as it describes them: hashes with "name", "description", and "inputSchema".
Constructor Details
#initialize(url: nil, command: nil, env: {}, token: nil, headers: {}, client_name: "mistri", open_timeout: 15, read_timeout: 120) ⇒ Client
Returns a new instance of Client.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/mistri/mcp/client.rb', line 33 def initialize(url: nil, command: nil, env: {}, token: nil, headers: {}, client_name: "mistri", open_timeout: 15, read_timeout: 120) if [url, command].compact.length != 1 raise ConfigurationError, "pass exactly one of url: or command:" end if url && token && URI(url).scheme == "http" && !LOOPBACK.include?(URI(url).host) raise ConfigurationError, "refusing to send a bearer token over plain HTTP to #{URI(url).host}" end @wire = if url Wires::Http.new(url: url, token: token, headers: headers, open_timeout: open_timeout, read_timeout: read_timeout) else Wires::Stdio.new(command: command, env: env, read_timeout: read_timeout) end @client_name = client_name @mutex = Mutex.new @serial = 0 @connected = false end |
Instance Attribute Details
#server_info ⇒ Object (readonly)
Returns the value of attribute server_info.
31 32 33 |
# File 'lib/mistri/mcp/client.rb', line 31 def server_info @server_info end |
Instance Method Details
#call_tool(name, arguments = {}) ⇒ Object
65 66 67 |
# File 'lib/mistri/mcp/client.rb', line 65 def call_tool(name, arguments = {}) @mutex.synchronize { request("tools/call", { name: name, arguments: arguments }) } end |
#close ⇒ Object
74 75 76 77 78 |
# File 'lib/mistri/mcp/client.rb', line 74 def close @wire.close @connected = false nil end |
#connect ⇒ Object
69 70 71 72 |
# File 'lib/mistri/mcp/client.rb', line 69 def connect @mutex.synchronize { ensure_connected } self end |
#tools(refresh: false) ⇒ Object
The server's tools as it describes them: hashes with "name", "description", and "inputSchema". Cached; refresh: true re-lists.
58 59 60 61 62 63 |
# File 'lib/mistri/mcp/client.rb', line 58 def tools(refresh: false) @mutex.synchronize do @tools = nil if refresh @tools ||= list_tools end end |