Any MCP tool that takes a URL as an argument and fetches it from the server side inherits a decades-old web vulnerability class: server-side request forgery, or SSRF. The tool isn’t malicious, the model isn’t being tricked into anything exotic — it just passes a URL to a tool called fetch or scrape_page, and whatever that tool’s host can reach, the tool can now be pointed at. What that host can reach is usually a lot more than the public web.

What SSRF means in an MCP tool

Classic SSRF is a web app that fetches a URL on a user’s behalf — an image proxy, a webhook tester, a “preview this link” feature — and never checks where that URL actually points. An attacker hands it http://169.254.169.254/latest/meta-data/ instead of a normal link, and the app’s own infrastructure makes the request on its behalf, from inside the network boundary the attacker could never reach directly.

An MCP tool with a URL parameter is the same shape with one difference: the thing filling in the URL isn’t a logged-in user clicking a feature, it’s a model following whatever instruction it was last given — including one it picked up from a web page, an email, or a tool result it was never supposed to treat as commands. See how prompt injection actually reaches a tool call for how that instruction gets there in the first place. The fetch-capable tool doesn’t know the difference between a URL the person asked for and a URL an injected instruction asked for. It just fetches.

Where this shows up in real MCP servers

Any server whose job is reading the web for you is a candidate: web search and scraping tools like Firecrawl or Apify’s scraping actors, documentation fetchers, link unfurlers, webhook or URL-preview tools, and any custom server you’ve built that takes a url field and calls it. None of that is a knock on those specific products — fetching a URL on request is the entire feature, and the risk is a property of the shape, not a flaw unique to one vendor. If you’re turning an existing API into an MCP server and that API has a “fetch by URL” endpoint, you’re wrapping this exact surface by default, without deciding to.

It’s also worse in MCP specifically than in a browser. A remote server often runs inside a cloud VPC with access to a metadata endpoint or internal services never meant to be reached from the public internet. A local, stdio-launched server is worse again: it runs as your own user, with access to localhost and your LAN.

What a URL argument can actually be pointed at

  • Cloud metadata endpoints. 169.254.169.254 on AWS, Azure, and GCP can return temporary credentials for whatever role the server’s host is running under — the single most common way SSRF turns into a full account compromise.
  • Internal services with no auth of their own. Admin dashboards, databases, and internal APIs that rely on “you can’t reach this from outside” as their only access control, which an inside-the-network fetch tool quietly defeats.
  • localhost and loopback addresses on whichever host is actually running the tool — your laptop, for a local server; the server’s own container, for a remote one.
  • A redirect to any of the above. A URL that looks public on first fetch but 302s to an internal address bypasses a naive “check the URL, then fetch it” implementation.

None of this requires the model to be unusually clever — just a tool that fetches whatever URL it’s handed, and a path, such as a crafted link in a scraped page or an injected instruction, that puts the wrong URL in that argument.

If you’re connecting a server like this

  • Read what the docs say about scope. Does the vendor mention a domain allow-list, a block on private IP ranges, or a redirect policy? If they don’t say either way, assume no restriction and treat the tool as able to reach wherever its host can.
  • Run a free MCP security scan before connecting. gate’s scanner lists every tool and parameter a server exposes and flags over-broad permissions, so a URL-fetching tool’s input shape is visible before you grant it access.
  • Prefer a remote, vendor-hosted server over a local one for anything that fetches URLs — a vendor’s infrastructure is a smaller, more deliberate network position than your laptop’s loopback interface and LAN.
  • Watch for a URL argument pointing outside the destinations you expected in your tool-call log — one of the few SSRF signals visible after the fact, provided the argument was logged at all.
  • Treat it as one line item in a normal pre-connection vetting pass, not a reason to avoid every fetch-capable server.

If you’re building a tool that fetches a URL

  • Resolve the hostname and check the IP, not the hostname string. Block private ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16), loopback, and link-local addresses including the cloud metadata range, after DNS resolution — checking the URL text alone misses DNS rebinding.
  • Re-check after every redirect, not just the first URL. Either disable redirect-following and return the redirect as a result, or re-validate the destination IP on each hop.
  • Set a short timeout and a response size cap so one slow host can’t tie up a tool call far longer than the task warranted.
  • Say what you restrict, in the tool description. “Fetches public HTTP(S) URLs only; private and link-local addresses are blocked” lets anyone evaluating the tool — a person or a scanner — see the mitigation without reading your source, and it’s also a description a model can act on correctly.
  • If the tool genuinely needs internal reach, say so explicitly and scope the credential narrowly instead of leaving it open by omission — see excessive agency for the broader version of this mistake.
How gate helps: gate can’t rewrite a third-party server’s internal fetch logic, but it can limit what reaches a fetch-capable tool in the first place. Set a URL-fetching tool to ask instead of allow for clients that also hold sensitive data, so a questionable fetch needs a human sign-off instead of running silently. Every tool call is logged with its full arguments, so if a fetch tool is ever pointed somewhere it shouldn’t be, the URL it was actually given is in the record, not just the fact that the tool ran. And the free security scanner flags over-broad permissions and hidden instructions in a server’s tool descriptions before you ever connect it. More on the layered approach on the MCP security page.

The bottom line

A tool that fetches a URL on request is doing exactly what it says — the risk isn’t that it lies about its job, it’s that “fetch this URL” and “fetch any URL, including the ones you didn’t mean” are the same tool unless someone deliberately narrowed it. If you’re connecting one, read the schema and the docs for a restriction before you assume one exists. If you’re building one, the restriction is a handful of lines of validation, and it’s a lot cheaper to add before the tool ships than to explain after a metadata endpoint answers a request it never should have received.