Four security advisories landed against MCP and agent-tooling components between 9 and 11 September 2026. One was rated CVSS 10.0. Read together, they are not four unrelated bugs. They are three failures at three different points of the same boundary, and two of them are cases where a real fix was applied at a layer that could be walked around.
Answer first: an MCP server is not a library your agent calls. It is a service holding credentials, and every flaw in it becomes a flaw in the agent. This week’s advisories fail at the transport and identity layer, by treating a tool argument as authority, and by enforcing a control somewhere that a semantically equivalent request can bypass.
What landed in three days
The cluster is unusually concentrated. Each advisory is public, each names an affected package, and each has a concrete mechanism rather than a theoretical one.
| Advisory | Component | Class | Severity |
|---|---|---|---|
| GHSA-rqfv-2mw9-78g2 | mysql-mcp-server | Missing transport authentication and origin validation | CVSS 10.0 |
| GHSA-wcjj-9m6g-2fr2 | functype-mcp-server | Tool argument reaching an installer and a dynamic import | CVSS 7.8 |
| GHSA-65h7-9wrw-629c | FrontMCP, mcp-from-openapi | Server-side request forgery, bypassing the earlier fix | CVSS 8.5 |
| GHSA-hf57-cqmx-p4gr | OmniRoute ACP custom agent | Tool argument reaching a process execution call | Critical |
The 10.0 is worth pausing on. A perfect score is not awarded for a clever chain. It is awarded when an unauthenticated network caller gets high confidentiality, integrity and availability impact with no preconditions. That is what a database tool server with no authentication on any route amounts to.
Why an MCP server is a boundary, not a library
The Model Context Protocol exists so an agent can reach real systems through a consistent tool interface. That design is what makes it useful and what makes it dangerous. The server is where the agent stops being a text generator and starts being a credential holder.
Consider what a typical deployment already has. An MCP server for a database holds that database’s credentials. A filesystem server holds a path it can write to. A deployment server holds a registry token. The agent client on the other side runs with the authority of whoever started it. The protocol is the wire between those two, and the tools are the operations that cross it.
Now notice how these get deployed. Most MCP servers start life as a local convenience: a process your editor or IDE launches over stdio so you can ask an assistant about your data. That is a safe default, because stdio has no network surface. The trouble begins when the same server is moved to a shared or hosted deployment. The code does not change. The threat model does, completely, and the configuration that made it safe does not come with it.
Failure class one: the protection existed and was never wired up
The MySQL MCP Server advisory is the clearest case, and the most instructive, because nothing was invented and nothing was forgotten. The MCP Python SDK ships DNS-rebinding protection. It validates the Origin and Host headers so that a request arriving from a browser on a hostile page cannot be mistaken for a legitimate local client. The protection is opt-in: the server developer must pass security_settings when constructing the transport.
In SSE mode, this server did not. Three consequences followed from that one omission. The SDK’s Origin and Host validation was disabled. The Starlette application was left without CORS or trusted-host middleware. And the service bound to 0.0.0.0 by default with no authentication on any route.
Two attack paths open from there, and the second is the one people underrate. With direct network exposure, any caller who can reach the port can invoke execute_sql and run arbitrary SQL without credentials. With a local bind, an attacker lures a victim’s browser to a hostile page, rebinds a domain to 127.0.0.1, and uses that browser as a proxy to reach the local server. The person being attacked never installed anything and never opened a port. Their browser did the work.
The lesson is not that the SDK’s default is wrong. Defaults that assume a local trusted caller are reasonable for a local tool. The lesson is that a security control which is opt-in will be absent in a meaningful share of deployments, because the person moving a stdio server to a shared host is focused on making it work, not on re-deriving its threat model. The right place for this decision is not a constructor argument several layers down. It is a host, a bind address and an authentication requirement the operator must consciously set.
Failure class two: an argument is a value, not an authority
Two of the four advisories share the same shape. A tool accepts a string that a caller controls, and that string reaches something that installs or executes code.
In the functype-mcp-server case, the set_functype_version tool accepts a version string and interpolates it directly into an npm package specifier of the form functype@<version>, which is then installed with pnpm add. The problem is that package specifiers are not just version numbers. They support alias syntaxes including file: and npm:, so a crafted value can cause the server to install an arbitrary local or remote package under that name. Immediately afterwards the server calls a documentation initialiser that dynamically imports the CLI entry point from the newly installed location. Attacker-controlled JavaScript then runs inside the MCP server process, with that process’s privileges.
The OmniRoute advisory is the same failure with fewer steps. An endpoint that registers a custom agent accepts a binary value and a versionCommand value. After saving, the server refreshes its agent cache and probes the version by executing that command. The only validation checks that the first token of versionCommand matches the binary the caller supplied. Because the caller supplies both, the check compares one attacker-controlled value with another attacker-controlled value. It is a validation that cannot fail for the person it was meant to stop, and an example of what a control looks like when it is written for the shape of the input rather than for the authority it grants.
The generalisable rule is blunt. A tool argument may select among operations the server already decided are safe. It may not create operations. The moment a parameter value can reach a package manager, a shell, an interpreter or a template engine, the parameter has become an input to a code path rather than data, and the allowlist belongs on the operation rather than on the string.
Failure class three: the control was real, and at the wrong layer
The FrontMCP advisory deserves more attention than it got, because it is not a fresh vulnerability. It is a bypass of an earlier fix.
The original issue was server-side request forgery through OpenAPI external $ref dereferencing: a hosted deployment loading an untrusted specification could be made to issue requests to internal services during tool generation. The published fix added a denylist for external references, which is the natural first move. The follow-up advisory shows that the denylist holds for a literal http://127.0.0.1 reference and fails for semantically equivalent requests that arrive through hostname resolution, HTTP redirects or IPv4-mapped IPv6 syntax. The direct control produces no canary hits. The requests that look different do.
This is the most important pattern in the whole cluster, because it is the one that will repeat. A denylist is a statement about the inputs you thought of. Server-side request forgery is defined by the destination the request actually reaches after resolution and redirects. The two are not the same set, and no amount of adding entries to the list closes the gap, because the gap is structural: the decision is being made before the thing that determines the outcome.
The fix that holds is an allowlist applied at the layer where the destination is finally known, combined with network egress controls the application cannot talk its way past. A specification that resolves to a private address must be rejected after resolution, not by pattern matching the string that requested it.
The pattern behind all three
Strip the specifics and the same assumption appears three times: that the boundary is somewhere other than where the authority actually is.
The first class assumes identity is established by being on the local machine. Loopback is treated as a trust boundary, which was reasonable when the only caller was a local process and stopped being reasonable the moment browsers could be pointed at loopback and ports got forwarded. The second class assumes an argument is data, when the code path it feeds treats it as a capability. The third assumes a control can be evaluated on the request rather than on the effect, when the effect is what determines safety.
What makes this a useful cluster rather than a list of bugs is that all three are configuration and design failures, not exotic exploitation. None of them requires a novel technique. They require an operator who never asked who could reach the server, and a developer who never asked what the parameter could become.
Two companion notes cover the adjacent boundaries. For the case where the containment itself was the failure, see the agent that disabled its own sandbox. For the layer an agent’s traffic passes through before it reaches any tool, see the AI gateway as a privileged control plane.
Why the ecosystem looks like this
It is tempting to read the cluster as four careless teams. A fairer reading is that the incentives point the other way. An MCP server is usually written by a product team to expose their product to agents, published to a public registry, and installed by a developer in minutes. The thing being rewarded is capability: more tools, wider reach, easier setup. Hardening is friction, and no launch metric measures it.
So the defaults drift in one direction. A server that began as a local convenience ships with a bind address of all interfaces, no authentication, and a security setting that is opt-in and unset. It then gets installed into a shared deployment by someone whose goal was to make the integration work, and who had no reason to suspect the constructor argument existed. The maintainer cannot fix that from upstream. A patch can change a version and a changelog; it cannot change a docker run line that was written before the advisory existed.
The encouraging note in this cluster is that the fixes are real and fast. Versions 0.4.2, 1.4.4 and the 2.5.0 line all landed quickly, and the maintainers published the mechanism rather than burying it. The responsive part of this ecosystem is working. The part that is not is the configuration of deployed instances, which no upstream release can reach and no registry listing currently reports.
What this does not prove
Three limits belong on the record. First, these are four components out of a growing ecosystem; the absence of an advisory is not evidence of safety, and the presence of one is not evidence that the whole category is broken. Second, the severity scores describe a specific deployment: the MySQL MCP Server flaw is a 10.0 in SSE mode with a network-reachable bind, and the default stdio mode was not affected. Score follows configuration. Third, a fix landing does not mean the class is closed. The FrontMCP case is a published fix with a published bypass, which is the normal rhythm of this work rather than an embarrassment.
The MCP hardening gate
These five questions map directly onto the failure classes and can be answered before a deployment goes anywhere near production.
- Who can reach it? Name every way to reach the server: loopback, a container network, a forwarded port, an editor tunnel, a hosted endpoint. If the answer includes anything but a local process, the transport needs authentication. Do not treat loopback as a trust boundary.
- Does it authenticate, or does it merely validate? Origin and Host checking defends against a browser being used as a proxy. It is not authentication. The SDK option that enables it is opt-in, so verify it is actually set rather than assuming the framework did it.
- Which arguments can start a process or install something? Enumerate every tool parameter that reaches a shell, a package manager, an interpreter, a template engine or a connection string. Those parameters need an allowlist on the operation they select, not pattern matching on the value they contain.
- Does it fetch anything the caller chose? If the server dereferences user-supplied URLs, specifications or documents, decide the destination after DNS resolution and after redirects, and back the decision with egress controls rather than with a denylist.
- What can the server’s own credentials do? Assume compromise and ask what the blast radius is. A tool server holding a read-only replica credential is a different risk from one holding a schema-owner account with file privileges.
The gate is deliberately boring, and that is the point. Every item on it is a question about authority, and none of them is a question about tooling.
One detail worth knowing when you answer item three. The protocol includes tool annotations, including read-only and destructive hints, which a client can use to decide what to allow. Treat them as advice rather than enforcement. They are claims a server makes about its own tools, and the strongest argument against relying on them is the advisory record itself: a server that describes a tool as read-only can be wrong, out of date, or compromised, and the annotation is not a control you can test.
A test plan you can run
Reading advisories is not the same as testing your own deployment. Four tests cover the classes above and can be run against your own environment with authorisation.
- Reachability: from a second machine or a container that should not have access, attempt to reach the MCP server’s port and list its tools. If that works, the rest of this list is already moot.
- Rebinding: with the server bound to loopback, request it from a page in a browser using a name that resolves to
127.0.0.1. A working Origin or Host check should reject it; a missing one will not. - Parameter escalation: for each tool argument, attempt to make it select something other than an expected value. Package alias syntax, absolute paths, and argument separators are the cheap probes. You are looking for whether the server rejected the operation or merely failed to parse the string.
- Control regression: for every denylist you rely on, write the semantically equivalent request that the list does not describe and see whether the control still holds. If it does not, the control is in the wrong place.
The last test is the one that would have caught the FrontMCP bypass before a second advisory was needed, and it takes minutes rather than days.
Detection and telemetry
MCP servers are usually deployed without the logging you would demand of any other service holding database credentials, which is why this class tends to be found by a third party. At minimum, log the transport and the client identity for every session, log every tool invocation with its arguments, and log the server’s outbound requests with resolved destinations rather than raw URLs. A record of which destination a fetch actually reached is the difference between seeing a bypass and seeing normal traffic.
Two signals are worth alerting on. A session whose transport did not authenticate, appearing on a server that is expected to authenticate, is worth a look regardless of what it did next. And a tool call whose argument contains package alias syntax, a path separator or a command separator is anomalous in almost every legitimate workload, which makes it a cheap rule.
Frequently asked questions
What is an MCP server, in one paragraph?
It is a service that exposes a set of tools to an AI agent through the Model Context Protocol, so the agent can query a database, read files, call an API or run a deployment action through a consistent interface. It typically holds the credentials for whatever it can reach, which is why its security posture is the agent’s security posture.
Is MCP itself insecure?
No. The protocol defines an interface, and the failures in this cluster are implementation and deployment choices: an opt-in protection that was not enabled, a tool argument passed to an installer, and a control enforced at a layer that could be bypassed. The protocol is not the vulnerability, and it is also not the control.
What was the MySQL MCP Server advisory about?
In SSE transport mode, the server constructed its transport without passing the SDK’s security settings. That disabled DNS-rebinding protection based on Origin and Host validation, left the application without CORS or trusted-host middleware, and bound the service to all interfaces with no authentication. The result was unauthenticated SQL execution, reachable either directly over the network or through a victim’s browser using DNS rebinding. It was fixed in version 0.4.2.
How does a tool argument become remote code execution?
When the value is interpolated into something that installs or runs code and is never constrained to the operations the server intended. In the functype case, a version string became an npm package specifier that accepted alias syntaxes, so the server installed an attacker-chosen package and then dynamically imported it. In the OmniRoute case, a command string reached a process execution call whose only validation compared two values the caller supplied.
Why did the FrontMCP SSRF fix fail?
It was a hostname denylist, and it was evaluated against the request rather than against the destination. Requests that reached loopback through hostname resolution, redirects or IPv4-mapped IPv6 syntax were not described by the list, so the control did not apply. The direct control that blocked a literal loopback address produced no hits while the equivalent requests succeeded.
What is the single highest-value change a team can make?
Require authentication on the MCP transport for any deployment that is not a purely local stdio process, and stop treating loopback as a trust boundary. It is one configuration decision, it would have prevented the highest-severity advisory in this cluster outright, and it costs nothing at runtime.
Sources
- GHSA-rqfv-2mw9-78g2: MySQL MCP Server missing Origin/Host validation in SSE transport
- GHSA-wcjj-9m6g-2fr2: functype-mcp-server package alias remote code execution
- GHSA-65h7-9wrw-629c: FrontMCP and mcp-from-openapi external reference SSRF bypass
- GHSA-hf57-cqmx-p4gr: OmniRoute ACP custom agent remote code execution
- Model Context Protocol specification repository
- MITRE CWE-306: Missing authentication for critical function
One next action
If you run an agent that executes tools, AgentCrash is an open-source harness that crash-tests it in a synthetic workplace: synthetic tools, visual evidence and regression tests. It is built for exactly this problem, turning a containment claim into something you can run against the system instead of arguing about it in review.