Every AI project eventually hits the same question, and it never comes from engineering:
"What can your agent actually touch?"
Usually it is a client, an auditor, or whoever signs the risk acceptance. And the honest answer is often "let me get back to you", because the answer is spread across a dozen config files and a tool list nobody ever wrote down.
I run several agent harnesses on one laptop. So I measured it, on my own machine, and I am writing down the procedure — including the parts that did not work.
1. Enumerate, don't describe
The install instructions are the source of truth, and almost all of them say npx -y <name>.
On my machine: 16 MCP servers, of which 12 are launched through a package manager, and all 12 are unpinned.
The split matters:
-
4 say
@latestoutright —chrome-devtools-mcp@latest,@playwright/mcp@latest,@upstash/context7-mcp@latest,superpowers-mcp@latest -
8 have no version string at all —
firecrawl-mcp,mcp-server-kubernetes,gitee-mcp-server,@edjl/docker-mcp, and so on
The second group surprised me more. @latest at least reads like a moving target. A bare package name reads like a stable dependency, and it resolves exactly the same way: whatever landed on the registry this morning.
2. Ask the servers what they expose
Listing tools is a separate step, because it starts processes. That distinction matters: a config parse executes nothing, an enumeration executes the servers.
That step produced 164 tools across 12 servers. The distribution was not what I expected: chrome-devtools 29, firecrawl 25, playwright 25, kubernetes 23, gitee 20, filesystem 14, docker 10, memory 9.
Kubernetes and Docker are what made the number real for me. A browser automation tool with 25 tools is one thing. kubectl verbs are another.
3. Compile a policy, with reasons
I compiled the inventory into a three-state policy: allow 43, approve 106, deny 15, plus 24 that came back as "the capability cannot be inferred" — those get flagged for a human instead of decided.
The 15 denials are the obvious ones: kubectl_delete, uninstall_helm_chart, docker_remove, three delete_* tools in the memory server, node_repl js, js_reset, playwright browser_drop.
The part that makes this worth writing about: the classifier is a keyword matcher, so it is wrong in both directions. It denied resolve-library-id because its description matched the word "format". It denied sequentialthinking because the description contained "clear".
Both stay visible in the output. Every verdict prints the phrase it matched. A policy that hides its reasons cannot be corrected — including by the person who wrote it.
4. Hand over something someone else can check
The last step is the one people skip, because it feels like extra work: shipping a delivery that the recipient verifies without trusting the sender.
Hashes are recomputed on the reader's side — in a browser, or with a standard-library script. Not because I expect to be distrusted, but because "trust me" does not survive being asked a second time by someone who does not know you.
What I am not claiming
- Unpinned is not the same as compromised. The point is narrower: you cannot tell the difference after the fact, because the artifact you approved is not the artifact that runs next time.
- Name and description matching says nothing about implementation. It ranks what to review; it does not review it.
- Four of my 16 servers could not be enumerated at all (one exits on initialize, three are remote and time out). I still do not know what they expose. Unknown is not safe, and nothing in my setup was telling me the difference.
Do it on your own machine
Two numbers are enough to start: how many servers, how many pinned. If the second one is zero, you are in the same place I was this morning.
I wrote the tool I used for this — read-only, and it executes nothing until you explicitly ask it to enumerate: https://podcloud.dlszjr.com
Top comments (1)
The inventory becomes even more actionable when capability is split into configured, advertised, and exercisable. A listed tool may lack usable credentials, while one broadly scoped tool can carry more risk than twenty read-only ones. I’d snapshot the package digest, server config hash, tool schema, credential scope, and allow/approve/deny reason so the next scan produces a meaningful diff. How are you handling a tool whose name stays stable while its schema or implementation changes?