Class: Pikuri::Mcp::Cache
- Inherits:
-
Object
- Object
- Pikuri::Mcp::Cache
- Defined in:
- lib/pikuri/mcp/cache.rb
Overview
On-disk cache for the per-server descriptions Servers#try_synthesize_description pays an LLM round-trip to produce. Wraps UrlCache for storage; this class owns the *key calculation* — the fingerprint of every input that could change the synthesized output, so an unchanged surface short-circuits to the stored description and a changed surface auto-invalidates.
What goes into the key
The key hashes a canonical JSON serialization of:
-
model_id— synthesis quality varies by model; cached output from a different model should not be served. -
prompt_version— Servers::PROMPT_VERSION. Bumped when the synthesizer prompt changes meaningfully so old cache entries stop being served without having to delete the cache file. -
Transport descriptor — for Registry::StdioEntry, the argv; for Registry::HttpEntry, the URL.
-
Server name + version from client.server_info.
-
Full tools surface: every tool’s
name,description, andinput_schema— including nested property names, types, and per-property descriptions. The surface is canonicalised (recursive sort) so a server that reorders its tools array or its schema keys doesn’t blow the cache.
The server’s id from the registry is intentionally not part of the key — renaming a server entry in the registry doesn’t change what the server actually does, and the id appears only as a passing reference in the synth prompt, never in the produced description.
Storage
Reuses UrlCache with a ttl: Float::INFINITY. There is no time-based expiry — cache entries are valid forever, until the keyed surface changes. To force a rebuild of everything, rm the DIR directory (or bump Servers::PROMPT_VERSION).
Fail-soft contract
#fetch mirrors UrlCache#fetch — yields on miss, returns the block’s result, and persists it. Servers additionally rescues StandardError around the #fetch call to keep startup robust against a corrupt cache file or a writer-side error.
Constant Summary collapse
- DIR =
On-disk root. Sibling of UrlCache::ROOT_DIR so all of pikuri’s caches live under one path.
File.join(File.dirname(UrlCache::ROOT_DIR), 'mcp_descriptions').freeze
Instance Method Summary collapse
-
#fetch(entry:, client:, tools:, &block) ⇒ String
Return the cached description for the given (entry, client, tools) triple if a fresh entry exists, otherwise yield to compute it (typically a Agent.think call inside Servers#try_synthesize_description), persist the result, and return it.
-
#initialize(model_id:, prompt_version:, dir: DIR) ⇒ Cache
constructor
A new instance of Cache.
-
#key_for(entry, client, tools) ⇒ String
Compute the canonical fingerprint string for a (entry, client, tools) triple.
Constructor Details
#initialize(model_id:, prompt_version:, dir: DIR) ⇒ Cache
Returns a new instance of Cache.
66 67 68 69 70 |
# File 'lib/pikuri/mcp/cache.rb', line 66 def initialize(model_id:, prompt_version:, dir: DIR) @model_id = model_id @prompt_version = prompt_version @url_cache = UrlCache.new(ttl: Float::INFINITY, dir: dir) end |
Instance Method Details
#fetch(entry:, client:, tools:, &block) ⇒ String
Return the cached description for the given (entry, client, tools) triple if a fresh entry exists, otherwise yield to compute it (typically a Agent.think call inside Servers#try_synthesize_description), persist the result, and return it.
83 84 85 |
# File 'lib/pikuri/mcp/cache.rb', line 83 def fetch(entry:, client:, tools:, &block) @url_cache.fetch(key_for(entry, client, tools), &block) end |
#key_for(entry, client, tools) ⇒ String
Compute the canonical fingerprint string for a (entry, client, tools) triple. The fingerprint is what UrlCache SHA-256s into the on-disk filename; we don’t hash here so the raw JSON is inspectable in tests.
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/pikuri/mcp/cache.rb', line 93 def key_for(entry, client, tools) info = (client.server_info || {})['serverInfo'] || {} fingerprint = { 'model_id' => @model_id, 'prompt_version' => @prompt_version, 'transport' => transport_descriptor(entry), 'server_name' => info['name'], 'server_version' => info['version'], 'tools' => tools_descriptor(tools) } JSON.generate(canonicalize(fingerprint)) end |