← wormexagent.tech

// Research · 2026-09-12

The name is not the tool.

An MCP guard sits between an agent and a server and decides, call by call, what to inspect. To do that it has to answer one question first: is this the tool that moves money? Every implementation we know of — including ours — answers by matching the tool's name against a list.

The name belongs to the server. It is chosen by whoever wrote the tool, it is not standardised, and nothing requires it to resemble anyone else's. So we measured the vocabulary a name list actually has to cover. 590 distinct tools across 601 servers place or settle value, and 88.1% of those names appear on exactly one server.

We found this by testing our own published package and watching it forward a $5,000 order while printing a $100 cap.

The defect, in our own software

mcp-trade-guard caps what an AI trading agent can spend. It ships a list of order-tool names — place_order, buy, sell and three more — written against one broker's vocabulary and never verified against it, because the endpoint requires credentials we do not have. We sent nine plausible order-tool names through the real dispatch path with a $5,000 order and a $100 cap.

place_orderREFUSED
place_equity_order_v2REFUSED
rh_place_orderREFUSED
orders.createFORWARDED — $5,000, uncapped
createOrderFORWARDED — $5,000, uncapped
tradeFORWARDED — $5,000, uncapped
place_market_orderFORWARDED — $5,000, uncapped
submit_equity_orderFORWARDED — $5,000, uncapped
orderFORWARDED — $5,000, uncapped

Five of nine reached the broker untouched. The failure is not that the guard blocked the wrong thing — it is that the guard reported itself healthy while inspecting nothing. The startup banner still read per-order $100. A tool that says it is protecting you while passing every trade is worse than no tool, because it ends the search for one.

Note that place_equity_order_v2 and rh_place_order were caught — by substring matching, not by design. The same permissiveness that saved those two is what makes the approach unsound in the other direction, as the false positives below show.

What the registry actually contains

To find out how wide the vocabulary is, we enumerated the official MCP registry and asked every reachable remote server for its tool list.

Registry entries enumerated101,888
Remote endpoints extracted42,546
Endpoints probed42,500 (99.9%)
Servers that answered and served tools22,458
Endpoints behind authentication11,595 (27.3%)
Tools retrieved500,831
Distinct tool names78,857

The registry has grown roughly fourfold since our last scan — 101,888 entries against 26,844 in early September. The 42,500 endpoints probed here are 99.9% of those exposing a remote URL; 46 were not reached before the run ended, and the figures below are stated over what was probed rather than extrapolated.

590 tools that move money

A tool that moves money is not the same as a tool whose name contains a money word. We used three levels of rigour on the same corpus.

Name looks like it moves value776

A money noun (order, payment, checkout, transfer, swap) with an action verb and no read-only verb. This is the instrument an MCP guard actually runs.

Description confirms it acts590

Of those, the ones whose own description does not say they advise, simulate, quote, or return a link. 186 did — a 24.0% false-positive rate for the name alone.

Matched by our shipped name list79

What mcp-trade-guard 0.1.0 would actually have inspected. Everything else reached the broker with no cap applied.

The middle step is the one that matters for honesty. 24.0% of the names that look like they move value do not, and their own descriptions say so:

inspect_swap

“FREE. Deterministically inspect a narrow Base USDC-to-WETH swap against explicit policy. No wallet act…”

agent_can_pay

“Free pre-payment policy decision. Agents ask whether an x402 endpoint should be paid…”

rsu_sell_vs_hold

“Use this when someone asks whether to sell RSUs at vest or hold them…”

anime_buy_wait

“Return BUY, WAIT, WATCH or AVOID for an identified Japanese anime collectible…”

swap_case

“Swap case of each character.”

swap_case swaps the case of characters in a string. A keyword scanner has no way to know that, which is the same result we published when measuring tool poisoning: the name is a weak signal, and a scanner that trusts it over-reports.

The vocabulary has no centre

If the 590 names clustered around a few conventions, a list would work. They do not. 520 of them (88.1%) appear on exactly one server, and covering 80% of server-tool pairs takes 418 distinct names. These all place or settle value, and none of them is matched by a list built from place_order:

create_checkout

Create the order and get a hosted payment link for a chosen offer.

purchase_confirm_intent

Confirm a purchase intent — re-validates price within 1p tolerance.

complete_checkout

Complete checkout with payment and start site provisioning.

services_purchase

Buy a fixed-price service in one call: validates the scope, computes…

batch_purchase

Buy up to 20 SPECIFIC listed ENS names in a SINGLE Seaport transaction.

write_account_withdraw

Withdraw from an account.

transfer_out_domain

Prepare a domain to transfer to another registrar.

place_spot_order

Place a spot order.

There is no shared root to anchor on. create_checkout, purchase_confirm_intent and write_account_withdraw describe the same class of action in three unrelated vocabularies. One is in Korean. A guard maintained by watching the ecosystem is a guard permanently one server behind it.

The protocol already has an answer, and it is not used well

MCP defines annotations, including readOnlyHint — a machine-readable declaration that a tool does not modify anything. 61.8% of tools carry annotations and 220,853 declare readOnlyHint.

That is genuinely useful, and it is the signal we now prefer over the name. But it is a hint, supplied by the same party that chose the name, and a hostile server can set it freely. It narrows what a guard must inspect; it cannot be what a guard trusts.

What we changed

The fix is not a longer list. A longer list fails the same way, later. What was missing was any signal that the guard had matched nothing — the silence looked identical to safety.

Since 0.2.0 the proxy reads the server's own tools/list response, reconciles it against the vocabulary it is running with, and prints every advertised tool that looks like it moves money but would not be intercepted. If it matches none of them, it exits rather than run:

!! UNGUARDED TOOLS — these move money and are NOT capped:
     orders.create
   Add them: MCP_ORDER_TOOLS="orders.create"

!! This guard matched NONE of the 2 tools this server advertises.
   Refusing to run as a guard that guards nothing.
mcp-trade-guard 0.2.0, startup against a mismatched server

This does not solve name-based interception. It converts a silent failure into a loud one, which is the most a guard can honestly do about a vocabulary it does not control. The operator still has to supply the real names — but now they are told, at startup, that they must.

Limits

The classifier is ours, and it is imperfect. Tier 1 is a regex over names; tier 2 reads descriptions for advisory language. Both will misjudge some tools. We report the 24.0% false-positive rate between them precisely so the tier-1 number is not mistaken for a measurement.

27.3% of endpoints require authentication and were not enumerated. Money-moving tools are more likely to sit behind auth, so 590 is a floor, not a total.

The scan covered 99.9% of remote endpoints, not all of them. 46 endpoints were not reached. Nothing here is extrapolated to them.

We can only prove this on software we control. We believe name matching is how other MCP guards decide what to inspect, because it is the only signal available before a call is forwarded. We have not audited anyone else's, and we are not claiming a specific competitor is vulnerable.

Repeating this

Enumerate registry.modelcontextprotocol.io/v0/servers, take every remotes[].url, and send each one initialize then tools/list over streamable HTTP. Two details cost us time: the response may arrive as an SSE frame rather than JSON even for a plain POST, and the session id returned by initialize must be echoed on the next request or the listing comes back empty.

The guard probe is nine lines against the published package: npm i mcp-trade-guard, construct a guard with a $100 cap, and call isOrderTool with each name before deciding whether guardOrder ever runs. Version 0.1.0 reproduces the five forwards; 0.2.0 reports them.