Skip to content

mcp_schema: two servers exposing the same tool name is invisible, which is how shadowing works #153

Description

@blitzcrieg1

The gap

SchemaStore.servers is a dict keyed by server name, and digest() walks it per server:

for name, rec in self.servers.items():
    parts.append(f"{server_id(name)}:{rec.fingerprint}")

There is no cross-server view. Two servers both exposing send_email produce two independent baselines, both quiet, and nothing anywhere compares them.

Tool shadowing is a named MCP attack class: a malicious server registers a tool with the same name as a legitimate one, and the client resolves to the wrong one. That is exactly what this arrangement cannot see.

The data already exists

tool_id is a hash of the name alone, so a collision is already visible in what is stored:

tool_id('send_email') from server A == 953f754fd010
tool_id('send_email') from server B == 953f754fd010

Identical ids, two servers. Nothing looks. The per-tool digests added in 0.6.0 made this detectable without intending to.

Proposal

At observation time, check whether any tool_id in the incoming listing already exists under a different server in the store, and emit that as its own signal.

Two cases with different meanings:

  • A new server claims a tool name an existing server already had. The interesting direction, and the shadowing shape.
  • Two servers have always both had it. Often benign. Generic names like search, read and list will collide constantly across unrelated servers.

The second case is why this needs care rather than a flat rule. A first pass that fires on every search collision is the same false-positive class as #44, and would be worse because collisions on common names are the normal state of a multi-server setup.

Ordering, and the arrival of a new claimant on an established name, is probably the discriminator. That is the same reasoning the sequence rules already use: the individual facts are ordinary, the order is the finding.

Open questions

  • Should a collision be reported at all when both servers are long-established, or only on the transition?
  • Does the client's own resolution order matter, and can the proxy see it? If the client silently prefers one server, which tool actually ran is a separate question from which were offered.
  • Does this belong in the digest, or as a separate event? Putting it in the digest would make an unrelated server's arrival move another server's fingerprint, which sounds wrong.

Not blocked by the freeze

core/diagnostics/mcp_schema.py is outside the ruleset fingerprint.

Related

Cross-server attacks and tool shadowing are documented MCP attack classes alongside tool poisoning and rug pulls. The rug pull is covered here. Shadowing is not.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions