a2ally™ Sign up

Answers · MCP security

How do I prevent MCP tool shadowing when an agent connects to several servers?

Last updated

TL;DR: Make every tool name unambiguous and keep servers from rewriting each other's tools. Shadowing comes in two forms. In a name collision, two servers offer a tool with the same name (search, send_email, query) and the client can't tell which one the model meant; the MCP specification only asks for tool names to be unique within a server, so across servers it's the client's job. Prefix every tool with the server it came from, as Claude Code does with mcp__<server>__<tool>, and reject or rename a duplicate when a server registers, instead of letting the last one loaded win. In the attack Invariant Labs described in April 2025, a malicious server's tool description carries instructions about another server's tool, such as sending every email to the attacker, so also review descriptions for text that mentions other tools, load only the servers a task needs, and keep confirmation on for tools that send, write or pay.

What should I check, step by step?

1. Namespace tool names with the server: <server>__<tool> or <server>.<tool>, using a server name you choose in your config, not one the server reports about itself.

  1. Reject duplicates at registration: when a newly added server offers a name another server already uses, fail with a clear error or require an explicit rename. Never silently replace the existing tool or pick one at random.
  2. Route calls by the qualified name and record which server handled each call, so a log line shows the server, the tool, the arguments and the result.
  3. Review descriptions for cross-tool instructions: text that mentions another tool or server, tells the model to change recipients, URLs or arguments elsewhere, or says not to tell the user. Show the full description, with invisible characters made visible.
  4. Load only the servers a task needs. Every connected server's descriptions sit in the same model context, so a server you don't need is one more place an instruction can come from.
  5. Keep human confirmation on for tools that send, write, pay or delete, and show the full arguments, including recipients and URLs, so an argument changed by another server's instructions is visible before it runs.
  6. Pin and re-check definitions: record a hash of each tool's name, description and schema at approval and compare on every tools/list, so a description that changes later to start shadowing is caught.

What are the two kinds of shadowing?

Name collision: the same tool name from two servers. Depending on the client, one silently replaces the other, the model picks between them, or the call goes to whichever loaded last. A malicious or careless server can win calls meant for a trusted one this way.

What does the protocol promise, and what doesn't it?

The MCP specification says tool names should be unique within a server, case-sensitive and limited to letters, digits, underscores, hyphens and dots. Nothing stops two servers from choosing the same name.

How do I ask a2ally about this (free, no key)?

Add the MCP server https://www.a2ally.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Does any other registered MCP server offer a tool with the same name as this one, and has this server's tool list changed since it was recorded?”

One-click setup

{
  "tool": "precall_check",
  "arguments": {
    "url": "<the MCP server's https URL>",
    "tool": "<the tool name, for example send_email>"
  }
}

a2ally compares tool names and definitions with the servers registered on it and scans for hidden text. It does not run your MCP client, rename your tools or block calls; namespacing and duplicate checks live in your client or gateway.

What are the sources?

Related

Not legal advice. JSON · Markdown · All answers

Get a free API key in one call

No card and no email needed. 100 free credits; the license and policy checks stay free without a key.

curl -s -X POST https://www.a2ally.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export A2ALLY_API_KEY=rs_...   # the api_key from the reply (shown once)
curl -s https://www.a2ally.com/v1/licenses/check -H "Authorization: Bearer $A2ALLY_API_KEY" \
  -H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'