Back to Blog
Security

MCP Server Security: A Practical Guide for Developers

December 1, 2025Updated: Jan 13, 20268 min readBy Nikhil Tiwari

đź“– TL;DR

  • MCP servers expose tools that AI agents can call automatically—without human review
  • Remote MCP servers are HTTP APIs; treat them with the same security as any public API
  • Core protections: authentication, input validation, least-privilege access, and logging

What Is an MCP Server?

The Model Context Protocol (MCP) is an open standard created by Anthropic that allows AI assistants to interact with external tools and data sources. An MCP server is a program that exposes these tools via a standardized interface.

There are two types of MCP servers:

Type Transport Security Context
Local STDIO (standard input/output) Runs on your machine, inherits your permissions
Remote HTTP/SSE Accessible over the network, requires authentication

When you run an MCP server locally, it operates within your system's security boundary. But when you host an MCP server remotely, it becomes a network-accessible API that anyone could potentially reach.

Why MCP Security Is Different

Traditional APIs have a human in the loop—someone reviews requests, catches errors, and provides oversight. MCP servers are different because AI agents operate autonomously.

The Automation Problem

When an AI agent interacts with your MCP server:

  • It can make hundreds of tool calls per minute
  • It follows instructions literally, including malicious prompts
  • It won't question suspicious requests or ask for confirmation
  • Errors compound quickly without human intervention

This means a single vulnerability can be exploited at machine speed, causing damage before anyone notices.

Real Attack Vectors

Here are specific ways unsecured MCP servers can be compromised:

Attack How It Works Impact
Path Traversal Input like ../../etc/passwd escapes safe directories Access to system files, credentials, configs
Prompt Injection Malicious instructions embedded in data the AI reads AI performs unintended actions using your tools
Unauthenticated Access No API key or token verification Anyone can call your tools directly
Over-Privileged Tools Tools that can delete, modify, or access too much Accidental or malicious data destruction

Essential Security Controls

These are the minimum security measures every remote MCP server should implement:

1. Authentication

Verify the identity of every client before processing requests.

// Express middleware for API key auth
app.use((req, res, next) => {
  const apiKey = req.headers["authorization"]?.replace("Bearer ", "");
  
  if (!apiKey || apiKey !== process.env.MCP_API_KEY) {
    return res.status(401).json({ 
      error: "Invalid or missing API key" 
    });
  }
  next();
});

Options: API keys (simple), OAuth 2.0 (for user context), or JWT tokens (for stateless auth).

2. Input Validation

Never trust input from the AI agent. Validate everything.

// Validate file path to prevent traversal
function validateFilePath(userInput, allowedDir) {
  // Normalize and resolve the path
  const resolved = path.resolve(allowedDir, userInput);
  
  // Ensure it's still within the allowed directory
  if (!resolved.startsWith(path.resolve(allowedDir))) {
    throw new Error("Path traversal detected");
  }
  
  return resolved;
}

Common validations:

  • Reject paths containing .. or starting with /
  • Sanitize SQL/NoSQL queries to prevent injection
  • Validate data types and lengths match expected schemas
  • Whitelist allowed values for enum-type parameters

3. Least Privilege

Only expose the minimum tools and permissions needed.

  • Read-only by default — Add write permissions only when necessary
  • Scope access — Limit file tools to specific directories
  • Separate environments — Use different API keys for dev/staging/production
  • Time-limited tokens — Expire credentials regularly

4. Rate Limiting

Prevent abuse and runaway AI loops.

import rateLimit from "express-rate-limit";

const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 100, // 100 requests per minute
  message: { error: "Rate limit exceeded" }
});

app.use("/mcp", limiter);

5. Logging and Monitoring

You can't secure what you can't see.

Log these events:

  • All tool calls with parameters (redact sensitive values)
  • Authentication attempts (success and failure)
  • Errors and exceptions
  • Response times and sizes

Do NOT log:

  • Full API keys or tokens
  • Passwords or credentials
  • Personal identifiable information (PII)
  • Full file contents from read operations

Complete Secure Server Example

Here's a production-ready MCP server with all security controls:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
import rateLimit from "express-rate-limit";
import { readFile } from "fs/promises";
import path from "path";

const app = express();
app.use(express.json());

// Security: Rate limiting
const limiter = rateLimit({
  windowMs: 60 * 1000,
  max: 100,
  standardHeaders: true,
});
app.use(limiter);

// Security: Authentication middleware
function authenticate(req, res, next) {
  const token = req.headers["authorization"]?.replace("Bearer ", "");
  
  if (!token || token !== process.env.MCP_API_KEY) {
    console.log(`Auth failed: ${req.ip}`);
    return res.status(401).json({ error: "Unauthorized" });
  }
  next();
}

// Security: Input validation
function validatePath(filename, baseDir) {
  if (!filename || typeof filename !== "string") {
    throw new Error("Invalid filename");
  }
  
  // Block path traversal
  if (filename.includes("..") || path.isAbsolute(filename)) {
    throw new Error("Invalid path");
  }
  
  const resolved = path.resolve(baseDir, filename);
  if (!resolved.startsWith(path.resolve(baseDir))) {
    throw new Error("Path outside allowed directory");
  }
  
  return resolved;
}

// Create MCP server
const server = new McpServer({
  name: "secure-file-server",
  version: "1.0.0",
});

const SAFE_DIR = process.env.SAFE_DIR || "./public-files";

// Read-only tool with validation
server.tool(
  "readFile",
  "Read a file from the public directory",
  {
    filename: {
      type: "string",
      description: "Filename (no path traversal allowed)",
    },
  },
  async ({ filename }) => {
    const safePath = validatePath(filename, SAFE_DIR);
    
    try {
      const content = await readFile(safePath, "utf-8");
      console.log(`File read: ${filename}`);
      return { content: [{ type: "text", text: content }] };
    } catch (err) {
      throw new Error("File not found or not readable");
    }
  }
);

// List files tool (read-only)
server.tool(
  "listFiles",
  "List files in the public directory",
  {},
  async () => {
    const { readdir } = await import("fs/promises");
    const files = await readdir(SAFE_DIR);
    return { 
      content: [{ type: "text", text: files.join("\n") }] 
    };
  }
);

// Mount with authentication
app.all("/mcp", authenticate, async (req, res) => {
  const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
  await server.connect(transport);
  await transport.handleRequest(req, res, req.body);
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Secure MCP server running on port ${PORT}`);
});

Security Checklist

Use this checklist before deploying any remote MCP server:

Check Status
HTTPS enabled (not HTTP) Required
Authentication on all endpoints Required
Input validation on all tool parameters Required
Rate limiting configured Required
Logging enabled (without sensitive data) Required
Tools follow least-privilege principle Required
Error messages don't leak internals Required
API keys stored in environment variables Required

Local vs Remote: When to Use Which

Use Case Recommendation
Personal development tools Local (STDIO) — simpler, inherits your permissions
Team shared tools Remote with auth — controlled access for team members
Public API integration Remote with strong auth — full security stack required
Accessing sensitive systems Local only — keep within your security boundary

Key Takeaways

  • MCP servers are powerful — they give AI agents real capabilities in your systems
  • Remote servers need full API security — authentication, validation, rate limiting, logging
  • AI agents don't verify requests — your server must validate everything
  • Start with least privilege — only expose what's needed, nothing more
  • Log for visibility — you need to see what's happening to respond to issues

Test your MCP server: Try it in our MCP Playground →

Frequently Asked Questions

Do local MCP servers need authentication?
Local MCP servers using STDIO transport run within your system's security context—they already have your permissions. Authentication isn't typically needed since they're not network-accessible. However, if your local server can perform destructive actions (deleting files, modifying databases), consider adding confirmation steps or scope restrictions.
What's the difference between STDIO and HTTP transport?
STDIO transport uses standard input/output streams, meaning the MCP server runs as a subprocess of the AI client (like Claude Desktop). HTTP transport makes the server accessible over the network via HTTP requests. STDIO is simpler and more secure for local use; HTTP is required for remote/shared access but requires full API security measures.
How do I prevent prompt injection attacks?
Prompt injection occurs when malicious instructions are embedded in data your AI reads. To mitigate: (1) Validate and sanitize all inputs, (2) Don't let tools return raw user content directly to the AI without marking it as untrusted, (3) Limit what actions tools can perform, (4) Use separate contexts for trusted vs untrusted data.
Should I use API keys or OAuth for MCP authentication?
API keys are simpler and work well for server-to-server communication or personal use. OAuth 2.0 is better when you need user-level permissions or want to integrate with existing identity providers. For most MCP servers, API keys with proper rotation policies are sufficient.
What should I do if my MCP server is compromised?
Immediately: (1) Revoke all API keys and tokens, (2) Take the server offline, (3) Review logs to understand what was accessed, (4) Check for any data exfiltration or modifications, (5) Rotate all credentials the server had access to, (6) Fix the vulnerability before bringing it back online, (7) Notify affected users if their data was exposed.
NT

Nikhil Tiwari

15+ years of experience in product development, AI enthusiast, and passionate about building innovative solutions that bridge the gap between technology and real-world applications. Specializes in creating developer tools and platforms that make complex technologies accessible to everyone.