Skip to content
back to projects
2026·shipped·Creator & Maintainer

Safe-Fetch MCP Server

An MCP fetch server built around one unified security pipeline, closing the SSRF gaps the reference implementation ships without.

TypeScriptNode.jsZodMCP

Pipeline

1
Zod validation
Input integrity
2
URL policy
Scheme + credential checks
3
resolveAndPin
Resolve once, pin IP
4
Redirect re-check
Same guards, every hop

Highlights

  • Every request, including every redirect hop, runs through the same guard code: there is no secondary fetch path to bypass.
  • Blocks cloud metadata addresses, RFC-1918 ranges, loopback, and IPv4-mapped IPv6 loopback by default.
  • resolveAndPin resolves a hostname once and pins the connection to that IP, closing the DNS-rebinding window most fetch servers leave open.
  • Validated against the OWASP MCP Top 10 via independent scanning; 62 unit tests cover the threat matrix.

The gap

The most-used reference MCP fetch server ships with no SSRF protection, by its own README’s admission. That’s not a minor omission. Any agent with fetch access is one crafted URL away from reaching your cloud metadata endpoint, an internal admin panel, or a service that never expected to be reachable from outside its network.

Safe-Fetch exists because getting SSRF defense right is genuinely hard, and doing it right, and proving it, is the whole point of the project.

Architecture

Every request travels through one pipeline, with no alternate code path that could skip a check:

  1. Zod validation on the input shape before anything else runs.
  2. URL policy enforcement: http/https only, no embedded credentials.
  3. resolveAndPin: the hostname resolves once, and the connection is pinned to that resolved IP for the life of the request.
  4. Redirect re-validation: every redirect the server follows runs back through the same guards, not a lighter secondary check.

That last point is the one most fetch servers get wrong. It’s common to validate the initial URL carefully and then follow redirects with a separate, looser path. Safe-Fetch deliberately has no such path.

Threat matrix

The server blocks, by default:

  • Cloud metadata addresses (169.254.169.254)
  • RFC-1918 private ranges (configurable via SAFE_FETCH_ALLOW_LOCAL)
  • Loopback addresses (127.0.0.1, ::1)
  • IPv4-mapped IPv6 loopback (::ffff:127.0.0.1)
  • DNS rebinding, via connection pinning rather than re-resolving on redirect
  • Non-HTTP(S) schemes and URLs with embedded credentials

Responses are also explicitly framed as untrusted data, so an agent consuming fetched content doesn’t treat it as instructions.

Usage

Zero-install via stdio:

{
  "mcpServers": {
    "safe-fetch": {
      "command": "npx",
      "args": ["-y", "safe-fetch-mcp-server"]
    }
  }
}

Configurable via environment variables: SAFE_FETCH_MAX_BYTES (5MB default), SAFE_FETCH_TIMEOUT_MS (10s default), plus allowlisting and rate-limit controls.

Why it matters

Agentic tools that can fetch arbitrary URLs are a growing attack surface, and most of the servers wiring that capability into agents today were not built by security engineers. Safe-Fetch is deliberately narrow: it does one thing, fetch content safely, and tries to do it in a way that survives an actual audit rather than a glance at the README.

What I would change now

The allowlist is currently environment-variable based, which works for single-server setups but doesn’t scale to teams managing many agents. A policy-file format (YAML or JSON) with per-domain rules would make configuration auditable and version-controllable. I’d also add structured logging for blocked requests — right now you know a request was blocked, but not enough about the pattern of blocks to tune the policy without reading test output.