Class: Pikuri::Mcp::Synthesizer
- Inherits:
-
Object
- Object
- Pikuri::Mcp::Synthesizer
- Defined in:
- lib/pikuri/mcp/synthesizer.rb
Overview
One-shot LLM synthesis of a short “what does this MCP server do” description for the <available_mcps> block, paired with the on-disk cache that persists the result. Owns the prompt, its version, the cleanup (whitespace collapse), and the fail-soft contract — Servers just calls #call and treats a nil return as “fall back.”
Cancellation
Cancellable::Cancelled raised from inside the @thinker propagates up through #call (it’s specifically not caught by the StandardError rescue) so a user’s Ctrl+C during a boot-time synthesis pass aborts startup cleanly rather than being silently logged as a “synthesis failure.”
Fail-soft on errors
Any other StandardError — a flaky LLM, garbage JSON, a cache write failure — is logged at WARN and #call returns nil. Pikuri::Mcp::Servers#resolve_description treats nil as the signal to fall back to serverInfo.name, so a single transient blip doesn’t take a server out of the <available_mcps> listing.
Constant Summary collapse
- PROMPT_VERSION =
Bump when #build_prompt changes meaningfully. Cache folds this into its key fingerprint via the
prompt_version:initializer kwarg, so a bump invalidates every cached entry from the previous prompt without anyone having tormthe cache directory. 1
Instance Method Summary collapse
-
#call(entry:, client:, tools:) ⇒ String?
Produce the description for one server.
-
#initialize(thinker:, cache: Cache::NULL) ⇒ Synthesizer
constructor
A new instance of Synthesizer.
Constructor Details
#initialize(thinker:, cache: Cache::NULL) ⇒ Synthesizer
Returns a new instance of Synthesizer.
46 47 48 49 |
# File 'lib/pikuri/mcp/synthesizer.rb', line 46 def initialize(thinker:, cache: Cache::NULL) @thinker = thinker @cache = cache end |
Instance Method Details
#call(entry:, client:, tools:) ⇒ String?
Produce the description for one server. Returns the cleaned description String, or nil when the thinker raised StandardError, returned blank, or otherwise failed to produce anything usable. Pikuri::Mcp::Servers#resolve_description treats nil as “fall back to serverInfo.name.”
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/pikuri/mcp/synthesizer.rb', line 64 def call(entry:, client:, tools:) raw = @cache.fetch(entry: entry, client: client, tools: tools) do # Only fires on cache miss — synthesis is the slow path (LLM # round-trip, easily 30+ s on a local model); a heads-up is # warranted so the user doesn't wonder if pikuri is hung. # Cache hits skip this and the thinker.call entirely. LOGGER.info("Synthesizing description for MCP server #{entry.id.inspect}, please wait...") @thinker.call(build_prompt(entry, tools)) end cleaned = raw.to_s.strip.gsub(/\s+/, ' ') return nil if cleaned.empty? cleaned rescue Agent::Control::Cancellable::Cancelled raise rescue StandardError => e LOGGER.warn( "MCP description synthesis failed for #{entry.id.inspect} " \ "(#{e.class}: #{e.}); falling back to serverInfo.name." ) nil end |