mcp-server-web-fetcher
This server fetches web pages and converts them into LLM-friendly formats via three read-only, idempotent tools:
fetch_page_markdown: Downloads any HTTP(S) URL and returns clean Markdown, stripping scripts, ads, navigation, and sidebars. Supports pagination for long pages via
startIndex/nextStartIndex. Resolves relative links, preserves tables/code blocks, and offers configurable options (max length, include links/images/metadata, main content only). Returns structured output with title, word count, redirect chain, cache status, etc.extract_metadata: Retrieves rich metadata without the full page body: title, description, canonical URL, language, author, publish/modified dates, Open Graph and Twitter card tags, JSON-LD blocks, h1–h6 outline, RSS/Atom feeds, hreflang alternates, and raw HTTP headers.
extract_links: Lists all links as absolute URLs, classifying them as internal or external, with anchor text, title, rel attributes, and nofollow status. Supports scope filtering (all/internal/external), de-duplication, inclusion of fragment links, and configurable limit (1–2000).
General capabilities:
SSRF Protection: Blocks requests to loopback, private, link-local, and cloud-metadata addresses on every redirect hop.
Pagination & Context Awareness: Long content is chunked to fit LLM context windows, with explicit
truncatedandnextStartIndexfields.Caching & Retries: Small TTL-based in-memory cache and automatic retries on timeouts, 5xx, 408, 429 responses.
Predictable Errors: Machine-readable error codes (e.g.,
TIMEOUT,HTTP_ERROR,BLOCKED_HOST) with retryability flags and recovery hints.Configurable: Per-request timeout, byte caps, redirect limits, user agent, robots.txt enforcement, and cache settings via environment variables or tool parameters.
Security: No JavaScript execution, credentials stripped from URLs, no filesystem or shell access.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-server-web-fetcherGet the markdown content from https://example.com"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-server-web-fetcher
A fast, dependency-light Model Context Protocol server that turns any web page into something a language model can actually read: clean Markdown, structured metadata, and a classified list of links.
Web pages are 90% chrome. Scripts, cookie banners, navigation, sidebars and tracking pixels burn context and derail reasoning. This server strips all of that on the way in, so the model sees the article and nothing else.
┌────────────┐ stdio/JSON-RPC ┌──────────────────────┐ HTTPS ┌──────────┐
│ MCP client │ ─────────────────► │ mcp-server-web- │ ────────► │ web page │
│ (Claude, │ ◄───────────────── │ fetcher │ ◄──────── │ │
│ Kiro, …) │ Markdown + JSON │ fetch → clean → md │ └──────────┘
└────────────┘ └──────────────────────┘Features
Clean Markdown output. Readability-style main-content detection, GFM tables, language-tagged code fences, absolute links. No leftover HTML, even for gnarly nested tables.
Context-window aware. Long pages are paginated with
startIndex/nextStartIndexinstead of being silently cut in half.Structured metadata. Title, description, canonical,
lang, author, publish/modify dates, Open Graph, Twitter cards, JSON-LD,hreflangalternates, RSS/Atom feeds, h1–h6 outline and raw HTTP headers.Link intelligence. Absolute URLs, anchor text,
rel,nofollow, internal vs external classification, scope filters, de-duplication.SSRF hardening. Loopback, private, link-local and cloud-metadata addresses are blocked on every redirect hop, not just the first request. Credentials in URLs are stripped.
Predictable failures. Every error carries a stable machine-readable
code(TIMEOUT,HTTP_ERROR,BLOCKED_HOST,RESPONSE_TOO_LARGE, …), a retryability flag and a recovery hint, so the model can self-correct instead of guessing.Typed end to end. Strict Zod input schemas plus published
outputSchema, so clients get validatedstructuredContent, not prose they have to re-parse.Well behaved. Byte caps, timeouts, redirect limits, charset sniffing (including legacy encodings), a small TTL cache, transient-failure retries and optional
robots.txtenforcement.115 tests, no network required. Vitest unit tests plus in-memory MCP protocol tests.
Related MCP server: crawl-mcp-server
Quickstart
Requires Node.js 20.18.1 or newer (inherited from cheerio, which needs undici 7).
# run it without installing
npx -y mcp-server-web-fetcher
# or install globally
npm install -g mcp-server-web-fetcher
mcp-server-web-fetcherThe server speaks MCP over stdio, so on its own it just waits for a client. Point a client at it:
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json on macOS,
%APPDATA%\Claude\claude_desktop_config.json on Windows:
{
"mcpServers": {
"web-fetcher": {
"command": "npx",
"args": ["-y", "mcp-server-web-fetcher"]
}
}
}With configuration, using a global install:
{
"mcpServers": {
"web-fetcher": {
"command": "mcp-server-web-fetcher",
"env": {
"WEB_FETCHER_TIMEOUT_MS": "20000",
"WEB_FETCHER_RESPECT_ROBOTS": "true"
}
}
}
}Restart Claude Desktop, then ask it to summarise a URL.
Claude Code
claude mcp add web-fetcher -- npx -y mcp-server-web-fetcherKiro, Cursor, Windsurf and other MCP clients
Same shape, in .kiro/settings/mcp.json / the client's MCP config file:
{
"mcpServers": {
"web-fetcher": {
"command": "npx",
"args": ["-y", "mcp-server-web-fetcher"],
"disabled": false,
"autoApprove": ["fetch_page_markdown", "extract_metadata", "extract_links"]
}
}
}MCP Registry
The server is listed in the official MCP Registry
as io.github.vojtisprime11/web-fetcher, so clients that read the registry can discover it
directly:
curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.vojtisprime11/web-fetcher"Docker
docker build -t mcp-server-web-fetcher .{
"mcpServers": {
"web-fetcher": {
"command": "docker",
"args": ["run", "--rm", "-i", "mcp-server-web-fetcher"]
}
}
}The image runs as the unprivileged node user and carries production dependencies only. CI builds
it and drives it with a real MCP client, so it is verified to start and answer introspection.
From source
git clone https://github.com/vojtisprime11/mcp-server-web-fetcher.git
cd mcp-server-web-fetcher
npm install
npm run build
node dist/index.js # or: npm run inspect{
"mcpServers": {
"web-fetcher": {
"command": "node",
"args": ["/absolute/path/to/mcp-server-web-fetcher/dist/index.js"]
}
}
}Tools
All three tools are read-only, idempotent and open-world (annotated as such in the protocol), and
all take an absolute http(s) URL.
fetch_page_markdown
Downloads a page and returns clean, LLM-friendly Markdown.
Parameter | Type | Default | Description |
| string | required | Absolute http(s) URL. |
| integer 500–1000000 |
| Markdown characters to return per call. |
| integer ≥ 0 |
| Character offset; use |
| boolean |
| Drop nav/header/footer/sidebar, keep the densest block. |
| boolean |
| Keep Markdown links ( |
| boolean |
| Keep images as |
| boolean |
| Attach a short metadata summary. |
| integer 1000–120000 | env / | Per-request timeout. |
Request:
{
"name": "fetch_page_markdown",
"arguments": {
"url": "https://example.com/blog/caching",
"maxLength": 8000,
"mainContentOnly": true,
"includeImages": false
}
}structuredContent:
{
"url": "https://example.com/blog/caching",
"requestedUrl": "https://example.com/blog/caching",
"status": 200,
"contentType": "text/html; charset=utf-8",
"title": "How caching works",
"markdown": "# How caching works\n\nCaching is the art of **not** doing work twice...",
"markdownLength": 7984,
"totalLength": 21874,
"startIndex": 0,
"endIndex": 7984,
"nextStartIndex": 7984,
"truncated": true,
"wordCount": 1203,
"bytesDownloaded": 148213,
"elapsedMs": 412,
"fromCache": false,
"redirects": [],
"metadata": {
"title": "How caching works",
"description": "A deep dive into HTTP caching.",
"canonical": "https://example.com/blog/caching",
"language": "en",
"author": "Ada Lovelace",
"publishedTime": "2026-01-15T09:00:00Z"
}
}To read the rest, call again with "startIndex": 7984. Keep going while nextStartIndex is not
null.
extract_metadata
Everything a model needs to classify a page, without spending context on its body.
Parameter | Type | Default | Description |
| string | required | Absolute http(s) URL. |
| boolean |
| Include parsed JSON-LD blocks (malformed ones are skipped). |
| boolean |
| Include the h1–h6 outline. |
| boolean |
| Include response headers, lower-cased. |
| integer 1000–120000 | env / | Per-request timeout. |
{
"name": "extract_metadata",
"arguments": { "url": "https://example.com/blog/caching", "includeJsonLd": true }
}structuredContent (abridged):
{
"url": "https://example.com/blog/caching",
"status": 200,
"charset": "utf-8",
"title": "How caching works",
"description": "A deep dive into HTTP caching.",
"canonical": "https://example.com/blog/caching",
"language": "en",
"author": "Ada Lovelace",
"publishedTime": "2026-01-15T09:00:00Z",
"modifiedTime": null,
"robots": "index, follow",
"favicon": "https://example.com/favicon.ico",
"openGraph": {
"title": "How caching works",
"type": "article",
"image": "https://example.com/img/cover.png"
},
"twitter": { "card": "summary_large_image", "site": "@example" },
"alternates": [{ "hreflang": "de", "href": "https://example.com/de/blog/caching" }],
"feeds": [
{ "title": "Feed", "href": "https://example.com/feed.xml", "type": "application/rss+xml" }
],
"jsonLd": [{ "@type": "Article", "headline": "How caching works" }],
"headings": [
{ "level": 1, "text": "How caching works", "id": null },
{ "level": 2, "text": "Directives", "id": "directives" }
],
"httpHeaders": { "content-type": "text/html; charset=utf-8", "x-cache": "HIT" },
"wordCount": 1203,
"redirects": [],
"fromCache": false
}extract_links
Parameter | Type | Default | Description |
| string | required | Absolute http(s) URL. |
|
|
| Same-site, off-site, or both. |
| boolean |
| Include in-page |
| boolean |
| Collapse repeated URLs. |
| integer 1–2000 |
| Maximum links returned. |
| integer 1000–120000 | env / | Per-request timeout. |
{
"name": "extract_links",
"arguments": { "url": "https://example.com/blog/caching", "scope": "external", "limit": 50 }
}structuredContent:
{
"url": "https://example.com/blog/caching",
"status": 200,
"totalFound": 2,
"returned": 2,
"internalCount": 0,
"externalCount": 2,
"truncated": false,
"links": [
{
"url": "https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching",
"text": "MDN article",
"title": null,
"rel": "noopener nofollow",
"internal": false,
"nofollow": true
}
],
"fromCache": false
}www.example.com and example.com count as the same site; blog.example.com does not.
Error handling
Failures come back as MCP tool errors (isError: true), never as silent empty results:
HTTP_ERROR: 404 Client Error for https://example.com/missing
Retryable: no
Hint: Check the URL, or retry later if the status is 429/5xx.structuredContent.error carries the same information as JSON: { code, message, retryable, url, status }.
Code | Meaning |
| Not an absolute URL. |
| Scheme other than |
| Loopback, private, link-local or metadata address (SSRF guard). |
| Host does not resolve. |
| Request exceeded |
| Response status ≥ 400, or a redirect without |
| Redirect chain longer than the limit. |
| Declared body larger than the byte cap. |
| Not a text/HTML/XML/JSON document. |
| Blocked by |
| Connection reset, TLS failure, and similar. |
| Document could not be parsed. |
Timeouts, 5xx, 408 and 429 responses are retried once automatically before the error surfaces.
Configuration
Everything is optional; the defaults are safe.
Variable | Default | Description |
|
| Outgoing |
|
| Default timeout (1000–120000). |
|
| Per-response byte cap (10000–50000000). |
|
| Redirect hops allowed (0–20). |
|
| Set to |
|
| Enforce |
|
| Response cache TTL; |
|
| Maximum cached responses. |
Security
SSRF guard on by default. Hostnames are resolved and checked against loopback, RFC 1918, CGNAT, link-local (including
169.254.169.254), multicast and IPv4-mapped IPv6 ranges — for the initial URL and every redirect target.localhost,*.localhostand*.internalare refused outright. Turning the guard off is an explicit opt-in.Byte and time caps. Responses are streamed and cut at the byte cap; every request is aborted at the timeout. Content-Length larger than the cap is rejected before download.
No code execution. JavaScript is never run;
<script>,<style>,<iframe>and friends are removed before conversion. Fetched content is data, not instructions — treat page text reaching a model as untrusted input.Credentials stripped.
user:pass@is removed from URLs before the request is made.Read-only. The server has no write, filesystem or shell surface, and no authentication state.
Found a vulnerability? See SECURITY.md.
Architecture
src/
├── index.ts # stdio entry point (logs to stderr only)
├── server.ts # transport-agnostic server factory + public API
├── types.ts # Zod input/output schemas for every tool
├── tools/
│ ├── fetchPageMarkdown.ts
│ ├── extractMetadata.ts
│ ├── extractLinks.ts
│ ├── shared.ts # result shaping, dependency injection
│ └── index.ts
└── lib/
├── config.ts # env-driven configuration
├── errors.ts # typed error codes + recovery hints
├── net.ts # URL parsing, SSRF guard, link resolution
├── http.ts # fetch pipeline: timeouts, caps, redirects, charset, cache
├── robots.ts # optional robots.txt support
├── html.ts # cheerio cleanup, metadata + link extraction
├── markdown.ts # turndown configuration, tables, pagination
└── cache.ts # TTL + LRU cache
tests/ # unit tests + in-memory MCP protocol testsTool handlers take an injectable fetchImpl, so every test runs offline against a scripted HTTP
stub. The library is also importable directly:
import { runFetchPageMarkdown } from 'mcp-server-web-fetcher';
const page = await runFetchPageMarkdown({ url: 'https://example.com' });
console.log(page.markdown);Development
The minimum supported Node.js version is 20.18.1, which comes from cheerio (it depends on undici 7,
and undici 7 needs the File global that landed in Node 20). CI runs the suite on Node 20/22/24 and
has a separate job that completes an MCP handshake against the built server on exactly 20.18.1, so
the declared floor is verified rather than assumed.
npm install
npm run dev # tsx watch
npm run typecheck
npm run lint
npm test # vitest run
npm run test:coverage # thresholds enforced
npm run build
npm run inspect # MCP Inspector against dist/index.js
node scripts/smoke.mjs https://example.com # live end-to-end checkContributions welcome — see CONTRIBUTING.md and CODE_OF_CONDUCT.md.
Limitations
Static HTML only. Client-rendered SPAs return whatever the server sends; no headless browser.
No PDF or Office document extraction.
Sites behind bot protection (Cloudflare challenges, login walls) will return 403.
Main-content detection is a text-density heuristic. Use
mainContentOnly: falsewhen a page has an unusual structure.
Roadmap
search_pagetool: return only the sections matching a query.Optional headless rendering behind a flag, for JS-only pages.
Streamable HTTP transport in addition to stdio.
Conditional requests (
ETag/If-Modified-Since) in the cache layer.
Acknowledgements
Built on the MCP TypeScript SDK, cheerio, turndown and zod.
License
MIT © Vojta Holes and contributors
Available Tools
3 toolsextract_linksExtract page linksARead-onlyIdempotent
List the links on a web page as absolute URLs, each flagged as internal (same site) or external, with anchor text, title, rel and nofollow status. Supports scope filtering, de-duplication and a result limit — useful for crawling a documentation tree, auditing outbound links or finding next pages.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Absolute http(s) URL of the page to fetch. | |
| limit | No | Maximum number of links to return. | |
| scope | No | Restrict results to same-site links, off-site links, or return both. | all |
| timeoutMs | No | Request timeout in milliseconds (1000-120000). | |
| deduplicate | No | Collapse repeated URLs to a single entry. | |
| includeAnchors | No | Include in-page fragment links such as "#section". |
Output Schema
| Name | Required | Description |
|---|---|---|
| url | Yes | |
| links | Yes | |
| status | Yes | |
| returned | Yes | |
| fromCache | Yes | |
| truncated | Yes | |
| totalFound | Yes | Links matching the scope before the limit was applied. |
| requestedUrl | Yes | |
| externalCount | Yes | |
| internalCount | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, destructiveHint=false, and idempotentHint=true. The description adds value by detailing the return structure (flags, anchor text, nofollow) and features like scope filtering and deduplication, enhancing the agent's understanding of behavior beyond the safe, read-only nature.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is three sentences long, with the core action in the first sentence and supporting details in the second. It is tightly written with no filler, effectively front-loading the most important information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
The description covers the tool's purpose, output details, filtering capabilities, deduplication, limit, and use cases. With an output schema present and annotations providing safety context, the description is fully adequate for an agent to understand and invoke the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents each parameter's purpose. The description reiterates the overall functionality but does not add new semantic detail for individual parameters beyond what the schema provides, justifying a baseline score of 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description begins with 'List the links on a web page as absolute URLs' which is a specific verb and resource. It details the output (internal/external flags, anchor text, etc.) and distinguishes from siblings by mentioning link extraction specifically, not page contents or metadata.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly suggests use cases: 'crawling a documentation tree, auditing outbound links or finding next pages.' This provides clear context for when to use the tool, but does not include exclusions or alternatives, which would push it to a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
extract_metadataExtract page metadataARead-onlyIdempotent
Extract structured metadata from a web page without downloading it twice: title, meta description, canonical URL, language, author, publish dates, Open Graph and Twitter card tags, JSON-LD blocks, RSS/Atom feeds, hreflang alternates, the h1-h6 outline and the raw HTTP response headers. Use this to classify or summarise a page cheaply before fetching its full text.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Absolute http(s) URL of the page to fetch. | |
| timeoutMs | No | Request timeout in milliseconds (1000-120000). | |
| includeJsonLd | No | Include parsed JSON-LD blocks. | |
| includeHeadings | No | Include the h1-h6 outline. | |
| includeHttpHeaders | No | Include response headers (lower-cased keys). |
Output Schema
| Name | Required | Description |
|---|---|---|
| url | Yes | |
| feeds | Yes | |
| title | Yes | |
| author | Yes | |
| jsonLd | Yes | |
| robots | Yes | |
| status | Yes | |
| charset | Yes | |
| favicon | Yes | |
| Yes | ||
| headings | Yes | |
| language | Yes | |
| canonical | Yes | |
| fromCache | Yes | |
| openGraph | Yes | |
| redirects | Yes | |
| wordCount | Yes | |
| alternates | Yes | |
| contentType | Yes | |
| description | Yes | |
| httpHeaders | Yes | |
| modifiedTime | Yes | |
| requestedUrl | Yes | |
| publishedTime | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds value beyond annotations by stating the tool avoids fetching the page twice, implying efficiency. It also discloses the breadth of metadata extracted (e.g., JSON-LD, RSS feeds, hreflang). No contradiction with annotations (readOnlyHint, idempotentHint, etc.).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two well-structured sentences: the first lists all extracted metadata types, the second provides usage guidance. No fluff, every phrase adds value. Front-loaded with key information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the complexity (5 parameters, sibling tools), the description is complete. It covers purpose, usage, output scope, and efficiency benefits. The presence of an output schema means return values need no further explanation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so baseline is 3. The description does not add parameter-level details beyond the schema, but the schema descriptions are self-explanatory (url, timeoutMs, booleans). No extra semantic value from description.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the tool's purpose: extracting structured metadata from a web page without downloading it twice. It lists 12 specific metadata elements (title, meta description, etc.), which distinguishes it from sibling tools fetch_page_markdown and extract_links.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description explicitly advises using this tool 'to classify or summarise a page cheaply before fetching its full text,' providing clear context for when to use it. It does not mention when not to use it or name alternatives, but the guidance is sufficient.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
fetch_page_markdownFetch page as MarkdownARead-onlyIdempotent
Fetch a web page and convert it to clean Markdown for reading or analysis. Removes scripts, styles, ads and (by default) navigation chrome, resolves relative links to absolute URLs, and keeps tables, code blocks and lists intact. Long pages are paginated: when truncated is true, call again with startIndex: nextStartIndex.
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Absolute http(s) URL of the page to fetch. | |
| maxLength | No | Maximum number of Markdown characters to return in one call. | |
| timeoutMs | No | Request timeout in milliseconds (1000-120000). | |
| startIndex | No | Character offset to start from. Use `nextStartIndex` to page through long pages. | |
| includeLinks | No | Keep Markdown links. When false, link text is inlined as plain text. | |
| includeImages | No | Keep images as Markdown image syntax. | |
| includeMetadata | No | Include a short metadata summary (title, description, canonical, language). | |
| mainContentOnly | No | Strip navigation, headers, footers and sidebars, keeping the densest content block. |
Output Schema
| Name | Required | Description |
|---|---|---|
| url | Yes | Final URL after redirects. |
| title | Yes | |
| status | Yes | |
| endIndex | Yes | |
| markdown | Yes | |
| metadata | Yes | |
| elapsedMs | Yes | |
| fromCache | Yes | |
| redirects | Yes | |
| truncated | Yes | |
| wordCount | Yes | |
| startIndex | Yes | |
| contentType | Yes | |
| totalLength | Yes | Length of the full Markdown document. |
| requestedUrl | Yes | |
| markdownLength | Yes | |
| nextStartIndex | Yes | Pass as `startIndex` to fetch the next chunk, or null when complete. |
| bytesDownloaded | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, openWorld, idempotent, non-destructive. Description adds specific behaviors: removes scripts, styles, ads, navigation chrome (by default), resolves relative links, keeps tables/code blocks/lists, and paginates. No contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, each serving a distinct purpose: purpose, behavior details, pagination instructions. Front-loaded and no redundant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the number of parameters, annotations, and presence of output schema, the description covers all key behaviors (what is stripped, pagination, link/image handling) without gaps. No missing critical information.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, but the description adds context beyond schema (e.g., pagination mechanism, default behavior for mainContentOnly). This extra value justifies above baseline 3.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states the verb (Fetch, convert) and resource (web page, Markdown) with a specific purpose (reading or analysis). It implicitly distinguishes from siblings extract_metadata and extract_links by focusing on full-page conversion to Markdown.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides explicit guidance on pagination ('when truncated is true, call again with startIndex: nextStartIndex') and mentions defaults. However, does not explicitly state when not to use this tool versus siblings.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
3 tool updates
v0.1.0- First observed
extract_links - First observed
extract_metadata - First observed
fetch_page_markdown
TDQS
Each tool has a clearly distinct purpose: fetching and converting to markdown, extracting metadata, and extracting links. No overlap or ambiguity.
All tool names follow a consistent verb_noun pattern using snake_case (fetch_page_markdown, extract_metadata, extract_links), making them predictable.
Three tools is well-scoped for a web fetcher: one to get content, one for metadata, one for links. No unnecessary tools and the set feels complete for the domain.
The tool set covers the main needs of a web fetcher: retrieving content, extracting metadata, and listing links. There are no obvious gaps for typical use cases.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Document-to-Markdown MCP server — convert PDF, Office and HTML into LLM-ready Markdown.
Hosted MCP server: convert PDFs to clean, LLM-ready Markdown with tables, formulas and OCR.
MCP server (stdio): fetch web pages as clean readable markdown via the AgentForge API
Web scraping for AI agents. Converts URLs to clean, LLM-ready Markdown with anti-bot bypass.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceA Python implementation of an MCP server that extracts webpage content, removes ads and non-essential elements, and transforms it into clean, LLM-optimized Markdown.4MIT
- FlicenseNot gradedqualityNot gradedmaintenanceAn MCP server for web content extraction that converts HTML pages into clean, LLM-optimized Markdown using Mozilla's Readability. It supports batch processing, intelligent multi-page crawling, and configurable caching while respecting robots.txt standards.43-
- AlicenseAqualityCmaintenanceAn MCP server that fetches web pages and extracts clean, AI-friendly Markdown content using Mozilla Readability. It provides secure web access for LLMs with built-in SSRF protection and automated content cleaning for improved context retrieval and summarization.1311MIT
- AlicenseAqualityCmaintenanceFetch URLs and return clean, LLM-ready markdown with metadata and layered prompt injection defense. Configurable timeouts, word limits, JS rendering, and link extraction. All-in-one MCP server + CLI.11MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/vojtisprime11/mcp-server-web-fetcher'
If you have feedback or need assistance with the MCP directory API, please join our Discord server