feat(mcp): share the claude mcp list inventory cache across sessions - #3
Open
mfbx9da4 wants to merge 1 commit into
Open
feat(mcp): share the claude mcp list inventory cache across sessions#3mfbx9da4 wants to merge 1 commit into
mfbx9da4 wants to merge 1 commit into
Conversation
The `claude mcp list` inventory used to resolve plugin- and claude.ai-connector MCP server transport was cached per session (keyed by session id), so every session paid the slow CLI probe once on its first unattributable MCP tool call. Make the cache global and additive instead: - One shared on-disk inventory (`<stateDir>/agenthooks-mcplist/inventory.json`), keyed by server name, merged additively so a session observing a momentarily smaller list can't clobber another session's entry. - A resolve miss re-probes at most once per interval (stale servers get picked up) then fails open; empty/failed runs are negative-cached. - Export RefreshClaudeMCPList (Runner) and WarmClaudeMCPList (package) so a detached session-start worker can warm the cache before the first tool call, removing the probe from the interactive path entirely. Concurrent sessions now share one warm copy: whichever probed most recently keeps every other session's first MCP tool call instant.
mfbx9da4
added a commit
to speakeasy-api/gram
that referenced
this pull request
Jul 23, 2026
…rt collector Route the detached collector through agenthooks' new shared MCP inventory cache instead of running its own `claude mcp list`. One CLI run now serves both purposes: it warms the global cache that per-tool-call URL attribution reads (so the first plugin/connector MCP tool call never stalls, not just session start), and returns the full inventory the collector relays for admin visibility. - RunMCPInventory calls agenthooks.WarmClaudeMCPList and maps the result to the inventory upload, dropping the relay's duplicate collect/parse code. - The upload is the shared additive inventory, so a later run unions with earlier ones rather than replacing them. Requires agenthooks with the shared/additive mcp-list cache (bumped here to the branch commit; see speakeasy-api/agenthooks#3).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
The
claude mcp listinventory that resolves plugin- and claude.ai-connector MCP server transport (URL/command) was cached per session, keyed by session id. So every session paid the slow CLI probe (claude mcp listhealth-checks every configured server, seconds of wall time) once, on its first MCP tool call the config fast path couldn't attribute. Concurrent sessions didn't share the work, and nothing could warm the cache ahead of the first tool call, so the probe sat on the interactive path.This is the upstream half of a Gram hooks change that moves the session-start MCP inventory collection off the critical path (DNO-622).
What changed (Before/After)
Before
Each session probes once. No sharing, no warming.
After
mcpListRefreshInterval, so an unresolvable name can't stall every event on the CLI timeout. Empty/failed runs are negative-cached.func (r *Runner) RefreshClaudeMCPList() []MCPListEntryand package-levelfunc WarmClaudeMCPList() []MCPListEntrylet a detached session-start worker populate the shared cache before the first tool call, removing the probe from the interactive path entirely. Both return the merged inventory so the caller can also relay it.Behavior change to note
A different session no longer re-probes; it reuses the shared warm cache. An event with no session id now resolves against the shared cache too (previously it never probed, having no key). Both are the intended consequence of sharing.
WithoutMCPListFallback/WithoutMCPResolutionare unchanged.Tests
WarmClaudeMCPListpopulates the default cache and a default runner resolves from it without re-probing.