Security Trade-offs: MCP vs CLI for Claude Code?
Should I use MCP servers or CLI tools with Claude Code? I kept seeing this question framed as a choice between convenience and security. But after evaluating both approaches in production, I found the security trade-offs are more nuanced than the community discussions suggest.
The Security Question Everyone Misses
When developers discuss MCP vs CLI, they focus on features and ease of setup. A Reddit comment shifted my perspective:
“Everyone here doesn’t understand the security benefits you get from a remotely hosted http mcp. Everything you install has more access than any remote service will ever have.”
This counter-intuitive insight made me reconsider my assumptions. Local CLI tools run with my user permissions. Remote MCP servers? They only get what I explicitly allow through HTTP requests.
MCP Security Model: Isolation Through Remote Hosting
Remote MCP servers provide security through limited attack surface. The server runs on infrastructure I don’t control, which paradoxically reduces my exposure.
┌─────────────────────────────────────────────────────────────┐│ My Machine ││ ┌──────────────┐ ││ │ Claude Code │ ──── HTTP ────> Remote MCP Server ││ └──────────────┘ (isolated environment) ││ │ ││ └─ Only HTTP requests can leave my machine ││ └─ No file system access from MCP to my machine ││ └─ No shell execution from MCP on my machine │└─────────────────────────────────────────────────────────────┘The MCP protocol supports granular permission controls. I configured sandbox policies for an enterprise deployment:
{ "mcpServers": { "production-api": { "command": "npx", "args": ["-y", "@company/mcp-api-server"], "env": { "API_URL": "https://api.company.internal", "API_TOKEN": "${PROD_API_TOKEN}" }, "sandbox": { "allowNetwork": ["api.company.internal"], "denyNetwork": ["*"], "allowRead": [], "allowWrite": [], "allowExecute": false } } }}This configuration ensures the MCP server can only reach our internal API. It cannot read files, write files, or execute commands on my machine.
Permission Scoping Without Wildcards
I learned to avoid wildcard permissions after seeing how quickly they escalate risk:
{ "mcpServers": { "github-tools": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }, "allowedTools": [ "search_repositories", "get_issue", "create_issue", "list_pull_requests" ], "denyTools": [ "delete_repository", "force_push", "update_repository_settings" ] } }}Explicit allowedTools means Claude can only call those four GitHub functions. The denyTools list adds a second layer of defense for destructive operations.
CLI Security Considerations: Full System Access
CLI tools run locally with my full user permissions. This transparency has benefits and risks:
┌─────────────────────────────────────────────────────────────┐│ My Machine ││ ┌──────────────┐ ││ │ Claude Code │ ││ └──────────────┘ ││ │ ││ ├──> CLI Tool ───> Full file system access ││ ├──> CLI Tool ───> Shell execution (rm, chmod, etc.) ││ └──> CLI Tool ───> Network access (unrestricted) ││ ││ Everything runs as my user with my permissions │└─────────────────────────────────────────────────────────────┘The upside: I can see exactly what each CLI tool does. The downside: malicious or compromised tools have full access.
Mitigation Through Scoped Permissions
I reduced CLI risk through aggressive scoping in my Claude Code settings:
{ "allowedTools": [ "Bash", "Read", "Write", "Edit" ], "toolPermissions": { "Bash": { "allowCommands": [ "git status", "git log", "git diff", "git branch", "npm run", "node", "python3" ], "denyCommands": [ "rm -rf", "sudo", "chmod", "curl * | *", "wget * | *" ], "requireApproval": [ "git push", "git reset --hard", "npm publish" ] } }}This configuration:
- Allows common safe commands without approval
- Blocks destructive commands entirely
- Requires explicit approval for potentially risky operations
Sandbox Network Restrictions
For CLI tools that need network access, I restricted which domains they can reach:
{ "toolPermissions": { "Bash": { "networkPolicy": { "allowDomains": [ "api.github.com", "registry.npmjs.org", "pypi.org" ], "denyDomains": ["*"], "allowLocalhost": false } } }}This prevents CLI tools from exfiltrating data to unexpected endpoints.
Remote MCP Hosting: When Isolation Matters
I deploy remote MCP servers in scenarios where isolation provides meaningful security benefits:
Enterprise Environments
✓ Sensitive data in managed services (databases, APIs)✓ Compliance requirements for data access auditing✓ Multi-tenant infrastructure with shared resources✓ Centralized secret management (HashiCorp Vault, AWS Secrets Manager)For these cases, I configure MCP servers to run on internal infrastructure:
apiVersion: apps/v1kind: Deploymentmetadata: name: mcp-api-serverspec: template: spec: securityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 seccompProfile: type: RuntimeDefault containers: - name: mcp-server image: company/mcp-api-server:v1.2.3 securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: - ALL env: - name: API_TOKEN valueFrom: secretKeyRef: name: mcp-secrets key: api-tokenThe MCP server runs as non-root with a read-only filesystem. Even if compromised, the container has limited options for persistence or lateral movement.
Cloud Infrastructure
When I work with cloud resources, remote MCP servers prevent credential exposure:
Risk: CLI tool with AWS credentials- Tool runs locally with full access to ~/.aws/credentials- Compromised tool could exfiltrate all account credentials
Alternative: Remote MCP with IAM role- MCP server runs on EC2 with instance profile- No credentials stored locally- Scoped IAM policy limits MCP server permissionsCLI Security Best Practices
For development work, I prefer CLI tools because they’re more efficient. But I follow strict security practices:
Practice 1: Pre-Allow Safe Tools
I pre-approve tools that have minimal risk:
{ "autoApproveTools": [ "Read", "Glob", "Grep" ]}These tools can only read, not modify. The risk is bounded.
Practice 2: Require Approval for Risky Operations
I configure approval requirements for destructive operations:
{ "requireApproval": { "Bash": [ "git push*", "git reset*", "npm publish", "docker push*", "kubectl delete*" ], "Write": [ "*.env", "*.pem", "*.key" ], "Edit": [ ".claude/settings.json", ".mcp.json" ] }}Claude must ask permission before these operations.
Practice 3: Deny Dangerous Commands
Some commands have no legitimate use case in AI-assisted development:
{ "denyCommands": [ "rm -rf /", "rm -rf ~", "> /dev/sda", "curl * | bash", "wget * | bash", "eval *", "exec *" ]}These are blocked at the configuration level.
Decision Framework: Security vs Efficiency
I developed a simple decision matrix for choosing between MCP and CLI:
┌─────────────────────────────────────────────────────────────┐│ Scenario │ Recommended Choice │├───────────────────────────────────────┼─────────────────────┤│ Production database access │ Remote MCP ││ Cloud infrastructure management │ Remote MCP ││ Multi-tenant SaaS operations │ Remote MCP ││ Secrets/credentials management │ Remote MCP ││ Compliance-regulated data │ Remote MCP │└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐│ Scenario │ Recommended Choice │├───────────────────────────────────────┼─────────────────────┤│ Local file editing │ CLI with scoping ││ Git operations on local repos │ CLI with scoping ││ Running tests/linters │ CLI with scoping ││ Package installation (dev) │ CLI with scoping ││ Docker build (local) │ CLI with scoping │└─────────────────────────────────────────────────────────────┘Hybrid Configuration
I use both approaches together. Sensitive operations go through remote MCPs; development work uses CLI tools:
{ "mcpServers": { "prod-database": { "url": "https://mcp.internal.company.com/database", "sandbox": { "allowNetwork": ["db.internal.company.com"], "allowRead": [], "allowWrite": [] } }, "secrets-manager": { "url": "https://mcp.internal.company.com/secrets", "sandbox": { "allowNetwork": ["vault.internal.company.com"], "allowRead": [], "allowWrite": [] } } }, "toolPermissions": { "Bash": { "allowCommands": ["git", "npm", "node", "python3"], "denyCommands": ["sudo", "rm -rf /*"], "requireApproval": ["git push", "npm publish"] } }}Production databases and secrets flow through isolated MCP servers. Local development uses CLI tools with strict permissions.
Summary
In this post, I showed the security trade-offs between MCP servers and CLI tools for Claude Code. Remote MCP servers provide isolation through limited attack surface - they can only make HTTP requests, not access my file system or execute commands. CLI tools run with full local permissions, which is more efficient but requires careful scoping. For enterprise environments and sensitive data, remote MCP hosting offers better isolation. For development work, CLI tools with proper security practices (pre-allowed safe tools, approval for risky operations, denied dangerous commands) are safe and more efficient. The best approach combines both: remote MCPs for sensitive operations, CLI tools with strict scoping for development.
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments