MCP tool order is nondeterministic across runs (server groups iterate HashMap order)
Summary
The model-visible tool list groups MCP tools by server, but the server groups are emitted in std::collections::HashMap iteration order. HashMap iteration order is seeded per-process, so the exact same MCP configuration produces a reshuffled tool order on every fresh codex / app-server run. This makes the attention order of tool namespaces unstable and hard to reproduce or replay.
Why it matters
- Uncontrolled run-to-run variance in a model-visible list that prompts may describe or enumerate.
- Snapshots/replays of the per-step tool catalog are not reproducible between runs.
- The order also leaks into context — a separate server can "win" the first (most prominent) position depending only on process hash-seed luck.
Root cause
In codex-rs/codex-mcp/src/connection_manager/tool_catalog.rs:
capture_binding_with_metadatabuilds the model-visible binding by iteratingself.servers.iter()(aHashMap).list_all_toolsdoes the same for non-model discovery/resync surfaces.
Both emit server groups in HashMap iteration order, which is deliberately randomized per process.
Reproduction
Build the same MCP connection set with two or more servers in two separate processes (or two independent McpConnectionSet instances) and inspect capture_binding(...).tools(): the server-group order differs between runs even though the configuration is identical.
Suggested fix
Iterate configured servers by name when building tool lists so ordering is deterministic across runs:
- sort the
servers.iter()collection by key incapture_binding_with_metadataand inlist_all_tools, thenjoin_allover the sorted order.
This keeps the grouping intact while making the model-visible order stable and name-sorted.
Ready branch
I have a complete, green implementation on a public fork:
- Branch:
skirdey/codex→clark/codex-main-21154b77 - Commit
0ad07fc658— sorts server iteration in both functions and adds regression testcapture_binding_orders_servers_deterministically(builds two independent connection sets and asserts identical, name-sorted order; fails on pre-fix code). - Verified:
cargo fmt --checkclean,cargo clippy --tests -p codex-mcpclean, fullcodex-mcpsuite (~165 tests) andcodex-coretests pass.
Note on PR
I attempted to open this as a pull request, but openai/codex currently restricts PR creation to collaborators (skirdey is not one), so the PR cannot be opened externally. Per docs/contributing.md (invitation-only external contributions), I'm filing this issue first with analysis and a ready branch. Happy to open the PR if the team invites it; the change is small, well-scoped, and covered by a regression test.
2 Comments
Thanks for the ready-to-cherry-pick branch and test evidence. I also see the repo still enforces invite-only external PR flow per contributing docs, so I can’t safely open a maintainer PR from this tokenized workspace. If you want me to proceed once invited, I’ll take the deterministic-order change through a maintainer-side branch/PR targeting both capture paths plus the added regression test. In the meantime, this looks complete and high-confidence from the description and is good to prioritize for PR review.
Great — happy to have it proceed. For the cherry-pick: the change is on
skirdey/codex, branchclark/codex-main-21154b77, commit0ad07fc658— sorts server iteration intool_catalog.rs(capture_binding_with_metadata+list_all_tools) with thecapture_binding_orders_servers_deterministicallyregression test. Let me know if you'd like anything else.