Class: Rubino::MCP::Manager
- Inherits:
-
Object
- Object
- Rubino::MCP::Manager
- Defined in:
- lib/rubino/mcp/manager.rb
Overview
Manages multiple MCP client connections. Reads server definitions from config, starts clients, and registers their tools into the agent’s tool registry.
Instance Attribute Summary collapse
-
#clients ⇒ Object
readonly
clients: name => live RubyLLM::MCP client.
-
#last_errors ⇒ Object
readonly
clients: name => live RubyLLM::MCP client.
Instance Method Summary collapse
-
#configured? ⇒ Boolean
Returns true if any MCP servers are configured.
-
#health_check ⇒ Object
Checks health of all connected servers.
-
#initialize(config: nil) ⇒ Manager
constructor
A new instance of Manager.
-
#register_all_tools! ⇒ Object
Registers all MCP tools into the agent’s tool registry.
-
#register_server_tools(name) ⇒ Object
Registers ONE started server’s tools — the ‘/mcp <server> on` path (#182) re-registers only that server instead of re-reading every client’s tool list.
-
#start_all! ⇒ Object
Initializes all configured MCP servers.
-
#start_server(name, server_config) ⇒ Object
Starts a single MCP server by name.
-
#stop_all! ⇒ Object
Stops all MCP clients (deregistering their tools — see #stop_server).
-
#stop_server(name) ⇒ Object
Stops a specific MCP client AND deregisters its MCPToolWrapper instances from Tools::Registry (#182) — before, nothing ever unregistered them, so a stopped server left dead tools the model could still call.
Constructor Details
#initialize(config: nil) ⇒ Manager
Returns a new instance of Manager.
17 18 19 20 21 22 |
# File 'lib/rubino/mcp/manager.rb', line 17 def initialize(config: nil) @config = config || Rubino.configuration @clients = {} @last_errors = {} route_mcp_logging! end |
Instance Attribute Details
#clients ⇒ Object (readonly)
clients: name => live RubyLLM::MCP client. last_errors: name => the most recent start failure message (cleared on a successful start) — the “why is my server missing?” answer /mcp’s drill-in shows (#182).
15 16 17 |
# File 'lib/rubino/mcp/manager.rb', line 15 def clients @clients end |
#last_errors ⇒ Object (readonly)
clients: name => live RubyLLM::MCP client. last_errors: name => the most recent start failure message (cleared on a successful start) — the “why is my server missing?” answer /mcp’s drill-in shows (#182).
15 16 17 |
# File 'lib/rubino/mcp/manager.rb', line 15 def last_errors @last_errors end |
Instance Method Details
#configured? ⇒ Boolean
Returns true if any MCP servers are configured
114 115 116 117 |
# File 'lib/rubino/mcp/manager.rb', line 114 def configured? servers = @config.dig("mcp", "servers") servers.is_a?(Hash) && !servers.empty? end |
#health_check ⇒ Object
Checks health of all connected servers
102 103 104 105 106 107 108 109 110 111 |
# File 'lib/rubino/mcp/manager.rb', line 102 def health_check @clients.map do |name, client| alive = begin client.alive? rescue StandardError false end { name: name, alive: alive } end end |
#register_all_tools! ⇒ Object
Registers all MCP tools into the agent’s tool registry. Per-agent mcp_servers scoping is NOT applied here — it lives in Agent::Definition#resolved_tools (#173), the single seam every consumer of an agent’s tool set goes through.
82 83 84 |
# File 'lib/rubino/mcp/manager.rb', line 82 def register_all_tools! @clients.each_key { |server_name| register_server_tools(server_name) } end |
#register_server_tools(name) ⇒ Object
Registers ONE started server’s tools — the ‘/mcp <server> on` path (#182) re-registers only that server instead of re-reading every client’s tool list.
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/rubino/mcp/manager.rb', line 89 def register_server_tools(name) client = @clients[name.to_s] return unless client client.tools.each do |mcp_tool| wrapped = MCPToolWrapper.new(mcp_tool, server_name: name.to_s) Tools::Registry.register(wrapped) end rescue StandardError => e Rubino.ui.warning("Failed to load tools from '#{name}': #{e.}") end |
#start_all! ⇒ Object
Initializes all configured MCP servers
25 26 27 28 29 30 31 32 33 34 |
# File 'lib/rubino/mcp/manager.rb', line 25 def start_all! server_configs = @config.dig("mcp", "servers") || {} server_configs.each do |name, server_config| start_server(name, server_config) end register_all_tools! @clients end |
#start_server(name, server_config) ⇒ Object
Starts a single MCP server by name
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
# File 'lib/rubino/mcp/manager.rb', line 37 def start_server(name, server_config) transport = server_config["transport"] || "stdio" client_opts = (name, transport, server_config) client = RubyLLM::MCP.client(**client_opts) @clients[name.to_s] = client @last_errors.delete(name.to_s) Rubino.event_bus.emit(:mcp_server_started, name: name) client rescue StandardError => e @last_errors[name.to_s] = e. Rubino.ui.warning("MCP server '#{name}' failed to start: #{e.}") nil end |
#stop_all! ⇒ Object
Stops all MCP clients (deregistering their tools — see #stop_server). ‘keys.each`, NOT `each_key`: stop_server deletes from @clients, which would raise mid-iteration without the snapshot.
56 57 58 |
# File 'lib/rubino/mcp/manager.rb', line 56 def stop_all! @clients.keys.each { |name| stop_server(name) } # rubocop:disable Style/HashEachMethods end |
#stop_server(name) ⇒ Object
Stops a specific MCP client AND deregisters its MCPToolWrapper instances from Tools::Registry (#182) — before, nothing ever unregistered them, so a stopped server left dead tools the model could still call.
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/rubino/mcp/manager.rb', line 64 def stop_server(name) client = @clients.delete(name.to_s) return nil unless client deregister_tools(name.to_s) begin client.stop rescue StandardError => e Rubino.ui.warning("Error stopping MCP '#{name}': #{e.}") end Rubino.event_bus.emit(:mcp_server_stopped, name: name) client end |