MCP servers are network services. They accept input from untrusted clients, make outbound requests, handle credentials, and run on shared infrastructure. The protocol itself is well-designed, but the security posture of most deployments is an afterthought.

We have tested hundreds of MCP servers through our compliance suite and proxied production traffic for dozens more. This is the checklist we wish every server operator had before going live.

1. Block SSRF from tool handlers

If any of your tools make outbound HTTP requests based on user input - fetching a URL, calling an API, loading a resource - you are exposed to Server-Side Request Forgery.

An attacker sends a tool call with a URL pointing to http://169.254.169.254/latest/meta-data/ (AWS instance metadata), http://metadata.google.internal/ (GCP), or a private RFC 1918 address. Your server dutifully fetches it and returns the result.

The fix has two parts:

// Validate resolved IP before connecting
import { isPrivateIP } from './network-utils';

async function safeFetch(url: string): Promise<Response> {
  const resolved = await dns.resolve4(new URL(url).hostname);
  for (const ip of resolved) {
    if (isPrivateIP(ip)) {
      throw new Error(`Blocked request to private IP: ${ip}`);
    }
  }
  return fetch(url);
}

We flagged this in our compliance findings. It remains the most dangerous gap in the ecosystem for any platform that accepts user-defined server URLs.

2. Never log credentials or tool arguments verbatim

MCP tool calls carry arguments in the params.argumentsfield. Those arguments frequently contain API keys, database connection strings, OAuth tokens, and other secrets that users paste into tool inputs.

If your logging pipeline writes tool call arguments to disk, to a log aggregator, or to an analytics store, you are storing credentials in plaintext. This is not hypothetical - we have seen it in production.

Rules:

3. Validate tool input schemas strictly

The MCP spec defines inputSchema on each tool. Most servers declare the schema but never validate incoming arguments against it. The result: tools receive unexpected types, extra fields, or missing required parameters and either crash or behave unpredictably.

This matters for security because an overly permissive tool handler can be manipulated through prompt injection. An LLM that has been tricked into calling a tool with crafted arguments will send whatever the attacker dictates. Your validation layer is the last line of defense.

// Validate arguments against the declared schema
import Ajv from 'ajv';
const ajv = new Ajv();

function validateToolCall(tool: Tool, args: unknown): void {
  const validate = ajv.compile(tool.inputSchema);
  if (!validate(args)) {
    throw {
      code: -32602,
      message: `Invalid params: ${ajv.errorsText(validate.errors)}`,
    };
  }
}

4. Scope everything to the authenticated tenant

We covered this in the auth post, but it bears repeating as a security concern specifically. If your MCP server is multi-tenant, every data path needs tenant scoping:

The pattern: extract the tenant identifier from the auth token at the middleware layer and thread it through every downstream call. Do not rely on individual tool handlers to remember to scope their queries.

5. Rate limit at the session and tool level

API-level rate limiting (requests per second per key) is table stakes. MCP servers need additional limits that account for how AI agents behave:

The cleanest place to enforce these limits is at the infrastructure layer - a reverse proxy or API gateway in front of the MCP server - so individual servers do not need to implement it themselves.

6. Use TLS everywhere, including local development

Remote MCP servers must use HTTPS. This is non-negotiable. But even local stdio servers should be aware of transport security:

7. Protect against prompt injection in tool results

Tool results are fed back into the LLM context. If a tool returns user-controlled content (web page text, database records, file contents), an attacker can embed instructions that manipulate the LLM’s behavior.

This is not something your MCP server can fully solve - it is fundamentally a client-side concern. But you can reduce the attack surface:

8. Audit every tool call

Every tool invocation should produce an audit record: who called it, when, which tool, success or failure, and the tenant context. You do not need to log arguments (see point 2), but you need to know what happened.

This is not just for security incidents. When a user reports that “the AI did something unexpected,” your audit log is the only way to reconstruct what actually happened. MCP tool calls are the actions an AI agent takes in the world. You need to be able to trace them.

9. Pin dependency versions in production

MCP servers built with npx -y pull the latest version of the package on every invocation. This is convenient for development and dangerous for production. A compromised or buggy upstream release deploys automatically to every instance.

10. Run compliance tests continuously

Security and compliance are related. A server that does not handle unknown methods correctly (a compliance failure) is also a server that might leak error details to attackers. A server that does not validate inputSchema (a compliance failure) is also a server that is vulnerable to crafted inputs.

Run mcp-compliance in your CI pipeline. Use --strict to fail on required test failures. Compliance is the baseline. Security is what you build on top.

# In CI: fail if any required compliance test fails
npx -y @yawlabs/mcp-compliance test $MCP_SERVER_URL --strict

The checklist

Before putting an MCP server in production:

  1. SSRF protection with DNS rebinding defense
  2. No credentials in logs or error reports
  3. Tool input validation against declared schemas
  4. Tenant isolation at every data layer
  5. Session-level and tool-level rate limiting
  6. TLS end-to-end, certificates verified
  7. Prompt injection mitigations in tool results
  8. Audit logging for every tool call
  9. Pinned dependencies, lockfile committed
  10. Continuous compliance testing in CI

Most of these are standard web security practices applied to a new context. The MCP-specific risks - SSRF through tool handlers, prompt injection through tool results, credential leakage through tool arguments - are the ones that catch people off guard.

Most of these are straightforward to implement yourself. The hard part is catching when one of them silently regresses - a header middleware reordered, a dependency bump that changed validation behavior, a new transport path that skipped the auth gate. An 88-test compliance suite against any MCP server (HTTP or stdio) graded A–F is the right final gate before you ship - wire it into CI.


Jeff Yaw, Yaw Labs. Follow along at tokenlimit.news for weekly notes on AI infrastructure.