Home/Journal/Teardown
TeardownCriticalmcpauthenticationai-agents

The MCP Server That Ran Production

An unauthenticated MCP server let anyone on the internet drive a production telephony fleet. Here is how we found it and how to close it.

Ajay Kumar··8 min read·a cloud-telephony platformMissing authentication on an administrative interface (CWE-306)

The setup

The target was a cloud-telephony platform: the kind of company that sits underneath other companies’ phone systems, routing calls and running contact centers at scale. That places a lot of sensitive infrastructure behind its brand - voice servers, call databases, the internal machines that keep the phones working.

We were interested in one thing in particular. The company had started shipping AI features, and AI features increasingly mean MCP servers - small services that expose “tools” an AI agent can call. An MCP server is, by design, a remote-control panel: it lets an agent query a database, check a service, restart a process. That is exactly the sort of surface worth looking at, because the tools behind it are usually a lot more powerful than a normal API.

Recon

Working black-box, from the open internet with no account, we enumerated the company’s subdomains and probed what answered. Most of the interesting internal-sounding hosts behaved correctly. One internal control host returned 401 Unauthorized. Another was a standard identity provider login. A third had already been decommissioned and simply did not resolve.

Then we found an MCP endpoint. It spoke the Model Context Protocol over HTTP, and it was reachable from anywhere.

The hypothesis

MCP is young. The protocol was designed for a trusted setting - an agent and its tools, talking on the same machine or the same private network. When teams push an MCP server onto a reachable host, the authentication story is often an afterthought, because the mental model is still “only our agent talks to this.”

So the hypothesis was simple: this server would either have no authentication, or it would have authentication in name only. And because MCP tools tend to wrap real infrastructure actions, if the door was open, whatever was behind it would be far more than an information leak.

What we tried

MCP has a small, well-defined handshake. A client sends initialize to start a session, tools/list to discover what the server can do, and tools/call to run one of those tools.

We sent initialize with no credentials. It succeeded.

We then tried to see whether any credential was actually required. We sent a request with an invalid bearer token - a made-up string in the Authorization header. A correctly configured server rejects an invalid token with 401 and a challenge telling the client how to authenticate. This server returned 200 OK and carried on. It was not checking the token. It was not even checking whether a token was present. The header was decorative.

What we deliberately did not do matters just as much. Once we understood what the tools were, we stopped short of running anything that changed state. The point of the engagement was to prove the door was open, not to walk through it and break things.

What we found

tools/list returned the catalog, and the catalog was the finding.

The server exposed tools that reached straight into the company’s internal production estate - across multiple regions, through jump hosts, into live databases. Grouped by what they could do:

-> tools/call { "name": "<region-inventory>", "arguments": { "region": "<id>" } }

<- {
     "servers": [
       { "host": "...", "private_ip": "10.x.x.x", "role": "voice" },
       { "host": "...", "private_ip": "10.x.x.x", "db_host": "...", "role": "app" },
       ... 5 live production servers, private IPs, and the internal
           voice-management endpoint with its username ...
     ]
   }

That response was real internal infrastructure data, returned to an anonymous caller over the public internet.

The transport tunneled into internal servers and databases behind jump hosts. So this was not a server exposing a bit of cached data - it was an internet-facing remote for the company’s production infrastructure, and it asked for no credentials to use it.

Why it happens

Nobody made a reckless decision here. This is what “AI-speed shipping meets a new protocol” looks like.

MCP’s default posture assumes a trusted caller. A team building an internal agent stands up a server so the agent can do useful things - look up a region’s servers, clean up a full disk, restart a stuck process. Inside a private network, with only the agent talking to it, that is reasonable. The gap opens in the one step that is easy to miss under deadline pressure: the server ends up on a host that is reachable from outside, and the authentication that would have made that safe was never the point of the sprint. The tools were the point. The auth was assumed.

The result is a category of exposure that did not exist 18 months ago: an administrative interface, as powerful as anything in the company, sitting on the internet because the framework it was built with treats authentication as someone else’s job.

For developers

If you run an MCP server, or you are about to, four controls close this:

  1. Authenticate the transport, and validate the token - not its presence. Reject a request with no Authorization header, and reject one with an invalid token, with a real 401 and a WWW-Authenticate challenge. A server that returns 200 to a made-up bearer is unauthenticated no matter what the header says.
  2. Keep it off the public internet. An MCP server that serves an internal agent has no reason to be internet-reachable. Bind it to the private network, put it behind your mesh or a VPN, and treat public exposure as a misconfiguration to alert on.
  3. Least-privilege the tool catalog. The tools you register are the blast radius. An agent that needs to read health metrics does not need a tool that deletes database rows. Split read from write, and scope tools to the narrowest job.
  4. Never expose destructive tools on a reachable endpoint. Delete, restart, and cleanup actions should not be callable from an MCP surface that anything untrusted can reach. If an agent genuinely needs them, gate them behind a separate, authenticated, audited path.

And one detection step: audit your MCP server’s access logs for tools/call requests you cannot attribute to your own agent. An open server has usually been open for a while.

The takeaway

An MCP server is not an API endpoint - it is a remote control for whatever its tools can touch, and its tools are usually chosen for power, not safety. The moment one is reachable without real authentication, its entire tool catalog becomes an anonymous capability. This is a new surface, it is being shipped fast, and it is one of the cleanest ways an AI-forward company hands an attacker the keys.

This teardown is one instance of a pattern we now see repeatedly. We wrote up the general case, and how to defend against it, in The Unauthenticated MCP Server.

Frequently asked

What is an unauthenticated MCP server vulnerability?
It is an MCP (Model Context Protocol) server exposed to a network without authentication, so any client can list and call its tools. Because MCP tools often wrap powerful internal actions, an unauthenticated server can hand an attacker the same control an internal agent has.
Why are MCP servers a new attack surface?
MCP servers exist to let AI agents call real tools: query databases, restart services, read internal inventory. Many are stood up quickly and inherit no authentication, because the assumption is that only a trusted agent will talk to them. On a reachable host, that assumption is the whole vulnerability.
How do you secure an MCP server?
Authenticate the transport and validate the token rather than its mere presence, keep the server off the public internet, expose only least-privilege tools, and never register destructive actions (delete, restart) as callable tools on an internet-reachable endpoint.

This is one finding from a harness that runs continuously. See how Greywatch finds, proves, and fixes them.

How it works →