Research

The state of MCP security

The Model Context Protocol turned “let an agent use a tool” into a one-line install. That’s the appeal and the risk: an MCP server runs with real access — files, network, shell, credentials — and until recently there was no way to tell a careful one from a careless one before you wired it in. So we graded all of them.

This covers the whole registry — 27,415 servers as of now, each scored A–F across six security checks — prompt-injection surface, supply-chain provenance, credential hygiene, permission scope, version-to-version drift, and transport/auth. Here is what the numbers say.

The distribution

Grade Servers Share
A 1,062 3.9%
B 16,583 60.5%
C 3,706 13.5%
D 689 2.5%
F 55 0.2%
insufficient 5,320 19.4%

Three things stand out.

Almost nothing earns an A. Only 3.9% of servers score high and expose enough to stand behind the grade. An A isn’t just “no problems found” — it requires enough of the server to be inspectable that a clean result actually means something. Most projects never clear that bar, not because they’re unsafe, but because there’s little to verify against.

The ecosystem is a wall of B. Six in ten servers land at B: clean on the checks that ran, but not enough was visible to certify an A. This is the honest majority — a manifest that looks fine, a package that installs without red flags, but a coverage ceiling that holds the grade. “Probably fine” is the modal state of MCP.

One in five can’t be graded at all. 19.4% come back insufficient — too little could be inspected to say anything. These aren’t failures; they’re blind spots. A remote server behind auth, a local server that needs a running process to reveal its tools. We treat that as a distinct state, never a silent pass — and closing it is where runtime analysis comes in (below).

The tail that matters

744 servers grade D or F — the ones where a check that ran actually failed. The patterns repeat:

  • Secrets as tool parameters. Servers that declare an api_key (or token, or password) as a tool input — meaning the model is expected to hold and pass the credential. Credentials belong in the environment, never in the model’s context.
  • Dynamic code execution. eval() and equivalent in tool handlers — arbitrary code paths reachable through a tool call.
  • Injection prose in tool descriptions. Imperatives aimed at the model sitting in the one field the model always reads — IMPORTANT: instructions, “ignore previous,” and in a few cases data-exfiltration URLs. A tool description is an attack surface, and some are already being used as one.

None of these require a sophisticated adversary. They’re in shipped, installable packages today.

Why so much is invisible

Three of the six checks want a server’s live surface — its actual tool list — which only exists when the server is running. Static analysis of a package and manifest can’t see it, which is why so much of the registry caps at B or insufficient.

So we pair static analysis with runtime probing: remote servers are connected to live, and local servers are booted inside an isolated sandbox to read their real tool inventory. That turns “we can’t see the tools, hold at insufficient” into a real grade for every server that boots cleanly. It’s how the blind spot shrinks over time — honestly, without ever guessing at what a server does.

When we get it wrong

Two maintainers disputed their grades shortly after we published, and both disputes were worth more than the grades were.

The first was our bug. Their servers used native MCP OAuth, where the endpoint allows an unauthenticated initialize handshake and only enforces auth on tool calls. Our probe saw a 200, concluded “no authentication required,” and failed their transport check. They were right, we were wrong, and the fix — reading the RFC 9728 discovery metadata as the authoritative signal — corrected not just their two servers but every properly-secured OAuth server in the registry. One maintainer reading their report carefully fixed a whole class of false positives.

The second was subtler. Their server is closed the way you’d want: anonymous requests get a 401, the OAuth metadata is well-formed, and the 37 tools behind it carry honest annotations, no credentials as parameters, no injection prose. We still can’t grade it, because three of our six checks need the tool surface and that surface is correctly locked. They are capped at insufficient by our blind spot, not their hygiene.

That second case is the honest limit of this whole exercise. A scanner that only sees what is public will systematically under-rate servers that are private for good reasons. We would rather say that in plain language than let a rating imply something about someone’s work that isn’t true — and we decline maintainer-offered credentials to fix it one server at a time, because grades that mean different things for different servers aren’t grades at all.

Those disputes prompted us to stop reading our own summary statistics and start reading the actual repositories behind the failing grades. That went badly, in a useful way. We were failing a server-management tool for referencing ~/.ssh, which is the entire point of a server-management tool. We were failing projects because an exec in one file and an HTTP call in a different file, on unrelated code paths, looked like “download and execute” if you squinted at a whole repository at once. We were reporting test fixtures as attack surface. Most embarrassingly, we failed a maintainer whose docstring said they deliberately refuse to pipe curl into bash for security reasons, and we failed a vulnerability scanner because its list of attack signatures contained the attack signatures it detects.

The common defect is worth naming, because it is not specific to us: a checker that greps text cannot tell an attack from a description of an attack. That bias lands hardest on exactly the people who deserve it least — security tooling and maintainers careful enough to document their reasoning. Fixing it moved the D/F tail down by roughly a fifth, which means a meaningful share of what this report would once have called dangerous was our error, not their code. The corrections are published in full on the rubric page.

What this means

The MCP ecosystem isn’t on fire — most servers are unremarkable, and the genuinely dangerous ones are a small minority. But “most are probably fine” is not a security posture, and the fifth you can’t see plus the 744 that fail are exactly the servers an agent shouldn’t install blind. The point of a grade isn’t to shame a maintainer; it’s to give you — and your agents — an honest signal before trust is extended.

Every server’s grade, the findings behind it, and the full methodology are public at mcpgrade.dev. Grades reflect facts, not intent, and every one is disputable.

Figures are read live from the register each time this page is built, so they track the current crawl rather than a frozen snapshot. Last built September 6, 2026.

← All research