Skip to main content

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.

urlstringrequired

The URL to convert.

querystring

A question, at least one character. Returns only the matching sections. See Query-scoped extraction.

max_tokensintegerdefault: 600

Token budget for query, 64 to 20000. Has no effect without query, but a value outside the range is rejected.

frontmatterbooleandefault: false

Include the full frontmatter block.

nocachebooleandefault: false

Skip the one-hour cache.

commentsbooleandefault: true

Include Reddit comments. Ignored for other URLs.

comment_depthinteger or 'all'default: 3

Reply depth, 1 to 10, or "all" for the whole thread. See Shape the comments.

comment_limitinteger

Maximum top-level comments, 1 to 500.

imagesstring

on or off. Describe and transcribe the images of a Reddit post with the vision model. See Describe images in Reddit posts.

langstringdefault: de

Comments heading language, de or en.

extractorstring

readability, trafilatura or playwright.

yt_timecodesstringdefault: links

links, plain or none. Without it, the MarkItDown sidecar uses MARKITDOWN_YT_TIMECODES, which defaults to links.

yt_chunkintegerdefault: 30

Transcript block length in seconds. 0 keeps each caption line. Without it, the MarkItDown sidecar uses MARKITDOWN_YT_CHUNK, which defaults to 30.

pdf_ocrbooleandefault: false

Use 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:

MessageCause
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 404The 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.

idstringrequired

The 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: 10

Number 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_SECRET is set.
  • In single-admin mode, the legacy PULLMD_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.