Skip to content

fix(cache): sort MCP tools deterministically to stabilize prompt cache#2

Open
bcherny wants to merge 3590 commits intomainfrom
cache/mcp-tools-sort
Open

fix(cache): sort MCP tools deterministically to stabilize prompt cache#2
bcherny wants to merge 3590 commits intomainfrom
cache/mcp-tools-sort

Conversation

@bcherny
Copy link
Copy Markdown
Owner

@bcherny bcherny commented Mar 25, 2026

Problem

createBundleMcpToolRuntime (src/agents/pi-bundle-mcp-tools.ts:122) is called on every turn from attempt.ts:1842. It collects tools via client.listTools() without sorting, then returns them in collection order.

The MCP spec doesn't guarantee listTools() order. If tool order differs between turns — from paginated responses, third-party server implementations, or future SDK changes — the tools array in the API request changes, busting the prompt cache at the tools block.

Fix

Sort the collected tools alphabetically by name before returning:

tools: tools.toSorted((a, b) => a.name.localeCompare(b.name))

This makes the tools block deterministic regardless of listTools() iteration order or server-config iteration order.

Semantics unchanged: collision resolution (first-server-wins via reservedNames set) happens during collection, before the sort.

Verification

  • Added regression test with a multi-tool server that registers zeta, alpha, mu — asserts runtime returns [alpha, mu, zeta]
  • All 4 tests pass
  • Lint clean
  • Checked downstream: all tool lookups in attempt.ts are name-based (allowlist, prefix-stripping at :620), not position-based

Follow-up (not in this PR)

Hoist runtime to session scope. createBundleMcpToolRuntime currently spawns stdio transports + listTools() on every turn and disposes in the finally at attempt.ts:3227. Bigger win: cache the runtime keyed on a hash of loaded.mcpServers config, dispose on session end. Stops re-spawning MCP servers per turn. More invasive — needs session-lifecycle wiring.

🤖 Generated with Claude Code

steipete and others added 30 commits April 3, 2026 18:55
…#60452)

* refactor: move bundled replay policy ownership into plugins

* test: preserve replay fallback until providers adopt hooks

* test: cover response replay branches for ollama and zai

---------

Co-authored-by: Shakker <shakkerdroid@gmail.com>
steipete and others added 29 commits April 3, 2026 23:06
Merged via squash.

Prepared head SHA: 28ae50b
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
…or [AI] (openclaw#59822)

* fix: address issue

* fix: address PR review feedback

* docs: add changelog entry for PR merge

* docs: normalize changelog entry placement

---------

Co-authored-by: Devin Robison <drobison@nvidia.com>
…a exceeds size limit (openclaw#60289)

Merged via squash.

Prepared head SHA: f33dd49
Co-authored-by: efe-arv <259833796+efe-arv@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
* fix(sandbox): block home credential binds
* fix(sandbox): harden blocked credential bind checks
Merged via squash.

Prepared head SHA: 64223e5
Co-authored-by: MonkeyLeeT <6754057+MonkeyLeeT@users.noreply.github.com>
Co-authored-by: mcaxtr <7562095+mcaxtr@users.noreply.github.com>
Reviewed-by: @mcaxtr
@odysseus0 odysseus0 force-pushed the cache/mcp-tools-sort branch from e134420 to 2ca9eed Compare April 4, 2026 00:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.