Class: Insika::McpToolRegistry
- Inherits:
-
Object
- Object
- Insika::McpToolRegistry
- Defined in:
- lib/insika/mcp_tool_registry.rb
Overview
LIVE MCP tools — kills the McpToolIngestor snapshot.
entries is CHEAP: it reads only McpStore#tools_cache (no I/O), the same
contract the ToolStore-backed dynamic entries already give
OverlayToolRegistry — Policy recomputes candidate_tools every turn, so
an unreachable MCP server must never add real latency there.
refresh is the ONLY thing that talks to a server ahead of time: it
connects, lists live, and writes the result back to tools_cache (display/
doctor). Calling a tool never depends on that cache — Insika::McpLiveTool
always goes through the live, memoized client (and RubyLLM::MCP::Client
does its own real tools/list on ITS first use per instance, regardless
of whether refresh ever ran).
Instance Method Summary collapse
-
#entries ⇒ Object
-> [Registry::Entry] one per cached tool of every ENABLED instance.
-
#evict(name) ⇒ Object
Stops and drops any memoized client for
name(no-op if none), so the nextclient_forbuilds a fresh one off the current record. -
#initialize(mcp_store:, client_factory: Insika::McpClient.method(:for)) ⇒ McpToolRegistry
constructor
A new instance of McpToolRegistry.
-
#refresh(name) ⇒ Object
Connects to
nameLIVE, lists its tools, and writes McpStore#tools_cache.
Constructor Details
#initialize(mcp_store:, client_factory: Insika::McpClient.method(:for)) ⇒ McpToolRegistry
Returns a new instance of McpToolRegistry.
18 19 20 21 22 23 |
# File 'lib/insika/mcp_tool_registry.rb', line 18 def initialize(mcp_store:, client_factory: Insika::McpClient.method(:for)) @mcp_store = mcp_store @client_factory = client_factory @clients = {} @mutex = Mutex.new end |
Instance Method Details
#entries ⇒ Object
-> [Registry::Entry] one per cached tool of every ENABLED instance.
26 27 28 |
# File 'lib/insika/mcp_tool_registry.rb', line 26 def entries @mcp_store.all_raw.select { |r| r["enabled"] }.flat_map { |r| entries_for(r) } end |
#evict(name) ⇒ Object
Stops and drops any memoized client for name (no-op if none), so the
next client_for builds a fresh one off the current record. Public:
called by refresh above AND by UpsertMcp/DeleteMcp right after they
write the store — a Studio/CLI edit takes effect on that instance's
NEXT tool call or refresh, no process restart required (there's no way
to restart a process by hand on a Railway dyno).
57 58 59 60 61 62 |
# File 'lib/insika/mcp_tool_registry.rb', line 57 def evict(name) client = @mutex.synchronize { @clients.delete(name.to_s) } client&.stop rescue StandardError nil # best-effort teardown of a possibly already-dead process end |
#refresh(name) ⇒ Object
Connects to name LIVE, lists its tools, and writes McpStore#tools_cache.
ALWAYS drops any memoized client first and rebuilds from the current
record: a stdio process can be alive? (still running) yet permanently
broken (e.g. wrong transport args — see grafana-stg gotcha), and an
edited command/url/env must take effect without a process restart —
there's no SSH into a Railway dyno to do that by hand.
-> the discovered ["name","description","inputSchema"]. Raises on a
missing/disabled instance or a transport failure — the caller (a CLI
verb/API route/UI button, PR3/PR4) decides how to surface it.
39 40 41 42 43 44 45 46 47 48 49 |
# File 'lib/insika/mcp_tool_registry.rb', line 39 def refresh(name) record = @mcp_store.get_raw(name.to_s) raise Insika::NotFoundError, "MCP instance '#{name}' not found" if record.nil? raise Insika::ValidationError, "MCP instance '#{name}' is disabled" unless record["enabled"] evict(record["name"]) client = client_for(record) discovered = client.tools.map { |t| { "name" => t.name, "description" => t.description, "inputSchema" => t.params_schema } } @mcp_store.set_tools_cache(name, discovered) discovered end |