mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-22 06:42:19 +03:00
Compare commits
1 Commits
dependabot
...
fix/13781-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ec45a46c20 |
1
changelog.d/fixes/13781-ccr-mcp-tool-prefix-matching.md
Normal file
1
changelog.d/fixes/13781-ccr-mcp-tool-prefix-matching.md
Normal 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).
|
||||
@@ -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).
|
||||
@@ -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;
|
||||
|
||||
@@ -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] });
|
||||
|
||||
Reference in New Issue
Block a user