Skip to main content
Glama
dguastaf

paperless-mcp

by dguastaf

paperless-mcp

An MCP server that exposes a self-hosted Paperless-ngx instance as tools an MCP client (Claude Desktop, Claude Code) can call — search documents, fetch a document's OCR'd text, and list tags/document types/correspondents/custom fields. Paperless already does OCR and full-text indexing; this server doesn't add its own index or vector store, it just gives Claude a way to query Paperless's.

Designed to run LAN-only: no public exposure, no router port-forwarding. Only reachable from MCP clients on the home network.

Two independent auth layers

  1. This server → Paperless — a Paperless API token, sent as Authorization: Token <token> on every request to Paperless. Generated from a dedicated read-only Paperless user (not your admin account), so read-only is enforced by Paperless itself, not just by this server's code never calling write endpoints.

  2. MCP client → this server — a static bearer token (Authorization: Bearer <token>), checked before any request reaches the MCP transport. The LAN isn't a trust boundary by itself, and these documents include tax/financial data, so this server should never be reachable without it.

Related MCP server: paperlessngx-mcp

1. Paperless-side setup

  1. In Paperless's admin UI, create a new user (e.g. mcp-readonly) and grant it view-only permissions on documents (and whatever tags/types/correspondents/custom fields it should be able to list) — not the admin account.

  2. Log in as that user and generate an API token: user profile → "API Auth Token", or POST /api/token/ with that user's username/password.

  3. Verify read-only is actually enforced, independent of this server's code:

    curl -i -X PATCH \
      -H "Authorization: Token <token>" \
      -H "Content-Type: application/json" \
      -d '{"title": "should not work"}' \
      http://<paperless-host>/api/documents/<some-id>/

    This should come back 403 Forbidden.

  4. Confirm the base URL this server should use — see "Networking on naspi" below.

2. Configure

cp .env.example .env

Fill in:

  • PAPERLESS_BASE_URL — how this container reaches Paperless (see below).

  • PAPERLESS_API_TOKEN — the read-only user's token from step 1.

  • MCP_BEARER_TOKEN — a long random string (e.g. openssl rand -hex 32). This is what MCP clients must send.

  • PORT — defaults to 8420.

  • LAN_BIND_IP — required. naspi's LAN-facing IP; docker compose up refuses to start without it, so port 8420 never binds to a public interface by accident. See "Networking on naspi" below.

3. Run locally (before containerizing)

npm install
npm test    # unit tests — no live Paperless instance needed
npm run dev # starts the server against your real Paperless instance

Then use the MCP inspector to call tools manually and confirm real results come back:

npx @modelcontextprotocol/inspector

Point it at http://localhost:8420/mcp with header Authorization: Bearer <MCP_BEARER_TOKEN>, and try search_documents, get_document, and list_facets.

4. Networking on naspi

Two options, in order of preference:

  • Join Paperless's existing Docker network (preferred — internal, no LAN hop). Find its network name with docker network ls / docker inspect <paperless-webserver-container>, then set that as the external network name in docker-compose.yml, and set PAPERLESS_BASE_URL to the Paperless container's name on that network (e.g. http://paperless-webserver-1:8000).

  • Reach Paperless over the LAN IP/port if the containers can't share a network, e.g. PAPERLESS_BASE_URL=http://10.4.0.100:<paperless-port>.

Set LAN_BIND_IP in .env to naspi's LAN-only IP so docker-compose publishes port 8420 only on that interface — required even if naspi doesn't currently have a public IP, since it's a cheap guard against future network changes exposing this server by accident.

5. Deploy

docker compose up -d --build

Verify it's LAN-only:

# From a device on the home network — should succeed (401 without the bearer token is expected/correct):
curl -i http://10.4.0.100:8420/healthz

# From outside the LAN — should fail/time out, confirming no public exposure.

6. Connect an MCP client

Point Claude Desktop / Claude Code's remote MCP server config at:

  • URL: http://10.4.0.100:8420/mcp

  • Header: Authorization: Bearer <MCP_BEARER_TOKEN>

Then ask it something like "what does error code E4 mean on my dryer" or "did I take the standard deduction on my 2024 taxes and what was my AGI" — it should call list_facets/search_documents to find the right document, get_document to read it, and answer from the actual OCR'd content.

Maintenance

  • Regenerating tokens: revoke/regenerate the Paperless API token from the mcp-readonly user's profile; update .env and redeploy. Rotate MCP_BEARER_TOKEN the same way — update .env, redeploy, and update every MCP client's config.

  • Redeploying: docker compose up -d --build picks up code or .env changes.

  • Scaling to more MCP servers on naspi later: keep each server on its own port for now. If a second one gets added, put a single LAN-only reverse proxy (e.g. Caddy) in front of all of them with path routing (/mcp/paperless, /mcp/other) instead of tracking a growing list of ports — this server doesn't need to change for that, only how it's fronted.

Related MCP Connectors

Related MCP Servers

  • F
    license
    C
    quality
    A
    maintenance
    Enables searching, tagging, uploading, and reading documents in Paperless-NGX, with management of tags, correspondents, document types, and custom fields via MCP tools and resources.
    50
    1
    -
  • F
    license
    A
    quality
    B
    maintenance
    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.
    13
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    A read-only and write MCP server for Paperless-ngx, enabling document listing, metadata retrieval, and creation of tags, correspondents, document types, and sorting workflows via natural language.
    MIT