1. ai
  2. /building
  3. /mcp-security

MCP Security

MCP gives agents the ability to call your tools. That is the same trust boundary as giving a junior engineer shell access — design for least privilege from day one.

Last reviewed: June 2026

MCP auth specs and SDK APIs evolve. Check modelcontextprotocol.io and your client's MCP settings before production deployment.

Threat model

ThreatExampleMitigation
Secret exfiltrationAPI key in mcp.json committed to gitEnv vars only; ${env:VAR} in config
Over-privileged toolsrun_sql accepts arbitrary queriesParameterized tools; read-only DB user
Prompt injection → tool abuseUser message tricks agent into destructive callHuman confirmation for writes; tool allowlists
Data leakage in responsesTool returns full user PIIReturn minimal fields; redact in handler
Supply chainUnvetted third-party MCP serverOrg allowlist; pin server versions
Lateral movementStaging MCP creds work on productionSeparate servers per environment

See also Security and Prompt Injection for user-facing LLM features and Security Anti-patterns for coding mistakes.

Secrets in client config

Never hardcode credentials in .cursor/mcp.json or Claude Code MCP settings:

{
  "mcpServers": {
    "staging-db": {
      "command": "node",
      "args": ["./mcp-server/dist/index.js"],
      "env": {
        "DATABASE_URL": "${env:DATABASE_URL}",
        "API_TOKEN": "${env:INTERNAL_API_TOKEN}"
      }
    }
  }
}
DoDon't
${env:VAR} referencesLiteral sk-... or connection strings in JSON
.env.local gitignored.env committed "for convenience"
Rotate on any leakReuse prod creds in dev MCP

Add .cursor/mcp.json to PR review when it changes — same scrutiny as CI config.

Server-side auth patterns

Read-only database user

Create a DB role with SELECT only on approved tables:

CREATE USER mcp_readonly WITH PASSWORD '...';
GRANT CONNECT ON DATABASE staging TO mcp_readonly;
GRANT SELECT ON users, orders TO mcp_readonly;
-- No INSERT, UPDATE, DELETE, DDL

Wire DATABASE_URL to that user in the MCP server's environment.

API token scoping

ScopeToken permissions
Docs searchRead-only search API
Ticket lookupRead issue by ID
Ticket createSeparate tool; requires confirmation param

Issue short-lived tokens per developer or per MCP server instance — not org-wide admin keys.

OAuth for remote MCP (2026)

Remote MCP servers may require OAuth 2.1 flows. The client opens a browser for consent; tokens refresh automatically. Verify your IDE's support — behavior differs between Cursor, Claude Code, and Codex.

Docs: MCP authorization spec (check current draft).

Tool design for security

server.tool(
  "lookup_user_by_email",
  { email: z.string().email().max(254) },
  async ({ email }) => {
    const user = await db.users.findByEmail(email);
    if (!user) return { content: [{ type: "text", text: "Not found" }] };
    // Return minimal fields — not full row
    return {
      content: [{
        type: "text",
        text: JSON.stringify({ id: user.id, status: user.status }),
      }],
    };
  }
);
PrincipleImplementation
Validate all inputsZod schemas; reject unknown fields
One action per toollookup_user, not manage_user
No raw SQL from modelFixed queries with bound parameters
Cap response sizePagination; truncate long JSON
Destructive ops gatedRequire explicit confirm: true param; log caller

Transport security

TransportTypical useNotes
stdioLocal dev; client spawns processProcess inherits env; isolate with dedicated user
SSE / HTTPRemote shared serversTLS required; auth on every request
DockerTeam-standardized serversPin image digest; no --privileged

For remote servers, terminate TLS at the edge and authenticate with OAuth or mTLS — not IP allowlists alone.

Audit and logging

Log at the MCP server, not only in the IDE:

async function auditLog(tool: string, argsHash: string, userId: string) {
  await logger.info({ tool, argsHash, userId, ts: Date.now() });
}
LogDo not log
Tool name, timestamp, user/session IDFull request bodies with PII
Hashed argument fingerprintRaw API keys or tokens
Success / failure statusEntire DB query results

Ship logs to your SIEM for production-adjacent servers (staging DB, internal APIs).

Team policy

PolicyRecommendation
AllowlistOnly approved MCP servers in Team AI Policy
No personal serversBan ad-hoc MCP on repos with prod secrets
PR reviewmcp.json + server code require platform team approval
Environment separationDifferent servers and creds for dev / staging / prod
OffboardingRevoke tokens when developer leaves

Pre-production checklist

  • [ ] Read-only credentials for exploration tools
  • [ ] Inputs validated with schema (Zod / Pydantic)
  • [ ] No secrets in repo or tool responses
  • [ ] Write tools require explicit confirmation
  • [ ] Audit log enabled
  • [ ] .cursorignore excludes .env*, keys, certs
  • [ ] Server tested with MCP Inspector before IDE connect
  • [ ] Incident runbook if token leaked (rotate, revoke, notify)