Last month Leslie Kadenge published something rare: a security review of 13 production MCP servers, all shipped by funded companies and actively maintained. Not toy projects. Not proofs of concept. Real servers handling real traffic.

He went in expecting the picture the security-tooling vendors paint: unguarded destructive tools, path traversal everywhere, prompt injection straight through to the model. That's not what he found.

The ecosystem hardened faster than the fear narrative admits

The single most encouraging result: ToolAnnotations — the machine-readable hints (readOnlyHint, destructiveHint, openWorldHint) that let a client decide which tools to gate — have gone from rare to expected among serious servers.

One cost-management server registers 127 tools through a wrapper whose TypeScript type makes all three hints non-optional. You physically cannot add a tool to that codebase without declaring whether it is read-only, destructive, and open-world. 57 of those tools mutate state; not one is missing a hint. Not discipline — a type that refuses to compile without the answer.

A project-management server marks destructive=True on every delete action and ships a conformance test that fails the build if a delete is ever added without it. The guarantee is not "we remembered"; it is "CI won't let us forget." A notebook server that literally executes code annotates all eighteen of its tools. An enterprise productivity server derives a read-only mode by filtering the exposed tool list on the readOnlyHint, so a locked-down deployment can't call a mutating tool because the mutating tools aren't offered.

The lesson: annotations are table stakes now. Shipping without them no longer reads as "early" — it reads as "behind."

Path handling is mostly done right

The classic MCP file-write bug: a tool takes a caller-supplied path, resolves it, and writes — without checking the result stayed inside an allowed directory. It's easy to get subtly wrong.

Most servers that touch the filesystem now get it right. The best example: a mobile-automation server whose screenshot tool resolves symlinks first and then checks the resolved path against an allowlist of roots — case-insensitively on Windows, which is the detail people miss. Resolve, then check; check the destination, not the link.

Where the gaps actually are

They exist, but they're specific, and they cluster in three places.

1. The README–reality gap

A server states a safety property in prose — "read only by default," "hard to misuse" — and it's true today but nothing enforces it. No annotation declares it, so a client can't act on it; and sometimes a full write path sits in the codebase, unreachable but written and waiting, one wired-up call away from making the README false.

What's missing is the machine check that keeps the promise real after the next contributor, or the next fork.

2. Untrusted content returned unmarked

Servers whose purpose is reading the open web frequently return page content into the model's context with nothing marking it as untrusted. That's the product working as designed; the gap is that openWorldHint exists to say "what comes back is not under your user's control" and it often goes undeclared. On a tool that reads arbitrary pages, that hint is the difference between a host that treats the result as data and one that treats it as instructions.

3. The occasional real hole

One server let a caller choose the output path for a written result with no containment at all — a write-anywhere primitive reachable through ordinary use, triggered by content the server was designed to read. That one was reported privately. It was the exception across thirteen servers, not the rule.

The gap worth money

Funded MCP servers are now, overwhelmingly, designed securely. Almost none of them can show that they are.

The teams have internalised the practices — annotations, allowlists, least-privilege, read-only modes. What they lack, nearly universally, is independent verification: a second pair of eyes that read the whole thing and wrote down what they found, with a test behind each finding. Their own CI proves their own assumptions. It cannot prove the assumption they didn't think to make.

That gap is not a criticism. It's the normal state of any codebase that grew faster than anyone had time to audit it. And it's exactly the thing an enterprise customer's security questionnaire asks about — not "is your code good?" but "who checked it, and can I see what they found?"

Why automated scanners aren't the answer

While Leslie was writing this, an automated index that scores public MCP servers for trust graded one of his own servers as deficient — its three tools flagged as "missing" their annotation hints — on a server that in fact declares all four hints, correctly, on every tool, including the destructive one.

The scanner had matched the protocol's camelCase hint names (readOnlyHint) against the source, which, being Python, spells them in snake_case (read_only_hint) exactly as the SDK requires, and reported them absent. A tool that cannot reliably tell whether a safeguard is even present is in no position to tell you whether it is correct.

That's the whole difference between scanning a server and reading one — and, for the person whose customer is asking, it's the difference worth paying for.

What this means for you

If you're building an MCP server: the bar has risen. Annotations and correct path handling are no longer things that make you look careful; their absence is a thing that makes you look behind. The good news: the practices are now well established enough to copy from the servers named in Leslie's review.

If you sell software with an MCP server in it: your code is probably fine. The question you will be asked is not whether it is fine, but whether anyone independent can say so. Right now, for almost everyone, the honest answer is no — and that answer is worth closing, because the person asking is usually a customer you're trying to win.

If you're buying or integrating: don't trust a badge. Ask for the readout. If the vendor can't produce a document that names the tools, names the findings, and attaches a test to each one — keep looking.


If this was useful, you can support my open-source work on Ko-fi or check out my services.