Skip to content

fix(cliproxyapi): rewrite mcp_ references in system prompt + tool descriptions - #2194

Closed
NomenAK wants to merge 1 commit into
diegosouzapw:release/v3.8.0from
NomenAK:upstream/cliproxyapi-mcp-name-refs-rewrite
Closed

NomenAK wants to merge 1 commit into
diegosouzapw:release/v3.8.0from
NomenAK:upstream/cliproxyapi-mcp-name-refs-rewrite

Conversation

@NomenAK

@NomenAK NomenAK commented May 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #2165 (Anthropic-shape body routing and ^mcp_[^_] tool-name rewrite). The original rewrite renamed the tools but left every textual reference to those names unchanged in the system prompt and tool descriptions. The model then reads a prompt that says "use `mcp_call`" while the tool catalog only contains `Mcp_call` — it cannot match the name, gives up on tool-calling, and falls back to plain-text responses.

Anthropic-SDK clients (Capy, claude-cli) render plain text outside of designated message tools as a reasoning trace, so the response visibly lands in the "Thought" panel rather than as a normal assistant message. The fix here closes that loop.

Related Issues

Motivation

Captured live artifact (Capy /v1/messages → OmniRoute → cliproxyapi → Anthropic, 2026-05-12) :

```
// providerRequest (post-#2165 rewrite)
tools: [
{ name: "Mcp_query", description: "Discover MCP tools via mcp_query. Use before mcp_call." },
{ name: "Mcp_call", description: "..." }
]
system: [{ text: "ALWAYS use mcp_query first to discover available tools, THEN use mcp_call." }]
```

```
// providerResponse — Claude does NOT use any tool, emits plain text
content: [{ type: "text", text: "Test received. What would you like me to do?" }]
stop_reason: "end_turn"
```

Same body shape with this PR applied :

```
// providerRequest (post-#2165 + this PR)
tools: [
{ name: "Mcp_query", description: "Discover MCP tools via Mcp_query. Use before Mcp_call." },
{ name: "Mcp_call", description: "..." }
]
system: [{ text: "ALWAYS use Mcp_query first... THEN use Mcp_call." }]
```

```
// providerResponse — Claude correctly uses the tool
content: [{ type: "tool_use", id: "...", name: "Mcp_query", input: {...} }]
stop_reason: "tool_use"
```

(Response-side tool name is restored to the client's original namespace by the existing reverse-map mechanism — Capy sees `mcp_query` back in `tool_use` blocks, no client-visible change.)

Changes

`open-sse/executors/cliproxyapi.ts` :

  1. Add `MCP_NAME_REF_RE = /\bmcp_(?=[^_\\s])/g` — the same shape as `MCP_RESERVED_PREFIX_RE` (the existing tool-name detector) but in a global-replace form with a left word-boundary and a right negative-lookahead.
  2. In `applyMcpToolNameRewrite`, after collecting the reverse map, apply the regex to :
    • Top-level `system` blocks (both string and array-of-text-blocks forms — Anthropic supports both).
    • Each `tools[*].description`.
  3. Skip `messages[*].content` text blocks — those may carry user-supplied text we shouldn't mutate (covered by the "does not mutate message content" test).
  4. The rewrite is gated on `reverseMap.size > 0` — bodies without any `mcp_*` tools incur no scan.

Regex shape — edge cases handled (covered by tests)

  • `mcp__experimental` → preserved (double underscore : `[^_]` lookahead fails)
  • `my_mcp_helper` → preserved (left `\b` doesn't match between two word chars)
  • `mcp_ ` (with trailing space) → preserved (`[^_\\s]` lookahead fails)
  • Markdown : `(mcp_call)` → rewritten to `(Mcp_call)` (intentional ; `\b` matches the `(` → `m` boundary)

Validation

  • `npm run lint` (clean)
  • `npm run test:unit` — `tests/unit/cliproxyapi-executor.test.ts` — 43/43 pass (6 new tests added)
  • Live smoke against real Capy /v1/messages → CPA → Anthropic : tool-calling restored, response renders as message (not Thought)

🤖 Generated with Claude Code

…criptions

The existing ^mcp_[^_] → Mcp_X tool-name rewrite (diegosouzapw#2165) dodged Anthropic's
MCP-connector billing gate but left every textual reference to those names
unchanged in the system prompt and tool descriptions. Models then saw an
inconsistent catalog : the prompt says "use mcp_call", the tool list only
contains `Mcp_call`. Result : the model gave up on tool-calling and fell
back to plain text — which Anthropic-SDK clients (Capy, claude-cli)
render as a reasoning trace rather than a normal message.

Apply the same regex transformation to all textual references in the
top-level `system` blocks and `tools[*].description`. Skip message
content blocks — those carry user-supplied text we shouldn't mutate.

The text-replace regex is `\bmcp_(?=[^_\s])` so it only touches the same
forms the tool-name rewrite touches : preserves `mcp__double_underscore`,
`my_mcp_helper`, and other non-gate-triggering shapes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@NomenAK
NomenAK requested a review from diegosouzapw as a code owner May 12, 2026 10:12

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces logic to ensure consistency between tool names and their textual references in system prompts and tool descriptions. It adds a regex-based transformation that converts 'mcp_' prefixes to 'Mcp_' within the system prompt and tool descriptions when MCP tools are being used, preventing potential tool-calling failures due to naming mismatches. Comprehensive unit tests were included to verify the transformation logic, handle various system prompt formats, and ensure that user-supplied message content is preserved. I have no feedback to provide as there were no review comments.

@NomenAK

NomenAK commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

Closing after 2026-05-12 empirical bisect.

This PR added two transformations to align model perception of mcp_* tool names:

  1. Tool name rewrite (mcp_XMcp_X on tool defs, tool_use blocks, tool_choice) — gate-dodge for Anthropic's reserved ^mcp_[^_] namespace.
  2. Prose rewrite (same regex applied to system prompt text + tool descriptions) — to keep the model's textual references consistent with the rewritten tool catalog.

Bisect against live Anthropic via CPA cloak (Claude Opus 4.7, full Capy body with 24 tools incl. mcp_query, 5-turn stress test):

  • With prose rewrite OFF (name rewrite still ON) → Claude calls the rewritten tools correctly, 5/5 turns. The prose rewrite was modifying client content with no measurable benefit.
  • With ALL rewrite OFF (OMNIROUTE_DISABLE_MCP_REWRITE=1) → still HTTP 200 + tool_use(message_user). The ^mcp_ gate apparently does not fire on the current Anthropic+CPA path on this body, though 2026-05-11 char-by-char probe had previously confirmed it. We're keeping the name rewrite (cheap, no harm, gate may resume) but dropping the prose pass.

Fork-side: NomenAK/OmniRoute@cleanup/remove-over-engineered-strips contains the name-only rewrite via commit 3166060 ("drop mcp_ prose rewrite, keep name-only rewrite"). If upstream wants the name-only rewrite as a clean standalone PR, happy to open one — let me know.

@NomenAK NomenAK closed this May 12, 2026
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