Compare commits

...

1 Commits

Author SHA1 Message Date
diegosouzapw
ec45a46c20 fix(sse): recognize MCP-gateway-namespaced CCR retrieve tool names (#13781, #13897)
callerSupportsCcrRetrieve() matched the omniroute_ccr_retrieve tool by exact
string equality. MCP aggregators/gateways (Docker MCP Toolkit, Claude Code)
rename re-exposed tools with a namespace prefix (e.g.
mcp__docker__omniroute__omniroute_ccr_retrieve, or a dotted/slashed prefix),
so a fully MCP-capable caller reachable only under such a name was treated as
unable to retrieve at all -- skipping both the protocol-instruction injection
and, per the #7746 safety guarantee, the entire CCR compression engine for
that request.

Add matchesCcrRetrieveToolName(), a separator-bounded trailing-segment match
(__, ., /, :) that accepts a namespaced form of the tool name while still
rejecting a near-miss like omniroute_ccr_retrieve_v2 -- never a bare
substring/endsWith check.
2026-09-17 18:00:19 -03:00
4 changed files with 85 additions and 11 deletions

View File

@@ -0,0 +1 @@
- **fix(sse):** CCR retrieve-tool detection now recognizes an MCP-gateway-namespaced tool name (e.g. Docker MCP Toolkit's `mcp__docker__omniroute__omniroute_ccr_retrieve` or a single `mcp__<server>__omniroute_ccr_retrieve` prefix) via a separator-bounded trailing-segment match instead of requiring an exact `omniroute_ccr_retrieve` string — previously an MCP-capable caller reachable only under a gateway-assigned name was treated as unable to retrieve, so the entire CCR compression engine was skipped for it (#13781).

View File

@@ -0,0 +1 @@
- **fix(sse):** the CCR protocol instruction (the system note teaching a model how to call `omniroute_ccr_retrieve`) is now injected for callers reaching OmniRoute's MCP server through a namespacing gateway (Claude Code / Docker MCP style `mcp__<gateway>__<server>__omniroute_ccr_retrieve`, or a dotted/slashed prefix) — `callerSupportsCcrRetrieve()` previously matched by exact string equality only, so a genuinely reachable but gateway-namespaced tool name was never recognized and the instruction (and CCR compression itself) was silently skipped (#13897).

View File

@@ -12,10 +12,37 @@
* but ONLY when the caller's advertised `tools[]` proves it can actually reach
* `omniroute_ccr_retrieve` (an MCP-capable caller). Plain OpenAI-compatible
* callers that cannot reach the tool must never be told to call it.
*
* MCP gateways/aggregators (Docker MCP Toolkit, Claude Code, and similar) rename
* tools when re-exposing them to the model to avoid cross-server name collisions,
* typically as `mcp__<gateway>__<server>__omniroute_ccr_retrieve` or with a
* dotted/slashed namespace prefix (`omniroute.omniroute_ccr_retrieve`). A caller
* reachable only under such a namespaced name is still MCP-capable, so the match
* below accepts a trailing-segment match bounded by a separator (`__`, `.`, `/`,
* `:`) — never a bare substring test — so a near-miss like
* `omniroute_ccr_retrieve_v2` still correctly does not match (#13781, #13897).
*/
const CCR_RETRIEVE_TOOL_NAME = "omniroute_ccr_retrieve";
/** Separators MCP gateways use to namespace a re-exposed tool name. */
const CCR_RETRIEVE_TOOL_NAME_SEPARATORS = ["__", ".", "/", ":"];
/**
* True when `name` is exactly the CCR retrieve tool name, or ends with it as a
* separator-bounded trailing segment (e.g. an MCP-gateway-namespaced name such
* as `mcp__docker__omniroute__omniroute_ccr_retrieve`). Deliberately NOT a bare
* `.includes()`/unqualified `.endsWith()` — that would also match an unrelated
* near-miss name like `omniroute_ccr_retrieve_v2`.
*/
function matchesCcrRetrieveToolName(name: string | undefined): boolean {
if (typeof name !== "string") return false;
if (name === CCR_RETRIEVE_TOOL_NAME) return true;
return CCR_RETRIEVE_TOOL_NAME_SEPARATORS.some((separator) =>
name.endsWith(`${separator}${CCR_RETRIEVE_TOOL_NAME}`)
);
}
/** Leading marker that identifies the injected instruction (also the idempotency sentinel). */
export const CCR_PROTOCOL_MARKER_SENTINEL = "[CCR protocol]";
@@ -41,7 +68,7 @@ export function callerSupportsCcrRetrieve(body: Record<string, unknown>): boolea
const t = tool as ToolLike;
const flatName = typeof t?.name === "string" ? t.name : undefined;
const nestedName = typeof t?.function?.name === "string" ? t.function.name : undefined;
return flatName === CCR_RETRIEVE_TOOL_NAME || nestedName === CCR_RETRIEVE_TOOL_NAME;
return matchesCcrRetrieveToolName(flatName) || matchesCcrRetrieveToolName(nestedName);
});
}
@@ -59,9 +86,7 @@ function messageContainsSentinel(message: MessageWithContent): boolean {
part &&
typeof part === "object" &&
typeof (part as Record<string, unknown>)["text"] === "string" &&
((part as Record<string, unknown>)["text"] as string).includes(
CCR_PROTOCOL_MARKER_SENTINEL
)
((part as Record<string, unknown>)["text"] as string).includes(CCR_PROTOCOL_MARKER_SENTINEL)
);
}
return false;

View File

@@ -158,19 +158,23 @@ describe("ccr protocol instruction (#8033)", () => {
instruction.includes("[CCR retrieve hash=<24hex> chars=N]"),
"must show the marker shape"
);
assert.match(
instruction,
/verbatim|exact/i,
"must stress verbatim/exact copying of the hash"
);
assert.match(instruction, /verbatim|exact/i, "must stress verbatim/exact copying of the hash");
assert.match(instruction, /24/, "must mention the 24-character length of the hash");
assert.ok(instruction.includes("dedup:ref"), "must mention the dedup:ref contract");
});
it("recognizes all three tools[] shapes: OpenAI nested, flat, Claude", () => {
assert.equal(callerSupportsCcrRetrieve({ tools: [RETRIEVE_TOOL_OPENAI] }), true, "OpenAI nested shape");
assert.equal(
callerSupportsCcrRetrieve({ tools: [RETRIEVE_TOOL_OPENAI] }),
true,
"OpenAI nested shape"
);
assert.equal(callerSupportsCcrRetrieve({ tools: [RETRIEVE_TOOL_FLAT] }), true, "flat shape");
assert.equal(callerSupportsCcrRetrieve({ tools: [RETRIEVE_TOOL_CLAUDE] }), true, "Claude shape");
assert.equal(
callerSupportsCcrRetrieve({ tools: [RETRIEVE_TOOL_CLAUDE] }),
true,
"Claude shape"
);
assert.equal(callerSupportsCcrRetrieve({ tools: [] }), false, "empty tools array");
assert.equal(callerSupportsCcrRetrieve({}), false, "absent tools field");
assert.equal(
@@ -180,6 +184,49 @@ describe("ccr protocol instruction (#8033)", () => {
);
});
it("recognizes MCP-gateway-namespaced tool names (#13781, #13897)", () => {
assert.equal(
callerSupportsCcrRetrieve({
tools: [
{
type: "function",
function: { name: "mcp__docker__omniroute__omniroute_ccr_retrieve" },
},
],
}),
true,
"Docker MCP Toolkit style double-prefix name"
);
assert.equal(
callerSupportsCcrRetrieve({ tools: [{ name: "mcp__docker__omniroute_ccr_retrieve" }] }),
true,
"single mcp__<server>__<tool> namespace prefix"
);
assert.equal(
callerSupportsCcrRetrieve({ tools: [{ name: "omniroute.omniroute_ccr_retrieve" }] }),
true,
"dotted namespace prefix"
);
assert.equal(
callerSupportsCcrRetrieve({ tools: [{ name: "omniroute/omniroute_ccr_retrieve" }] }),
true,
"slashed namespace prefix"
);
});
it("does NOT loosen matching into a plain substring test", () => {
assert.equal(
callerSupportsCcrRetrieve({ tools: [{ name: "omniroute_ccr_retrieve_v2" }] }),
false,
"near-miss suffix must not match"
);
assert.equal(
callerSupportsCcrRetrieve({ tools: [{ name: "xomniroute_ccr_retrieve" }] }),
false,
"no separator boundary before the suffix must not match"
);
});
it("injectCcrProtocolInstruction is a pure helper usable directly", () => {
const messages: Msg[] = [{ role: "user", content: "hi" }];
const withInstruction = injectCcrProtocolInstruction(messages, { tools: [RETRIEVE_TOOL_FLAT] });