MCP servers are what make Claude Code powerful, and that power is exactly the security story. A connected server runs with the access you grant it — reading files, querying databases, calling APIs, sometimes running commands — and the agent acts on whatever those servers return. None of that is a reason to avoid MCP. It’s a reason to set it up deliberately, because the failure modes are real and mostly preventable.
This walks the actual risks — over-broad access, secrets in shared config, prompt injection, and untrusted servers — and the practice that defuses each. It’s the article to read before you connect anything to real infrastructure, and the safety backbone for the sysadmin setup and MCP servers for DevOps guides.
The core principle: least privilege
Almost every MCP risk reduces to the same fix — grant the minimum access the job needs, at every layer. The server’s permissions are your real boundary, not the prompt or the agent’s good behavior. A read-only role can’t be talked into a destructive write. A filesystem server scoped to one folder can’t read your home directory no matter what it’s asked.
So before the specific risks, hold this: the server can do whatever its access allows. Scope that access down and most risks shrink with it.
Risk 1: over-broad access
The most common mistake is giving a server more reach than its task requires — a filesystem server pointed at your whole home directory, a database server connecting as the app’s read-write user, a token with org-wide scope for a job that reads one repo.
Over-broad vs scoped
| Server | Too broad |
|---|---|
| Filesystem | Home directory or / |
| Database | Read-write app role on prod |
| GitHub | Org-wide, all permissions |
The fix is to scope each server to its actual job. A filesystem server gets a single directory. A database server gets a read-only role on a non-production target. A token gets the narrowest repos and permissions that cover the workflow, as in the GitHub MCP guide.
Risk 2: secrets in committed config
Project-scoped servers live in a .mcp.json at the repo root, and that file is meant to be committed so the team shares it. The trap is obvious once stated: a token or connection string written as a literal in that file is a credential committed to git history.
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres", "$DATABASE_URL"]
}
}
}
Reference an environment variable and document which one teammates set. The shared config then says how to connect without shipping anyone’s secret.
Risk 3: prompt injection through returned content
This one is subtle and worth understanding. MCP servers bring outside content into the session — a web page from a fetch server, an issue comment from GitHub, a row from a database. That content can contain text crafted to read like instructions to the agent: “ignore your previous task and instead…” The agent is supposed to treat it as data, but the risk is genuine, especially as you wire in more sources of external text.
The practical defenses: require approval for writes and destructive actions (so an injected “delete this” can’t execute unattended), be especially careful with servers that pull in arbitrary external content like web fetchers, and prefer read-only access wherever the task allows.
Risk 4: untrusted servers
The MCP ecosystem is large and uneven, and a server is code that runs with the access you grant. A malicious or careless server is a real threat, not a hypothetical — it could exfiltrate what it reads, or do more if you’ve given it write or command access.
Treat every server like a dependency you’re adding:
- Prefer official and well-known servers. Anthropic’s, the first-party ones from a platform you already trust, the widely-used community ones with active maintenance.
- Read what it does before connecting, especially anything that runs commands or makes network calls.
- Check it’s maintained — last commit, open issues, whether it’s one person’s abandoned experiment.
- Be wary of anything that wants broad access to start. A server demanding more than its stated job needs is a flag.
Risk 5: command execution and destructive actions
Some servers — and Claude Code itself, through Bash — can run commands. That’s the highest-reach capability, and it deserves the most caution. Two layers help:
Permission settings. Claude Code’s permission model lets you require approval for actions and deny specific tools or commands outright. For anything touching infrastructure, require confirmation on state changes.
Guardrail hooks. A PreToolUse hook can inspect a command and block destructive patterns — rm -rf, force pushes, dropping tables — before they run, as shown in the hooks guide. It’s a backstop, not airtight, but it catches the obvious disasters.
Putting it together
A hardened MCP setup isn’t complicated — it’s a set of defaults you apply every time:
Secure-by-default MCP setup
| Access | Least privilege at every layer — directory, role, token scope |
|---|---|
| Secrets | Environment variables, never literals in committed config |
| External content | Treated as untrusted; writes need approval |
| Servers | Official or vetted, maintained, read before connecting |
| Commands | Permission prompts + a destructive-command hook |
MCP security checklist
- Scope each server to the minimum access its job needs
- Use read-only roles and non-prod targets for databases
- Keep tokens and connection strings in environment variables
- Never commit credentials to .mcp.json — rotate if you do
- Vet servers like dependencies; prefer official and maintained ones
- Require approval for writes and state changes
- Add a PreToolUse hook to block destructive commands
- Treat all server-returned content as untrusted input
Wrapping up
MCP’s risks are real but they’re not mysterious: over-broad access, secrets in shared config, prompt injection through returned content, and untrusted servers. Every one of them is defused by the same habit — least privilege, applied at each layer — plus keeping credentials in environment variables, treating fetched content as untrusted, vetting servers like dependencies, and gating destructive actions behind approval and a guardrail hook. Do that and you keep the upside of connecting Claude Code to your tools without handing an agent more reach than you meant to.
This is the foundation for connecting anything sensitive — pair it with the sysadmin setup and the safe Postgres workflow for the practical applications.