Skip to content

fix(mcp): prevent normalized tool collisions - #1259

Open
yansigit wants to merge 1 commit into
1jehuang:masterfrom
yansigit:fix/mcp-collision-safe-dispatch
Open

yansigit wants to merge 1 commit into
1jehuang:masterfrom
yansigit:fix/mcp-collision-safe-dispatch

Conversation

@yansigit

Copy link
Copy Markdown

Summary

Prevent normalized MCP tool names from colliding at provider boundaries while preserving the original server-qualified dispatch identity.

Changes

  • Generate deterministic unique aliases when normalization produces duplicates.
  • Apply the same mapping to eager, cached, and live registration paths.
  • Keep original names available for dispatch back to the MCP registry.
  • Cover collision ordering, aliases, and dispatch behavior with focused tests.

Validation

  • git diff --check upstream/master...HEAD
  • Rebasing onto current upstream/master completed successfully.
  • Focused jcode-base MCP tests passed.
  • The broader app-core suite still has unrelated baseline failures in communication, session-search, tool-schema, and selfdev tests.

Refs #972

@greptile-apps

greptile-apps Bot commented Sep 15, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 2/5

Not safe to merge until the outstanding MCP registry-refresh and session-policy enforcement issues are fixed.

Findings

  1. P1 Reconcile refresh aliases
  2. P1 Security Match policies to aliases
Fix with agent prompt
### Issue 1
crates/jcode-base/src/mcp/tool.rs:125-128
Cached startup registration can assign an unsuffixed name when it sees only one member of a normalized collision. Live discovery then assigns suffixed names after it sees both members, but leaves the original cached key registered. The same MCP tool family is therefore exposed under three names, and the obsolete cached alias can continue advertising cached metadata instead of the refreshed definition. Remove obsolete MCP registry keys during refresh, or assign names from one stable set of server/tool identities.

### Issue 2
crates/jcode-base/src/mcp/tool.rs:148-155
For normalized MCP-name collisions, eager registration and execution use generated suffixed aliases, but session allow/deny entries continue to match the historical unsuffixed name. A deny entry for the configured MCP capability therefore does not match either eager collision alias, allowing that external capability to execute despite being disabled. Resolve policy entries and generated aliases through the same MCP identity.

**How this was verified:** A session deny for the unsuffixed name allowed a suffixed collision alias to invoke the test external capability.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This change adds deterministic names for MCP tools whose normalized provider-facing names collide, including combined handling for cached schemas across configured servers. The change is not ready to merge: two blocking MCP behaviors remain open. A cached unsuffixed alias can survive after live discovery assigns collision-safe aliases, and a generated alias can bypass a per-tool session deny entry written for the historical unsuffixed name.

Reviews (2) · Last reviewed commit: "fix(mcp): prevent normalized tool collis..."

Comment on lines +125 to +128
let mut counts = std::collections::HashMap::<&str, usize>::new();
for base in &bases {
*counts.entry(base).or_default() += 1;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Reconcile refresh aliases

Cached startup registration can assign an unsuffixed name when it sees only one member of a normalized collision. Live discovery then assigns suffixed names after it sees both members, but leaves the original cached key registered. The same MCP tool family is therefore exposed under three names, and the obsolete cached alias can continue advertising cached metadata instead of the refreshed definition. Remove obsolete MCP registry keys during refresh, or assign names from one stable set of server/tool identities.

Knowledge Base Used: Agent runtime and tool loop

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/mcp/tool.rs
Line: 125-128

Comment:
**Reconcile refresh aliases**

Cached startup registration can assign an unsuffixed name when it sees only one member of a normalized collision. Live discovery then assigns suffixed names after it sees both members, but leaves the original cached key registered. The same MCP tool family is therefore exposed under three names, and the obsolete cached alias can continue advertising cached metadata instead of the refreshed definition. Remove obsolete MCP registry keys during refresh, or assign names from one stable set of server/tool identities.

**Knowledge Base Used:** [Agent runtime and tool loop](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/agent-runtime-and-tool-loop.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +148 to +155
let suffix = format!("__{:08x}", stable_dispatch_hash(server, &tool.name));
let mut candidate = format!("{base}{suffix}");
let mut counter = 2u32;
while !used.insert(candidate.clone()) {
candidate = format!("{base}{suffix}_{counter}");
counter = counter.saturating_add(1);
}
names[index] = candidate;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 security Match policies to aliases

For normalized MCP-name collisions, eager registration and execution use generated suffixed aliases, but session allow/deny entries continue to match the historical unsuffixed name. A deny entry for the configured MCP capability therefore does not match either eager collision alias, allowing that external capability to execute despite being disabled. Resolve policy entries and generated aliases through the same MCP identity.

How this was verified: A session deny for the unsuffixed name allowed a suffixed collision alias to invoke the test external capability.

Knowledge Base Used:

Artifacts

Authored complete executed Rust test source: trex-artifacts/pr1259-mcp-suffixed-policy-01-before.rs (SHA-256 dfb6b42cd0e93e335324fb9f59dada609068ca56864c4ee9d5bced00d61d0024).

  • The complete regression test creates the normalized collision, applies the historical deny entry, and invokes the generated alias.

Execution output: trex-artifacts/pr1259-mcp-suffixed-policy-02-after.log (SHA-256 b9898c96afc2174a4888da8682b765a5b6c3f7ab5579e16bc8330103fc166cdb).

  • The output records exit code 0 and `test tool::tests::unsuffixed_mcp_deny_does_not_block_a_suffixed_collision_alias ... ok`, showing that the denied capability executed through the suffixed alias.

View artifacts

T-Rex Ran code and verified through T-Rex

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/jcode-base/src/mcp/tool.rs
Line: 148-155

Comment:
**Match policies to aliases**

For normalized MCP-name collisions, eager registration and execution use generated suffixed aliases, but session allow/deny entries continue to match the historical unsuffixed name. A deny entry for the configured MCP capability therefore does not match either eager collision alias, allowing that external capability to execute despite being disabled. Resolve policy entries and generated aliases through the same MCP identity.

**How this was verified:** A session deny for the unsuffixed name allowed a suffixed collision alias to invoke the test external capability.

**Knowledge Base Used:**
- [Agent runtime and tool loop](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/agent-runtime-and-tool-loop.md)
- [Agent execution and tooling](https://app.greptile.com/solo-systems/-/custom-context/knowledge-base/1jehuang/jcode/-/docs/agent-execution-and-tooling.md)

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

@yansigit
yansigit force-pushed the fix/mcp-collision-safe-dispatch branch from 795fd91 to beeb0e2 Compare September 16, 2026 01:47
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