Skip to content

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

Merged
odysseus0 merged 1 commit into
openclaw:mainfrom
bcherny:cache/mcp-tools-sort
Apr 4, 2026
Merged

fix(cache): sort MCP tools deterministically to stabilize prompt cache#58037
odysseus0 merged 1 commit into
openclaw:mainfrom
bcherny:cache/mcp-tools-sort

Conversation

@bcherny

@bcherny bcherny commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

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

@openclaw-barnacle openclaw-barnacle Bot added agents Agent runtime and tooling size: S labels Mar 31, 2026
@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR makes the tools array returned by createBundleMcpToolRuntime stable across turns by sorting tools alphabetically by name before returning them. The fix prevents prompt-cache busting caused by non-deterministic listTools() ordering from the MCP SDK or third-party servers.

  • The one-line change (tools.toSorted(...)) is applied after all collision-deduplication logic, so first-server-wins semantics are preserved.
  • Array.prototype.toSorted is available on Node.js ≥ 20 — the project's minimum engine is >=22.14.0, so there is no compatibility concern.
  • A new integration test spins up a real MCP server that registers tools in zeta → alpha → mu order and asserts the runtime returns [alpha, mu, zeta], giving direct regression coverage for the fix.
  • The writeExecutable helper imported from the test harness was already exported there; the import is consistent with the existing test pattern.
  • Overall: a minimal, correct, well-tested fix with no semantic side-effects.

Confidence Score: 5/5

  • Safe to merge — minimal, correct change with direct regression test coverage and no semantic side-effects.
  • The fix is a single non-mutating sort applied at the right place in the pipeline. Node.js ≥ 22.14.0 is required (toSorted available since Node 20). No P1 or P0 issues found; all remaining observations are informational.
  • No files require special attention.

Reviews (1): Last reviewed commit: "fix(cache): sort MCP tools deterministic..." | Re-trigger Greptile

@byungsker

This comment was marked as spam.

@odysseus0 odysseus0 self-assigned this Apr 4, 2026
@odysseus0
odysseus0 force-pushed the cache/mcp-tools-sort branch from e134420 to 2ca9eed Compare April 4, 2026 00:19
@odysseus0
odysseus0 merged commit bc16b9d into openclaw:main Apr 4, 2026
3 checks passed
@odysseus0

Copy link
Copy Markdown
Contributor

Landed via manual rebase-port onto main (original base was stale after upstream MCP runtime refactor).

  • Ported sort fix to materializeBundleMcpToolsForRun in src/agents/pi-bundle-mcp-materialize.ts
  • Added sort-order test to src/agents/pi-bundle-mcp-tools.materialize.test.ts
  • Land commit: 2ca9eed
  • Merge commit:

Thanks @bcherny!

KimGLee pushed a commit to KimGLee/openclaw that referenced this pull request Apr 4, 2026
vincentkoc pushed a commit that referenced this pull request Apr 5, 2026
 (#60633)

* docs: correct overstated prompt-cache comments from #58036 #58037 #58038

* docs: restore purpose context in MCP tool sort comment

* docs: drop misleading 'legacy' framing from image-prune comments

* docs: restore useful context stripped from image-prune comments

* docs: restore 'deterministically' in MCP tool sort comment

* docs: restore 'idempotent' at attempt.ts callsite

* docs: restore 'provider prompt cache' in context-guard comment
mdrxy added a commit to langchain-ai/deepagents that referenced this pull request Apr 7, 2026
…#2497)

MCP's `list_tools()` does not guarantee tool ordering. If tool order
differs between turns — from paginated responses, server restarts, or
SDK changes — the tools array in the API request changes, busting the
prompt cache at the tools block. Sort the collected tools by name in
`_load_tools_from_config` to make the tools block deterministic.
Inspired by
[openclaw/openclaw#58037](openclaw/openclaw#58037).
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…enclaw#58037 openclaw#58038 (openclaw#60633)

* docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038

* docs: restore purpose context in MCP tool sort comment

* docs: drop misleading 'legacy' framing from image-prune comments

* docs: restore useful context stripped from image-prune comments

* docs: restore 'deterministically' in MCP tool sort comment

* docs: restore 'idempotent' at attempt.ts callsite

* docs: restore 'provider prompt cache' in context-guard comment
james8814 pushed a commit to james8814/deepagents that referenced this pull request May 1, 2026
…langchain-ai#2497)

MCP's `list_tools()` does not guarantee tool ordering. If tool order
differs between turns — from paginated responses, server restarts, or
SDK changes — the tools array in the API request changes, busting the
prompt cache at the tools block. Sort the collected tools by name in
`_load_tools_from_config` to make the tools block deterministic.
Inspired by
[openclaw/openclaw#58037](openclaw/openclaw#58037).
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…enclaw#58037 openclaw#58038 (openclaw#60633)

* docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038

* docs: restore purpose context in MCP tool sort comment

* docs: drop misleading 'legacy' framing from image-prune comments

* docs: restore useful context stripped from image-prune comments

* docs: restore 'deterministically' in MCP tool sort comment

* docs: restore 'idempotent' at attempt.ts callsite

* docs: restore 'provider prompt cache' in context-guard comment
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…enclaw#58037 openclaw#58038 (openclaw#60633)

* docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038

* docs: restore purpose context in MCP tool sort comment

* docs: drop misleading 'legacy' framing from image-prune comments

* docs: restore useful context stripped from image-prune comments

* docs: restore 'deterministically' in MCP tool sort comment

* docs: restore 'idempotent' at attempt.ts callsite

* docs: restore 'provider prompt cache' in context-guard comment
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 24, 2026
…enclaw#58037 openclaw#58038 (openclaw#60633)

* docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038

* docs: restore purpose context in MCP tool sort comment

* docs: drop misleading 'legacy' framing from image-prune comments

* docs: restore useful context stripped from image-prune comments

* docs: restore 'deterministically' in MCP tool sort comment

* docs: restore 'idempotent' at attempt.ts callsite

* docs: restore 'provider prompt cache' in context-guard comment
Nachx639 pushed a commit to Nachx639/clawdbot that referenced this pull request Jun 17, 2026
Nachx639 pushed a commit to Nachx639/clawdbot that referenced this pull request Jun 17, 2026
…enclaw#58037 openclaw#58038 (openclaw#60633)

* docs: correct overstated prompt-cache comments from openclaw#58036 openclaw#58037 openclaw#58038

* docs: restore purpose context in MCP tool sort comment

* docs: drop misleading 'legacy' framing from image-prune comments

* docs: restore useful context stripped from image-prune comments

* docs: restore 'deterministically' in MCP tool sort comment

* docs: restore 'idempotent' at attempt.ts callsite

* docs: restore 'provider prompt cache' in context-guard comment
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants