paperlessngx-mcp

paperlessngx-mcp

A privacy-first MCP server for Paperless-ngx that lets an LLM agent search, organize, tag, and reference documents without exposing full text unless explicitly requested.

Category
Visit Server

README

paperless-mcp

A privacy-first Model Context Protocol (MCP) server for Paperless-ngx. It lets an LLM agent search, organize, tag, and reference your documents without the full text of those documents entering the model's context window — unless you explicitly ask for it.

Built with the official @modelcontextprotocol/sdk as a stdio server, TypeScript/ESM, Node 20+.


Why this exists: the three privacy tiers

The whole point of this server is to enforce a boundary between document metadata and document content. Most document management — "find my 2024 tax return", "tag this as an invoice", "what did the electric company send me" — needs metadata, not the OCR'd text of the document. Yet the naive Paperless API call (GET /api/documents/{id}/) returns the entire OCR content field by default, which would silently dump full document text into the model's context.

Every tool here falls into exactly one tier, and the tier dictates what data reaches the calling model:

Tier 1 — Metadata only (default)

Returns titles, dates, tags, correspondents, document types, custom fields, page counts, file sizes. Never returns OCR content. This covers the large majority of practical use cases. Search runs server-side inside Paperless and comes back as metadata.

Two mechanisms enforce this:

  1. List/search calls pass Paperless's sparse fieldset (fields=id,title,correspondent,...) so the content field is never even serialized by the server.
  2. The single-document metadata tool calls the detail endpoint (which includes content) but explicitly deletes the content field before returning.

Tier 2 — Local extraction (server-side processing)

The MCP server fetches content into its own process, runs a local parser (regex today; a local LLM in the future), and returns only the extracted value(s). The full text never leaves the server. Example: extract every dollar amount, or the "amount due" line, from an invoice — without the invoice body entering the model context.

This is the extensibility point: paperless_extract_field is designed so a future local-model extraction backend drops in behind the same contract (read content locally → parse → return only the field).

Tier 3 — Full content (explicit intent required)

Returns the full OCR text or the document binary to the calling model. These tools are clearly named and their descriptions carry a privacy_warning. Reserve them for when the user explicitly asks to read, summarize, or analyze a document's contents.

The tiers are deliberately not collapsed. A tool that searches returns metadata; a tool that fetches content is a separate, clearly-marked tool.


Tools by tier

Tool Tier Returns
paperless_search_documents 1 Matching docs as metadata (id, title, names, tags, dates, page_count)
paperless_get_document_metadata 1 One doc's full metadata (content stripped), incl. checksum, size, custom fields, notes
paperless_list_tags 1 All tags: id, name, document_count
paperless_list_correspondents 1 All correspondents: id, name, document_count
paperless_list_document_types 1 All document types: id, name, document_count
paperless_get_statistics 1 Totals, inbox count, type & tag breakdowns
paperless_tag_document 1 Sets a document's tags (by name)
paperless_set_correspondent 1 Sets a document's correspondent (by name)
paperless_set_document_type 1 Sets a document's type (by name)
paperless_suggest_tags 1 Paperless's own server-side suggestions (no content to the model)
paperless_extract_field 2 Only the field(s) extracted locally from content
paperless_get_document_content 3 Full OCR text ⚠
paperless_download_document 3 Saves the binary PDF to disk, returns the path ⚠

All Tier 1 tools resolve IDs to human-readable names. There is exactly one tool that returns OCR text (paperless_get_document_content) and one that materializes the binary (paperless_download_document).


Configuration

All config is via environment variables.

Variable Required Default Description
PAPERLESS_BASE_URL yes — e.g. http://paperless-ngx:8000
PAPERLESS_API_TOKEN yes — Paperless API token (Authorization: Token <...>)
PAPERLESS_VERIFY_SSL no true Set false for self-signed / plain-http local instances
MCP_TRANSPORT no http http (URL-based, for hermes/Portainer) or stdio
MCP_HTTP_PORT no 7411 Port the HTTP transport listens on
MCP_HTTP_HOST no 0.0.0.0 Bind address for the HTTP transport
PAPERLESS_DOWNLOAD_DIR no <tmp>/paperless-mcp-downloads Where Tier 3 downloads are written

Get an API token in Paperless under Settings → My Profile → API Token.


Transports

The server speaks two transports. Pick with MCP_TRANSPORT:

  • http (default) — listens on an HTTP port and serves the modern Streamable HTTP transport at POST/GET/DELETE /mcp and the legacy HTTP+SSE transport at GET /sse + POST /messages. A GET /health endpoint is also exposed. This is what URL-based clients like the hermes web UI connect to.
  • stdio — classic stdio transport, launched by a local MCP client over a pipe.

Run with HTTP (default)

npm install && npm run build
PAPERLESS_BASE_URL=http://localhost:8000 \
PAPERLESS_API_TOKEN=xxxxxxxx \
node dist/index.js
# -> listening on http://0.0.0.0:7411  (endpoints: /mcp, /sse, /health)

Run with stdio

MCP_TRANSPORT=stdio PAPERLESS_BASE_URL=... PAPERLESS_API_TOKEN=... node dist/index.js

stdio client config example:

{
  "mcpServers": {
    "paperless": {
      "command": "node",
      "args": ["/path/to/paperless-mcp/dist/index.js"],
      "env": {
        "MCP_TRANSPORT": "stdio",
        "PAPERLESS_BASE_URL": "http://localhost:8000",
        "PAPERLESS_API_TOKEN": "xxxxxxxx"
      }
    }
  }
}

Deploying via Portainer

The included docker-compose.yml runs the server as a long-lived HTTP service on port 7411.

  1. Portainer → Stacks → Add stack.
  2. Name: paperless-mcp. Build method: Repository.
  3. Repository URL: this repo. Reference: refs/heads/main. Compose path: docker-compose.yml.
  4. Add environment variables: PAPERLESS_BASE_URL, PAPERLESS_API_TOKEN, PAPERLESS_VERIFY_SSL.
  5. Deploy the stack. First deploy builds the image (a minute or two).

The container stays running and serves:

  • Streamable HTTP: http://<host>:7411/mcp
  • Legacy SSE: http://<host>:7411/sse
  • Health: http://<host>:7411/health

If your client (e.g. hermes) runs in the same Docker network, use the service name as host: http://paperless-mcp:7411/mcp. Otherwise use the Docker host's IP and the published port 7411.

Connecting from a URL-based MCP client (e.g. hermes)

In the client's "Add MCP server" form, set the transport to HTTP/SSE and the URL to:

http://<host>:7411/mcp     # try this first (Streamable HTTP)
http://<host>:7411/sse     # use this if the client expects the legacy SSE style

No env vars are needed in the client — credentials are configured on the server (the Portainer stack).


Privacy guarantees & limits

  • Tier 1 list/search calls send fields= so Paperless never serializes content.
  • paperless_get_document_metadata deletes content from the detail response in-process before returning.
  • paperless_extract_field reads content only inside the server process and returns just the extracted value, with a privacy_note confirming content was not returned.
  • Only paperless_get_document_content and paperless_download_document surface full content; both carry an explicit privacy_warning.

This server controls what it returns. It cannot stop a client/agent from separately calling the Tier 3 tools — that's exactly why those tools are named and described to make the privacy cost obvious to the model and the user.


Extending Tier 2 extraction

src/tools.ts contains an EXTRACTORS registry of named, dependency-free regex extractors (dollar_amounts, dates, emails, phone_numbers, addresses, total_amount). extraction_pattern also accepts a raw regex.

To plug in a local LLM (the design goal): keep the same contract — fetch content into the process, run your local model, return only the requested field(s). Replace the body of paperless_extract_field's handler (or add a new named extractor that calls your local inference endpoint). The privacy boundary is preserved as long as only the extracted value is returned.


Project layout

paperless-mcp/
├── src/
│   ├── index.ts            # entrypoint: stdio transport, tool registration, dispatch
│   ├── paperless-client.ts # REST client: auth, pagination, TTL cache, ID↔name resolution
│   └── tools.ts            # tool defs + handlers, grouped by privacy tier
├── package.json
├── tsconfig.json
├── Dockerfile
├── docker-compose.yml      # Portainer stack
├── .env.example
└── README.md

License

MIT

Recommended Servers

playwright-mcp

playwright-mcp

A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.

Official
Featured
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

graphlit-mcp-server

The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.

Official
Featured
TypeScript
Kagi MCP Server

Kagi MCP Server

An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

Exa Search

A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured