MCP Security Guide: How to Safely Give AI Access to Your Systems

The uncomfortable truth: An MCP server with full database access means your AI can DROP TABLE. Without proper security, MCP is a footgun. Here’s how to do it safely.

The Risk Spectrum

  • Low risk: Read-only access to public data (documentation, search)
  • Medium risk: Read access to internal data (databases, files, analytics)
  • High risk: Write access to anything (creating PRs, sending emails, modifying data)
  • Critical risk: Infrastructure access (deploying code, managing servers, deleting resources)

Rule #1: Principle of Least Privilege

Never give an MCP server more access than the specific task needs.

Bad: Database MCP with root credentials (can read/write/delete anything)

Good: Database MCP with a read-only user that can only access specific tables

-- Create a restricted database user for MCP
CREATE USER mcp_readonly WITH PASSWORD 'xxx';
GRANT SELECT ON users, orders, products TO mcp_readonly;
-- NO INSERT, UPDATE, DELETE granted

Rule #2: Scope API Keys

When connecting to external services:

  • GitHub: Create a fine-grained token with only the repos and permissions needed
  • AWS: Use an IAM role with minimal policy, not admin credentials
  • Stripe: Use restricted keys (read-only for analytics, limited write for specific operations)

Rule #3: Add Confirmation for Destructive Actions

Your MCP server should require human confirmation before:

  • Deleting anything
  • Sending emails/messages
  • Deploying to production
  • Modifying financial data
  • Changing permissions
server.tool("delete_record", { id: z.string() }, async ({ id }) => {
  // Return confirmation prompt instead of executing
  return {
    content: [{ type: "text", 
      text: `CONFIRMATION REQUIRED: Delete record ${id}? This cannot be undone. Reply 'confirm delete ${id}' to proceed.`
    }]
  };
});

Rule #4: Audit Everything

Log every tool call with: timestamp, who triggered it, what was called, what parameters were passed, what was returned.

// Wrap every tool with audit logging
function auditedTool(name, schema, handler) {
  return server.tool(name, schema, async (args) => {
    console.log(JSON.stringify({ ts: Date.now(), tool: name, args }));
    const result = await handler(args);
    console.log(JSON.stringify({ ts: Date.now(), tool: name, result: "completed" }));
    return result;
  });
}

Rule #5: Don’t Expose in Production Without Auth

Remote MCP servers (HTTP transport) MUST have authentication. Anyone who knows the URL can call your tools otherwise.

  • Use OAuth2 for remote MCP servers
  • Use API key validation in middleware
  • Never expose on public internet without auth

Common MCP Security Mistakes

  • Using personal access tokens instead of scoped service accounts
  • Giving write access when only read is needed
  • Not rotating credentials after setup
  • Running MCP servers as root/admin
  • Trusting AI output without validation for critical operations

The Safe Default

Start with read-only access. Prove the use case works. Then selectively add write permissions for specific operations with human-in-the-loop confirmation.

Learn to build secure AI systems at hackathons on Reskilll

Scroll to Top