Adding a tool to an agent used to mean writing a function. Now it often means adding four lines to a config file.
{
"mcpServers": {
"some-service": {
"command": "npx",
"args": ["-y", "some-mcp-server@latest"],
"env": {"SOME_API_KEY": "sk-..."}
}
}
}
The block is worth reading slowly. Three separate trust decisions live in it, wearing the costume of configuration.
One: their code runs on your machine
MCP's simplest transport is stdio, and stdio means the client launches the server as a subprocess. Not a sandbox, not a container by default. A process, on your machine, with your user's permissions.
So the npx -y some-mcp-server@latest above fetches a package and executes it, with access to your
filesystem, your environment, your network position, and anything else your account can reach.
Launching a subprocess is an entirely reasonable design for a local tool runner. The grant is also much bigger than "added a tool" suggests, and the phrasing in most setup guides does not slow anyone down.
Two: you handed it credentials
Look at the env block. A server that talks to an external service needs to authenticate to it, so
you give it a key.
Two questions worth asking every time, neither of which is exotic:
How scoped is that key? A server that reads should hold a read-only credential. Many services issue one key with everything on it, and the path of least resistance is to paste that one in.
Where does it go? The key sits in a config file, gets passed into a subprocess environment, and is handled by code you have not read.
Three: your context goes to it
The third grant is the one people miss, and the most interesting of the three.
Every tool call sends arguments to the server. Those arguments are produced by a model that has been reading your conversation, your files, and the results of earlier tools. So a search server receives your queries, and your queries are shaped by whatever you have been working on.
The server sees a slice of your working context, on every call, by design. Whether that matters depends entirely on what you were working on and who runs the server.
Adding a server grants code execution, credentials and a slice of your working context.
Why it does not feel like a dependency
Every one of those grants is normal for a dependency, and we have decades of practice managing dependencies. The trouble is that this one does not arrive through any of the machinery we built.
A library you npm install | An MCP server you configure | |
|---|---|---|
| Appears in the manifest | Yes | No, it is in an app config |
| Pinned by a lockfile | Yes | Often @latest |
| Shows up in your SBOM | Yes | Usually not |
| Reviewed on version bump | Diff in the PR | Fetched fresh at launch |
| Owner is obvious | Registry metadata | Whoever published it |
A dependency that skips all five is a dependency nobody is tracking. And because the server also publishes its own tool descriptions, an update can change what your agent is told about when to act, with no diff anywhere in your repository.
The remote case adds a fourth
Switch from stdio to streamable HTTP and the server stops being a subprocess and becomes a network service.
Network services have well-understood requirements, and they do not change because the client is an agent. A service that accepts execution requests needs to know who is asking. A service bound to all interfaces is reachable by anyone who can route to it. A service without authentication executes for whoever connects.
The security work here is not novel and that is the point: it is the ordinary checklist, applied to a category of service that has grown very quickly.
The checklist
| Control | Why |
|---|---|
Pin exact versions, never @latest | An unpinned server changes its code and its tool descriptions between runs |
| Read or vendor anything on a critical path | You cannot review what you fetch fresh each launch |
| Scope every credential to what the server needs | A read tool holding a write key is a choice, not a default |
| Prefer few servers over many | Each one is code, credentials and context, multiplied |
| Bind local servers to loopback and require auth on remote ones | An execution endpoint is an execution endpoint |
| Log which servers and versions a run connected to | Otherwise a post-incident question has no answer |
| Keep the sensitive path off shared context | The agent talking to third-party servers is not the one holding your keys |
The controls are ordinary supply-chain hygiene, applied to a surface that grew faster than the habits.
What this touches
For the frameworks, this sits across the MAESTRO Agent Frameworks and Deployment layers. The primary mapping is T17 Supply Chain Compromise (a third-party component brought into the agent, able to manipulate its actions, obtain data or run code) and T16 Insecure Inter-Agent Protocol Abuse, which names MCP explicitly and covers consent bypass and context hijacking. T11 Unexpected RCE and Code Attacks applies to the subprocess grant, and T3 Privilege Compromise to the credential scoping. In the OWASP Agentic Top 10 the anchor is ASI04, with ASI05 on the code-execution side and ASI07 on the protocol side.
On the regulatory side, framed as scope rather than a citation: this is third-party and outsourcing risk, plus change management, and every GCC rulebook covers both at length. Those obligations generally assume a vendor relationship with a contract, a due-diligence file and a named owner. A server added from a config file has none of those, while granting more local access than most contracted vendors ever get. Which obligations apply depends on your regulator and architecture, and that mapping is an advisory estimate until someone checks it properly.
The part worth keeping
MCP is a good standard solving a real problem. Writing a wrapper for every API an agent might touch was genuinely unsustainable, and a shared protocol is the right answer.
None of this is an argument against using it. It is an argument for treating a server the way you already treat a library: pinned, reviewed, scoped, and listed somewhere a person can find it.
The ecosystem moved faster than the habits around it, which is what ecosystems do. The habits are not new and we do not have to invent them, only apply them one layer over from where we usually do.