Skip to content

Fetch errors

MCP fetch and localhost: why your fetch tool refuses a dev server

Why WebFetch and mcp-server-fetch refuse localhost, how to let the reference server read one local host on purpose, and why a remote fetch server never can.

By Lucian Dyer, FetchMCP team · Published

This article was drafted with AI assistance from the sources listed below and edited and published by the FetchMCP team. Disclosure: the FetchMCP API, which this article mentions, is this site's own product.

Who can read http://localhost:3000
CriterionDefaultHow to reach a local server
Claude Code WebFetchRefused before any requestcurl through the Bash tool
Reference mcp-server-fetchRefuses loopback, private and link-local addresses--allow-private-ips, ideally with --allowed-hosts
Remote fetch MCP serverRuns on another networkNot possible; it reads public URLs

You ask the agent to read http://localhost:3000 and check the page you just changed. The fetch fails. Depending on the tool, you see a refusal, an error about private addresses, or a page that is not yours. All three are working as designed.

Checked against the Claude Code docs, the reference fetch server README and the Docker Desktop docs on 2026-11-10.

Which tool refused it

Find the tool name in the failed call first. The fix depends on it:

  1. WebFetch is Claude Code's built-in tool. It never fetches localhost.
  2. fetch from mcp-server-fetch is the reference server, running on your machine. It refuses private addresses until you allow them.
  3. A remote fetch MCP server runs on someone else's network. It cannot reach your machine at all.

WebFetch: refused before any request

WebFetch refuses localhost and every other hostname without a dot, such as a bare intranet name like http://wiki/. No request is sent. The error reads:

WebFetch cannot fetch localhost or other hostnames without a dot. To reach a local server, use Bash with curl instead.

Usually you do nothing. The message points Claude at curl through the Bash tool, which can reach local and intranet servers. Before v2.1.268, Claude Code reported the same URLs as a generic Invalid URL error, so an older build gives you less to go on.

The docs list no setting that lifts this check. If you want the page text through an MCP tool instead, use the reference server below.

mcp-server-fetch: a guard you can switch off

mcp-server-fetch refuses hosts that resolve to loopback (127.0.0.0/8, ::1), private ranges such as 10.0.0.0/8 and 172.16.0.0/12, link-local (169.254.0.0/16, fe80::/10) and other non-public addresses. Cloud metadata endpoints are included. Every redirect hop goes through the same check, so a public URL that redirects to localhost fails too.

--allow-private-ips turns the guard off. Add it to the args list in your client config.

The README opens its caution block with a warning that the server can access local and internal IP addresses and may be a security risk. With the guard off, any URL the model picks can reach your internal network. Scope it.

Allow one local host, not your whole network

--allowed-hosts limits fetches to the hosts you list: exact hosts, *.domain wildcards or IP addresses. Pair it with --allow-private-ips:

{
  "mcpServers": {
    "fetch-local": {
      "command": "uvx",
      "args": ["mcp-server-fetch", "--allow-private-ips", "--allowed-hosts", "localhost", "127.0.0.1"]
    }
  }
}

The two checks are separate, and a request must pass both:

  • Listing localhost in --allowed-hosts does not, on its own, let the server reach it. The guard still refuses.
  • --allow-private-ips does not lift the allowlist. Public sites outside the list stay refused.

So this entry reads your dev server and nothing else. Keep your normal fetch server as a separate entry for public pages, and give the local one a name that says so.

Two limits of the guard, from the README: it checks the addresses a hostname resolves to before each request, so it does not protect against DNS rebinding between that check and the connection. And a hostname that only a proxy can resolve is not checked.

The Docker variant

localhost inside a container is the container, not your machine. If you run the server as docker run -i --rm mcp/fetch, a dev server on your host is not at localhost.

On Docker Desktop, use the special name host.docker.internal, which resolves to the internal IP address of your host. It is a private address, so the guard still applies. The README shows the Docker config without extra flags, so test that --allow-private-ips and --allowed-hosts host.docker.internal reach the server before you rely on them.

Then ask for http://host.docker.internal:3000 instead of http://localhost:3000. To test the name outside MCP, the Docker docs run curl http://host.docker.internal:8000 from an Alpine container against a python -m http.server 8000 on the host.

Remote fetch servers: the wrong tool for local pages

A remote fetch MCP server sends its requests from its own network. localhost there is the server's machine, and your private IPs are not routable from it. No config key changes that.

Treat this as a split, not a bug:

  1. Local and intranet pages: curl through Bash, or a scoped mcp-server-fetch entry.
  2. Public pages: whichever fetch server you already use.

If the result is a refusal or another error rather than a page, the fetch errors guide covers what each one means. How WebFetch and a fetch MCP tool differ beyond localhost is in WebFetch vs a fetch MCP server.

Try it with a real server

FetchMCP API's remote MCP server is the public half of that split. get_page_contents reads one public URL per call, so it will not reach your dev server; keep a local tool for that. The MCP server registers 2 tools. Failed fetches cost 0 credits. The config for each client is in the MCP docs.

Frequently asked questions

Why does my fetch MCP server refuse localhost?

The reference mcp-server-fetch refuses hosts that resolve to loopback, private, link-local and other non-public addresses by default, and checks every redirect hop the same way. Start it with --allow-private-ips to turn that guard off.

Does adding localhost to --allowed-hosts let mcp-server-fetch reach it?

No. The allowlist and the private-address guard are separate checks and a request must pass both. To reach localhost you need --allow-private-ips as well, and that flag does not lift the allowlist.

What does 'WebFetch cannot fetch localhost or other hostnames without a dot' mean?

Claude Code's WebFetch refuses localhost and any hostname without a dot before it sends a request. The message tells Claude to use curl through the Bash tool, which can reach local and intranet servers. Before v2.1.268 the same case showed a generic Invalid URL error.

Can a hosted fetch MCP server read my local dev server?

No. A remote server fetches from its own network, so localhost there is not your machine. Use a local tool for local pages and the remote server for public URLs.

Sources

  1. Fetch MCP server README (modelcontextprotocol/servers) (Documentation)
  2. Error reference: WebFetch cannot fetch localhost (Claude Code docs) (Documentation)
  3. Tools reference: WebFetch tool behavior (Claude Code docs) (Documentation)
  4. Networking how-tos on Docker Desktop (Docker docs) (Documentation)