Class: Pikuri::Mcp::ClientWrapper
- Inherits:
-
Object
- Object
- Pikuri::Mcp::ClientWrapper
- Defined in:
- lib/pikuri/mcp/client_wrapper.rb
Overview
Wraps one MCP::Client + its transport with retry-and-restart on
stdio-subprocess death. On construction it builds a fresh transport and
runs the initialize handshake; on a subprocess-died #call_tool it
closes the dead transport, respawns, re-handshakes, and retries up to
MAX_CALL_ATTEMPTS times, after which the RequestHandlerError propagates
(the synthesized tool's closure turns it into an Error: ... observation).
Only subprocess-death messages trigger the restart: once an stdio
subprocess dies, MCP::Client::Stdio is irreversibly broken
(+@wait_thread.alive?+ stays false, every call raises from
ensure_running!), so restarting is the only way back. Other
+RequestHandlerError+s (protocol mismatch, JSON parse, ValidationError)
leave the transport usable — a restart would just retry the same mistake.
Registry::HttpEntry is accepted but no HTTP failure matches those
patterns, so the wrapper is a pass-through for HTTP.
Lifecycle: if the handshake fails, #initialize closes its own half-opened
transport before re-raising — a caller who saw ClientWrapper.new raise
need not close anything (fully-initialized object, or no object at all).
Constant Summary collapse
- MAX_CALL_ATTEMPTS =
Maximum number of
call_toolattempts including the initial one, before propagating the underlying exception. 3 means: one normal try plus up to two restart-then-retry attempts. 3
Instance Attribute Summary collapse
-
#client ⇒ MCP::Client
readonly
The current live client.
-
#entry ⇒ Registry::StdioEntry, Registry::HttpEntry
readonly
The registry entry the wrapper builds (and rebuilds) transports from.
Instance Method Summary collapse
-
#call_tool(tool:, arguments:) ⇒ Hash
Call an MCP tool.
-
#close ⇒ void
Close the underlying transport.
-
#initialize(entry) ⇒ ClientWrapper
constructor
A new instance of ClientWrapper.
-
#server_info ⇒ Hash?
Server's cached
InitializeResult, as exposed by the transport. -
#tools ⇒ Array<MCP::Client::Tool>
Enumerate the server's tools via
MCP::Client#tools.
Constructor Details
#initialize(entry) ⇒ ClientWrapper
Returns a new instance of ClientWrapper.
63 64 65 66 67 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 63 def initialize(entry) @entry = entry @closed = false spawn_fresh! end |
Instance Attribute Details
#client ⇒ MCP::Client (readonly)
Returns the current live client. Replaced on
each restart, so callers must not cache this across
#call_tool invocations. Exposed for one-shot boot-time
reads of server_info / tools by Servers, Verifier,
Synthesizer, and Pikuri::Mcp::Cache.
56 57 58 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 56 def client @client end |
#entry ⇒ Registry::StdioEntry, Registry::HttpEntry (readonly)
Returns the registry entry the wrapper builds (and rebuilds) transports from.
49 50 51 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 49 def entry @entry end |
Instance Method Details
#call_tool(tool:, arguments:) ⇒ Hash
Call an MCP tool. On a SUBPROCESS_DEAD_PATTERNS failure, restart and retry up to MAX_CALL_ATTEMPTS times; other failures propagate on the first attempt (JSON-RPC errors don't reach here — they ride in the response Hash).
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 98 def call_tool(tool:, arguments:) attempt = 1 begin @client.call_tool(tool: tool, arguments: arguments) rescue MCP::Client::RequestHandlerError => e raise unless subprocess_dead?(e) raise if attempt >= MAX_CALL_ATTEMPTS LOGGER.warn( "MCP server #{@entry.id.inspect} subprocess died " \ "(#{e..inspect}); restarting and retrying " \ "(attempt #{attempt + 1}/#{MAX_CALL_ATTEMPTS})." ) restart! attempt += 1 retry end end |
#close ⇒ void
This method returns an undefined value.
Close the underlying transport. Idempotent — subsequent calls are no-ops. After close, #call_tool will fail on the dead client.
122 123 124 125 126 127 128 129 130 131 132 133 134 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 122 def close return if @closed begin @transport&.close rescue StandardError => e LOGGER.warn( "Error closing MCP transport for #{@entry.id.inspect}: " \ "#{e.class}: #{e.}" ) end @closed = true end |
#server_info ⇒ Hash?
Server's cached InitializeResult, as exposed by the
transport. Delegates to #client.
73 74 75 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 73 def server_info @client.server_info end |
#tools ⇒ Array<MCP::Client::Tool>
Enumerate the server's tools via MCP::Client#tools. Each
call is a real round-trip — callers typically invoke this
once at boot and cache the result.
82 83 84 |
# File 'lib/pikuri/mcp/client_wrapper.rb', line 82 def tools @client.tools end |