paperless-ngx-mcp
Health Warn
- License — License: ISC
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Low visibility — Only 5 GitHub stars
Code Warn
- network request — Outbound network request in package-lock.json
- network request — Outbound network request in package.json
- network request — Outbound network request in src/api/PaperlessAPI.test.ts
- network request — Outbound network request in src/api/PaperlessAPI.ts
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
MCP server for Paperless-NGX (2.x and 3.x). Lets AI assistants manage documents, tags, correspondents, document types, custom fields, saved views, storage paths, workflows, share links, notes, trash, and tasks via the Paperless-NGX REST API.
paperless-ngx-mcp
A Model Context Protocol server for Paperless-NGX. Exposes the full Paperless-NGX REST API to AI assistants — documents, tags, correspondents, document types, custom fields, storage paths, saved views, share links and bundles, workflows, mail accounts and rules, document versions, notes, trash, and tasks.

Why this one?
- Complete, and it stays that way. A CI test checks every endpoint in Paperless's
/api/schema/against the tools here and fails when upstream adds one that is neither wrapped nor deliberately skipped. - Tested against real Paperless. The end-to-end suite runs the tools against a live Paperless-ngx 3.2.1 container, not mocks.
- Asks before it breaks things. Every
delete_*tool,empty_trash,merge_documents_as_versionsand bulkdeleterequireconfirm: true; bulk edits across "all matching documents" refuse filters Paperless would silently ignore; and thetriage_inboxprompt proposes changes and waits for your go-ahead before writing anything. - Easy to allowlist. Verb-first tool names (
list_*,get_*,delete_*, …) group into one permission wildcard each. - Install it your way:
npx, a Docker image, a one-click Claude Desktop extension, or the official MCP Registry.
Compatibility
Targets Paperless-ngx 3.2 (tested against 3.2.1). Older Paperless versions are not supported — use [email protected] for Paperless 2.x. The package major version tracks the Paperless-ngx major it targets; there are no 1.x or 2.x releases.
Quick Start
The server is published to npm as paperless-ngx-mcp. You can run it with npx — no clone or build required.
Claude Code
claude mcp add paperless --scope user \
--env PAPERLESS_URL=https://your-paperless-instance \
--env PAPERLESS_API_KEY=your-api-token \
-- npx -y paperless-ngx-mcp
Drop --scope user to install for the current project only. See claude mcp add --help for more options.
Codex CLI
codex mcp add paperless \
--env PAPERLESS_URL=https://your-paperless-instance \
--env PAPERLESS_API_KEY=your-api-token \
-- npx -y paperless-ngx-mcp
This writes the entry to ~/.codex/config.toml.
Claude Desktop (extension)
Download paperless-ngx-mcp.mcpb from the latest release and double-click it, or install it from Settings → Extensions. Claude Desktop asks for your Paperless URL and API token.
Claude Desktop, Cursor, Cline, and other MCP clients
The buttons install placeholder values; replace PAPERLESS_URL and PAPERLESS_API_KEY afterwards. Or add this to your client's MCP config file (e.g. claude_desktop_config.json, ~/.cursor/mcp.json, ~/.config/cline/mcp.json):
{
"mcpServers": {
"paperless": {
"command": "npx",
"args": ["-y", "paperless-ngx-mcp"],
"env": {
"PAPERLESS_URL": "https://your-paperless-instance",
"PAPERLESS_API_KEY": "your-api-token",
"PAPERLESS_PUBLIC_URL": "https://your-public-domain"
}
}
}
}
Docker
A multi-arch image (amd64, arm64) is published to ghcr.io/cubinet-code/paperless-ngx-mcp. As a stdio server in any MCP client config:
{
"mcpServers": {
"paperless": {
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "PAPERLESS_URL", "-e", "PAPERLESS_API_KEY", "ghcr.io/cubinet-code/paperless-ngx-mcp"],
"env": {
"PAPERLESS_URL": "https://your-paperless-instance",
"PAPERLESS_API_KEY": "your-api-token"
}
}
}
}
Or as a long-running Streamable HTTP server. It has no authentication, so keep it off untrusted networks:
docker run -d -p 127.0.0.1:3000:3000 \
-e PAPERLESS_URL=https://your-paperless-instance \
-e PAPERLESS_API_KEY=your-api-token \
ghcr.io/cubinet-code/paperless-ngx-mcp --http --port 3000
Get your Paperless-NGX API token
- Log into your Paperless-NGX instance.
- Click your username (top right) → My Profile.
- Click the circular arrow button to generate a new token.
Configuration
| Variable | Required | Purpose |
|---|---|---|
PAPERLESS_URL |
yes | Base URL the MCP server uses to talk to Paperless-NGX. |
PAPERLESS_API_KEY |
yes | API token (see above). |
PAPERLESS_PUBLIC_URL |
no | Public URL the assistant uses when constructing browser links to documents. Falls back to PAPERLESS_URL. |
CLI flags (--baseUrl, --token, --publicUrl, --http, --port, plus the HTTP session limits) take precedence over environment variables.
Example Usage
Things you can ask Claude (or any MCP-aware assistant):
- "Show me all documents tagged as 'Invoice'"
- "Search for documents containing 'tax return'"
- "Create a new tag called 'Receipts' with color #FF0000"
- "Download document #123"
- "List all correspondents"
- "Create a new document type called 'Bank Statement'"
- "Empty the trash"
- "Show me pending consumption tasks"
- "Move every document from correspondent 'ACME GmbH (mail)' to 'ACME GmbH'"
- "Remove the password from document #55 and keep the unlocked file as a new version"
- "Create a workflow that strips PDF passwords from uploaded bank statements"
- "Which mail rule is creating a new correspondent for every sender?"
Available Tools
The server registers tools across twelve domains.
Documents
list_documents, get_document, get_document_content, search_documents, download_document, download_documents_bulk, get_document_thumbnail, get_document_preview, get_document_history, get_document_metadata, update_document, post_document, email_document, edit_documents_bulk, delete_document, search_autocomplete, get_document_suggestions, get_document_ai_suggestions, get_next_asn, upload_document_version, update_document_version, delete_document_version, merge_documents_as_versions
Tags
list_tags, get_tag, create_tag, update_tag, delete_tag, edit_tags_bulk
Correspondents
list_correspondents, get_correspondent, create_correspondent, update_correspondent, delete_correspondent, edit_correspondents_bulk
Document Types
list_document_types, get_document_type, create_document_type, update_document_type, delete_document_type, edit_document_types_bulk
Custom Fields
list_custom_fields, get_custom_field, create_custom_field, update_custom_field, delete_custom_field, edit_custom_fields_bulk
Storage Paths
list_storage_paths, get_storage_path, create_storage_path, update_storage_path, delete_storage_path, test_storage_path
Saved Views
list_saved_views, get_saved_view, create_saved_view, update_saved_view, delete_saved_view
Share Links
list_share_links, list_document_share_links, get_share_link, create_share_link, delete_share_link, list_share_link_bundles, get_share_link_bundle, create_share_link_bundle, rebuild_share_link_bundle, delete_share_link_bundle
Workflows
list_workflows, get_workflow, create_workflow, update_workflow, delete_workflow, list_workflow_actions, get_workflow_action, create_workflow_action, update_workflow_action, delete_workflow_action, list_workflow_triggers, get_workflow_trigger, create_workflow_trigger, update_workflow_trigger, delete_workflow_trigger
list_mail_accounts, get_mail_account, create_mail_account, update_mail_account, delete_mail_account, test_mail_account, process_mail_account, list_mail_rules, get_mail_rule, create_mail_rule, update_mail_rule, delete_mail_rule
System / Notes / Trash / Tasks
get_statistics, get_system_status, list_document_notes, create_document_note, delete_document_note, list_trash, restore_from_trash, empty_trash, list_tasks, list_active_tasks, get_task_status_counts, get_task_summary, acknowledge_tasks
Tool naming convention (for permission allowlists)
Tool names are verb-first, so wildcard-based permission rules group cleanly by operation:
| Wildcard | Covers |
|---|---|
mcp__paperless__list_* |
All list/index reads |
mcp__paperless__get_* |
All single-item reads |
mcp__paperless__search_* |
Full-text search and autocomplete |
mcp__paperless__download_* |
download_document and download_documents_bulk |
mcp__paperless__create_* |
All create endpoints |
mcp__paperless__update_* |
Per-item PATCH updates |
mcp__paperless__edit_*_bulk |
All bulk-edit operations across entity types |
mcp__paperless__delete_* |
⚠️ Destructive — system-wide deletes |
mcp__paperless__test_* |
Dry-run checks: test_storage_path, test_mail_account |
mcp__paperless__upload_* |
upload_document_version |
mcp__paperless__rebuild_* |
rebuild_share_link_bundle |
mcp__paperless__merge_* |
⚠️ merge_documents_as_versions — merged documents stop existing on their own |
mcp__paperless__process_* |
process_mail_account — fetches mail now and runs its rules (which may delete or move mail on the server) |
A read-only allowlist is therefore: list_*, get_*, search_*, download_*, test_*. Write access without destructive operations: add create_*, update_*, edit_*_bulk, upload_*, rebuild_*, post_document, email_document, restore_from_trash, acknowledge_tasks. delete_*, merge_*, process_* and empty_trash should require explicit user approval.
Prompts
The server also registers MCP prompts — reusable, parameterized instructions that surface as slash commands in clients like Claude Code (e.g. /mcp__paperless__triage_inbox).
triage_inbox
Walks the assistant through inbox triage: gather existing tags / correspondents / document types, propose metadata for each inbox document preferring existing items, present a confirmation table, and only apply changes after the user replies apply. New correspondents / types / tags are flagged (NEW) so you can veto creations before they happen.
Argument:
limit(optional, default25): maximum number of inbox documents to triage in one pass.
Notable tool details
edit_documents_bulk
Perform bulk operations on multiple documents.
Parameters:
- Selection:
documents(array of IDs), orall: true+filterswith optionalexcluded_documents.filterstakes Paperless document filter names such ascorrespondent__id,tags__id__all,document_type__id,title_contentorquery— notlist_documents' tool arguments. Keys Paperless doesn't know are refused (it would otherwise ignore them and select every document), a preview query catches invalid values, and the result reportsmatched_documents.all: trueisn't supported formerge,split,delete_pages,edit_pdforremove_password. method: one ofset_correspondent,set_document_type,set_storage_path,add_tag,remove_tag,modify_tags,modify_custom_fields,delete,reprocess,set_permissions,merge,split,rotate,delete_pages,edit_pdf,remove_password- Method-specific parameters:
correspondent,document_type,storage_path,tag,add_tags,remove_tags,add_custom_fields,remove_custom_fields,set_permissions,owner,merge,metadata_document_id,delete_originals,pages,degrees,operations,update_document,include_metadata,password,delete_original,remote_ocr
// Add a tag to multiple documents
edit_documents_bulk({ documents: [1, 2, 3], method: "add_tag", tag: 5 })
// Merge documents
edit_documents_bulk({
documents: [6, 7, 8],
method: "merge",
metadata_document_id: 6,
delete_originals: true,
})
// Split a document into parts
edit_documents_bulk({ documents: [9], method: "split", pages: "[1-2,3-4,5]" })
// Modify multiple tags at once
edit_documents_bulk({
documents: [10, 11],
method: "modify_tags",
add_tags: [1, 2],
remove_tags: [3, 4],
})
// Move every document from a duplicate correspondent (12) to the canonical one (34)
edit_documents_bulk({ all: true, filters: { correspondent__id: 12 }, method: "set_correspondent", correspondent: 34 })
// Unlock a password-protected PDF, keeping the result as a new version
edit_documents_bulk({ documents: [55], method: "remove_password", password: "…", update_document: true })
Paperless answers remove_password with OK even when it skips a document (its latest version isn't encrypted) or the password is wrong, so check the document's versions afterwards.
Workflows
A workflow — its triggers plus the actions they run — is what Paperless executes; create_workflow builds one in a single call. Standalone triggers and actions (create_workflow_trigger / create_workflow_action) do nothing on their own, and Paperless deletes unattached ones whenever any workflow is updated.
- Action types: 1 assignment, 2 removal, 3 email, 4 webhook, 5 password removal, 6 move to trash, 7 remote OCR, 8 apply AI suggestions. Remote OCR needs a consumption-started trigger; AI suggestions need a trigger other than consumption-started.
update_workflowchanges only what you pass — but atriggersoractionslist replaces the whole list: entries with anidare updated, entries without one are created, and omitted ones are deleted.get_workflowoutput can be edited and sent straight back.- PDF passwords of password-removal actions are always returned masked (
**********). Sending the masked list back keeps the stored passwords; a new action needs the real ones.
Document versions
upload_document_version adds a new file to an existing document (a signed copy, a corrected scan), and merge_documents_as_versions folds duplicate documents into one. Content, search, downloads and get_document_metadata follow the latest version, but page_count and the file names on get_document describe the original (root) version — check get_document_content to see whether the current version is readable.
post_document
Upload a new document.
Parameters: file, filename, plus optional title, created, correspondent, document_type, storage_path, tags, archive_serial_number, custom_fields, poll, poll_timeout_seconds.
file accepts base64-encoded contents (the universal method — works for any deployment, since the bytes travel over the wire) or an absolute file path that the server reads from its own filesystem. The path option only works when the MCP server runs on the same machine as the file (local/stdio deployments); for a remote server, use base64.
Upload is asynchronous. By default the tool returns a task UUID (track it with list_tasks). Set poll: true to wait for the consumer to finish and get the result in one call — the new document_id on success, or the consumer error on failure. poll_timeout_seconds (default 30, max 300) caps the wait; raise it for large scans where OCR is slow.
Matching algorithms
create_tag, create_correspondent, create_document_type, and create_storage_path accept a matching_algorithm (0–6). Workflow triggers accept 0–5 (no Automatic):
| Value | Meaning |
|---|---|
| 0 | None |
| 1 | Any word |
| 2 | All words |
| 3 | Exact match |
| 4 | Regular expression |
| 5 | Fuzzy word |
| 6 | Automatic |
Since Paperless-ngx 3.2, Automatic matching only assigns when the classifier is confident enough (PAPERLESS_CLASSIFIER_MATCH_THRESHOLD, default 0.6), and regular-expression matching gives up after PAPERLESS_MATCH_REGEX_TIMEOUT_SECONDS (default 0.1 s) — raise it if regex rules miss on long documents.
Running the MCP Server
stdio (default)
The default mode. The server communicates over stdio — that's what every MCP client config in the Quick Start uses. You usually never run this manually; the MCP client launches it for you.
If you do want to run it directly (e.g. for debugging):
# via env vars (recommended)
PAPERLESS_URL=http://localhost:8000 PAPERLESS_API_KEY=xxx npx -y paperless-ngx-mcp
# or via CLI flags
npx -y paperless-ngx-mcp --baseUrl http://localhost:8000 --token xxx
HTTP (Streamable HTTP transport)
Use the --http flag to expose the server over HTTP. --port defaults to 3000.
npx -y paperless-ngx-mcp --baseUrl http://localhost:8000 --token xxx --http --port 3000
- The MCP API is available at
POST /mcpon the chosen port, backed byStreamableHTTPServerTransportin stateful mode. - The first request (an
initializecall) creates a session and returns anMcp-Session-Idheader; subsequent requests must send that header back to reuse the same session. Transports are kept in an in-memoryMap, so this only works for single-instance deployments. GET /mcpstreams server-initiated messages for a session;DELETE /mcpterminates it and evicts it from the map. Both require a validMcp-Session-Idheader.- A legacy
GET /sse+POST /messagesSSE transport is also exposed for clients that don't yet support the streamable transport.
Session limits
Every session holds its own MCP server instance (~3.5 MB), and the HTTP port has no authentication — so sessions are bounded:
| Flag | Environment variable | Default | Purpose |
|---|---|---|---|
--maxSessions |
PAPERLESS_MAX_SESSIONS |
50 |
Concurrent sessions allowed. Past this, initialize is refused with HTTP 503. |
--sessionIdleMinutes |
PAPERLESS_SESSION_IDLE_MINUTES |
30 |
Evict a session after this long with no activity. |
A client that is actively connected — including one holding a GET /mcp stream open — is never evicted, no matter how long it stays idle. Only genuinely abandoned sessions are reclaimed.
Because sessions live in memory, --http only works for single-instance deployments. Do not expose the port to an untrusted network: there is no auth, and it binds all interfaces.
Error Handling
Tool calls return clear errors when:
PAPERLESS_URLorPAPERLESS_API_KEYis missing or wrong- The Paperless-NGX server is unreachable
- The underlying API rejects the operation — Paperless's own message is passed through (e.g.
{"non_field_errors":["password not specified"]} (HTTP 400)), while HTML error pages are reduced to the status line - A Paperless call doesn't finish within 90 seconds (downloads are exempt); for a timed-out write the error warns that the change may already have been applied
- Tool parameters fail validation
Development
You only need this section if you're modifying the server itself. End users should follow the Quick Start instead — there's no need to clone or build.
git clone https://github.com/cubinet-code/paperless-ngx-mcp.git
cd paperless-ngx-mcp
npm install # install dependencies
npm run start # run the server with tsx (no build step)
npm run build # compile TypeScript to build/
npm test # unit tests (node:test + tsx)
npm run inspect # build, then launch @modelcontextprotocol/inspector
npm run start accepts the same flags / env vars as the built binary.
End-to-end tests
E2E tests spin up a real Paperless-NGX container via Docker Compose:
npm run test:e2e:up # start the test stack (paperless + redis)
npm run test:e2e # run the e2e suite against it
npm run test:e2e:down # tear down and remove volumes
Runs against ghcr.io/paperless-ngx/paperless-ngx:3.2.1.
Built with:
- @modelcontextprotocol/sdk — MCP server SDK
- zod — schema validation
- axios — HTTP client (with keep-alive agents, a 60s idle timeout and a 90s per-request deadline)
API Documentation
This MCP server wraps endpoints from the Paperless-NGX REST API. See the official API documentation for details on the underlying behaviour and field semantics.
License
ISC. See LICENSE.
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found