Connect AI agents to Paperless-ngx. This Model Context Protocol server exposes the Paperless-ngx REST API as 115+ tools, so assistants like Claude can search, upload, tag, and organize your documents — with optional semantic search over your whole archive using local vector embeddings.
Works with any MCP client: Claude Code, Claude Desktop, or your own agent.
- Full document management — search, upload, download, update, and bulk-edit documents; manage tags, correspondents, document types, storage paths, custom fields, and saved views
- Semantic search (optional) — vector similarity search with OpenAI or Ollama embeddings, stored locally in sqlite-vec; no external vector DB
- AI-assisted workflows — auto-classify documents, process your inbox, bulk-tag by content
- Mail, sharing, and trash — configure mail accounts and rules, create public share links, email documents, and restore from the trash
- Paperless-ngx 3.0 ready — document versions, PDF editing, share bundles, and task management, with the REST API version pinned so 2.x and 3.x both behave predictably
- Single- or multi-user — stdio transport for personal use, or an HTTP transport where every user authenticates with their own Paperless token and only sees their own documents
- Docker-ready — prebuilt image on GHCR, drop-in sidecar for your Paperless-ngx compose stack
Things you can ask once it's connected:
"Find all invoices from my ISP in 2025 and tag them as telecom"
"Upload this PDF, set the correspondent to my landlord, and file it as a contract"
"Which documents in my inbox are missing a correspondent or document type?"
"Give me just the page of my insurance policy that mentions the deductible, as a PDF"
"Find the document about the espresso machine warranty" (semantic search — no keyword match needed)
The package is published as @orellbuehler/paperless-mcp and runs directly with npx — no clone or build needed.
- Get your API token from Paperless-ngx (Settings > Administration, or
POST /api/token/) - Register the server with your MCP client. With Claude Code:
claude mcp add paperless \
--env PAPERLESS_URL=https://your-paperless-instance.example.com \
--env PAPERLESS_TOKEN=your-api-token \
-- npx -y @orellbuehler/paperless-mcpOr as JSON config (Claude Desktop and most other MCP clients use the same shape):
{
"mcpServers": {
"paperless": {
"command": "npx",
"args": ["-y", "@orellbuehler/paperless-mcp"],
"env": {
"PAPERLESS_URL": "https://your-paperless-instance.example.com",
"PAPERLESS_TOKEN": "your-api-token"
}
}
}
}- Restart your MCP client. The tools are available immediately.
Semantic search is off by default. To enable it, add "EMBEDDINGS_ENABLED": "true" (and "OPENAI_API_KEY" if using the OpenAI embedding provider), then run sync_embeddings once to index your documents. The better-sqlite3 and sqlite-vec native modules are installed automatically as optional dependencies (this requires a build toolchain on your platform); the core document tools work without them.
| Category | Tools |
|---|---|
| Search | search_documents, search_autocomplete |
| Documents | list_documents, get_document, get_documents, download_document, update_document, delete_document, upload_document |
| Document details | get_document_metadata, get_document_suggestions, get_document_notes, add_document_note, delete_document_note, get_document_history |
| Bulk operations | bulk_edit_documents, bulk_set_object_permissions, get_next_asn, get_selection_data, bulk_download_documents |
| Storage path tools | test_storage_path |
| Correspondents | list_correspondents, get_correspondent, create_correspondent, update_correspondent, delete_correspondent |
| Document types | list_document_types, get_document_type, create_document_type, update_document_type, delete_document_type |
| Tags | list_tags, get_tag, create_tag, update_tag, delete_tag |
| Saved views | list_saved_views, get_saved_view, create_saved_view, update_saved_view |
| Storage paths | list_storage_paths, get_storage_path, create_storage_path, update_storage_path |
| Custom fields | list_custom_fields, get_custom_field, create_custom_field, update_custom_field |
| Users | list_users, get_user, create_user, update_user |
| Groups | list_groups, get_group, create_group, update_group |
| Paperless workflows | list_workflows, get_workflow, create_workflow, update_workflow |
| Mail accounts | list_mail_accounts, get_mail_account, create_mail_account, update_mail_account, test_mail_account, process_mail_account |
| Mail rules | list_mail_rules, get_mail_rule, create_mail_rule, update_mail_rule, list_processed_mail |
| Sharing | list_share_links, get_share_link, create_share_link, get_document_share_links |
email_document, email_documents |
|
| Trash | list_trash, restore_from_trash, empty_trash |
| System | get_status, get_statistics, list_tasks, acknowledge_tasks, get_config, update_config, get_ui_settings, get_profile, get_remote_version, list_logs, get_log |
Note:
list_documentsandsearch_documentsreturn document metadata only (no OCR text) to keep responses small. Useget_document(single) orget_documents(batch) to retrieve full content.
list_documentssupports inclusive and exclusive date ranges (created/added__date__gt|gte|lt|lte), multi-value filters (correspondent__id__in,document_type__id__in,tags__id__in|all), andcustom_field_query(JSON expressions like["Amount", "gt", 100]) for filtering by custom field values.yearly_document_checkcompares a year against the previous one per correspondent + document type — useful for spotting missing recurring documents (e.g. when preparing a tax report).Saved views, users/groups, and workflows support read + create + update only — no delete tools (use the Paperless web UI to delete). User management covers accounts and group membership; it does not set per-document permissions. Notes support add and delete only (no edit), so there is no note-editing tool.
The
update_*tools for tags, correspondents, document types, storage paths, saved views, and custom fields acceptownerandset_permissions({ view, change }→{ users, groups }) to share objects.bulk_set_object_permissionssets owner/permissions on many tags, correspondents, document types, or storage paths in one call (saved views and custom fields are not supported by the bulk endpoint — share those individually).
| Category | Tools | Description |
|---|---|---|
| Semantic search | semantic_search, sync_embeddings, embedding_status |
Vector similarity search using local sqlite-vec database |
| Content | get_document_content |
Extract OCR'd text content from documents |
| Workflows | auto_classify_document, process_inbox, bulk_tag_by_content |
AI-assisted classification and bulk operations |
| Helpers | get_documents_by_correspondent, monthly_summary, yearly_document_check, upload_from_url |
Convenience tools for common workflows |
| PDF pages | extract_document_pages, find_document_pages |
Extract pages into a new PDF file or find the pages containing a text snippet — processed locally, without modifying the document in Paperless |
These call endpoints that only exist on Paperless-ngx 3.0 and later. On older servers they return a 404 error; everything else in the table above works on 2.x too.
| Category | Tools |
|---|---|
| Document versions | upload_document_version, set_document_version_label, delete_document_version, get_document_root |
| Document editing | rotate_documents, merge_documents, edit_pdf_document, remove_document_password, reprocess_documents |
| AI | get_document_ai_suggestions |
| Share bundles | list_share_link_bundles, get_share_link_bundle, create_share_link_bundle, rebuild_share_link_bundle |
| Task management | run_task, get_task_summary, get_task_status_counts, get_active_tasks |
On Paperless-ngx 2.x, use
bulk_edit_documentswithmethod: "rotate" | "merge" | "split"instead of the dedicated document-editing tools. Both paths still work on 3.x.
Paperless-ngx negotiates its REST API version through the Accept header. This server pins API v9 by default, which is what every tool above is written against and what both 2.x and 3.x servers accept.
This matters because Paperless-ngx 3.0 made v10 the server-side default. Sending no version header at all means a 3.x server answers with v10 shapes, which differ in ways that break clients written for v9:
/api/tasks/becomes paginated, andtask_name/typeare renamed totask_type/trigger_source- Saved views drop
show_on_dashboardandshow_in_sidebar(they move to UI settings), socreate_saved_viewwould silently create views that never appear in the sidebar
Set PAPERLESS_API_VERSION=10 to opt into v10 once you are ready for those changes, or PAPERLESS_API_VERSION=none to send no header and let the server pick. Note that create_saved_view and list_tasks still assume v9 semantics.
| Variable | Required | Description |
|---|---|---|
PAPERLESS_URL |
Yes | Base URL of your Paperless-ngx instance |
PAPERLESS_TOKEN |
Yes | API token. In stdio mode this is the user's token; in http mode it is the admin/indexer token (builds the shared embedding index and gates sync_embeddings) |
PAPERLESS_API_VERSION |
No | Paperless REST API version to request (default: 9). Set to 10 for Paperless-ngx 3.0+ v10 semantics, or none to send no version header |
MCP_TRANSPORT |
No | stdio (default) or http |
PORT |
No | Port for the HTTP server (default: 3001, http mode only) |
EMBEDDINGS_ENABLED |
No | Set to true to enable semantic search tools (default: off) |
MCP_ALLOWED_ORIGINS |
No | Comma-separated Origin allowlist for browser clients (http mode). Empty (default) blocks all cross-origin browser requests; use * to allow any |
MCP_ALLOWED_HOSTS |
No | Comma-separated Host allowlist for DNS-rebinding protection (http mode). Empty (default) disables host validation |
EMBEDDING_PROVIDER |
No | openai or ollama (default: openai) |
OPENAI_API_KEY |
If using OpenAI | Required for OpenAI embeddings |
OLLAMA_URL |
If using Ollama | Ollama server URL (default: http://localhost:11434) |
EMBEDDING_MODEL |
No | Model name (defaults per provider) |
EMBEDDING_DIMENSIONS |
No | Vector dimensions (defaults per provider) |
PAPERLESS_MCP_DATA |
No | Directory for the vector DB (default: ~/.paperless-mcp) |
The server supports two transports, selected by MCP_TRANSPORT.
Single-user. The MCP client launches the server as a subprocess and it uses
PAPERLESS_TOKEN for all requests. This is the configuration shown above.
Run the server as a shared HTTP service (e.g. a sidecar next to your Paperless-ngx deployment) so other users on your network can connect:
MCP_TRANSPORT=http PORT=3001 \
PAPERLESS_URL=https://paperless.example.com \
PAPERLESS_TOKEN=<admin-token> \
node dist/index.jsClients connect to http://<host>:3001/mcp and authenticate with their own
Paperless API token via an Authorization: Bearer <token> header (or
X-Paperless-Token). Every Paperless call is made with that token, so each user
only sees the documents their account permits.
PAPERLESS_TOKEN is the admin/indexer token: it builds the shared semantic-search
index, and the sync_embeddings tool is only available to a session using the
admin token. semantic_search results are filtered through the requesting user's
token, so users never see documents they cannot access.
Non-browser MCP clients (which don't send an Origin header) work out of the box.
Browser-based clients are blocked unless you list their origin in
MCP_ALLOWED_ORIGINS. If the server is reachable on a public hostname, set
MCP_ALLOWED_HOSTS to the expected host(s) for DNS-rebinding protection.
A prebuilt image is published to the GitHub Container Registry on every release
and every push to main:
ghcr.io/orellbuehler/paperless-mcp:latest # tracks main
ghcr.io/orellbuehler/paperless-mcp:1 # latest 1.x release
ghcr.io/orellbuehler/paperless-mcp:1.0.0 # exact version
Pull it directly:
docker pull ghcr.io/orellbuehler/paperless-mcp:latestAdd the server as a service next to your existing Paperless-ngx compose stack:
paperless-mcp:
image: ghcr.io/orellbuehler/paperless-mcp:latest
restart: unless-stopped
depends_on:
- webserver
ports:
- 3001:3001
volumes:
- /mnt/ssd/paperless_ngx/mcp:/data
environment:
MCP_TRANSPORT: http
PORT: 3001
PAPERLESS_URL: http://webserver:8000
PAPERLESS_TOKEN: <admin-token>
PAPERLESS_MCP_DATA: /data
EMBEDDINGS_ENABLED: "true"
EMBEDDING_PROVIDER: openai
OPENAI_API_KEY: <key>The image already defaults to MCP_TRANSPORT=http, PORT=3001, and
PAPERLESS_MCP_DATA=/data, so the only variables you must set are
PAPERLESS_URL and PAPERLESS_TOKEN (plus OPENAI_API_KEY when semantic
search is enabled). Mount a volume at /data to persist the embedding index
across restarts.
LAN clients connect to http://<host>:3001/mcp with their own Paperless API
token. Run sync_embeddings once with the admin token to build the shared
semantic index.
To build the image yourself instead of pulling it, a Dockerfile is included:
docker build -t paperless-mcp .For local development, clone the repo and build the dist/ output:
npm install
npm run build
npm testTo run your local build instead of the npm package, use "command": "node" with
"args": ["/path/to/paperless-mcp/dist/index.js"] in your MCP config.
Optionally enable the git pre-commit hooks, which run the same format:check /
lint / typecheck / test gates as CI before each commit. They use
prek (a fast, dependency-free pre-commit runner);
install it, then activate the hooks once per clone:
prek installThe server runs from the compiled dist/ output, so updating is just rebuild + restart — there's no need to re-run claude mcp add (the launch command and path don't change):
git pull # if you track a remote
npm install # only if dependencies changed
npm run build # recompile src/ -> dist/Then restart your MCP client so it re-spawns the server with the new build. Verify with claude mcp list (should show paperless ✓ connected) or run /mcp inside a session.
To change connection settings (URL, token, embedding provider), edit the env block in your config, or re-register the server:
claude mcp remove paperless
claude mcp add paperless --scope user \
--env PAPERLESS_URL=http://localhost:8000 \
--env PAPERLESS_TOKEN=your-api-token \
-- node /path/to/paperless-mcp/dist/index.jspaperless-openapi.yaml is the Paperless-ngx OpenAPI schema used as a reference when building tools. Pull a fresh copy straight from a running instance (no Docker needed):
PAPERLESS_URL=https://your-paperless-instance.example.com \
PAPERLESS_TOKEN=your-api-token \
npm run spec:updateThis fetches GET /api/schema/ and overwrites paperless-openapi.yaml. Run it whenever you upgrade Paperless-ngx.