A tool the server hid was still there to call
A check that reads a server's advertised tools misses the ones it does not advertise, unless it asks whether they answer anyway.
When a client connects to a Model Context Protocol server, it asks what tools are available and trusts the answer. The server returns a list, and everything the client is expected to be able to do is on it. That list is a presentation. It says what the server chooses to show. Whether a given tool actually runs is decided somewhere else, on the path that executes a call, not on the path that builds the list.
Those two paths are supposed to agree. When a server means to withhold a tool, it should both leave the tool off the list and refuse to run it. The trouble starts when the withholding is done in only one place. If a tool is filtered out of the advertised list but the call handler was never told to refuse it, the two surfaces come apart. The tool is invisible and fully operational at the same time. A client that only reads the list would never know the tool exists; a client that guesses its name, or a compromised one that already knows, can call it and get an answer.
An auditor that trusts the list inherits the same blindness. It can confirm that the advertised tools behave, check their inputs, note their permissions, and report that everything on the list is in order, and the tool that matters was never on the list to be checked. The instrument is watching the surface where the behaviour does not happen.
The audit here does the plain thing that a list-reading checker will not: it takes the tools the server does not advertise and asks them to run. On a server built to exhibit the bug, they run. What the server declares and what the server will actually do are different objects, and the distance between them is the entire finding.
That distance is drawn out in the figure below: the advertised set and the reachable set, with the tools that appear in one but not the other.
The honesty of the tool is in what it refuses to claim. The vulnerability class is real and catalogued as GHSA-cr22-wjx7-2w6m, but the counts here come from a mock server written to show the shape of the failure, not from a survey of servers in the wild, so the result is a demonstration of a mechanism, not a measurement of a population. And the audit leans the safe way: a denial it does not recognise is reported as inconclusive, never quietly counted as a pass. It would rather admit it cannot tell than claim a server is safe it has not really checked.
This is the wrong-observation-boundary failure in one concrete instance: the declared surface is not the surface where authorisation has to hold, and a check that reads only the declared surface can pass while the real one is open. It sits in the security-engineering theme of a wider programme about whether checks establish what they appear to. Read where it fits in the programme.
declared (tools/list)
- echo
reachable (tools/call)
- echo
- ping
- whoami
The two highlighted tools are hidden from the listing yet still callable.