MCP tools
The Model Context Protocol (MCP) server at /mcp provides read_url, get_share and list_recent. To connect a client, see MCP server.
read_url
Converts a URL to Markdown with the same extraction and cache as GET /api. On a cache miss, Hacker News URLs use the general web extraction, so read_url returns no Hacker News comment tree. A cached Hacker News conversion, such as one made by GET /api, comes back as it is stored, including any comments it has. To get the comment tree reliably, call GET /api.
urlstringrequiredThe URL to convert.
querystringA question, at least one character. Returns only the matching sections. See Query-scoped extraction.
max_tokensintegerdefault: 600Token budget for query, 64 to 20000. Has no effect without query, but a value outside the range is rejected.
frontmatterbooleandefault: falseInclude the full frontmatter block.
nocachebooleandefault: falseSkip the one-hour cache.
commentsbooleandefault: trueInclude Reddit comments. Ignored for other URLs.
comment_depthinteger or 'all'default: 3Reply depth, 1 to 10, or "all" for the whole thread. See Shape the comments.
comment_limitintegerMaximum top-level comments, 1 to 500.
imagesstringon or off. Describe and transcribe the images of a Reddit post with the vision model. See Describe images in Reddit posts.
langstringdefault: deComments heading language, de or en.
extractorstringreadability, trafilatura or playwright.
yt_timecodesstringdefault: linkslinks, plain or none. Without it, the MarkItDown sidecar uses MARKITDOWN_YT_TIMECODES, which defaults to links.
yt_chunkintegerdefault: 30Transcript block length in seconds. 0 keeps each caption line. Without it, the MarkItDown sidecar uses MARKITDOWN_YT_CHUNK, which defaults to 30.
pdf_ocrbooleandefault: falseUse the OCR provider for PDFs. The REST equivalent is pdf=ocr. Requires PULLMD_PDF_OCR_API_KEY; without it, PDFs use the default text extraction and no error is returned.
read_url has no format or render parameter. To force a browser render, set extractor to playwright. A call that sets nocache, extractor, yt_timecodes, yt_chunk or pdf_ocr skips the cache.
Output: Markdown text that starts with a short YAML block, whether or not frontmatter is on. Fields without a value are left out, and when PULLMD_FRONTMATTER_FIELDS names at least one known field, only the fields it lists appear, so the block can be shorter or missing:
---
source: readability
share_id: 1db53bd711064ea50853ce8dc7288a50
share_url: http://localhost:3000/s/1db53bd711064ea50853ce8dc7288a50
quality: 1
cached: true
---
share_url uses your PUBLIC_URL. With query and no match, the body starts with <!-- query-extract: no match; returning full page -->.
Errors come back as an error result (isError: true). Messages include:
| Message | Cause |
|---|---|
URL not allowed: … | The URL, a redirect, or a page loaded in the browser points at a private or metadata address, or the host doesn't resolve. See SSRF protection. |
Site is behind a bot-protection challenge (Cloudflare); … | The site served a bot-protection challenge. The message names the provider, such as Cloudflare or Bunny Shield. The message includes the workaround: save the page and send it to POST /api/html. |
Page fetch failed with status 404 | The site answered with an error status. The message carries the status. |
MCP error -32602: Input validation error: … | A parameter is out of range, for example max_tokens below 64. The message names the parameter and the limit. |
get_share
Returns a cached conversion by share id. If it's more than an hour old, PullMD fetches the source again first; if the source is gone, you get the last good copy. Use it as a stable, live link to content that changes, such as a subreddit feed.
idstringrequiredThe 32-character hex share id from a read_url result.
Output: Markdown with a YAML block that includes refreshed (whether it was fetched again) and age_ms (age of the copy in milliseconds). An unknown, expired or malformed id returns an error result: Share id not found or expired. On an instance without a cache, every call returns Cache not available.
list_recent
Lists recent conversions, newest first. With sign-in enabled, you see only your own. With sign-in off and DISABLE_PUBLIC_HISTORY=true, it returns an error result: Public history is disabled on this instance.
limitintegerdefault: 10Number of items, 1 to 50.
Output: A JSON array with one object per conversion. This example is shortened to one item:
[
{
"url": "https://en.wikipedia.org/wiki/Markdown",
"title": "Markdown - Wikipedia",
"source": "readability",
"share_id": "1db53bd711064ea50853ce8dc7288a50",
"share_url": "http://localhost:3000/s/1db53bd711064ea50853ce8dc7288a50",
"created_at": "2026-10-23 23:48:55"
}
]
Authentication
With sign-in enabled, /mcp needs one of these as Authorization: Bearer …:
- An API key (
pmd_…). - An OAuth access token. PullMD issues these only when
OAUTH_JWT_SECRETis set. - In
single-adminmode, the legacyPULLMD_AUTH_TOKEN.
A request without valid credentials gets HTTP 401 with a WWW-Authenticate header. When PUBLIC_URL is set, the header carries a resource_metadata URL that points MCP clients to the OAuth metadata, which is how claude.ai starts its sign-in. That metadata exists only when OAUTH_JWT_SECRET is set. Without PUBLIC_URL, the header is Bearer realm="pullmd". Without sign-in, /mcp is open.