15,465 MCP Servers, 0 Governance: What OX Security's Audit Means for Your Agent Stack

·11 min read·Evergreen Tools Team

Enterprise security teams spent a decade building governance around the cloud: data residency requirements, Zero Trust boundaries, granular IAM policies, and continuous supply-chain audits. All of it rested on one comfortable assumption, that you roughly know where your data lands and who operates the infrastructure it touches. On September 24, 2026, OX Security published a study titled 15,465 MCP Servers, 0 Governance, and the finding is that the Model Context Protocol is routing around most of that governance. MCP lets AI agents, developer workstations, and automated pipelines connect to third-party servers on demand. It is genuinely useful, and in many cases it is shadow AI infrastructure.

1. What the Research Measured

The method was direct. OX Security Research collected 15,465 published MCP servers across three public registries, mcp-official-registry, cline-marketplace, and github-mcp-registry, then narrowed the dataset to 5,095 unique hostnames for infrastructure analysis. MCP is an open standard Anthropic introduced in November 2024 that lets AI applications connect to external tools and data sources. But as OX points out, MCP itself does not establish where a server should run, who should operate it, what data it may receive, or whether the code deployed behind a live server matches its published source. Those decisions largely fall to whoever connects. Research lead Moshe Siman Tov Bustan put it well: you can inspect the code, but the server might contain a different version, and you do not know who controls it, what data they collect, or what they can change in future updates.

# Finding the MCP servers you are actually running is step one.
# Every config file, every IDE, every pipeline is a candidate.

import json, pathlib

CONFIG_HINTS = [".mcp.json", "mcp.json", ".cursor/mcp.json",
                ".vscode/mcp.json", "claude_desktop_config.json"]

def inventory(root: pathlib.Path):
    found = []
    for path in root.rglob("*"):
        if path.name in [p.split("/")[-1] for p in CONFIG_HINTS] and path.is_file():
            try:
                cfg = json.loads(path.read_text())
            except Exception:
                continue
            for name, spec in (cfg.get("mcpServers") or {}).items():
                found.append({
                    "server": name,
                    "command": spec.get("command"),
                    "url": spec.get("url"),
                    "declared_in": str(path.relative_to(root)),
                })
    return found

for row in inventory(pathlib.Path.home()):
    print(row["server"], row["url"] or row["command"])
Server racks in a data centre

MCP lets agents connect to third-party servers on demand

2. Finding One: Data Residency Blind Spots

The first finding is a data residency blind spot. Of 5,095 hostnames, 15.6 percent, or 796, resolved to infrastructure outside the United States, including 19 in China and 18 in Russia. The important part is that MCP has no protocol-level concept of geographic region at all. An enterprise can enforce strict residency controls on its own cloud workloads while its AI agents connect freely to servers sitting outside those same controls. Code sample 3 turns that invisible exposure into a queryable fact: resolve every hostname, check registration details, and flag recently created, cheap domains for review.

// Governance gate 1: egress is deny-by-default. MCP has no protocol-level
// concept of geography, so the boundary has to live in your network.

const mcpEgressPolicy = {
  default: "deny",
  rules: [
    { server: "github-mcp", allow: ["api.github.com"], ports: [443] },
    { server: "postgres-mcp", allow: ["db.internal.corp"], ports: [5432] },
  ],
  // Anything that does not match is blocked AND recorded. A blocked
  // request you can see beats an allowed request you never noticed.
  onBlock: (req) => audit.record({ kind: "mcp_egress_blocked", ...req }),
};

export function checkEgress(server, host, port) {
  const rule = mcpEgressPolicy.rules.find((r) => r.server === server);
  if (!rule || !rule.allow.includes(host) || !rule.ports.includes(port)) {
    mcpEgressPolicy.onBlock({ server, host, port });
    return false;
  }
  return true;
}

3. Findings Two and Three: Home Networks and Abandoned Domains

The second and third findings are closer to day-to-day operations. About 0.45 percent of hostnames routed through consumer ISP networks or personal tunnelling tools, which means production AI workflows can depend on infrastructure with no uptime guarantee, no enterprise access controls, and no real auditability, because it was never built to be enterprise infrastructure. At the other end, 2.3 percent of hostnames no longer resolved, and six of those domains were unregistered and available for four to twelve dollars a year. Those domains are likely still referenced in someone's config or pipeline. Anyone can buy one and start impersonating the server it used to point to. That is the classic takeover path: the trust relationship outlives the thing you trusted.

#!/usr/bin/env python3
# Governance gate 2: resolve every hostname. Where does it live, does it
# still exist, and could someone else point it somewhere new tomorrow?

import socket, whois  # whois via python-whois

def classify(hostname):
    try:
        ip = socket.gethostbyname(hostname)
    except socket.gaierror:
        return {"hostname": hostname, "status": "does_not_resolve",
                "risk": "abandoned domain may be re-registered"}

    record = whois.whois(hostname)
    created = record.creation_date
    expires = record.expiration_date
    return {
        "hostname": hostname,
        "ip": ip,
        "status": "resolves",
        "created": str(created),
        "expires": str(expires),
        # Cheap, recently created domains deserve a second look before
        # you let an agent send them your code and credentials.
        "risk": "review" if is_recent(created) else "ok",
    }

for host in reported_hostnames:
    print(classify(host))
Analysing security data

796 of 5,095 hostnames resolved outside the United States

4. Finding Four: Trust That Outlives the Permission

The fourth finding should worry security teams the most. OX ran a trust-based prompt injection test. Using Claude Code paired with Haiku 3.5, a malicious MCP server first asked for access to a harmless file, and the user approved it with an always-allow permission. The server then requested a sensitive file, .env among them, and got it with no further prompt. The same attack was detected and blocked by Opus 4.6 and 4.7. Anthropic's response was that once always-allow is granted, that is the documented behaviour, and model-level detection of malicious content is a best-effort heuristic rather than a security boundary. That sentence deserves rereading: the convenience you bought with one click becomes a permanent grant.

// Governance gate 3: never grant always-allow. One benign approval should
// not become permanent access to every file the server asks for later.
// The OX Security test showed a single always-allow on a harmless file
// unlocked a sensitive .env read with no further prompt.

const permissionModel = {
  // Approvals are per (server, resource), never per server.
  grant: (server, resource) => ({
    server,
    resource,                 // exact path/pattern, not "*"
    expiresInMinutes: 30,     // time-boxed, not permanent
    maxUses: 1,               // single use unless reviewed
  }),
  deny: ["**/.env*", "**/.ssh/**", "**/id_rsa*", "**/.aws/**", "**/secrets/**"],
  alwaysAllow: false,         // this whole option is off
  onRequest: async (req) => {
    if (permissionModel.deny.some((p) => minimatch(req.resource, p))) {
      return deny(req);
    }
    return promptHuman(req);  // fresh consent, every time
  },
};

5. From Pre-Deployment Review to Runtime Control

Taken together, the findings do not argue against using MCP. Its value is real and its adoption is climbing fast. They argue that governance has to move from pre-deployment review to runtime control. Code sample 2 makes egress a deny-by-default allowlist, since MCP has no protocol-level geography and the boundary therefore has to live in your network; every rule is scoped to a server and port, and anything that does not match is blocked and recorded. Code sample 4 ties the tool call and the destination together in one record, so a question like which agent sent data outside our approved regions becomes a query instead of an incident investigation.

# Governance gate 4: log the tool call and the destination together, so a
# question like "which agent talked to a host outside our region?" is a query
# rather than an incident investigation.

def log_tool_call(session, call, destination, decision):
    ledger.append({
        "ts": time.time(),
        "agent": session.agent,
        "server": call.server,
        "tool": call.tool,                 # e.g. read_file, run_query
        "destination": destination.host,   # where the bytes went
        "residency": destination.region,   # us | eu | other | unknown
        "decision": decision,              # allow | block | ask
        "resourceHash": call.resource_hash,
        "approvedBy": decision.approver,
    })

# The monthly question: how much traffic left our approved regions?
outside = [r for r in ledger.tail(10000) if r["residency"] not in ("us", "eu")]
print(len(outside), "tool calls reached unapproved regions")
Auditing and logs

Undeclared egress should produce an audit record, not a silent success

6. A Seven-Step Checklist

A practical checklist. First, inventory: how many MCP servers you run, which config files declare them, and which hosts they reach, as code sample 1 demonstrates. Second, deny egress by default and make every undeclared destination produce an audit record. Third, resolve and check registration for each hostname, block the ones that no longer resolve, and review recently registered low-cost domains. Fourth, forbid always-allow and move to per-server, per-resource grants that are time-boxed and single-use. Fifth, give each MCP server its own narrowly scoped credentials rather than a shared token. Sixth, log tool calls together with egress destinations, and report regularly on calls that left approved regions. Seventh, fold MCP into procurement and vendor review, and ask who operates the server, where the data goes, and who controls its updates.

📌 Frequently Asked Questions

How many MCP servers did OX Security analyse?

The research collected 15,465 published MCP servers across three public registries, then narrowed the dataset to 5,095 unique hostnames for infrastructure analysis.

How many MCP hostnames resolve outside the United States?

15.6 percent, or 796 of 5,095, resolved to infrastructure outside the United States, including 19 in China and 18 in Russia. MCP has no protocol-level concept of geographic region.

What is the abandoned-domain risk?

2.3 percent of hostnames no longer resolved, and six of those domains were unregistered and available for four to twelve dollars a year. They may still be referenced in configs or pipelines, so anyone could buy one and impersonate the original endpoint.

What is wrong with always-allow permissions?

In OX Security's test using Claude Code paired with Haiku 3.5, a single always-allow approval for a harmless file let a malicious MCP server read sensitive files including .env with no further prompt. The same attack was blocked by Opus 4.6 and 4.7.

How should organisations reduce MCP risk?

Move governance from pre-deployment review to runtime control: deny egress by default, resolve and check registration for every hostname, forbid always-allow, use per-server credentials, and log tool calls together with egress destinations.