Let an agent read the web without becoming a way into your internal network - HTTP with SSRF protection on by default, plus markdown, reader mode, metadata, sitemaps, feeds and robots.txt.
@yawlabs/fetch-mcp is a comprehensive HTTP fetch MCP server for AI assistants. Bring your own client: it runs as a stdio MCP server, so Claude Code, Claude Desktop, Cursor and anything else that speaks the protocol can fetch web content safely. MIT licensed, from Yaw Labs, published on npm.
npx -y @yawlabs/fetch-mcp@latestGitHub · npm · Add to Yaw MCP
Anyone who wants an agent that can read the web, and would rather that ability not double as a blind-SSRF probe against their own network. Giving a model a raw HTTP client is easy; giving it one that refuses to dial your metadata endpoint, and hands pages back small enough to fit in context, is the work:
1. No API key. There is nothing to provision and no account to create.
2. Add the server to your client. In your client's MCP config - usually claude_desktop_config.json or ~/.claude.json:
{
"mcpServers": {
"fetch": {
"command": "npx",
"args": ["-y", "@yawlabs/fetch-mcp@latest"]
}
}
}The @latest tag means each spawn picks up the current release. If you would rather pin it yourself, install it globally and point your client at the binary instead:
npm i -g @yawlabs/fetch-mcp
fetch-mcp3. Restart and approve. Restart Claude Code (or your MCP client) and approve the Fetch server when prompted. Then ask it something: "Fetch this page in reader mode and summarize it."
Running Yaw MCP? One click adds it to your local config, and it is then available in every Yaw Terminal session.
| Tool | What it does |
|---|---|
http_get / http_head / http_options | Bare HTTP requests with headers, auth, timeout, size cap and retry |
http_post / http_put / http_patch / http_delete | Write-method HTTP with a JSON or raw body |
fetch_html_to_markdown | GET a page and convert it to clean markdown - 3-8x smaller than the raw HTML |
fetch_html_to_text | GET a page and convert it to plain text with block structure preserved |
fetch_reader | Reader-mode extraction - isolates the article body and returns title plus markdown |
fetch_meta | Extract head metadata: title, description, canonical, OpenGraph, Twitter cards, JSON-LD, feeds, icons |
fetch_links | Every outbound link, resolved to absolute URLs and classified internal or external |
fetch_sitemap | Parse sitemap.xml, including gzipped sitemaps and sitemap-index chaining |
fetch_feed | Parse an RSS 2.0 or Atom 1.0 feed into entries |
fetch_robots | Parse a site's robots.txt and return the verdict for a given path and user-agent |
The HTTP tools share a common parameter set: custom headers, request timeout, a byte cap that truncates oversized bodies and flags the response as truncated, a redirect-hop limit, and a retry count that backs off on 408, 425, 429 and 5xx while honoring Retry-After. Text decoding is auto-detected from the response Content-Type and can be forced either way; binary bodies come back base64-encoded. JSON responses are parsed for you, and the response carries the final URL after redirects along with the chain of intermediate ones.
The structured parsers are deliberately more than regex. fetch_reader tries <article>, then <main>, then itemprop="articleBody", then common CMS class names, falling back to <body>. fetch_sitemap returns a partial failure - one child sitemap returning a 500 while the others succeed - under warnings rather than aborting the whole call, and can list an index's children without fetching any of them when you only want to discover structure. fetch_robots follows Google's rules: longest match wins, * is a wildcard segment, $ anchors the end of the path, a specific user-agent group beats the wildcard group, and Allow beats Disallow on equal-length ties.
SSRF protection is on by default. The server refuses requests to:
127.0.0.0/8, ::1) and the hostname localhost, including any *.localhost10/8, 172.16/12, 192.168/16169.254/16, fe80::/10) - which is what keeps an agent away from the cloud metadata endpoint at 169.254.169.254100.64/10), unique-local IPv6 (fc00::/7), multicast and broadcast::ffff:0:0/96), re-checked against the IPv4 rules so the mapping is not an escape hatchhttp/https schemes - file://, gopher://, javascript: and the restBlocking a list of ranges is the easy half. The harder half is that a hostname can resolve to a public address when you check it and a private one when you connect - so DNS is resolved once per redirect hop, every returned address is checked, and the verified IP is pinned into the HTTP dispatcher so the subsequent TCP connection dials that exact address. That closes the DNS-rebinding time-of-check-to-time-of-use window. Redirects are checked at every hop too: a 302 to http://127.0.0.1 served through a public host gets caught rather than followed. Authorization headers are stripped on cross-origin redirects, so a credential you pass for one host does not leak to whatever it redirects you to.
When you genuinely do need internal access - a development server on your own machine - set allow_private_hosts: true on that request. It is per-request rather than a global switch on purpose: opting in names the one call that needs it instead of turning the protection off for everything the agent does afterwards.
The repo's own tests take the same line. They spin up a real loopback HTTP server and exercise the actual request and response path rather than mocking HTTP, and the SSRF tests verify that the default-deny still applies to that local server unless the request explicitly opts in.
It gives a model the web in shapes it can actually use. Bare HTTP methods with headers, auth, timeouts, a size cap and retries; page conversion to clean markdown or plain text; reader-mode extraction that isolates the article body; head metadata including OpenGraph, Twitter cards and JSON-LD; every outbound link resolved to an absolute URL; sitemap, RSS and Atom parsing; and a robots.txt verdict for a given path and user-agent.
Yes, by default. Requests to loopback, RFC1918 private ranges, link-local including the cloud metadata endpoint, CGNAT, unique-local IPv6, multicast and broadcast are refused, as are non-http schemes and localhost by name. DNS is resolved once per redirect hop, every returned address is checked, and the verified IP is pinned into the dispatcher so the TCP connection dials that exact address, which closes the DNS-rebinding window. Authorization headers are stripped on cross-origin redirects.
Yes. Set allow_private_hosts to true on the individual request, for example when fetching from a development server on your own machine. It is per-request rather than a global switch on purpose: opting in names the one call that needs internal access instead of disabling the protection for everything the agent does afterwards.
Context budget. The markdown conversion strips scripts, styles, iframes, svg and canvas along with nav, footer and aside, and comes out 3-8x smaller than the raw HTML. Reader mode goes further by isolating just the article body and returning the title with it, so a long page costs a fraction of the tokens it otherwise would.
No. There is no key and no account. It runs as a stdio MCP server, so any MCP-compatible client can use it: add the config block, restart your client, approve the server. Per-request basic auth and bearer tokens are available for sites that need them, and those credentials are stripped on cross-origin redirects.
Published by Yaw Labs.