Class: Pikuri::Mcp::Extension
- Inherits:
-
Object
- Object
- Pikuri::Mcp::Extension
- Includes:
- Agent::Extension
- Defined in:
- lib/pikuri/mcp/extension.rb
Overview
An Agent::Extension that wires MCP (Model Context Protocol) support onto an agent: builds an Servers runtime from a Registry, appends the <available_mcps> block to the system prompt, registers an on_close handler that tears down the live MCP clients, and (in bind) installs a per-agent mcp_connect tool so the LLM can pull MCP-exposed tools into its toolset on demand.
Configure / bind split
configure runs once on the parent’s Configurator and creates the shared Servers runtime — that’s the resource the extension owns. bind fires once on the parent agent and installs a Connect tool keyed to it, registered via Agent#internal_add_tool so the tool’s execute closure can call back into the right chat when the LLM activates a server. Sub-agents do not inherit extensions, so they receive neither the Connect tool nor any MCP-backed tools — personas own their toolset by construction.
Usage
registry = Pikuri::Mcp::Registry.new(entries: [
Pikuri::Mcp::Registry::StdioEntry.new(id: 'gmail', command: %w[gmail-mcp])
])
Pikuri::Agent.new(transport: ..., system_prompt: ...) do |c|
c.add_extension Pikuri::Mcp::Extension.new(registry: registry)
end
Empty registry
When the registry is Registry#empty?, the extension is a no-op — no Servers, no snippet, no tool, no on_close. Same semantics as the legacy mcp_registry: kwarg on Agent#initialize, which routes through this extension as a transition layer.
Instance Attribute Summary collapse
-
#servers ⇒ Mcp::Servers?
readonly
The runtime built in
configure, ornilwhen the registry was empty (extension is a no-op).
Instance Method Summary collapse
-
#bind(agent) ⇒ void
Register a per-agent
mcp_connecttool on the agent’s chat. -
#configure(c) ⇒ void
Build the shared Servers runtime, append the <available_mcps> block to the system prompt, and register the close handler.
-
#initialize(registry: Registry::EMPTY, synthesize_descriptions: true, verify_mcp_servers: true) ⇒ Extension
constructor
A new instance of Extension.
Constructor Details
#initialize(registry: Registry::EMPTY, synthesize_descriptions: true, verify_mcp_servers: true) ⇒ Extension
Returns a new instance of Extension.
57 58 59 60 61 62 63 64 |
# File 'lib/pikuri/mcp/extension.rb', line 57 def initialize(registry: Registry::EMPTY, synthesize_descriptions: true, verify_mcp_servers: true) @registry = registry @synthesize_descriptions = synthesize_descriptions @verify_mcp_servers = verify_mcp_servers @servers = nil end |
Instance Attribute Details
#servers ⇒ Mcp::Servers? (readonly)
Returns the runtime built in configure, or nil when the registry was empty (extension is a no-op).
69 70 71 |
# File 'lib/pikuri/mcp/extension.rb', line 69 def servers @servers end |
Instance Method Details
#bind(agent) ⇒ void
This method returns an undefined value.
Register a per-agent mcp_connect tool on the agent’s chat. The tool’s execute closure captures the agent reference so activations register their tools on the correct chat — see IDEAS.md §“Two invariants worth recording” for the static-vs-dynamic tool boundary.
113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/pikuri/mcp/extension.rb', line 113 def bind(agent) return if @servers.nil? || @servers.empty? if agent.tools.any?(Mcp::Servers::Connect) raise 'Mcp::Servers::Connect cannot be passed in tools: when an MCP runtime is wired; ' \ 'Agent auto-registers it.' end connect = @servers.build_mcp_connect_tool(agent) agent.internal_add_tool(connect.to_ruby_llm_tool) nil end |
#configure(c) ⇒ void
This method returns an undefined value.
Build the shared Servers runtime, append the <available_mcps> block to the system prompt, and register the close handler. The Synthesizer / Verifier thinker closure captures the Configurator’s transport + cancellable so the LLM-driven boot passes run against the same model the agent itself uses and honor the same cancel flag.
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/pikuri/mcp/extension.rb', line 80 def configure(c) return if @registry.empty? if @synthesize_descriptions || @verify_mcp_servers thinker = build_thinker(c.transport, c.cancellable) end synthesizer = build_synthesizer(thinker, c.transport.model) if @synthesize_descriptions verifier = build_verifier(thinker, c.transport.model) if @verify_mcp_servers # Two-phase on purpose: construct (pure), arm cleanup, then # start. Arming +on_close+ *before* the failure-prone # +start_all+ is what makes a start failure (Cancelled, # injection) non-leaking: +c.on_close+ writes straight to the # agent's live handler list, so if +start_all+ raises, the # agent's constructor rescue closes these half-started servers. # See {Mcp::Servers}' "Lifecycle: two-phase, and the cleanup # gap" and {Agent#run_configure}. @servers = Mcp::Servers.new(@registry, synthesizer: synthesizer, verifier: verifier) c.on_close { @servers.close } @servers.start_all c.append_system_prompt(@servers.system_prompt_snippet.lstrip) unless @servers.empty? nil end |