Skip to content

fix: Deterministic MCP Tool Order for a Stable Prompt Prefix - #17097

Closed
Shabonasar wants to merge 1 commit into
LibreChat-AI:devfrom
Shabonasar:fix/deterministic-tool-order
Closed

Shabonasar wants to merge 1 commit into
LibreChat-AI:devfrom
Shabonasar:fix/deterministic-tool-order

Conversation

@Shabonasar

Copy link
Copy Markdown

Summary

Continuation of #16369. Providers serialize the tools parameter ahead of the system prompt, so the tool list is the very first thing a prefix cache (vLLM/SGLang automatic prefix caching, OpenAI automatic caching, Anthropic and Bedrock breakpoints, Gemini implicit caching) has to match. Any reordering of tools invalidates the cache from position 0, including the whole conversation history.

Today the MCP part of tools follows the order in which servers were selected in the chat picker and the key order of the server tool catalog. Selecting the same servers in a different order, a different spec/picker merge, or a reconnect that returns tools in a different order produces a different tools array for the same agent, so conversations that should share a prefix do not, and a single conversation can lose its cache between turns.

What changes

  • sortNames (packages/api/src/mcp/utils.ts): code-unit ascending order, independent of locale.
  • loadEphemeralAgent and loadAddedAgent materialize picker-selected and spec-pinned MCP servers, and the tools of each server, in that order. The sorted server list is also what is published to ephemeralAgent.mcp, which the instruction path reads.
  • mcp_all expansion in loadToolDefinitions iterates server tools by name.
  • MCPManager.getInstructions returns servers by name, so MCP server instructions in the system prompt keep a stable order.

Intentionally unchanged: the explicit tools list of a saved agent (the author's order is preserved), and the order of non-MCP groups (MCP, tool_search, actions, built-ins, registered tools). Sorting happens only inside the MCP group.

Testing

  • Unit: agents/load.spec.ts (every permutation of the picker, shuffled catalog keys, similar names A/a/a_b, duplicates, empty server, failed server placeholder, merge with spec.mcpServers), tools/definitions.spec.ts (mcp_all expansion), mcp/__tests__/utils.test.ts, mcp/__tests__/MCPManager.test.ts (identical instructions text for any requested order), updated agents/__tests__/load.spec.ts.
  • Functional: agents/__tests__/toolOrderStability.test.ts (30 tests). Real loadEphemeralAgent, loadToolDefinitions, initializeAgent registrations, MCPManager.formatInstructionsForContext, applyContextToAgent and createRun; the serialized provider request is captured before it leaves the process. Mixed tool set: four MCP servers, deferred tools with tool_search, an action, file_search, web_search, code execution, memory, file authoring and a self-spawn subagent. Providers: OpenAI, an OpenAI-compatible endpoint, Anthropic and Bedrock with prompt caching, Google. Serialized tools and system are byte-identical across three picker orders, a reversed catalog key order and a repeated turn; the order of non-MCP groups is pinned by an explicit list. With sorting disabled, 25 of the 30 tests fail.
  • Local runs: packages/api src/agents, src/tools, src/mcp with MongoDB, all passing except suites that need Redis or outbound network; api agents controllers and services passing; tsc --noEmit clean; eslint and prettier clean on touched files.

Next

Part of the series started in #16369: context toggled mid-conversation (artifacts, project context, memory) delivered as <context_update> history events, then deferred loading for MCP servers selected in the chat so the tools parameter stays fixed when a server is enabled mid-conversation.

Related: #16369, #16877.

Type of change

  • Bug fix
  • Performance improvement

Providers serialize the tools parameter ahead of the system prompt, so any
reordering of MCP tools invalidates the prompt prefix cache from position 0.
Selecting the same servers in a different order, or a catalog returning tool
keys in a different order, produced a different tools array.

- loadEphemeralAgent and loadAddedAgent materialize selected and spec-pinned
  MCP servers, and the tools of each server, in code-unit order
- mcp_all expansion iterates tool names in the same order
- MCPManager.getInstructions returns servers by name, so MCP instructions in
  the system prompt keep a stable order
- explicit agent.tools and the order of non-MCP groups are unchanged

Tests: unit coverage for permutations, shuffled catalog keys, similar names,
duplicates, empty and failed servers, spec merge; toolOrderStability.test.ts
captures provider payloads through createRun for OpenAI, an OpenAI-compatible
endpoint, Anthropic, Bedrock and Google.
@Shabonasar

Copy link
Copy Markdown
Author

Closing: sorting alone does not keep the prefix stable, because enabling an MCP server mid-conversation still changes tools. The name ordering will be folded into the follow-up that keeps tools fixed for the whole conversation and delivers MCP servers added mid-dialog as history events (continuation of #16369).

@Shabonasar

Copy link
Copy Markdown
Author

Tracked in #17102; the name ordering moves into the stable-tools PR for MCP servers selected mid-chat.

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.

1 participant