Class: Pikuri::Mcp::Cache

Inherits:
Object
  • Object
show all
Defined in:
lib/pikuri/mcp/cache.rb

Overview

On-disk cache for the per-server descriptions Synthesizer 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 output, so an unchanged surface short-circuits and a changed one auto-invalidates. The key hashes canonical JSON over: model_id (quality varies by model), prompt_version, the transport descriptor (argv or URL), server name+version, and the full tools surface (name, description, input_schema including nested properties), canonicalised by recursive sort so a reordered tools array / schema keys don't blow the cache. The server's registry id is deliberately excluded — a rename doesn't change what the server does.

ttl: Float::INFINITY: no time expiry, entries valid until the keyed surface changes; force a full rebuild by +rm+-ing DIR or bumping the prompt version. #fetch mirrors UrlCache#fetch (yield on miss, persist the result); callers rescue around it for corrupt-file robustness.

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

Constructor Details

#initialize(model_id:, prompt_version:, dir: DIR) ⇒ Cache

Returns a new instance of Cache.

Parameters:

  • model_id (String, nil)

    model id; folded into the key so a model swap doesn't serve stale output.

  • prompt_version (Integer)

    the caller's PROMPT_VERSION; folded in so a prompt edit invalidates the world.

  • dir (String) (defaults to: DIR)

    storage directory; defaults to DIR.



34
35
36
37
38
# File 'lib/pikuri/mcp/cache.rb', line 34

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 (entry, client, tools) triple, or yield to compute + persist + return it on a miss.

Parameters:

Yield Returns:

  • (String)

    freshly-computed description

Returns:

  • (String)

    cached or freshly-computed description



48
49
50
# File 'lib/pikuri/mcp/cache.rb', line 48

def fetch(entry:, client:, tools:, &block)
  @url_cache.fetch(key_for(entry, client, tools), &block)
end

#key_for(entry, client, tools) ⇒ String

The canonical fingerprint for a (entry, client, tools) triple — UrlCache SHA-256s it into the filename; we return the raw JSON (not a hash) so it's inspectable in tests.

Returns:

  • (String)

    canonical JSON over the keyed inputs



57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/pikuri/mcp/cache.rb', line 57

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