An MCP server is an API whose caller takes instructions from whatever it read last. Secure it as if that were true, because it is.
The systems an agent reaches through MCP were built for people, or for code that does exactly what it was written to do. An agent is neither. It decides what to call from text: the user's request, the tool descriptions, the documents it retrieves, the results of its earlier calls. Any of that text can carry instructions, and the agent cannot reliably tell an instruction from data.
OWASP's 2026 Top 10 for LLM applications, published this summer, still ranks prompt injection first and now ranks excessive agency third, up from sixth in the 2025 edition. MCP is where the two meet. It is the mechanism that turns what a model read into what your systems do.
This is a checklist for the CTO or platform lead about to connect agents to production systems through MCP servers, whether you write them yourselves or install someone else's. It follows the 2026-07-28 revision of the specification, the current one. If you are still choosing between protocols, start with A2A vs MCP.
What has already gone wrong
The failure modes are not hypothetical. A short record from the protocol's first year in the wild:
- •April 2025. Invariant Labs described tool poisoning: "malicious instructions are embedded within MCP tool descriptions that are invisible to users but visible to AI models." They also described the rug pull, where a server changes a tool's description after the user has approved it.
- •May 2025. Invariant showed that a malicious issue in a public GitHub repository could hijack an agent connected to the GitHub MCP server and make it leak data from the user's private repositories. Their own conclusion: not a bug in the server's code, but "a fundamental architectural issue that must be addressed at the agent system level."
- •June 2025. CVE-2025-49596: MCP Inspector, a developer tool, had no authentication between its client and its proxy in versions below 0.14.1, so unauthenticated requests could launch MCP commands. Rated critical, at 9.4.
- •July 2025. CVE-2025-6514: mcp-remote versions 0.0.5 to 0.1.15 could be made to execute operating system commands when they connected to an untrusted MCP server. Rated critical, at 9.6, and fixed in 0.1.16.
- •September 2025. Koi Security found that version 1.0.16 of an npm package called postmark-mcp had been quietly copying every email it sent to the attacker. "Version 1.0.16 adds the BCC. Nothing else changes."
Two classes of failure run through that list. In the first, the model reads something it should not have trusted. In the second, the plumbing itself is buggy or malicious. The controls below cover both.
Simon Willison named the combination that makes the first class dangerous. An agent with access to private data, exposure to untrusted content and the ability to communicate externally can be tricked into sending the first to an attacker. In his words: "The only way to stay safe there is to avoid that lethal trifecta combination entirely."
1. Keep a register, and pin every server
Treat each MCP server as a dependency that holds privileges, because that is what it is. Keep one register with, for every server: who owns it, where it runs, which credentials it holds, which agents use it and which version is approved.
Then pin the version. Many clients launch local servers from a JSON configuration, and the gap between these two entries is the gap the postmark-mcp backdoor went through:
{
"mcpServers": {
"tickets-floating": {
"command": "npx",
"args": ["-y", "@example/tickets-mcp"]
},
"tickets-pinned": {
"command": "npx",
"args": ["-y", "@example/tickets-mcp@2.3.1"]
}
}
}
The first installs whatever is newest each time it starts. The second runs the version you reviewed. Invariant's advice after the rug-pull finding was the same: "Clients should pin the version of the MCP server and its tools to prevent unauthorized changes." Upgrades go through review like any other dependency, including a diff of the tool descriptions.
2. One scoped credential per server, and no passthrough
The specification is unusually blunt here. MCP servers "MUST validate that access tokens were issued specifically for them as the intended audience" and "MUST NOT accept or transit any other tokens." A server that calls an upstream API must not hand that API the token it received from the client. Clients must name the server they want a token for, so a token minted for the ticketing server is useless against the finance server.
In practice:
- •Remote servers use the OAuth flow the specification describes, with scopes that start small. Its security guidance lists, as common mistakes, publishing every possible scope and using wildcard scopes such as
*orfull-access. - •Local servers take credentials from their environment. Give each one its own narrowly scoped service credential, never a developer's personal token with access to everything they can see.
This is the per-server version of the identity problem we described in The 144:1 Problem: every agent and every server it calls needs an identity you can scope, audit and revoke.
3. Separate reading from changing, and put a person before anything irreversible
The specification says "there SHOULD always be a human in the loop with the ability to deny tool invocations." That only works if the confirmations are rare enough to be read. Prompts that appear on every call get approved on reflex.
So split the tools:
- •Read tools can run without confirmation, once their scopes are right.
- •Write tools that are easy to undo can run with logging and an alert.
- •Destructive or external tools (deleting records, moving money, sending messages outside the company) wait for a named person.
Do not let the server decide which bucket a tool belongs in. MCP tools can carry annotations such as readOnlyHint and destructiveHint, but the specification says clients "MUST consider tool annotations to be untrusted unless they come from trusted servers." A third-party server that labels its tools read-only has told you nothing you can rely on. The client's policy decides.
4. Never give one agent the whole trifecta
Design each agent so that it holds at most two of the three ingredients.
- •A triage agent that reads inbound email is exposed to untrusted content. It should not also hold write access to the CRM and an outbound email tool.
- •A research agent that browses the web should not have the finance server connected in the same session.
- •An agent that does need private data and external communication should not read untrusted content. If it must, a person approves every outbound message.
Invariant's mitigation for the GitHub case was the same principle at a smaller scale: limit the agent's access to only the repositories it needs. Add egress controls for any server that fetches from the internet, so that a poisoned instruction cannot reach an arbitrary address.
5. Treat descriptions and results as untrusted input
Tool descriptions are prompts. The model reads them and follows them. Review a third-party server's descriptions the way you would review its code, and diff them on every upgrade.
The specification puts duties on both sides:
- •Servers must "Validate all tool inputs", "Implement proper access controls", "Rate limit tool invocations" and "Sanitize tool outputs".
- •Clients should prompt for confirmation on sensitive operations, show tool inputs to the user before calling the server, validate results before passing them to the model, set timeouts and log tool usage.
Results deserve the same suspicion as descriptions. A customer record, a support ticket or a web page returned by a tool is content someone else wrote.
6. Lock down local servers and developer tooling
A local server started over stdio runs with the privileges of whoever started it. On a developer laptop that can mean source code, cloud credentials and production access.
The specification's security guidance says a client offering one-click local server setup "MUST implement proper consent mechanisms prior to executing commands," must show the exact command without truncation, and should run server commands in a sandbox with minimal default privileges.
Both CVEs in the record above were in developer tools, not production servers. Keep that tooling patched, keep inspection proxies bound to the local machine, and do not point developer clients at remote MCP servers nobody has vetted.
7. Verify every request, and log every call
The July revision made MCP stateless: there are no protocol sessions, and servers that need state across calls pass explicit handles. The security guidance follows suit. Servers that implement authorization "MUST verify all inbound requests" and "MUST NOT treat possession of a state handle as authentication."
Then log every tool call: the tool, the arguments with secrets removed, which agent called it on whose behalf, the result and the duration. When something goes wrong, that log is the difference between an incident review and a guess.
The one-page version
Before any agent connects to a production system through MCP, you should be able to say yes to each of these:
- •Every server is in a register, with an owner and an approved version.
- •Versions are pinned, and upgrades include a diff of the tool descriptions.
- •Each server holds its own narrowly scoped credential.
- •Servers validate token audience and never pass tokens upstream.
- •Destructive and external tools wait for a named person.
- •No agent holds private data, untrusted content and outbound communication at once.
- •Approval policy ignores annotations from third-party servers.
- •Local servers run sandboxed, and developer tools are patched.
- •Every tool call is logged with its caller, arguments and outcome.
None of the nine needs new tooling. Most need a decision and an owner. Together they are also the groundwork for safe AI over your internal tools and data.
If you want an outside check before go-live, our AI audit and hardening engagement reviews AI systems against security standards and production practice, and fixes the critical gaps as part of the work.
Connecting agents to production systems and want a second opinion on the access model? Contact us and we will go through the checklist with you.
