Anydoc Serve
FreeNot checkedHTTP + MCP server for firecrawl/anydoc with local OCR (English, Japanese, Thai). Docker, one command.
About
HTTP + MCP server for firecrawl/anydoc with local OCR (English, Japanese, Thai). Docker, one command.
README
Office documents, PDFs and scans to Markdown over HTTP and MCP, in one Docker container, with local OCR for English, Japanese and Thai.
anydoc-serve wraps firecrawl/anydoc, the fast Rust converter for Word, PowerPoint, Excel, OpenDocument, RTF, EPUB, CSV and PDF, in a server you can run anywhere:
docker run -p 8080:8080 ghcr.io/bon5co/anydoc-serve
curl -F [email protected] http://localhost:8080/v1/convert
What it adds over plain anydoc
| anydoc | anydoc-serve | |
|---|---|---|
| Office formats, text PDFs to Markdown | yes | yes, through anydoc, output unchanged |
| Run as a service | library and CLI | HTTP API, OpenAPI docs, Docker image (anydoc#11) |
| Scanned or image-only PDF pages | NeedsOcr error, or upload the whole file to Firecrawl's hosted OCR |
OCR'd locally, on CPU; nothing leaves the container |
Images (.png, .jpg, .tiff, .webp, ...) |
not supported | OCR'd locally (anydoc#146) |
| Mixed PDFs (some pages scanned) | the whole file fails | text pages go through anydoc, only the scanned pages go through OCR (anydoc#157) |
| AI agents | agent skill (CLI) | MCP server at /mcp with a convert_document tool |
| Sideways or upside-down scans | turned upright before OCR |
OCR is Tesseract 5 reading every configured language in one pass, so a page that mixes Japanese, Thai and English is read correctly line by line. It was picked by measuring it against RapidOCR/PaddleOCR on English, Japanese, Thai and mixed-script pages: see the benchmark.
Quickstart
docker run -d -p 8080:8080 -e API_KEY=change-me ghcr.io/bon5co/anydoc-serve
or docker compose up -d with the docker-compose.yml in this repo.
Convert an upload:
curl -H "Authorization: Bearer change-me" -F [email protected] http://localhost:8080/v1/convert
{
"markdown": "# Quarterly report\n\n...",
"metadata": {
"filename": "scan.pdf", "bytes": 460689, "format": "pdf", "page_count": 4,
"ocr_pages": [3, 4], "ocr_engine": "tesseract", "ocr_langs": ["eng", "jpn", "tha"], "duration_ms": 1735.0
}
}
ocr_pages lists the 1-indexed pages that went through OCR; every other page came from anydoc.
Just the Markdown:
curl -H "Authorization: Bearer change-me" -H "Accept: text/markdown" -F [email protected] http://localhost:8080/v1/convert
Have the server fetch a URL:
curl -H "Authorization: Bearer change-me" -H "Content-Type: application/json" \
-d '{"url": "https://example.com/report.pdf"}' http://localhost:8080/v1/convert
Interactive docs are at /docs (OpenAPI JSON at /openapi.json).
API
POST /v1/convert
multipart/form-data:file(required),format,ocr,outputapplication/json:{"url": "...", "format": ..., "ocr": ..., "output": ...}
| Option | Values | Default | Meaning |
|---|---|---|---|
format |
docx, pdf, csv, ... |
detected | Force the format. Needed for CSV sent without a .csv file name. |
ocr |
auto, off |
auto |
off refuses scanned input with 422 needs_ocr and the page list, like anydoc. |
output |
json, markdown |
json |
markdown answers text/markdown, with the metadata as JSON in the X-Anydoc-Metadata header. Also accepted as a query parameter, or send Accept: text/markdown. |
Errors answer {"error": {"code": ..., "message": ...}}: 400 bad request or unfetchable URL, 401 missing or wrong key, 413 over MAX_UPLOAD_MB or OCR_MAX_PAGES, 415 unknown format, 422 encrypted, malformed, or needs OCR with OCR off, 504 over CONVERT_TIMEOUT_S.
GET /health answers {"status": "ok", "ocr": {...}, "auth": true} and never needs the key.
MCP
The server speaks MCP over streamable HTTP at /mcp, with one tool, convert_document(url | content_base64, filename?, format?, ocr?), which returns the Markdown.
Claude Code:
claude mcp add --transport http anydoc http://localhost:8080/mcp --header "Authorization: Bearer change-me"
Clients configured with an mcpServers JSON block that supports remote HTTP servers (Cursor, for example):
{
"mcpServers": {
"anydoc": {
"type": "http",
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer change-me" }
}
}
}
Configuration
| Variable | Default | Meaning |
|---|---|---|
PORT |
8080 |
Port to listen on. Railway and most platforms set it. |
API_KEY |
empty | When set, every endpoint except /health requires Authorization: Bearer <API_KEY>. Empty means open, and the server logs a warning at startup. |
OCR_LANGS |
eng,jpn,tha |
OCR languages, comma-separated: eng, jpn, tha (en, ja, th also accepted). Every page is read with all of them in one pass, always in the order jpn, tha, eng (the order was measured: eng first loses most Thai). |
OCR_ENABLED |
true |
false never OCRs: scanned input fails with 422, as in plain anydoc. |
OCR_DPI |
200 |
Resolution scanned PDF pages are rendered at for OCR. |
OCR_MAX_PAGES |
200 |
A document needing OCR on more pages than this is refused with 413. |
MAX_UPLOAD_MB |
50 |
Largest document accepted, uploaded or fetched. |
CONVERT_TIMEOUT_S |
120 |
Wall-clock limit per conversion, OCR included; over it answers 504. |
MAX_CONCURRENT_CONVERSIONS |
2 |
Conversions that run at once (the rest wait). Each slot holds its own loaded OCR models. |
FETCH_TIMEOUT_S |
30 |
Limit for downloading a {"url": ...} source, redirects included. |
ALLOW_PRIVATE_URLS |
false |
Allow URL sources that resolve to private, loopback or link-local addresses. Off by default so the server cannot be used to reach its own network. |
Resource use
Measured on linux/amd64, 2026-09-28:
- Image: 376 MB on disk (94 MB compressed download). Tesseract models for English, Japanese, Thai and script detection are 18 MB of that.
- Memory: about 305 MiB RSS after a round of conversions (
docker stats); each conversion slot loads about 90 MiB of OCR models on first use. - Speed: a born-digital
.docxconverts in about 2 ms; OCR takes about 1.1 s per page (median, 4 cores of an Intel i5-10500T). A 4-page PDF with 2 scanned pages took 1.5 to 1.7 s.
OCR benchmark
Character error rate on synthetic 200 dpi pages: degraded scans, two pages per language plus one page each mixing Japanese+English, Thai+English and all three. Median seconds per page. Full method, clean-render numbers, every engine row and reproduction steps: docs/ocr-benchmark.md.
| Engine (scans) | eng | jpn | tha | jpn+eng | tha+eng | jpn+tha+eng | s/page |
|---|---|---|---|---|---|---|---|
anydoc-serve as shipped (Tesseract 5.5 tessdata_fast, Sauvola, jpn+tha+eng, rotation detection) |
0.3% | 12.3% | 2.6% | 3.1% | 2.8% | 4.3% | 1.12 |
| Tesseract, told each page's language (oracle) | 0.3% | 11.1% | 2.6% | 3.9% | 2.8% | 4.3% | 0.39 |
| Tesseract, one language per page from script detection (earlier build) | 0.3% | 11.1% | 2.6% | 26.2% | 43.2% | 45.2% | 1.00 |
Tesseract, eng+jpn+tha (English first) |
0.2% | 16.4% | 81.2% | 4.4% | 26.8% | 21.4% | 0.51 |
| RapidOCR 3.9 PP-OCRv6 small (Thai: PP-OCRv5) | 7.6% | 1.1% | 1.7% | 2.6% | 0.4% | 21.0% | 3.44 |
| RapidOCR 3.9 PP-OCRv6 medium | 7.2% | 0.9% | 0.5% | 1.7% | 1.6% | 21.4% | 34.31 |
RapidOCR reads pure Japanese far better and two-script pages slightly better. It fails the three-script page, drops lines of English, and is 3 times slower than the server as shipped. Synthetic pages give exact ground truth but are not your documents: treat these as a comparison between engines, not an accuracy promise.
Deploy on Railway
One service, no volume. API_KEY is generated at deploy (read it from the service's Variables tab) and OCR_LANGS defaults to eng,jpn,tha. Measured on Railway: 246 to 263 MiB of RAM idle, 272 MiB after a round of conversions.
Development
uv sync
uv run pytest # unit tests, no Tesseract needed
docker build -t anydoc-serve .
docker run -d -p 8080:8080 -e API_KEY=test-key anydoc-serve
ANYDOC_SERVE_URL=http://127.0.0.1:8080 ANYDOC_SERVE_API_KEY=test-key uv run pytest -m integration
Test fixtures anydoc-text.docx and anydoc-text.pdf are copied from anydoc's own test corpus (MIT); the scanned fixtures are generated by tests/fixtures/make_fixtures.py.
Credits and license
The document conversion is all anydoc by Firecrawl (MIT). OCR is Tesseract (Apache 2.0) through tesserocr (MIT); PDF pages are rendered with pypdfium2. anydoc-serve is not affiliated with Firecrawl.
MIT, see LICENSE.
Install Anydoc Serve in Claude Desktop, Claude Code & Cursor
unyly install anydoc-serveInstalls into Claude Desktop, Claude Code, Cursor & VS Code — handles npx, uvx and build-from-source repos for you.
First time? Get the CLI: curl -fsSL https://unyly.org/install | sh
Or configure manually
Run in your terminal:
claude mcp add anydoc-serve -- uvx --from git+https://github.com/bon5co/anydoc-serve anydoc-serveStep-by-step: how to install Anydoc Serve
FAQ
Is Anydoc Serve MCP free?
Yes, Anydoc Serve MCP is free — one-click install via Unyly at no cost.
Does Anydoc Serve need an API key?
No, Anydoc Serve runs without API keys or environment variables.
Is Anydoc Serve hosted or self-hosted?
Self-hosted: the server runs locally on your machine via the install command above.
How do I install Anydoc Serve in Claude Desktop, Claude Code or Cursor?
Open Anydoc Serve on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.
Related MCPs
GitHub
PRs, issues, code search, CI status
by GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Unyly Deploy
Deploy sites and MCP servers to Unyly straight from your AI agent
by UnylyAmap Maps Mcp Server
MCP server for using the AMap Maps API
by duxiaohuiPayMCP
Lightweight payments layer for MCP servers: turn tools into paid endpoints with a two-line decorator. [PyPI](https://pypi.org/project/paymcp/) · [npm](https://w
by blustAITemplate MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
by mcpdotdirectSupabase
Database, auth and storage
by SupabaseEverything
Reference / test server with prompts, resources, and tools.
Git
Tools to read, search, and manipulate Git repositories.
Compare Anydoc Serve with
Not sure what to pick?
Find your stack in 60 seconds
Author?
Embed badge for your README
Browse similar
All development MCPs
