DEV Community

Dmitrii P
Dmitrii P

Posted on

What 15,341 MCP servers can do: a static scan of the official registry

Disclosure: I wrote SkillTotal, the open-source (Apache-2.0) scanner used for this survey. The method, the harness and the aggregate data are public, and the links are at the end.

When an agent calls a tool from an MCP server, it inherits whatever that server's code can do. I wanted to know what that looks like across the whole official MCP registry, so I scanned it. Below is what I measured, how I measured it, and where the numbers stop being useful.

The population

The registry API at registry.modelcontextprotocol.io/v0/servers lists one entry per published version, so a raw count overstates the ecosystem. On the 2026-08-16 snapshot, 73,460 entries collapsed to 17,535 distinct components once I deduplicated them by source.

Of those, 15,341 were scanned (87.5%). The other 2,194 were counted, by reason:

Why a component was not scanned Components Share
repository or package no longer reachable 1,637 9.3%
slower than the 60 s time bound 302 1.7%
larger than the 50 MB size bound 188 1.1%
other 65 0.4%
access denied 2 0.0%

Coverage differs a lot by where a component lives:

Ecosystem Scanned Listed Coverage
npm 6,967 7,152 97.4%
PyPI 2,985 3,071 97.2%
git 5,389 7,312 73.7%

Most of the gap is on the git side. Across the whole population, nearly one component in ten (9.3%) points at a repository or package that is gone or private.

Method

The scan is deterministic static analysis: regex rules plus Python AST analysis. Each component's code is downloaded and read, never executed, and no LLM is involved. The same code through the same engine on the same Python version gives the same findings.

  • Engine: SkillTotal 0.49.0, rulesets 56 to 59. The run finished on 2026-09-19.
  • Bounds: 50 MB per fetch and 60 seconds of wall clock per component.
  • Version read: each component's latest release or default branch at the time of the run. A re-run today reads newer code.
  • Rounding: shares are rounded with the largest-remainder method so each table sums to exactly 100%. A share can sit up to 0.1 point from its unrounded value; the counts beside it are exact.

Two caveats about the run.

First, it mixes rulesets. 11,774 components were scanned with ruleset 56, 9 with 57, 30 with 58 and 3,528 with 59. Midway through, a hand-checked sample showed that most Node.js shell-execution hits from ruleset 58 were regular-expression and database .exec() calls, not process spawns. Ruleset 59 requires an actual child_process import, and every component that carried a shell finding was re-scanned. The shell-execution share fell from 30.3% to 21.1%. The other capability figures moved by 1 to 2 points, because 85 components that scanned on the first pass ran past a bound on the re-scan and left the scanned set.

Second, the rules were tuned on this population. Before this run I read every hit of the attack-shape rules from earlier runs by hand and fixed the false positives I found (rulesets 50 and 52). The attack-shape figure below is measured after that tuning.

What these servers can do

A capability is something the shipped code is able to do. It is not a vulnerability; a server that runs shell commands may exist to run shell commands. The numbers describe the reach an agent picks up when it hands work to one of these components.

Capability Components Share of 15,341
exposes MCP tools 13,524 88.2%
can reach the network 9,938 64.8%
reads the filesystem 3,828 25.0%
can execute shell commands 3,230 21.1%
writes the filesystem 3,206 20.9%
runs code at install time 1,134 7.4%
uses delegated (OAuth/OIDC) authentication 895 5.8%
evaluates code dynamically 475 3.1%
uses a scoped, short-lived identity 81 0.5%

A few things stood out to me.

About two in three components can make outbound network requests. If you worry about data leaving through an agent, assume the server can send it.

Roughly one in five can spawn a shell, and one in five can write to disk. Both are expected for build tools, file managers and dev-environment servers. They are also what turns a prompt injection into code execution or persistence, so this is the number I'd want a reviewer to see before approving a server.

1,134 components (7.4%) run code at install time: npm install/prepare scripts or Python install hooks. That code runs before anyone has called a tool or read a tool description.

Only 81 components (0.5%) showed a scoped, short-lived identity pattern, against 895 (5.8%) with delegated OAuth/OIDC authentication. Both counts cover only the patterns the rules recognize; I didn't measure how the rest authenticate.

Capabilities vs risk

SkillTotal scores two kinds of findings: risky constructs, and matches against rules for known attack shapes (decode-and-execute, hidden Unicode, instructions planted for the agent, auto-run hooks, credentials sent out). A capability on its own adds zero to the score, and so does a secret a component ships (that is reported separately as an exposure). That is why the risk table is much flatter than the capability table:

Risk level Components Share
low 15,225 99.3%
medium 90 0.6%
high 22 0.1%
critical 4 <0.1%

With rulesets 56 to 59, no scanned component matched an attack-shape rule. That zero has three limits. The rules were tuned on this population first. A static scan can't see code fetched at run time, or an attack that no rule describes yet. And later rulesets read more than these did (the current one is 76, and it reads MCP launch commands and nested tool-schema fields that 56 to 59 did not), so the zero holds for this run only. It is not a statement that any component is safe.

OWASP Agentic Skills Top 10

Each rule that a static check can fairly cover carries an OWASP Agentic Skills Top 10 class. The two classes most present in this population:

  • AST03 Over-Privileged Skills: 1,905 components (12.4%)
  • AST02 Supply Chain Compromise: 1,216 components (7.9%)

A class count is a count of components carrying at least one finding from a rule mapped to that class. It is not a verdict. The full class table, including the classes with fewer hits, is in the report.

The shape of the registry

Of the 17,535 components, 7,312 are hosted on GitHub across 4,325 owners. One owner accounts for 18.0% of those, and the ten largest owners for 29.1%, so the git side is more concentrated than the raw count suggests.

What this survey does not claim

  • No component is named. These are population statistics.
  • No claim that anything is safe, for the reasons in the risk section.
  • No runtime behavior. Code fetched at run time is out of sight. Behavior is analyzed only in Python, JavaScript/TypeScript and shell; components in other languages are analyzed only in part.

How to check or reproduce it

Everything that produced these numbers is in the repository:

A small smoke run:

git clone https://github.com/pezhik/skilltotal
cd skilltotal
pip install -e ".[dev]"
python tests/manual_eval/survey_registry.py --out survey.jsonl --limit 50
Enter fullscreen mode Exit fullscreen mode

A re-run uses the current rules and reads the current code, so expect different numbers from mine.

How far to trust the scanner

The detection rules have their own public benchmark: a labeled corpus of 125 synthetic attack samples and 89 benign look-alikes across 38 attack techniques. On ruleset 76 every attack sample is flagged and no benign one is. I built that corpus myself, so a perfect score there guards against regressions; it says nothing about attacks the corpus doesn't contain. The harness accepts any folder of positive/ and negative/ samples (--corpus), so you can run it on your own set: https://www.skilltotal.ai/detection-benchmark

If you find a wrong number, or a way past a rule, I'd like to hear about it in the comments or as a GitHub issue.

Top comments (0)