SmeltSecSmeltSec
    Features
    |Security
    |How It Works
    |Pricing
    |Docs
    |Blog
    |About
    npm
    1. Home
    2. /
    3. Blog
    4. /
    5. We read the code of the 100 most popular MCP servers. Here's where the risk actually is.
    All Posts
    Security

    We read the code of the 100 most popular MCP servers. Here's where the risk actually is.

    The team|November 13, 2026|12 min read
    English

    The Model Context Protocol (MCP) is how AI agents get tools: read files, query databases, drive browsers, run commands. Every MCP server you install is code your agent can call on your behalf, so "is this server safe?" is a fair question. It's also hard to answer from a README.

    So we took the 100 most-starred open-source MCP servers and read their source code. We didn't just run a scanner over them; we looked at how each one listens, who it lets in, and what an attacker could reach. Here's what we found, including an uncomfortable result about automated security grades, our own among them.

    TL;DR

    • Most popular MCP servers aren't network services at all. 79 of the top 100 default to stdio: your AI client launches them as a local process, and nothing on the network can talk to them.
    • The risk is in the optional HTTP mode. 72 of the 100 also ship an HTTP or SSE server mode, usually opt-in. Of those 72, 21 do no Origin/Host validation and 23 have no authentication at all. 15 servers are reachable without auth in at least one supported configuration.
    • One SDK detail explains most of the real issues.
      • The official MCP SDKs protect you from DNS rebinding only on their high-level, happy path.
      • Drop down one level (use the transport directly, or wire up your own HTTP server) and the protection is off by default.
      • Every DNS-rebinding issue we found took that lower path.
    • We found 25 verified issues across 23 servers: 12 medium and 13 low, none high. Most need an opt-in mode plus a victim doing something specific. We've reported them privately to maintainers.
    • Automated grades are noise.
      • Our own scanner failed all 100 servers, and roughly 96% had at least one "critical".
      • When we checked a sample of 423 of those critical-level hits by hand, not one was a real vulnerability.
      • Every real issue came from reading transport and auth code, which none of the automated rules looked at.

    How we did it

    Selection. We started from the full MCP registry (about 249,000 entries), ranked by GitHub stars, and kept the 100 most-starred repositories that contain real TypeScript, JavaScript or Python MCP server code. That meant at least 929 stars to make the cut. As a comparison group we also scanned 66 smaller, long-tail servers.

    Automated pass. Each repository went through our scanner, SmeltSec, run fully offline: static analysis, secret scanning (gitleaks), Semgrep rules, and dependency checks against an offline copy of the OSV vulnerability database.

    Manual pass. For every one of the top 100 we recorded, with file-and-line evidence:

    • which transports it supports, and which is the default;
    • what address it binds to;
    • whether it validates the Origin and Host headers;
    • what authentication it requires.

    We then sampled the automated findings, rule family by rule family, and judged each one a true or false positive.

    Full transparency on the "manual" part:

    • The code reading was done with an AI coding agent (Claude) working through the source under our direction. Every finding is tied to a specific file and line.
    • The findings we reported to maintainers were re-verified against the source a second time.
    • The SDK behaviour described below was confirmed by reading the SDKs' own installed source code.

    Static only. We never ran any of these servers, never exploited anything, and never touched anyone's deployment. Everything here comes from reading code. That's also the main limitation: none of these issues has a working exploit demonstration behind it.

    Time. The whole run took about four hours of wall-clock time on a modest machine (4 CPUs, 15 GB of RAM). The median automated scan took about 40 seconds per repository; a few large monorepos took up to 25 minutes. Most of the effort was in the manual review, not the scanning.

    Finding 1: most MCP servers aren't on the network

    There's a common mental model of MCP servers as little web services sitting on the internet. For the most popular ones, that's mostly wrong.

    Default transport (top 100) Servers
    stdio (local process, no network listener) 79
    HTTP / streamable HTTP 13
    Remote / hosted only 8

    A stdio server talks to exactly one thing: the AI client that launched it, over a pipe. A website can't reach it, nor can another machine on your Wi-Fi. That doesn't make stdio servers safe, since they still run with your permissions and do whatever their tools do. But it does mean that most of the "MCP servers exposed to the internet" panic doesn't apply to the servers most people install.

    Finding 2: the risk lives in the optional HTTP mode

    Here's the catch: 72 of the 100 also ship an HTTP or SSE mode, usually behind a flag like --transport http or as a Docker image. That's where the interesting problems were.

    Among those 72 servers with an HTTP mode:

    Check Yes Partial None
    Validates Origin/Host headers 29 20 21
    Authentication Servers
    None 23
    Optional (off unless you configure it) 25
    API key 13
    OAuth 10
    Platform-provided 1

    Put together, 15 of the top 100 can be reached without authentication in at least one supported configuration: an opt-in flag, the official Docker image, or a setup the docs recommend. To be clear, that means a configuration the project supports, not the default.

    The patterns we kept seeing:

    • "It's only on localhost" plus no Host check. A server bound to 127.0.0.1 feels private, but a web page you visit can still talk to it through DNS rebinding (explained below) unless the server checks the Host header.
    • Docker images that bind 0.0.0.0 with an empty token. Inside a container, binding all interfaces is normal. Combine it with a published port and an auth token that defaults to empty, and anyone on your network can drive the server. Several projects print a warning at startup; few refuse to start.
    • Access-Control-Allow-Origin: * on the MCP endpoint. With no auth, this lets any website you visit call the server's tools directly, no rebinding required.
    • "Optional" auth that nobody turns on. A token setting exists but is off by default, and the quickstart doesn't mention it.

    The tools behind these endpoints matter. Among the issues we verified were servers whose tools can run shell commands, execute SQL with the operator's database credentials, or read local source code.

    A 60-second explainer: DNS rebinding

    Browsers stop a page on evil.example from reading responses from localhost. DNS rebinding sidesteps that:

    1. You visit evil.example. Its DNS record points to the attacker's server, which serves a script.
    2. The attacker changes the DNS record so evil.example now resolves to 127.0.0.1.
    3. The script sends requests to evil.example:<port>. The browser treats them as same-origin (same name), but they now land on your machine's local server.

    The local server sees a request with Host: evil.example:<port>. If it checks the Host header against localhost/127.0.0.1, it rejects the request and the attack fails. If it doesn't, the attacker's page can call every tool the server exposes. An Origin check helps only if it compares against a fixed allowlist: a check that accepts any Origin matching the Host header lets rebinding requests straight through, because after rebinding the two match.

    Some browsers are adding protections that ask before a public site can reach your local network, which blunts this attack where it applies. Servers shouldn't rely on that.

    Finding 3: safe defaults exist, but only on the happy path

    This is the most useful thing we learned, because it explains why so many careful developers ended up with the same gap.

    Both official MCP SDKs include DNS-rebinding protection. Whether you get it depends on which layer of the SDK you use:

    You use… Protection
    Python MCPServer.streamable_http_app() / sse_app() bound to localhost On automatically
    TypeScript createMcpExpressApp() bound to localhost (the default) On automatically
    Python StreamableHTTPSessionManager or SseServerTransport directly Off unless you pass security_settings
    TypeScript StreamableHTTPServerTransport / NodeStreamableHTTPServerTransport directly Off: enableDnsRebindingProtection "Default is false"
    Your own express() or http.createServer() in front of the transport Off unless you add the validation middleware
    Any of the above bound to 0.0.0.0 Off unless you configure allowed hosts

    The Python SDK's own source states it plainly: when no settings are passed, the transport security middleware disables rebinding protection "by default for backwards compatibility."

    Real servers drop to the lower layer constantly, and for good reasons: custom routing, health endpoints, auth middleware, multiple sessions, an existing web app. Every DNS-rebinding issue we found was on one of the "Off" rows. None of the projects did anything unusual. They used documented SDK APIs, and those APIs left protection off.

    Finding 4: our scanner failed every server, and that's the point

    We built SmeltSec, so we'd love to tell you it nailed this. It didn't.

    Top 100 Long tail (64 scanned)
    Grades D: 74, F: 26 A: 1, B: 1, D: 49, F: 13
    Failed the security gate 100 of 100 62 of 64
    Median score 46 / 100 49 / 100
    Had at least one "critical" 96% 92%

    If those grades meant anything, the most popular MCP servers would be a catastrophe, and the obscure long-tail ones would be roughly as bad. Neither is true. The grades mostly track how much code a repository has and how noisy the rules are.

    We checked a sample of the critical-level ("error") hits by hand:

    Rule family Sampled Real issues False positives Needed more context
    Secrets / gitleaks 146 0 146 0
    "Dangerous call" AST rules 125 0 124 1
    Code execution / command injection 76 0 74 2
    SSRF / cloud metadata 55 0 55 0
    Toxic tool-flow 21 0 9 12

    Zero of 423. Typical false positives:

    • secret patterns matching test fixtures and placeholder keys;
    • getattr and constant-argument subprocess calls flagged as dangerous;
    • SSRF rules firing on code that fetches a URL the user asked for;
    • "code execution" in build scripts and research notebooks that aren't part of the server.

    Meanwhile, every real issue we found (missing Host validation, a 0.0.0.0 bind with no auth, wildcard CORS, a broken OAuth redirect check) came from reading transport and auth code, which none of our automated rules looked at.

    What we're changing:

    • New rules for the things that actually mattered:
      • HTTP transports created without rebinding protection;
      • non-loopback binds with no auth;
      • Access-Control-Allow-Origin: * on MCP routes;
      • OAuth redirect URIs not checked against the registered client.
    • Precision work on the noisy families: skip test and example code, require untrusted input to actually reach a dangerous call before flagging it, and demote capability-level "toxic flow" labels to informational.
    • No letter grades until precision is fixed. A grade that fails 100 out of 100 of the most popular servers tells you nothing.

    This matches what we saw on an earlier 25-server pilot. Fixes from that round cut the false positives counted toward the grade from 20,699 to 738, with every confirmed finding kept. The 100-server run shows that wasn't enough. If you're evaluating any MCP security scanner, ours included, ask what its precision is on real servers, not how many findings it produces.

    If you build MCP servers: a checklist

    1. Default to stdio. If your server is used by one local client, you probably don't need HTTP at all.

    2. If you add HTTP, use the SDK's high-level helper. It turns on Host/Origin validation for localhost binds:

      # Python (mcp 2.x)
      from mcp.server.mcpserver import MCPServer
      
      mcp = MCPServer("my-server")
      app = mcp.streamable_http_app(host="127.0.0.1")  # rebinding protection on for localhost
      
      // TypeScript (@modelcontextprotocol/express)
      import { createMcpExpressApp } from "@modelcontextprotocol/express";
      
      const app = createMcpExpressApp(); // binds 127.0.0.1, Host/Origin validation on
      
    3. If you use the transport directly, turn protection on yourself:

      # Python: low-level session manager
      from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
      from mcp.server.transport_security import TransportSecuritySettings
      
      # server: your low-level mcp.server.lowlevel.Server instance
      manager = StreamableHTTPSessionManager(
          app=server,
          security_settings=TransportSecuritySettings(
              enable_dns_rebinding_protection=True,
              allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
              allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
          ),
      )
      
      // TypeScript: plain node:http in front of the transport
      import http from "node:http";
      import { localhostHostValidation, localhostOriginValidation } from "@modelcontextprotocol/node";
      
      const validateHost = localhostHostValidation();
      const validateOrigin = localhostOriginValidation();
      
      // transport: your NodeStreamableHTTPServerTransport instance
      http.createServer((req, res) => {
        if (!validateHost(req, res)) return;
        if (!validateOrigin(req, res)) return;
        void transport.handleRequest(req, res);
      });
      
    4. Check Host, not just Origin. An Origin check that accepts "whatever matches the Host header" is exactly what a rebinding request sends.

    5. Never ship Access-Control-Allow-Origin: * on an unauthenticated MCP endpoint.

    6. Require a token even on localhost. Generate a random one at startup and print it once; it closes rebinding, CORS mistakes and other local processes in one go.

    7. Make Docker images safe by default. If the image binds 0.0.0.0, refuse to start without a token, rather than printing a warning nobody reads.

    8. Validate OAuth redirect_uri by exact match against the registered client, and never put user credentials inside authorization codes or tokens.

    If you use MCP servers: a checklist

    1. Prefer stdio. If a server offers both, use stdio unless you need remote access.
    2. If you run a server in HTTP mode, set its auth token, even on your own laptop.
    3. Don't publish MCP ports from Docker to all interfaces. Use 127.0.0.1:8080:8080 rather than 8080:8080.
    4. Treat "localhost only" as "any web page I visit" unless the server validates the Host header.
    5. Look at what the tools can do. A server with shell, SQL or file-write tools deserves the same care as SSH access.

    Disclosure

    We've reported the issues we verified privately to the affected maintainers, through each project's own security channel, under a standard 90-day coordinated disclosure policy. That's why this post names no projects. We'll publish detailed case studies, with the maintainers' consent, once fixes ship or the disclosure window closes. If you maintain an MCP server and want us to look at yours, reach us at security@smeltsec.com.

    Limitations

    • Static review only: no exploit was demonstrated and no deployment was tested. Severity ratings assume the preconditions described above.
    • Not a ranking of individual projects. The "top 100" are the most-starred servers, not the most-used or the most important.
    • AI-assisted review: the manual review was carried out with an AI coding agent under human direction. Every finding carries file and line evidence, and reported findings were re-verified, but reviewers can still miss things. Treat absence of a finding as "not found", not "secure".
    • Dependency vulnerabilities were scanned but aren't the focus here. Whether a vulnerable dependency is reachable in a given deployment is something a static scan can't settle.

    SmeltSec is a security toolkit for MCP servers. smeltsec.com

    Related Posts

    Security

    Why Most AI Tool Integrations Are Dangerously Insecure

    6 min read

    Security

    MCP Servers Need Zero Trust. You're Treating Them Like Microservices.

    6 min read

    Ready to try SmeltSec?

    Generate secure MCP servers in 60 seconds. Free to start.

    Product

    FeaturesSecurityPricingHow It WorksDocumentation

    Resources

    Quick StartAPI ReferenceCLI ReferenceLeaderboardBlogChangelogGitHubnpm (@smeltsec/cli)npm (@smeltsec/core)

    Company

    PrivacyTerms

    SmeltSec
    © 2026 SmeltSec. Open source CLI · Proprietary SaaS.
    PrivacyTerms