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
访问服务器

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 → StacksAdd 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

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选