A setup guide told me to add nine lines of JSON and restart my editor. I checked the package before writing the file. It did not exist.
The config was for an MCP server that would fetch AI news, which is a genuinely useful thing for an agent to have. The instructions were unremarkable and looked like every other MCP setup guide I have read: a .mcp.json snippet, a note about restarting, a list of example prompts.
{
"mcpServers": {
"ai-news": {
"command": "npx",
"args": ["-y", "@treesoop/ai-news-mcp"]
}
}
}
Nine lines, and every one of them ordinary.
The check took one command
Before writing a config that runs a package, I looked the package up.
@treesoop/ai-news-mcp 404
@modelcontextprotocol/server-filesystem 200
The second line is the control. Querying the registry for a package that certainly exists returns 200, so a 404 on the first line means the registry has no such package, not that my request was malformed. I searched the registry for similar names and for the scope. Nothing related came back.
Had I written the file, nothing dramatic would have happened. npx would have failed to resolve the package on every session start, the server would never have appeared in the client's server list, and I would have spent an evening debugging a typo I did not make.
Why a missing name is worse than a broken one
npx -y means install without prompting, and the argument carries no version. Both halves matter. The flag removes the confirmation step, and the missing version means the command resolves to whatever the registry serves at the moment it runs, not to anything I reviewed.
An unregistered name is therefore not a dead end. It is an unclaimed one. A config that references a package nobody has published is a standing instruction to run whatever eventually appears under that name, on every machine that pasted it, with the privileges of the person who pasted it. Stdio MCP servers are ordinary local subprocesses; there is no sandbox implied by the protocol.
I want to be careful about what I am claiming. I have no evidence that this guide was written in bad faith, and I think the likeliest explanation by far is that a model produced a plausible-looking package name that was never real. That is exactly why it is worth writing down. The failure mode does not require anyone to be malicious. It only requires a name to be wrong, published widely, and left in configs that quietly retry it forever.
What my own config actually contained
Having been suspicious of someone else's config, I audited mine.
configured MCP servers 2
remote HTTP 2
local stdio subprocesses 0
.mcp.json files on disk 0
One is a documentation service reached over HTTPS. The other is a Unity bridge bound to 127.0.0.1. Neither is a package that gets downloaded and executed at startup, because I had never added one of those. The server I was about to install would have been the first.
That is a duller result than I expected, and I am reporting it as it came out. The interesting part is not that I found risk in my setup. It is that my exposure to this particular failure was zero purely by accident, and one pasted snippet would have changed that without any step in the process asking me to think about it.

The spec spent this revision on a different door
The 2026-07-28 MCP revision landed three weeks before I read that guide, and a real share of it is security work. Authorization servers should now include the iss parameter per RFC 9207, and clients must validate it against the recorded issuer before redeeming an authorization code. Client credentials are explicitly bound to the authorization server that issued them: clients must key them by issuer, must not reuse them elsewhere, and must re-register when the server changes. Dynamic Client Registration is deprecated in favour of Client ID Metadata Documents. Clients must declare an application_type to avoid redirect URI conflicts.
Every one of those hardens how a client proves who it is to a server that is already running. None of them has anything to say about how that server got onto the machine, because that is not the protocol's job. Installation happens before any of this applies.
So the trust boundary that got attention this revision is the one after the subprocess is already executing with your permissions. The one before it is still a JSON blob in a blog post.
The runtime caught what the config format does not
One more thing happened that I did not plan. Wanting to see the actual failure text, I tried to execute the package directly. My agent's permission layer refused, on the grounds that this was an unverified third-party package from a pasted guide with no explicit authorisation to run it.
It was right, and I did not need the command: the registry lookup had already settled the question. But the shape of it is worth noticing. The harness applied a judgement at execution time that the config format never applies at write time. npx -y inside .mcp.json gets none of that scrutiny, because by then it is configuration rather than a command, and configuration does not look like it is running anything.
What I do now
Three checks, none of which are clever, all of which I skipped for years:
- Resolve the package before writing the config. One registry request, and a control request so a 404 means what you think it means.
- Pin the version.
@scope/name@1.2.3instead of a bare name, so a reviewed release is the one that runs tomorrow. - Prefer a remote endpoint over a local subprocess when the server offers both. An HTTPS server you authenticate to is a smaller grant than a process running as you.
The narrow claim I will defend: the ecosystem's install convention is paste-and-restart, and the protocol's security work sits entirely downstream of it. My audit found nothing wrong, which is the least interesting way this could have gone and the reason I trust the finding.
