MCP Security Best Practices: A Config Checklist
Six MCP security best practices for your config file: pin versions, keep tokens out, require sign-in, keep approval prompts on, limit tools, know your files.
An MCP server is a program your AI agent starts, or a remote service it calls, and its tools act with whatever access you give them. Most of that access is decided in one small config file. This checklist covers six things to get right in that file, in Claude Code, Cursor, VS Code, Codex and Gemini CLI.
Each item says why it matters, what to do, and how to check it. For the questions to ask before you add a server at all, such as who publishes it and what it can reach, see the companion checklist: https://deepsweep.ai/blog/mcp-server-security-checklist
1. Pin every server to an exact version
Why. npx some-mcp-server@latest, npx some-mcp-server with no version, uvx some-mcp-server with no version, and a Docker image tagged :latest or with no tag all fetch whatever was published most recently. The code your agent runs can change without you doing anything. If a package is taken over, or a release changes what a tool does, the next start runs the new code with the same access.
What to do. Pin an exact version for packages, and a version tag or, better, a sha256 digest for images. Move the pin only after you have looked at what changed. The versions below are placeholders.
// Risky
"args": ["-y", "some-mcp-server@latest"]
"command": "uvx", "args": ["some-mcp-server"]
"args": ["run", "-i", "--rm", "ghcr.io/example/mcp-server:latest"]
// Safer
"args": ["-y", "some-mcp-server@1.2.3"]
"command": "uvx", "args": ["some-mcp-server@1.2.3"]
"args": ["run", "-i", "--rm", "ghcr.io/example/mcp-server@sha256:<digest>"]In Codex, the same goes for command and args under each [mcp_servers.<name>] table in config.toml.
Check it. For each npx, uvx or docker line this prints, look for an exact version after the package name, or an image tag other than latest:
grep -snE '@latest|:latest|npx|uvx|docker' .mcp.json .cursor/mcp.json .vscode/mcp.json .gemini/settings.json .codex/config.toml ~/.claude.json ~/.cursor/mcp.json ~/.gemini/settings.json ~/.codex/config.tomlChecked by DeepSweep. It flags npx, uvx, pipx and docker launches that have no exact version or digest.
2. Keep tokens out of the config file
Why. Many servers need a token for the service they talk to, and the quickest way to give them one is to paste it into the server's env block. Then anything that can read the file can read the token: other extensions, other agents, backups, and git if the file is ever committed.
What to do. Reference an environment variable, or use your editor's secret prompt, which asks once and stores the value outside the file. The syntax differs by tool:
// Claude Code, .mcp.json
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
// Cursor, mcp.json
"env": { "GITHUB_TOKEN": "${env:GITHUB_TOKEN}" }
// Gemini CLI, settings.json
"env": { "GITHUB_TOKEN": "$GITHUB_TOKEN" }
// VS Code, mcp.json: asks once and stores it for you
"env": { "GITHUB_TOKEN": "${input:github-token}" }
"inputs": [
{ "type": "promptString", "id": "github-token", "password": true }
]# Codex, config.toml: pass the variable through from your environment
[mcp_servers.github]
env_vars = ["GITHUB_TOKEN"]For a remote server, the same goes for the token in its header: "Bearer ${MY_SERVER_TOKEN}" in Claude Code's headers, or bearer_token_env_var in Codex.
Then rotate any token that was already written in the file. It has sat on disk in plain text, and maybe in git history too.
Check it. A value that starts with $ or ${ is a reference. A long string of letters and numbers is probably the real thing:
grep -sniE 'token|key|secret|password' .mcp.json .cursor/mcp.json .vscode/mcp.json .gemini/settings.json ~/.cursor/mcp.json ~/.gemini/settings.json ~/.codex/config.tomlChecked by DeepSweep. It finds tokens and keys written in env, headers, URLs and args, and shows where each one is, never the value.
3. Prefer remote servers that require sign-in
Why. A remote server is a url in your config instead of a command. Every tool call, and whatever data it carries, goes to someone else's machine. A server that requires sign-in ties each call to you, limits the token to what you approved, and lets you revoke it. A server with no sign-in at all is fine for public data, and a poor choice for anything that touches your code, accounts or customers.
What to do. Prefer servers that sign you in, usually through your browser. In Claude Code you finish sign-in from /mcp, in Codex with codex mcp login <name>, and in Gemini CLI with /mcp auth <name>. Cursor asks when a server requires it. If a server takes a token instead, send it in a header from a variable, as in item 2, never written out.
// Claude Code, .mcp.json: the token comes from a variable
"my-server": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ${MY_SERVER_TOKEN}" }
}Check it. For each url in your configs, find out how the server signs you in. If it does not, decide whether that is acceptable for what it can reach.
Checked by DeepSweep. It flags a remote server whose entry sets no sign-in, so you can confirm the server asks for one.
4. Keep approval prompts on
Why. By default, agents ask before they start a project's servers or call their tools. Two settings turn that question off in bulk, so one decision covers every tool a server has now and every tool a later release adds.
Gemini CLI. A server with "trust": true in settings.json skips every confirmation for that server's tools. Gemini CLI's own docs say to use it only for servers you completely control. Remove it, or set it to false.
Claude Code. "enableAllProjectMcpServers": true approves every server in a project's .mcp.json without asking, and a repository you clone can list any server it likes in that file. Remove the setting and approve servers one at a time when Claude Code asks. To block one for good, add its name to disabledMcpjsonServers.
// Gemini CLI, settings.json: remove this line
"trust": true
// Claude Code, settings.json: remove this line
"enableAllProjectMcpServers": trueThe same goes for settings that approve every tool at once, such as VS Code's chat.tools.global.autoApprove. The guide to what your agent can do without asking covers those: https://deepsweep.ai/blog/ai-coding-agent-auto-approve-settings
Check it.
grep -snE '"trust"|enableAllProjectMcpServers' ~/.gemini/settings.json .gemini/settings.json ~/.claude/settings.json .claude/settings.json .claude/settings.local.jsonChecked by DeepSweep. It flags trust: true in every MCP config it reads, and enableAllProjectMcpServers in your Claude Code settings files.
5. Review the tools each server exposes
Why. One server can offer dozens of tools. Some only read. Others write files, send messages, delete things or run commands. Your agent decides which one to call from the tool's name and description, and that text comes from the server.
What to do. List a server's tools before you rely on it, and read what each one does. In Claude Code and Gemini CLI, /mcp shows them. A description that gives the agent instructions about something else, such as reading a file and including its contents, is a warning sign. More on how that works: https://deepsweep.ai/blog/tool-poisoning-confused-deputy-mcp
Then turn off the tools you do not need, where your tool lets you:
// Gemini CLI, settings.json: only these tools
"github": {
"command": "...",
"includeTools": ["get_issue", "list_issues"]
}
// Claude Code, settings.json: allow reads, block deletes
"permissions": {
"allow": ["mcp__github__get_*"],
"deny": ["mcp__github__delete_*"]
}# Codex, config.toml: only these tools
[mcp_servers.github]
enabled_tools = ["get_issue", "list_issues"]Look again after each update you take. A new release can add tools, and with a pinned version you decide when that happens.
6. Know which config files your editor reads
Why. Servers can be listed in several files, and most tools load all of them. A server you removed from one file can still start from another. And project files arrive with the code: clone a repository and you get its MCP config too.
Claude Code .mcp.json
~/.claude.json (yours, global and per project)
Cursor .cursor/mcp.json
~/.cursor/mcp.json
VS Code .vscode/mcp.json and .mcp.json
mcp.json in your user profile
Codex .codex/config.toml (trusted projects only)
~/.codex/config.toml and profile files
Gemini CLI .gemini/settings.json
~/.gemini/settings.jsonIn each pair, the first file sits in the project and the second applies to you everywhere. In VS Code, the command MCP: Open User Configuration opens the user file. Claude Code and Gemini CLI can also get servers from plugins or extensions, and Claude Code from your organization's settings. To see the full list Claude Code will load, run claude mcp list. For Codex, run codex mcp list.
What to do. Read a project's MCP config before you open it with an agent, and before you accept any trust prompt. Keep the servers you use everywhere in your user file, and the servers a project needs in the project file, so each one has one home.
Related: how to lock down Claude Code permissions: https://deepsweep.ai/blog/claude-code-permissions
And what your agent can do without asking, across Claude Code, Codex, VS Code and Gemini CLI: https://deepsweep.ai/blog/ai-coding-agent-auto-approve-settings