Connects Claude to your Paperless-NGX instance through its REST API. You get full document management: search, download, bulk edit, upload with metadata, and thumbnail previews returned as base64 WebP. The tag, correspondent, and document type endpoints let you build and modify your taxonomy on the fly. Bulk operations cover the usual suspects like tagging and deletion, plus less common ones like merging PDFs, splitting by page ranges, and rotating. Runs via npx or Docker with just your instance URL and API token. Reach for this when you want conversational access to your document archive without opening the web UI.
An MCP (Model Context Protocol) server for interacting with a Paperless-NGX API server. This server provides tools for managing documents, tags, correspondents, and document types in your Paperless-NGX instance.
Add these to your MCP config file:
// STDIO mode (recommended for local or CLI use)
"paperless": {
"command": "npx",
"args": [
"-y",
"@baruchiro/paperless-mcp@latest",
],
"env": {
"PAPERLESS_URL": "http://your-paperless-instance:8000",
"PAPERLESS_API_KEY": "your-api-token",
"PAPERLESS_PUBLIC_URL": "https://your-public-domain.com"
}
}
// HTTP mode (recommended for Docker or remote use)
"paperless": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"ghcr.io/baruchiro/paperless-mcp:latest",
],
"env": {
"PAPERLESS_URL": "http://your-paperless-instance:8000",
"PAPERLESS_API_KEY": "your-api-token",
"PAPERLESS_PUBLIC_URL": "https://your-public-domain.com"
}
}
Get your API token:
Replace the placeholders in your MCP config:
http://your-paperless-instance:8000 with your Paperless-NGX URLyour-api-token with the token you just generatedhttps://your-public-domain.com with your public Paperless-NGX URL (optional, falls back to PAPERLESS_URL)| Variable | Required | Default | Description |
|---|---|---|---|
PAPERLESS_URL | Yes | — | Base URL of your Paperless-NGX instance |
PAPERLESS_API_KEY | Yes | — | API token from your Paperless-NGX profile |
PAPERLESS_PUBLIC_URL | No | PAPERLESS_URL | Public-facing URL for document links |
PAPERLESS_API_VERSION | No | 5 | Paperless-ngx REST API version. Use 10 for Paperless-ngx v3+. If you see HTTP 406 errors, set this to 10. |
PAPERLESS_MCP_UPLOAD_PATHS | No | — | Colon-separated list of allowed directories for file_path uploads. Recommended for security. Example: /var/uploads:/tmp/scans |
That's it! Now you can ask Claude to help you manage your Paperless-NGX documents.
Here are some things you can ask Claude to do:
Get a paginated list of all documents.
Parameters:
list_documents({
page: 1,
page_size: 25
})
Get a specific document by ID.
Parameters:
get_document({
id: 123
})
Full-text search across documents.
Parameters:
search_documents({
query: "invoice 2024"
})
Download a document file by ID.
Parameters:
download_document({
id: 123,
original: false
})
Get a document thumbnail (image preview) by ID. Returns the thumbnail as a base64-encoded WebP image resource.
Parameters:
get_document_thumbnail({
id: 123
})
Perform bulk operations on multiple documents.
Parameters:
Examples:
// Add a tag to multiple documents
bulk_edit_documents({
documents: [1, 2, 3],
method: "add_tag",
tag: 5
})
// Set correspondent and document type
bulk_edit_documents({
documents: [4, 5],
method: "set_correspondent",
correspondent: 2
})
// Merge documents
bulk_edit_documents({
documents: [6, 7, 8],
method: "merge",
metadata_document_id: 6,
delete_originals: true
})
// Split document into parts
bulk_edit_documents({
documents: [9],
method: "split",
pages: "[1-2,3-4,5]"
})
// Modify multiple tags at once
bulk_edit_documents({
documents: [10, 11],
method: "modify_tags",
add_tags: [1, 2],
remove_tags: [3, 4]
})
// Modify custom fields
bulk_edit_documents({
documents: [12, 13],
method: "modify_custom_fields",
add_custom_fields: [
{ field: 2, value: "year" }
],
remove_custom_fields: []
})
// Set an empty custom field value, e.g. a date field used as a pending marker
bulk_edit_documents({
documents: [14],
method: "modify_custom_fields",
add_custom_fields: [
{ field: 9, value: "" }
],
remove_custom_fields: []
})
Upload a new document to Paperless-NGX.
Two upload modes:
file (base64-encoded content) + filenamefile_path (absolute path on server)Security Note: When using file_path, set the PAPERLESS_MCP_UPLOAD_PATHS environment variable (colon-separated list of allowed directories) to restrict uploads to specific locations. Without this, any file on the server's filesystem could be uploaded.
Parameters:
file or file_path required.file or file_path required.file, optional with file_path (derives from path).File size limit: 100MB for both modes
// Base64 mode (traditional)
post_document({
file: "base64_encoded_content",
filename: "invoice.pdf",
title: "January Invoice",
created: "2024-01-19",
correspondent: 1,
document_type: 2,
tags: [1, 3],
archive_serial_number: "2024-001",
custom_fields: [1, 2]
})
// Filesystem mode (more efficient for large files)
post_document({
file_path: "/var/uploads/invoice.pdf",
title: "January Invoice",
correspondent: 1,
document_type: 2,
tags: [1, 3]
})
Get all tags.
list_tags()
Create a new tag.
Parameters:
create_tag({
name: "Invoice",
color: "#ff0000",
match: "invoice",
matching_algorithm: 5
})
Get all correspondents.
list_correspondents()
Create a new correspondent.
Parameters:
create_correspondent({
name: "ACME Corp",
match: "ACME",
matching_algorithm: 5
})
Get all document types.
list_document_types()
Create a new document type.
Parameters:
create_document_type({
name: "Invoice",
match: "invoice total amount due",
matching_algorithm: 1
})
Get all custom fields.
list_custom_fields()
Get a specific custom field by ID.
Parameters:
get_custom_field({
id: 1
})
Create a new custom field.
Parameters:
create_custom_field({
name: "Invoice Number",
data_type: "string"
})
Update an existing custom field.
Parameters:
update_custom_field({
id: 1,
name: "Updated Invoice Number",
data_type: "string"
})
Delete a custom field.
Parameters:
delete_custom_field({
id: 1
})
Perform bulk operations on multiple custom fields.
Parameters:
bulk_edit_custom_fields({
custom_fields: [1, 2, 3],
operation: "delete"
})
Tools for managing Paperless mail accounts and the mail rules that drive automatic email ingestion. Account passwords/tokens are never exposed: they are redacted from every tool response.
List mail accounts so you can pick the account ID needed when creating a mail rule. Passwords are redacted.
Parameters:
list_mail_accounts()
Get a single mail account by ID. Password/token fields are redacted.
Parameters:
get_mail_account({
id: 1
})
Manually trigger Paperless mail processing for one account. This can consume matching mails according to the account's enabled mail rules.
Parameters:
process_mail_account({
id: 1
})
List mail rules with optional pagination.
Parameters:
list_mail_rules()
Get a single mail rule by ID.
Parameters:
get_mail_rule({
id: 1
})
Create a mail rule. Use list_mail_accounts first to choose the account.
Required parameters:
Common optional parameters:
create_mail_rule({
name: "Invoices",
account: 1,
folder: "INBOX",
filter_subject: "invoice",
action: 3,
attachment_type: 1
})
Patch an existing mail rule. Only the fields you supply are changed.
Parameters:
create_mail_rule fields to updateupdate_mail_rule({
id: 1,
enabled: false
})
Delete a mail rule. Requires an explicit confirmation flag. This changes future mail ingestion behavior but does not delete any existing documents.
Parameters:
true to confirm deletiondelete_mail_rule({
id: 1,
confirm: true
})
The server will show clear error messages if:
Run the unit test suite (no external dependencies required):
npm test
The E2E suite boots an empty Paperless-ngx instance, runs the compiled MCP server, and drives a deterministic serial scenario through tools/call requests — creating a tag, correspondent, and document type, uploading a PDF, then exercising list / get / search / download / thumbnail / bulk-edit on the same document. No LLM and no Paperless REST client outside MCP.
Prerequisites: Docker, Docker Compose, and jq.
# 1. Build the MCP server
npm run build
# 2. Start Paperless-ngx
docker compose -f docker-compose.e2e.yml up -d
# 3. Wait for Paperless to be ready, then get a token
TOKEN=$(curl -s -X POST http://localhost:8000/api/token/ \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin123"}' | jq -r '.token')
# 4. Start the MCP server
node build/index.js --http --port 3001 \
--baseUrl http://localhost:8000 --token "$TOKEN" &
MCP_PID=$!
# 5. Run the E2E tests
MCP_URL=http://localhost:3001/mcp \
PAPERLESS_URL=http://localhost:8000 \
PAPERLESS_TOKEN="$TOKEN" \
npm run test:e2e
# 6. Cleanup
kill "$MCP_PID"
docker compose -f docker-compose.e2e.yml down -v
E2E tests also run automatically in CI on every pull request and push to main, covering both the build/index.js CLI and the published Docker image.
Want to contribute or modify the server? Here's what you need to know:
npm install
node server.js http://localhost:8000 your-test-token
The server is built with:
This MCP server implements endpoints from the Paperless-NGX REST API. For more details about the underlying API, see the official documentation.
The MCP server can be run in two modes:
This is the default mode. The server communicates over stdio, suitable for CLI and direct integrations.
npm run start -- <baseUrl> <token>
To run the server as an HTTP service, use the --http flag. You can also specify the port with --port (default: 3000). This mode requires Express to be installed (it is included as a dependency).
npm run start -- <baseUrl> <token> --http --port 3000
POST /mcp on the specified port./mcp will return 405 Method Not Allowed.In HTTP mode, clients authenticate by supplying a Paperless-NGX API token via the standard Authorization header:
Authorization: Bearer <paperless-ngx-api-token>
The token is passed straight through to Paperless-NGX, so each client's own Paperless permissions are enforced end-to-end. This lets a single server instance serve multiple users, each with their own token. The same behaviour applies to both /mcp and /sse endpoints.
⚠️ Breaking change in v2.0.0 — HTTP mode is now authenticated by default.
Previously, a request with no
Authorizationheader silently fell back to the server-configuredPAPERLESS_API_KEY, which left the HTTP endpoint open to anyone who could reach the port. As of v2.0.0, requests without aBearertoken are rejected with401 Unauthorized. The server token is never used for unauthenticated requests unless you explicitly opt in with--no-auth.
| Scenario | --no-auth off (default) | --no-auth on |
|---|---|---|
Client sends Authorization: Bearer <tok> | <tok> (client-supplied) | <tok> (client-supplied) |
No header, PAPERLESS_API_KEY / --token set | 401 Unauthorized | server token |
| No header, no server token | 401 Unauthorized | 401 Unauthorized |
Migrating from v1.x: if you relied on the old fallback (a single shared PAPERLESS_API_KEY with clients that don't send a token), you have two options:
Authorization: Bearer <paperless-token>.--no-auth flag, e.g. append it to the Docker command/args or your CLI invocation. This requires a server token (PAPERLESS_API_KEY or --token) to be configured.The MCP server can be deployed using Docker and Docker Compose. The Docker image automatically runs in HTTP mode with SSE (Server-Sent Events) support on port 3000.
Create a docker-compose.yml file:
services:
paperless-mcp:
container_name: paperless-mcp
image: ghcr.io/baruchiro/paperless-mcp:latest
environment:
- PAPERLESS_URL=http://your-paperless-ngx-server:8000
- PAPERLESS_API_KEY=your-paperless-api-key
- PAPERLESS_PUBLIC_URL=https://paperless-ngx.yourpublicurl.com
ports:
- "3000:3000"
restart: unless-stopped
Then run:
docker-compose up -d
If you're using the Continue VS Code extension, you can configure it to use the Dockerized MCP server via SSE.
Create or edit .continue/mcpServers/paperless-mcp.yaml at your workspace root:
name: Paperless
version: 0.0.1
schema: v1
mcpServers:
- name: Paperless
type: sse
url: http://localhost:3000/sse
Notes:
localhost with your Docker host's IP address or hostname if running on a remote server/sse on the configured port (default: 3000)This project is a fork of nloui/paperless-mcp. Many thanks to the original author for their work. Contributions and improvements may be returned upstream.
To debug the MCP server in VS Code, use the following launch configuration:
{
"type": "node",
"request": "launch",
"name": "Debug Paperless MCP (HTTP, ts-node ESM)",
"program": "${workspaceFolder}/node_modules/ts-node/dist/bin.js",
"args": [
"--esm",
"src/index.ts",
"--http",
"--baseUrl",
"http://your-paperless-instance:8000",
"--token",
"your-api-token",
"--port",
"3002"
],
"env": {
"NODE_OPTIONS": "--loader ts-node/esm",
},
"console": "integratedTerminal",
"skipFiles": [
"<node_internals>/**"
]
}
Important: Before debugging, uncomment the following line in src/index.ts (around line 175):
// await new Promise((resolve) => setTimeout(resolve, 1000000));
This prevents the server from exiting immediately and allows you to set breakpoints and debug the code.
csoai-org/pdf-document-mcp
xt765/mcp-document-converter
io.github.xjtlumedia/markdown-formatter
io.github.ai-aviate/better-notion
suekou/mcp-notion-server
meterlong/mcp-doc