I audited an MCP setup for a client last quarter and found a filesystem server with read-write access to the developer’s entire home directory, a shell tool that ran arbitrary commands with no approval step, and an API key for a production database sitting in plain text inside a config file. The developer was not careless. He had followed a popular tutorial that treated MCP servers like browser extensions: install, enable, move on. That tutorial never mentioned that every tool you connect to a model is a capability an attacker can reach if they can influence the model’s inputs.
MCP servers are not plugins. They are privileged processes that act on your behalf, and the model deciding when to call them reads untrusted text all day. That combination is the whole threat. This post walks through the threat model I use for MCP deployments, then the hardening steps that actually close the gaps, in the order I apply them.
The Threat Model: Four Ways MCP Servers Get You
Before touching a config file, get clear on what can go wrong. I group MCP risk into four buckets.
Tool poisoning. The Model Context Protocol works by having each server advertise its tools: names, descriptions, parameter schemas. The model reads those descriptions to decide when and how to call each tool. Researchers at Invariant Labs showed in 2025 that a malicious server can hide instructions inside tool descriptions that the user never sees. A weather tool’s description can quietly tell the model to also read your SSH keys and pass them as a parameter. The user interface shows “get_weather” and the model does something else entirely. This is not theoretical; it is a demonstrated attack against stock clients.
Overprivileged access. Most community MCP servers ask for far more than they need. A filesystem server pointed at / or your home folder. A database server with a superuser connection string. A shell tool with no command filter. Once the model can be talked into calling the tool, the tool’s permissions become the attacker’s permissions.
Secrets in environment variables. The standard MCP config pattern puts API keys and tokens directly in the JSON config as environment variables. Those files sync to dotfiles repos, get pasted into Discord help threads, and sit unencrypted on disk. I have seen real client configs with production Stripe keys and database passwords in them.
Prompt injection through tool results. This is the one people miss. Even a trustworthy server returns data the model will treat as instructions. If your agent reads a web page, a ticket, or an email through an MCP tool, an attacker who controls that content can plant instructions the model may follow. I covered the general pattern in our prompt injection protection guide; MCP makes it worse because the injected instructions now have real tools behind them.
Rule One: Pin and Audit Every Server
Treat MCP servers like production dependencies, because that is what they are. If you would not npm install a random package without reading it, do not connect a random MCP server to a model that can touch your files.
Three concrete practices:
- Prefer official servers. Anthropic and the MCP project maintain reference servers for filesystem, Git, GitHub, Postgres, and others. Start there. Community servers go through a code read first, or they do not get installed.
- Pin versions. In your client config, reference exact versions or commit hashes, not
latest. A server that auto-updates can change its tool descriptions underneath you, which reopens the tool poisoning risk on every update. - Read the tool descriptions yourself. Before enabling a server, dump its tool list and read every description and schema. Anything that instructs the model to access unrelated files, URLs, or other tools is a red flag. So are descriptions longer than the tool’s actual function warrants.
If you are still picking your first servers, our guide on how to install MCP servers in VS Code covers the setup mechanics. The difference in this post is that setup is step one of ten, not the finish line.
Rule Two: Scope Everything to Least Privilege
Every MCP server should be able to touch exactly what its job requires and nothing else. This is boring advice and almost nobody follows it.
For the filesystem server, point it at a single project directory, never your home folder. The reference filesystem server accepts an allowlist of directories as arguments:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/[email protected]",
"/home/dev/projects/client-portal"
]
}
}
}
One directory. If the agent needs another directory later, add it deliberately.
For database access, create a dedicated read-only role for the agent. Do not hand the model your app credentials. A Postgres role with SELECT on the specific tables the agent needs takes five minutes to create and removes the entire class of “the model dropped a table” incidents. If the agent genuinely needs writes, scope them to specific tables and consider requiring approval, which I cover below.
For shell tools, either do not run them at all or wrap them in an allowlist of exact commands. git status, npm test, ls. No arbitrary execution. The moment a model can run any command, every prompt injection becomes remote code execution on your machine.

Rule Three: Sandbox the Servers Themselves
Least privilege at the tool level is necessary but not sufficient, because the server process itself can be compromised. The next layer is isolating the process.
The cleanest option in 2026 is running MCP servers in containers. Docker’s MCP toolkit and several community launchers run each server in its own container with a read-only root filesystem, no network access except what you explicitly grant, and CPU and memory limits. A compromised server inside that container can see almost nothing.
A minimal Docker-based pattern looks like this:
docker run --rm -i \
--network none \
--read-only \
--cap-drop ALL \
-v /home/dev/projects/client-portal:/data:ro \
mcp/filesystem:2026.1.8 /data
No network, read-only mount, all Linux capabilities dropped. If the server is malicious or gets hijacked, it cannot phone home and cannot write outside the mounted path.

If containers are too heavy for your setup, at minimum run MCP servers under a dedicated OS user with no sudo rights and no access to your personal files. On macOS and Linux this is an hour of work. On a team, make it standard. The broader discipline here mirrors what we recommend for agent systems generally: our post on unsupervised AI agents and runaway loops covers what happens when capable agents run without containment.
Rule Four: Fix Your Secrets Handling
Stop putting API keys in MCP config files. Two better patterns:
Use a secrets manager or OS keychain. Several MCP clients now support referencing secrets from the system keychain or from tools like 1Password CLI, so the config file holds a reference, not the key itself. The MCP specification’s security best practices document covers the transport and credential guidance the ecosystem is converging on.
Rotate anything that ever sat in plaintext. If a key lived in a config file that was committed, synced, or screenshotted, treat it as compromised and rotate it. This is unpleasant and it is also the only correct answer.
Also check where your configs live. Claude Desktop, Cursor, and VS Code each store MCP configs in predictable locations. Add those paths to your mental list of “files that must never be shared,” right next to .env.
Rule Five: Human Approval for Destructive Tools
Some tool calls should never execute without a person clicking yes. Deletes, writes to production, payments, emails to customers, anything public-facing. Most MCP clients support per-tool approval prompts; the defaults are often too permissive.
My rule of thumb: if undoing the action costs more than approving it, require approval. Reading a file, no approval. Writing a file in a scratch directory, no approval. Running a migration, pushing a branch, sending a message: approval every time.
Yes, approval fatigue is real, and a lazy fix is to bulk-approve everything. The better fix is to reduce what needs approval by tightening scopes (Rule Two) so the remaining prompts are rare and meaningful. If you approve forty prompts a day, your scopes are too wide, not your patience too thin.
Rule Six: Log Every Tool Call
You cannot audit what you did not record. Every MCP deployment beyond a toy should log, at minimum: timestamp, server name, tool name, parameters, and whether the call was approved or auto-run.
Some clients write these logs natively. Where they do not, a thin proxy between the client and the server can capture the JSON-RPC traffic; MCP’s stdio transport makes this straightforward. I pipe tool-call logs into the same place as application logs so a single query answers “what did the agent do between 2pm and 3pm yesterday.”

Logs pay for themselves the first time something weird happens. They also change behavior: developers are noticeably more careful about scopes when they know there is a record. For teams with compliance obligations, this log trail is not optional; auditors will ask what your AI systems did and “we think nothing bad” is not an answer.
When NOT to Harden This Way
Two honest caveats. First, if you are experimenting locally with toy data and no credentials anywhere on the machine, full containerization is overkill. Pin your servers, scope the filesystem, skip the rest until real data enters the picture. Second, do not harden around a use case that should not exist. If a workflow does not actually need tool access, the safest MCP server is the one you never installed. I have talked clients out of connecting agents to production systems entirely when a read-only export to a scratch database solved the same problem.
Also note what hardening does not fix. A determined prompt injection against a well-scoped agent can still waste tokens, return bad answers, or exfiltrate whatever the agent can legitimately see. Hardening shrinks the blast radius; it does not remove the need to think about what the agent can see in the first place. That design question sits upstream of everything in this post, and it is the same question behind good AI agent workflow design generally.
The Bottom Line
MCP servers turn a chat model into an actor on your systems. That is the point of the protocol, and it is also the risk. The hardening path is not exotic: pin and audit servers like dependencies, scope every tool to least privilege, run servers in containers, keep secrets out of config files, require approval for destructive calls, and log everything. Six steps, most of them an afternoon of work.

The failure mode I see is teams treating MCP setup as a one-time install task instead of a security boundary. Treat it as a boundary. The protocol’s own maintainers say as much, and the incident reports from 2025 onward back them up. If you are building agent workflows for a business and want a second set of eyes on the setup before production, that audit is exactly the kind of work we do at Veduis.



