This is a copy of an article first published on AgentShortlist, where the tables and cards live.
On 18 and 19 October 2026 we read the documentation and source of 25 MCP servers and wrote down, line by line, what each one says and does. Every line on a verified card carries a source and a review date. This article adds the 25 cards up.
It isn't a ranking or a security audit. It records what the projects document and what their code shows. Where we couldn't check something, the card says "not verified", and that turned out to be a big part of the result.
Read-only modes: documented in 5 of 25
Plenty of these servers can change data. Whether you can run them read-only is the question that moves the risk the most.
| Documented read-only mode or recipe | None found in the docs and source we read | Not verified |
|---|---|---|
| GitHub MCP server, n8n-mcp, MongoDB MCP server, DBHub, pREST | Playwright MCP, Desktop Commander, MySQL MCP server, Chrome DevTools MCP, Blender MCP, IDA Pro MCP, Talk to Figma MCP | The other 13 |
"None found" means we didn't find one in what we read, not that none exists. For browser and design tools a read-only mode isn't really the right idea anyway. For the MySQL server it is, and there isn't one, so the limit has to live in the database user.
Telemetry: three on by default, and mostly silence
Chrome DevTools MCP collects usage statistics by default, and its performance tools can send trace URLs to Google's CrUX API. The Apify MCP server collects telemetry about tool calls by default, with Sentry error tracking. Blender MCP collects a minimal anonymous usage record by default; prompts, generated code and scene data are collected only if you opt in.
All three document how to turn it off. Two more projects, codebase-memory-mcp and context-mode, say they send nothing; we didn't verify that claim. For the other 20 we found no statement either way in the docs we read. What stood out to us is how few projects say anything about what leaves your machine.
Advisories: 40 published, none on the latest versions we checked
We matched the cards to packages on npm, PyPI and Go, but only when we could confirm the package belongs to the same repository. That gave 24 packages across 20 of the 25 cards, with 40 published advisories on OSV.dev in total. Half of those 20 cards have none.
We could check the latest version for 23 of the 24 packages, the npm and PyPI ones, and none of them is affected by a published advisory as of 19 October. We didn't check the latest version of the one Go module, the GitHub MCP server.
| Card | Published advisories |
|---|---|
| n8n-mcp | 11 |
| MCP reference servers (filesystem and git packages) | 10 |
| GitHub MCP server | 4 |
| MySQL MCP server | 4 |
| Chrome DevTools MCP, DBHub, Apify, Desktop Commander and Serena | 2 each |
Read the table with care. More advisories often just means a project is more used and more closely examined, not that it's worse. A package with none isn't proven safe. The practical takeaway is smaller: older versions are affected, so pin a version you've actually checked.
Upkeep: the issue backlogs differ a lot
Open issues created before 20 July 2026, counted on 19 October:
| Server | Open issues | Of which older than that date |
|---|---|---|
| Playwright MCP | 4 | 0 |
| n8n-mcp | 64 | 16 |
| Chrome DevTools MCP | 173 | 46 |
| GitHub MCP server | 380 | 112 |
| Desktop Commander | 311 | 126 |
| MySQL MCP server | 6 | 1 |
A busy project collects more issues. The number tells you where to look, not what to conclude.
Projects that point somewhere else
A few READMEs say another option fits better, which is useful to read before you install:
- The Playwright MCP README recommends Playwright CLI with Skills for coding agents, because it avoids loading large tool schemas and page snapshots into the model's context.
- The awslabs/mcp README recommends the Agent Toolkit for AWS for production software built with coding agents, and calls it the successor to the servers in that repository.
- The IDA Pro MCP README opens by recommending the official Hex-Rays server instead.
- The MCP reference servers README says they are reference implementations, not production-ready solutions.
What to do with this
If a server can change data, look for its read-only option before anything else. Search the README for telemetry and set the opt-out if you want it. Pin versions and check them against advisories; the config checker does that in your browser. And treat "not stated" as a question to ask the maintainers, not as a yes.
Every card, with its sources, is on the verified cards page. You can compare servers in the live comparator or filter by what you need in the picker. If a card has a mistake, send us the primary source and we'll correct it.

Top comments (17)
The read-only finding is the most actionable part: 5 of 25 documented is low, but the MySQL example shows the fix is not always in the MCP layer itself. Enforcing the limit in the underlying credential, like a read-only database user or a narrowly scoped token, survives a server update or a misconfigured flag in a way that a README recipe does not. Did any of the five documented read-only modes enforce it that way at the credential level, rather than just filtering tools inside the server process?
Closest is DBHub: its README says it checks statements and runs them under the database's own read-only mechanism, so enforcement sits at the engine, not only in the server. n8n-mcp suggests a read-only API key as defence in depth. GitHub and MongoDB filter tools inside the server. That's what the docs say; we haven't tested any of it. Your point stands: a read-only credential is the layer that survives a bad flag or an update.
One layer below the read-only credential: who holds it. In the sandbox I run, the records key exists as a Secret in two namespaces, the service's and the MCP server's, and admission refuses any agent pod that references a Secret in a volume or its environment.
The agent's one credential is a ServiceAccount token for the server's audience, valid ten minutes, bound to its pod and refused by the API server. A fooled agent can still misuse a tool it was granted. It can't carry the key out.
That is a useful extra layer: a read-only credential limits what a leaked key can do, but who holds it decides whether it can leak at all. Short-lived, pod-bound tokens and refusing secrets in the agent’s environment is a stronger setup than anything the cards we’ve read describe. Most documented setups put the credential in an env var in the client config. We can’t verify your sandbox from here, but “where the credential lives” is a good axis to add next to “what it allows”.
You can check it from there: the refusal is a test in a public repo, not a description of one.
On every change, the e2e on a fresh kind cluster applies twelve planted manifests, among them a Secret in the environment, a Secret as a volume, a token with no audience and a token valid for a day. Each must be refused for the reason it names, and two compliant controls must be admitted, so the check can't pass by refusing everything.
The rules are ValidatingAdmissionPolicies, so the API server enforces them itself, with no webhook that can be down. kubernetes-ai-agent-sandbox
Thanks, that is the distinction I was missing: a refusal that is a test, not a description of one. I read the repo’s README and tree: two ValidatingAdmissionPolicies in manifests/admission.yaml, and an e2e on kind with twelve planted manifests, each refusal paired with compliant controls so it can’t pass by refusing everything. I haven’t run it or read the CEL rules, so on our side I would record it as “documented by the project, not tested by us”, with the link. The axis I’d add to our cards is yours: where the credential lives, separate from what it allows. If I got any detail wrong, tell me and I’ll fix it.
Those details are right, and the credential axis belongs on the cards.
To move it from documented to tested, it is two commands on a laptop with kind and Docker:
One trap first:
make testfails on purpose on a machine with swap, because under gVisor a pod's memory limit then becomes its RAM plus unlimited swap. Docker Desktop has swap, so run it on a Linux host with swap off, as the CI runner does.Thanks for checking the details, and for the swap warning: I’d have hit that on Docker Desktop. I’ll add the credential axis to our cards as “documented by the project” for now, with a link. I won’t run the kind test yet, since it needs a Linux host with swap off and I’d rather do that on an isolated machine than on a laptop. One question: may I cite your repo as an example of a refusal that is a test, not a description, in a future post? I won’t name you or the project unless you say it’s fine.
Yes, cite it, and name the project and me; it is public so that it can be checked. Link a tag rather than main, so the refusal your readers check is the one you read: v1.2.0.
Until you run it yourself, the e2e workflow is a record you can point to. It runs on main every night on a public GitHub runner, and its log shows
swapoff -abefore the test starts.Thanks, Vladimir. With your permission I will cite it by name. We have not run kubernetes-ai-agent-sandbox ourselves, so I will describe it as a record, not as something we tested: the repository's e2e workflow runs on a schedule on a public GitHub runner, and at tag v1.2.0 it runs
sudo swapoff -aand prints /proc/swaps before and after, as you said. The last three scheduled runs on main passed. I will link the tag: github.com/heyvaldemar/kubernetes-...That description is accurate, and the permission stands.
Today the nightly runs and v1.2.0 are the same commit,
7a13a20. The schedule checks out main, so after the next merge "the last three runs passed" describes main, not the tag you link.If the citation has to stay true, point at the push run on the tagged commit, from 20 October, instead of the nightly list. A tag stays put. A branch moves.
A correction to my DBHub answer: since then we matched a published advisory to its card (GHSA-mwwr-p57h-56pf). It says DBHub’s read-only mode did not prevent database writes before 0.22.6. So “the README says it enforces at the engine” did not hold for those versions. It’s the same lesson as your credential point: documented is not enforced, and a read-only database user would have covered it.
The pinning advice runs into how most people install these. A lot of README config snippets start the server with
npx -y package-nameand no version, so every client restart pulls whatever is latest. A version you checked against OSV only stays checked if the version sits in the client config itself.On telemetry, "not stated" could become a measured line on the card: run each server once with outbound traffic logged and record which hosts it actually contacts. That would move several of the 20 silent ones from unknown to observed, without waiting on the maintainers.
Agreed on pinning: a version only stays checked if it's in the client config, which is why the checker flags
npxwithout a version. The traffic-log idea is good. I'd label it as observed on one version with the tools actually exercised, so it doesn't claim more than it saw. Starting with the silent ones makes sense.The “not verified” category matters: silence about telemetry or read-only mode is not evidence of absence. For the server cards, I'd separate a documented switch, source-level enforcement, and a negative write probe under a constrained credential. Your MySQL example shows the real boundary may be the database role, not the MCP server. Are runnable capability checks on your roadmap?
Yes, that's the next step: a documented switch, what the source enforces, and a write attempt under a constrained credential, each line labelled by how it was learned. We'll start with read-only on a handful of servers and publish the failures too. Not started yet, so no date.
the readme says read-only, the readme also says a lot of things