{"_id":"@dellle/paperless-mcp","name":"@dellle/paperless-mcp","dist-tags":{"latest":"1.1.0"},"versions":{"1.1.0":{"name":"@dellle/paperless-mcp","version":"1.1.0","description":"Model Context Protocol (MCP) server for interacting with Paperless-NGX document management system. Enables AI assistants to manage documents, tags, correspondents, and document types through the Paperless-NGX API.","main":"build/index.js","bin":{"paperless-mcp":"build/index.js"},"scripts":{"start":"ts-node src/index.ts","build":"tsc -p tsconfig.build.json","typecheck":"tsc --noEmit","lint":"biome check src tests","lint:fix":"biome check --write src tests","format":"biome format --write src tests","test":"vitest run","test:watch":"vitest","test:coverage":"vitest run --coverage","inspect":"npm run build && npx -y @modelcontextprotocol/inspector node build/index.js","prepublishOnly":"npm run lint && npm run typecheck && npm test && npm run build"},"publishConfig":{"access":"public"},"keywords":["mcp","paperless-ngx","document-management","ai","claude","model-context-protocol","paperless"],"author":{"name":"Nick Loui"},"contributors":[{"name":"Sebastian Dellwig"}],"license":"ISC","repository":{"type":"git","url":"git+https://github.com/Dellle/paperless-mcp.git"},"dependencies":{"@modelcontextprotocol/sdk":"^1.11.1","express":"^5.1.0","typescript":"^5.8.3","zod":"^3.24.1"},"devDependencies":{"@biomejs/biome":"2.4.13","@types/express":"5.0.6","@types/node":"^22.15.17","@vitest/coverage-v8":"4.1.5","ts-node":"^10.9.2","vitest":"4.1.5"},"_id":"@dellle/paperless-mcp@1.1.0","gitHead":"6df55ba2034ecb1782be71d17ecba6de98decaed","bugs":{"url":"https://github.com/Dellle/paperless-mcp/issues"},"homepage":"https://github.com/Dellle/paperless-mcp#readme","_nodeVersion":"22.18.0","_npmVersion":"10.9.3","dist":{"integrity":"sha512-U09Gf4ICsXKAFdaFjkLxs6Xy5wMc8aBkmhkr/a7RZ39acXDNLHwQQ2ELcRaAzsl3MdWdN/DdbzrGDtjF65lGSA==","shasum":"b04157559f6f90992d884d87d06d7bc27003c54f","tarball":"https://registry.npmjs.org/@dellle/paperless-mcp/-/paperless-mcp-1.1.0.tgz","fileCount":20,"unpackedSize":110423,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCZ3TI5VCTo7hxvI1ZM+NminP+M2iWGG3En/ptneaAujAIgLPCx3r2GFhLjmenUJ/08qqIK14KQOlQ/TBAenT2KVM0="}]},"_npmUser":{"name":"dellle","email":"baschdy@googlemail.com"},"directories":{},"maintainers":[{"name":"dellle","email":"baschdy@googlemail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/paperless-mcp_1.1.0_1777313362378_0.798050953695949"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-27T18:09:22.252Z","1.1.0":"2026-04-27T18:09:22.588Z","modified":"2026-04-27T18:09:22.923Z"},"maintainers":[{"name":"dellle","email":"baschdy@googlemail.com"}],"description":"Model Context Protocol (MCP) server for interacting with Paperless-NGX document management system. Enables AI assistants to manage documents, tags, correspondents, and document types through the Paperless-NGX API.","homepage":"https://github.com/Dellle/paperless-mcp#readme","keywords":["mcp","paperless-ngx","document-management","ai","claude","model-context-protocol","paperless"],"repository":{"type":"git","url":"git+https://github.com/Dellle/paperless-mcp.git"},"contributors":[{"name":"Sebastian Dellwig"}],"author":{"name":"Nick Loui"},"bugs":{"url":"https://github.com/Dellle/paperless-mcp/issues"},"license":"ISC","readme":"# Paperless-NGX MCP Server\n\nAn 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.\n\n## Quick Start\n\n### Installation\n1. Install the MCP server:\n```bash\nnpm install -g @dellle/paperless-mcp\n```\n\n2. Add it to your Claude's MCP configuration:\n\nFor VSCode extension, edit `~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`:\n```json\n{\n  \"mcpServers\": {\n    \"paperless\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dellle/paperless-mcp\", \"http://your-paperless-instance:8000\", \"your-api-token\"]\n    }\n  }\n}\n```\n\nFor Claude desktop app, edit `~/Library/Application Support/Claude/claude_desktop_config.json`:\n```json\n{\n  \"mcpServers\": {\n    \"paperless\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dellle/paperless-mcp\", \"http://your-paperless-instance:8000\", \"your-api-token\"]\n    }\n  }\n}\n```\n\n3. Get your API token:\n   1. Log into your Paperless-NGX instance\n   2. Click your username in the top right\n   3. Select \"My Profile\"\n   4. Click the circular arrow button to generate a new token\n\n4. Replace the placeholders in your MCP config:\n   - `http://your-paperless-instance:8000` with your Paperless-NGX URL\n   - `your-api-token` with the token you just generated\n\nThat's it! Now you can ask Claude to help you manage your Paperless-NGX documents.\n\n## Example Usage\n\nHere are some things you can ask Claude to do:\n\n- \"Show me all documents tagged as 'Invoice'\"\n- \"Search for documents containing 'tax return'\"\n- \"Create a new tag called 'Receipts' with color #FF0000\"\n- \"Download document #123\"\n- \"List all correspondents\"\n- \"Create a new document type called 'Bank Statement'\"\n\n## Available Tools\n\n### Document Operations\n\n#### list_documents\nGet a paginated list of all documents.\n\nParameters:\n- page (optional): Page number\n- page_size (optional): Number of documents per page\n\n```typescript\nlist_documents({\n  page: 1,\n  page_size: 25\n})\n```\n\n#### get_document\nGet a specific document by ID.\n\nParameters:\n- id: Document ID\n\n```typescript\nget_document({\n  id: 123\n})\n```\n\n#### search_documents\nFull-text search across documents using the Paperless-NGX query DSL (Tantivy-based).\n\nReturns metadata WITHOUT the OCR `content` field to protect token budgets — call `get_document` for full content.\n\nUse `search_documents` for free-text queries; use `filter_documents` for typed/structured predicates (numeric custom fields, \"must have ALL tags\", \"no correspondent\", etc.).\n\nParameters:\n- query: Paperless-NGX DSL string (see syntax below)\n- page (optional)\n- page_size (optional)\n\nQuery DSL — all combinable, default operator is AND:\n\n| Field | Example | Notes |\n|---|---|---|\n| `title:` / `content:` | `title:invoice` | Search title or OCR content |\n| `type:` | `type:invoice` | Match document type by name |\n| `correspondent:` | `correspondent:bank` | Match correspondent by name |\n| `tag:` | `tag:unpaid` | Repeat for multiple tags |\n| `asn:` | `asn:1234` | Archive serial number |\n| `custom_fields.value:` | `custom_fields.value:1312` | Match any custom field value |\n| `custom_fields.name:` | `custom_fields.name:\"Contract Number\"` | Quote multi-word names |\n| `notes.user:` / `notes.note:` | `notes.user:alice` | Note author or content |\n| `created:` / `added:` / `modified:` | `created:[2020 to 2024]` | Inclusive range |\n| Date keywords | `added:yesterday`, `modified:\"this year\"` | `today`, `yesterday`, `\"previous week\"`, `\"this month\"`, `\"previous month\"`, `\"this year\"`, `\"previous year\"`, `\"previous quarter\"` |\n| Booleans | `invoice AND unpaid`, `(a OR b) NOT c` | Case-sensitive operators |\n| Wildcards | `prod*name` | `*` = zero or more chars |\n| Exact phrase | `\"Contract Number\"` | Quoted |\n\nNotes: matching is accent-insensitive (`resume` finds `résumé`) and separator-agnostic (`1312` finds `A-1312/B`). Custom date fields do **not** support relative date keywords — use `filter_documents.custom_field_query` with `range`/`gt`/`lt` instead.\n\n```typescript\nsearch_documents({\n  query: 'type:invoice tag:unpaid created:[2024 to 2024]'\n})\n\nsearch_documents({\n  query: 'correspondent:bank custom_fields.name:\"Contract Number\" custom_fields.value:1312'\n})\n```\n\n#### filter_documents\nStructured filtering of documents — use this when the DSL of `search_documents` is insufficient.\n\nWhen to prefer `filter_documents`:\n- Typed comparisons on custom fields: `amount > 100`, date in range, boolean true/false, `is null`, `exists`.\n- Strict tag-set requirements: documents that have **ALL** of tags `[1,2,3]`.\n- Negative filters: no correspondent, not in inbox, no tags assigned.\n- Structural facets: by owner, mime_type, original_filename, archive_serial_number, has_custom_fields.\n- Date-bounded queries on standard fields with precise ISO dates.\n\nParameters (all optional, all combined with AND):\n\n| Param | Type | Description |\n|---|---|---|\n| `query` | string | Optional full-text DSL — combined with structured filters |\n| `correspondent__id__in` | number[] | Match any of these correspondent IDs |\n| `correspondent__isnull` | boolean | True = no correspondent |\n| `document_type__id__in` | number[] | Match any of these document type IDs |\n| `document_type__isnull` | boolean | True = no document type |\n| `storage_path__id__in` | number[] | Match any of these storage path IDs |\n| `tags__id__all` | number[] | Must have **all** these tag IDs |\n| `tags__id__in` | number[] | Has **any** of these tag IDs |\n| `tags__id__none` | number[] | Has **none** of these tag IDs |\n| `is_tagged` | boolean | Has at least one tag |\n| `is_in_inbox` | boolean | Currently in inbox |\n| `owner__id__in` | number[] | Owned by any of these user IDs |\n| `owner__isnull` | boolean | No owner |\n| `title__icontains` | string | Case-insensitive substring |\n| `content__icontains` | string | Case-insensitive substring on OCR content |\n| `original_filename__icontains` | string | |\n| `archive_serial_number` | string | Exact match |\n| `mime_type` | string | e.g. `application/pdf` |\n| `created__gte` / `created__lte` | string | ISO date or datetime |\n| `added__gte` / `added__lte` | string | |\n| `modified__gte` / `modified__lte` | string | |\n| `has_custom_fields` | boolean | |\n| `custom_fields__id__all` | number[] | Must have all these custom fields **assigned** (existence, not value) |\n| `custom_field_query` | JSON tree | Typed predicates on custom field **values** — see below |\n| `ordering` | string | e.g. `-created`, `title` |\n| `page`, `page_size` | number | |\n\n`custom_field_query` is a recursive JSON tree (max depth 10, max 20 atoms total):\n\n```\nAtom:    [fieldRef, operator, value]    where fieldRef = custom field id (number) or name (string)\nAND/OR:  [\"AND\", [subq, ...]]   |   [\"OR\", [subq, ...]]\nNOT:     [\"NOT\", subquery]\n```\n\nOperators by `data_type` (call `list_custom_fields` to discover field types):\n\n| data_type | operators |\n|---|---|\n| string / url / longtext | exact, in, isnull, exists, icontains, istartswith, iendswith |\n| integer / float | exact, in, isnull, exists, gt, gte, lt, lte, range |\n| date | exact, in, isnull, exists, gt, gte, lt, lte, range, year__exact, month__exact, day__exact |\n| monetary | numeric ops + icontains/istartswith/iendswith (currency stripped for compare) |\n| boolean | exact, in, isnull, exists |\n| select | exact, in, isnull, exists (value is option id or label) |\n| documentlink | exact, in, isnull, exists, contains (subset check on linked document ids) |\n\n```typescript\n// All unpaid invoices from a specific correspondent, must have BOTH \"urgent\" and \"review\" tags\nfilter_documents({\n  correspondent__id__in: [3],\n  document_type__id__in: [7],\n  tags__id__all: [12, 19],\n  is_in_inbox: true,\n})\n\n// Custom field value predicate: Invoice Total > 100 AND status = pending\nfilter_documents({\n  custom_field_query: [\"AND\", [\n    [\"Invoice Total\", \"gt\", 100],\n    [\"status\", \"exact\", \"pending\"]\n  ]]\n})\n\n// Combined full-text + structured\nfilter_documents({\n  query: \"tax\",\n  created__gte: \"2024-01-01\",\n  has_custom_fields: true,\n  ordering: \"-created\"\n})\n```\n\n#### list_custom_fields\nList all custom fields defined in this Paperless instance. **Call this before building a `custom_field_query`** — it returns each field's `id`, `name`, `data_type`, and `extra_data` (e.g. select options).\n\n```typescript\nlist_custom_fields()\n```\n\n#### download_document\nDownload a document file by ID.\n\nParameters:\n- id: Document ID\n- original (optional): If true, downloads original file instead of archived version\n\n```typescript\ndownload_document({\n  id: 123,\n  original: false\n})\n```\n\n#### bulk_edit_documents\nPerform bulk operations on multiple documents.\n\nParameters:\n- documents: Array of document IDs\n- method: One of:\n  - set_correspondent: Set correspondent for documents\n  - set_document_type: Set document type for documents\n  - set_storage_path: Set storage path for documents\n  - add_tag: Add a tag to documents\n  - remove_tag: Remove a tag from documents\n  - modify_tags: Add and/or remove multiple tags\n  - delete: Delete documents\n  - reprocess: Reprocess documents\n  - set_permissions: Set document permissions\n  - merge: Merge multiple documents\n  - split: Split a document into multiple documents\n  - rotate: Rotate document pages\n  - delete_pages: Delete specific pages from a document\n- Additional parameters based on method:\n  - correspondent: ID for set_correspondent\n  - document_type: ID for set_document_type\n  - storage_path: ID for set_storage_path\n  - tag: ID for add_tag/remove_tag\n  - add_tags: Array of tag IDs for modify_tags\n  - remove_tags: Array of tag IDs for modify_tags\n  - permissions: Object for set_permissions with owner, permissions, merge flag\n  - metadata_document_id: ID for merge to specify metadata source\n  - delete_originals: Boolean for merge/split\n  - pages: String for split \"[1,2-3,4,5-7]\" or delete_pages \"[2,3,4]\"\n  - degrees: Number for rotate (90, 180, or 270)\n\nExamples:\n```typescript\n// Add a tag to multiple documents\nbulk_edit_documents({\n  documents: [1, 2, 3],\n  method: \"add_tag\",\n  tag: 5\n})\n\n// Set correspondent and document type\nbulk_edit_documents({\n  documents: [4, 5],\n  method: \"set_correspondent\",\n  correspondent: 2\n})\n\n// Merge documents\nbulk_edit_documents({\n  documents: [6, 7, 8],\n  method: \"merge\",\n  metadata_document_id: 6,\n  delete_originals: true\n})\n\n// Split document into parts\nbulk_edit_documents({\n  documents: [9],\n  method: \"split\",\n  pages: \"[1-2,3-4,5]\"\n})\n\n// Modify multiple tags at once\nbulk_edit_documents({\n  documents: [10, 11],\n  method: \"modify_tags\",\n  add_tags: [1, 2],\n  remove_tags: [3, 4]\n})\n```\n\n#### post_document\nUpload a new document to Paperless-NGX.\n\nParameters:\n- file: Base64 encoded file content\n- filename: Name of the file\n- title (optional): Title for the document\n- created (optional): DateTime when the document was created (e.g. \"2024-01-19\" or \"2024-01-19 06:15:00+02:00\")\n- correspondent (optional): ID of a correspondent\n- document_type (optional): ID of a document type\n- storage_path (optional): ID of a storage path\n- tags (optional): Array of tag IDs\n- archive_serial_number (optional): Archive serial number\n- custom_fields (optional): Array of custom field IDs\n\n```typescript\npost_document({\n  file: \"base64_encoded_content\",\n  filename: \"invoice.pdf\",\n  title: \"January Invoice\",\n  created: \"2024-01-19\",\n  correspondent: 1,\n  document_type: 2,\n  tags: [1, 3],\n  archive_serial_number: \"2024-001\"\n})\n```\n\n### Tag Operations\n\n#### list_tags\nGet all tags.\n\n```typescript\nlist_tags()\n```\n\n#### create_tag\nCreate a new tag.\n\nParameters:\n- name: Tag name\n- color (optional): Hex color code (e.g. \"#ff0000\")\n- match (optional): Text pattern to match\n- matching_algorithm (optional): One of \"any\", \"all\", \"exact\", \"regular expression\", \"fuzzy\"\n\n```typescript\ncreate_tag({\n  name: \"Invoice\",\n  color: \"#ff0000\",\n  match: \"invoice\",\n  matching_algorithm: \"fuzzy\"\n})\n```\n\n### Correspondent Operations\n\n#### list_correspondents\nGet all correspondents.\n\n```typescript\nlist_correspondents()\n```\n\n#### create_correspondent\nCreate a new correspondent.\n\nParameters:\n- name: Correspondent name\n- match (optional): Text pattern to match\n- matching_algorithm (optional): One of \"any\", \"all\", \"exact\", \"regular expression\", \"fuzzy\"\n\n```typescript\ncreate_correspondent({\n  name: \"ACME Corp\",\n  match: \"ACME\",\n  matching_algorithm: \"fuzzy\"\n})\n```\n\n### Document Type Operations\n\n#### list_document_types\nGet all document types.\n\n```typescript\nlist_document_types()\n```\n\n#### create_document_type\nCreate a new document type.\n\nParameters:\n- name: Document type name\n- match (optional): Text pattern to match\n- matching_algorithm (optional): One of \"any\", \"all\", \"exact\", \"regular expression\", \"fuzzy\"\n\n```typescript\ncreate_document_type({\n  name: \"Invoice\",\n  match: \"invoice total amount due\",\n  matching_algorithm: \"any\"\n})\n```\n\n## Error Handling\n\nThe server will show clear error messages if:\n- The Paperless-NGX URL or API token is incorrect\n- The Paperless-NGX server is unreachable\n- The requested operation fails\n- The provided parameters are invalid\n\n## Development\n\nWant to contribute or modify the server? Here's what you need to know:\n\n1. Clone the repository\n2. Install dependencies:\n```bash\nnpm install\n```\n\n3. Make your changes to server.js\n4. Test locally:\n```bash\nnode server.js http://localhost:8000 your-test-token\n```\n\nThe server is built with:\n- [litemcp](https://github.com/wong2/litemcp): A TypeScript framework for building MCP servers\n- [zod](https://github.com/colinhacks/zod): TypeScript-first schema validation\n\n## API Documentation\n\nThis MCP server implements endpoints from the Paperless-NGX REST API. For more details about the underlying API, see the [official documentation](https://docs.paperless-ngx.com/api/).\n\n## Running the MCP Server\n\nThe MCP server can be run in two modes:\n\n### 1. stdio (default)\n\nThis is the default mode. The server communicates over stdio, suitable for CLI and direct integrations.\n\n```\nnpm run start -- <baseUrl> <token>\n```\n\n### 2. HTTP (Streamable HTTP Transport)\n\nTo 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](https://expressjs.com/) to be installed (it is included as a dependency).\n\n```\nnpm run start -- <baseUrl> <token> --http --port 3000\n```\n\n- The MCP API will be available at `POST /mcp` on the specified port.\n- Each request is handled statelessly, following the [StreamableHTTPServerTransport](https://github.com/modelcontextprotocol/typescript-sdk) pattern.\n- GET and DELETE requests to `/mcp` will return 405 Method Not Allowed.\n\n## API Version Compatibility\n\nPaperless-NGX uses Accept-header API versioning (`Accept: application/json; version=<n>`). This MCP server defaults to **version 10**, which is what current Paperless-NGX servers support and what `filter_documents.custom_field_query` / `list_custom_fields` require (minimum version 9).\n\nFor older Paperless-NGX instances:\n\n- The client **auto-negotiates downward**: every response includes an `X-Api-Version` header reporting the server's max supported version. If it's lower than the configured ceiling, subsequent requests are sent with that lower version automatically.\n- If the very first request returns `406 Not Acceptable` (because the server doesn't accept the configured version at all), the client retries once at the version reported in `X-Api-Version`.\n- The configured version is a **ceiling** — auto-negotiation only ever downgrades; it never upgrades past what you set.\n\nYou can override the default via:\n\n```bash\npaperless-mcp <baseUrl> <token> --api-version 9\n# or, in --http mode:\nPAPERLESS_API_VERSION=9 paperless-mcp <baseUrl> <token> --http\n```\n\nIf your Paperless server is too old to support `custom_field_query` (i.e. negotiated version < 9), `filter_documents` (with `custom_field_query`) and `list_custom_fields` will return a clear error message — all other tools work normally.\n","readmeFilename":"README.md","_rev":"1-8ddcd6d12af76594d32716782385073e"}