MCP Security Best Practices for Production
MCP servers are powerful — they give AI access to your files, databases, and services. Here's how to deploy them safely.
The Security Landscape
MCP servers run with real permissions on real systems. Unlike a chatbot that can only generate text, an MCP-connected AI can read files, execute queries, make API calls, and modify data. This power demands careful security practices.
The good news: MCP's architecture has security built into its design. The challenge is configuring and deploying it correctly.
Principle 1: Least Privilege
The most important security principle for MCP: grant only the minimum access necessary.
Filesystem Access
// ❌ Don't expose your entire home directory
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you"]
// ✅ Expose only the specific project directory
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/you/projects/current-project"]
Database Access
-- ❌ Don't use admin/root database credentials
-- ✅ Create a read-only user for the MCP server
CREATE USER mcp_reader WITH PASSWORD 'secure_password';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO mcp_reader;
-- Only grant write access if the server specifically needs it
API Tokens
When configuring API tokens for MCP servers (GitHub, Slack, etc.), create tokens with minimal scopes. If the server only needs to read repos, don't give it write access.
Principle 2: Input Validation
If you're building custom MCP servers, validate all inputs rigorously:
// Validate and sanitize file paths
server.tool("read_config", "Read a config file", {
filename: z.string()
.regex(/^[a-zA-Z0-9_-]+\.json$/) // Only allow safe filenames
.describe("Config filename (e.g., 'app.json')")
}, async ({ filename }) => {
// Prevent path traversal
const safePath = path.join(CONFIG_DIR, path.basename(filename));
if (!safePath.startsWith(CONFIG_DIR)) {
return { content: [{ type: "text", text: "Invalid path" }], isError: true };
}
const content = await fs.readFile(safePath, "utf-8");
return { content: [{ type: "text", text: content }] };
});
Common Injection Attacks to Prevent
- Path traversal:
../../etc/passwd— Always usepath.basename()and validate resolved paths - SQL injection: Use parameterized queries, never string concatenation
- Command injection: Never pass user input directly to
exec()or shell commands - Prompt injection: Be aware that AI-generated inputs may contain adversarial content
Principle 3: Secure Credential Management
Never hardcode credentials in MCP server code or configuration files that get committed to git.
Environment Variables
// In claude_desktop_config.json — credentials via env vars
{
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
}
}
}
Important: The Claude Desktop config file contains your tokens in plain text. Ensure this file has appropriate permissions:
# macOS/Linux: Restrict to owner only
chmod 600 ~/Library/Application\ Support/Claude/claude_desktop_config.json
Secret Managers
For production deployments, use a secret manager (AWS Secrets Manager, HashiCorp Vault, etc.) instead of environment variables.
Principle 4: Audit and Monitor
Review Server Source Code
Before installing any MCP server, especially from unknown publishers:
- Check the GitHub repository — look at stars, contributors, and recent activity
- Review the source code, especially around file access and network calls
- Check if the server is listed in the MCP Hub directory (verified servers are reviewed)
- Look for known vulnerabilities in dependencies (
npm audit)
Log MCP Activity
If building production servers, log all tool invocations:
server.tool("sensitive_operation", "...", schema, async (args) => {
console.error(JSON.stringify({
timestamp: new Date().toISOString(),
tool: "sensitive_operation",
args: args,
// Don't log sensitive data like passwords
}));
// ... execute operation
});
Principle 5: Network Security
Local vs Remote Servers
Local servers (stdio) communicate through standard input/output — no network exposure. This is the most secure transport.
Remote servers (SSE/HTTP) communicate over the network. For these:
- Always use TLS (HTTPS)
- Implement authentication (API keys, OAuth)
- Use network-level access controls (firewalls, VPNs)
- Rate limit requests to prevent abuse
Don't Expose Internal Services
An MCP server should never be the only security boundary for internal services. If your MCP server connects to an internal database, ensure the database itself has proper access controls.
Principle 6: Data Protection
- Sensitive data handling: Be careful about what data MCP servers return. The AI model processes all returned data and may include it in responses
- PII awareness: If your server accesses personal data, ensure compliance with GDPR, CCPA, etc.
- Data minimization: Return only the data the AI needs, not entire database dumps
Security Checklist
Use this checklist before deploying MCP servers to production:
- ☐ Servers use minimum required permissions
- ☐ All file paths are validated and sandboxed
- ☐ Database connections use read-only users where possible
- ☐ API tokens have minimal scopes
- ☐ Config files have restricted OS permissions
- ☐ Server source code has been reviewed
- ☐ Dependencies are up to date (
npm audit) - ☐ Input validation is in place for all tools
- ☐ Logging is enabled for sensitive operations
- ☐ Remote servers use TLS and authentication
FAQ
Are MCP servers secure by default?
MCP servers running locally over stdio are as secure as any local program — they run with your user permissions. However, remote MCP servers need additional security measures like TLS, authentication, and input validation.
Can MCP servers access all my files?
Only if configured to. The filesystem server only accesses directories you explicitly specify in the configuration. Always use the principle of least privilege.
Should I audit MCP servers before installing them?
Yes, especially for servers from unknown sources. Check the GitHub repository, review the source code, and only install servers from trusted publishers.