MCP Permission Audit for Cursor
Cursor MCP servers run with your system permissions. Audit checklist: what each server can access, credential scoping, network exposure, data exfiltration risk.
MCP servers in Cursor can make network calls, read files, and execute commands without explicit per-action approval from the user. Once you add an MCP server to your configuration, it runs with whatever permissions its implementation claims. Auditing what your MCP servers actually access -- and restricting their scope -- is a security requirement, not a nice-to-have.
How MCP permissions work in Cursor
When you add an MCP server to Cursor's configuration, the AI assistant can invoke any tool that server exposes. The server itself determines what those tools do. A "search" MCP might only make HTTP requests to a search API. A "filesystem" MCP might read and write any file on your machine. Cursor shows you which tools exist but does not sandbox their execution. The trust boundary is the MCP server implementation itself.
# Cursor MCP configuration (~/.cursor/mcp.json or project .cursor/mcp.json)
# Each server gets full access to whatever its tools implement
# Example: what you configure
mcp_config = {
"mcpServers": {
"scavio": {
"url": "https://mcp.scavio.dev/mcp",
"headers": {
"Authorization": "Bearer sk-your-api-key"
}
},
"filesystem": {
"command": "npx",
"args": ["-y", "@anthropic/filesystem-mcp", "/home/user/projects"]
},
"shell": {
"command": "npx",
"args": ["-y", "mcp-shell-server"]
}
}
}
# Risk levels:
# scavio: LOW -- makes HTTP calls to a specific API with your key
# filesystem: MEDIUM -- reads/writes files within the specified directory
# shell: HIGH -- executes arbitrary shell commands on your machineAudit step 1: inventory your MCP servers
import json, os
from pathlib import Path
def audit_mcp_configs() -> list:
"""Find and catalog all MCP server configurations."""
configs = []
# Check common locations
paths = [
Path.home() / ".cursor" / "mcp.json",
Path.cwd() / ".cursor" / "mcp.json",
Path.cwd() / ".mcp.json",
Path.home() / ".claude" / "mcp.json",
]
for path in paths:
if path.exists():
data = json.loads(path.read_text())
servers = data.get("mcpServers", {})
for name, config in servers.items():
entry = {
"name": name,
"config_path": str(path),
"type": "remote" if "url" in config else "local",
"url": config.get("url", ""),
"command": config.get("command", ""),
"args": config.get("args", []),
"has_auth": bool(config.get("headers", {})),
}
configs.append(entry)
return configs
servers = audit_mcp_configs()
for s in servers:
risk = "HIGH" if s["command"] in ["bash", "sh"] or "shell" in s["name"] else "MEDIUM" if "filesystem" in str(s["args"]) else "LOW"
print(f"[{risk}] {s['name']} ({s['type']}) -- {s['config_path']}")Audit step 2: log what MCP servers actually do
# Add a logging proxy between Cursor and your MCP servers
# This records every tool invocation for review
import logging
from datetime import datetime
logging.basicConfig(
filename="mcp_audit.log",
level=logging.INFO,
format="%(asctime)s %(message)s",
)
def log_mcp_call(server_name: str, tool_name: str, params: dict):
"""Log every MCP tool invocation."""
entry = {
"timestamp": datetime.utcnow().isoformat(),
"server": server_name,
"tool": tool_name,
"params": params,
}
logging.info(json.dumps(entry))
# Alert on high-risk operations
high_risk_tools = ["execute", "shell", "write_file", "delete", "run_command"]
if tool_name in high_risk_tools:
logging.warning(f"HIGH RISK: {server_name}/{tool_name} with {params}")
# Review mcp_audit.log weekly to understand actual tool usage patternsAudit step 3: restrict scope
# Filesystem MCP: restrict to specific directories
# BAD: gives access to entire home directory
bad_config = {
"filesystem": {
"command": "npx",
"args": ["-y", "@anthropic/filesystem-mcp", "/home/user"]
}
}
# GOOD: restrict to project directory only
good_config = {
"filesystem": {
"command": "npx",
"args": ["-y", "@anthropic/filesystem-mcp", "/home/user/projects/myapp/src"]
}
}
# API keys: use scoped keys when possible
# BAD: admin API key with full access
# GOOD: read-only key scoped to specific endpoints
api_key_rules = {
"scavio": "Create a separate API key for MCP with usage limits",
"database": "Use read-only credentials, never read-write",
"cloud_provider": "Use IAM roles with minimum permissions",
"git": "Use fine-grained personal access tokens, not classic tokens",
}Security checklist for MCP deployments
- Inventory: list every MCP server in every config file (project + global)
- Classify risk: remote API (low), filesystem (medium), shell/command (high)
- Scope credentials: use read-only keys, limit to specific directories/resources
- Audit logs: record tool invocations and review weekly
- Separate configs: project-level MCP for project tools, never global for everything
- Review dependencies:
npx -yinstalls packages at runtime -- pin versions - Network monitoring: check if MCP servers make unexpected outbound connections
- Remove unused servers: if you added an MCP server to test and forgot about it, remove it
Pin MCP server versions
# BAD: installs latest version every time (supply chain risk)
# "args": ["-y", "@anthropic/filesystem-mcp"]
# GOOD: pin to specific version
# "args": ["-y", "@anthropic/filesystem-mcp@1.2.3"]
# BEST: install globally once, reference the installed binary
# npm install -g @anthropic/filesystem-mcp@1.2.3
# Then in config:
# "command": "filesystem-mcp",
# "args": ["/home/user/projects/myapp/src"]MCP is powerful because it gives AI assistants direct access to tools and data. That same power makes it a security surface. Most developers add MCP servers during setup and never audit them again. Spend 30 minutes running through this checklist. Remove servers you do not use, scope credentials to minimum access, and add logging so you can review what your MCP servers actually do in practice.