<!-- [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/nloui-paperless-mcp-badge.png)](https://mseep.ai/app/nloui-paperless-mcp) -->

# Paperless-NGX MCP Server

![CodeRabbit Pull Request Reviews](https://img.shields.io/coderabbit/prs/github/baruchiro/paperless-mcp?utm_source=oss&utm_medium=github&utm_campaign=baruchiro%2Fpaperless-mcp&labelColor=171717&color=FF570A&link=https%3A%2F%2Fcoderabbit.ai&label=CodeRabbit+Reviews)

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.

## Quick Start

[![Install MCP Server](https://cursor.com/deeplink/mcp-install-light.svg)](https://cursor.com/install-mcp?name=paperless&config=eyJjb21tYW5kIjoibnB4IC15IEBiYXJ1Y2hpcm8vcGFwZXJsZXNzLW1jcEBsYXRlc3QiLCJlbnYiOnsiUEFQRVJMRVNTX1VSTCI6Imh0dHA6Ly95b3VyLXBhcGVybGVzcy1pbnN0YW5jZTo4MDAwIiwiUEFQRVJMRVNTX0FQSV9LRVkiOiJ5b3VyLWFwaS10b2tlbiJ9fQ%3D%3D)

### Installation

Add these to your MCP config file:

// STDIO mode (recommended for local or CLI use)
```json
"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)
```json
"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"
  }
}
```

3. Get your API token:
   1. Log into your Paperless-NGX instance
   2. Click your username in the top right
   3. Select "My Profile"
   4. Click the circular arrow button to generate a new token

4. Replace the placeholders in your MCP config:
   - `http://your-paperless-instance:8000` with your Paperless-NGX URL
   - `your-api-token` with the token you just generated
   - `https://your-public-domain.com` with your public Paperless-NGX URL (optional, falls back to PAPERLESS_URL)

### Environment Variables

| 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.

### Example Usage

Here are some things you can ask Claude to do:

- "Show me all documents tagged as 'Invoice'"
- "Search for documents containing 'tax return'"
- "Create a new tag called 'Receipts' with color #FF0000"
- "Download document #123"
- "List all correspondents"
- "Create a new document type called 'Bank Statement'"

## Available Tools

### Document Operations

#### list_documents
Get a paginated list of documents with simple filters. Use this for straightforward listing tasks. For full-text queries, structured custom field filtering, or advanced Paperless filters, use `query_documents`.

Parameters:
- page (optional): Page number
- page_size (optional): Number of documents per page
- search (optional): Simple Paperless search term
- correspondent (optional): Correspondent ID
- document_type (optional): Document type ID
- tag (optional): Tag ID
- storage_path (optional): Storage path ID
- created__date__gte (optional): Created date on or after YYYY-MM-DD
- created__date__lte (optional): Created date on or before YYYY-MM-DD
- ordering (optional): Paperless ordering field
- archive_serial_number (optional): Archive serial number
- archive_serial_number__isnull (optional): Whether the archive serial number is empty
- custom_field_query (optional): Raw JSON-encoded Paperless custom field query string
- custom_fields__icontains (optional): Case-insensitive substring match across custom field values

```typescript
list_documents({
  page: 1,
  page_size: 25
})
```

#### query_documents
Canonical document query tool. Supports full-text querying, simple Paperless search, custom field filters, and documented `/api/documents/` Paperless query parameters.

Parameters:
- page (optional): Page number
- page_size (optional): Number of documents per page
- ordering (optional): Paperless ordering field
- query (optional): Full-text query string
- search (optional): Simple Paperless search term
- more_like_id (optional): Find documents similar to this document ID
- correspondent (optional): Correspondent ID
- document_type (optional): Document type ID
- tag (optional): Tag ID
- storage_path (optional): Storage path ID
- created__date__gte (optional): Created date on or after YYYY-MM-DD
- created__date__lte (optional): Created date on or before YYYY-MM-DD
- custom_field_query (optional): Structured Paperless custom field query using `[field_name_or_id, operator, value]` leaves or `["AND" | "OR", [clause1, clause2]]` groups
- paperless_filters (optional): Additional documented `/api/documents/` Paperless query parameters, passed as key/value pairs

```typescript
// Full-text query
query_documents({
  query: "invoice 2024"
})

// Simple search term
query_documents({
  search: "acme"
})

// Custom field exact match
query_documents({
  custom_field_query: ["Invoice Number", "exact", "12345"]
})

// Custom field empty
query_documents({
  custom_field_query: ["OR", [
    ["Invoice Number", "isnull", true],
    ["Invoice Number", "exact", ""]
  ]]
})

// Custom field missing
query_documents({
  custom_field_query: ["Invoice Number", "exists", false]
})

// Combined filters
query_documents({
  query: "invoice",
  tag: 5,
  created__date__gte: "2024-01-01",
  custom_field_query: ["Invoice Number", "exists", true]
})

// One documented Paperless filter that is not a first-class argument
query_documents({
  paperless_filters: {
    id__in: [101, 202, 303]
  }
})
```

#### get_document
Get a specific document by ID.

Parameters:
- id: Document ID

```typescript
get_document({
  id: 123
})
```

#### search_documents
Deprecated compatibility wrapper for full-text search. Prefer `query_documents({ query: ... })` for new integrations.

Parameters:
- query: Search query string

```typescript
search_documents({
  query: "invoice 2024"
})
```

#### download_document
Download a document file by ID.

Parameters:
- id: Document ID
- original (optional): If true, downloads original file instead of archived version

```typescript
download_document({
  id: 123,
  original: false
})
```

#### get_document_thumbnail
Get a document thumbnail (image preview) by ID. Returns the thumbnail as a base64-encoded WebP image resource.

Parameters:
- id: Document ID

```typescript
get_document_thumbnail({
  id: 123
})
```

#### bulk_edit_documents
Perform bulk operations on multiple documents.

Parameters:
- documents: Array of document IDs
- method: One of:
  - set_correspondent: Set correspondent for documents
  - set_document_type: Set document type for documents
  - set_storage_path: Set storage path for documents
  - add_tag: Add a tag to documents
  - remove_tag: Remove a tag from documents
  - modify_tags: Add and/or remove multiple tags
  - delete: Delete documents
  - reprocess: Reprocess documents
  - set_permissions: Set document permissions
  - merge: Merge multiple documents
  - split: Split a document into multiple documents
  - rotate: Rotate document pages
  - delete_pages: Delete specific pages from a document
- Additional parameters based on method:
  - correspondent: ID for set_correspondent
  - document_type: ID for set_document_type
  - storage_path: ID for set_storage_path
  - tag: ID for add_tag/remove_tag
  - add_tags: Array of tag IDs for modify_tags
  - remove_tags: Array of tag IDs for modify_tags
  - permissions: Object for set_permissions with owner, permissions, merge flag
  - metadata_document_id: ID for merge to specify metadata source
  - delete_originals: Boolean for merge/split
  - pages: String for split "[1,2-3,4,5-7]" or delete_pages "[2,3,4]"
  - degrees: Number for rotate (90, 180, or 270)

Examples:
```typescript
// 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: []
})
```

#### post_document
Upload a new document to Paperless-NGX.

**Two upload modes:**

1. **Base64 mode** (traditional): Provide `file` (base64-encoded content) + `filename`
2. **Filesystem mode** (efficient): Provide `file_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 (optional): Base64 encoded file content. Either `file` or `file_path` required.
- file_path (optional): Absolute path to file on server's filesystem. Either `file` or `file_path` required.
- filename (optional): Name of the file. Required with `file`, optional with `file_path` (derives from path).
- title (optional): Title for the document
- created (optional): DateTime when the document was created (e.g. "2024-01-19" or "2024-01-19 06:15:00+02:00")
- correspondent (optional): ID of a correspondent
- document_type (optional): ID of a document type
- storage_path (optional): ID of a storage path
- tags (optional): Array of tag IDs
- archive_serial_number (optional): Archive serial number
- custom_fields (optional): Array of custom field IDs

**File size limit:** 100MB for both modes

```typescript
// 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]
})
```

### Document Notes

#### list_document_notes
List all notes attached to a document.

Parameters:
- id: Document ID

```typescript
list_document_notes({
  id: 123
})
```

#### create_document_note
Add a note to a document. Returns the document's full list of notes.

Parameters:
- id: Document ID
- note: The note text to add

```typescript
create_document_note({
  id: 123,
  note: "Invoice paid on 2026-06-30 from Commerzbank account."
})
```

#### delete_document_note
⚠️ Delete a single note from a document by its note ID. This operation is irreversible.

Parameters:
- id: Document ID
- note_id: The ID of the note to delete
- confirm: Must be `true` to confirm this destructive operation

```typescript
delete_document_note({
  id: 123,
  note_id: 5,
  confirm: true
})
```

### Tag Operations

#### list_tags
Get all tags.

```typescript
list_tags()
```

#### create_tag
Create a new tag.

Parameters:
- name: Tag name
- color (optional): Hex color code (e.g. "#ff0000")
- match (optional): Text pattern to match
- matching_algorithm (optional): Number between 0 and 6:
  0 - None
  1 - Any word
  2 - All words
  3 - Exact match
  4 - Regular expression
  5 - Fuzzy word
  6 - Automatic

```typescript
create_tag({
  name: "Invoice",
  color: "#ff0000",
  match: "invoice",
  matching_algorithm: 5
})
```

### Correspondent Operations

#### list_correspondents
Get all correspondents.

```typescript
list_correspondents()
```

#### create_correspondent
Create a new correspondent.

Parameters:
- name: Correspondent name
- match (optional): Text pattern to match
- matching_algorithm (optional): Number between 0 and 6:
  0 - None
  1 - Any word
  2 - All words
  3 - Exact match
  4 - Regular expression
  5 - Fuzzy word
  6 - Automatic

```typescript
create_correspondent({
  name: "ACME Corp",
  match: "ACME",
  matching_algorithm: 5
})
```

### Document Type Operations

#### list_document_types
Get all document types.

```typescript
list_document_types()
```

#### create_document_type
Create a new document type.

Parameters:
- name: Document type name
- match (optional): Text pattern to match
- matching_algorithm (optional): Number between 0 and 6:
  0 - None
  1 - Any word
  2 - All words
  3 - Exact match
  4 - Regular expression
  5 - Fuzzy word
  6 - Automatic

```typescript
create_document_type({
  name: "Invoice",
  match: "invoice total amount due",
  matching_algorithm: 1
})
```

### Custom Field Operations

#### list_custom_fields
Get all custom fields.

```typescript
list_custom_fields()
```

#### get_custom_field
Get a specific custom field by ID.

Parameters:
- id: Custom field ID

```typescript
get_custom_field({
  id: 1
})
```

#### create_custom_field
Create a new custom field.

Parameters:
- name: Custom field name
- data_type: One of "string", "url", "date", "boolean", "integer", "float", "monetary", "documentlink", "select"
- extra_data (optional): Extra data for the custom field, such as select options

```typescript
create_custom_field({
  name: "Invoice Number",
  data_type: "string"
})
```

#### update_custom_field
Update an existing custom field.

Parameters:
- id: Custom field ID
- name (optional): New custom field name
- data_type (optional): New data type
- extra_data (optional): Extra data for the custom field

```typescript
update_custom_field({
  id: 1,
  name: "Updated Invoice Number",
  data_type: "string"
})
```

#### delete_custom_field
Delete a custom field.

Parameters:
- id: Custom field ID

```typescript
delete_custom_field({
  id: 1
})
```

#### bulk_edit_custom_fields
Perform bulk operations on multiple custom fields.

Parameters:
- custom_fields: Array of custom field IDs
- operation: One of "delete"

```typescript
bulk_edit_custom_fields({
  custom_fields: [1, 2, 3],
  operation: "delete"
})
```

### Mail Operations

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
List mail accounts so you can pick the account ID needed when creating a mail
rule. Passwords are redacted.

Parameters:
- page (optional): Page number
- page_size (optional): Number of results per page

```typescript
list_mail_accounts()
```

#### get_mail_account
Get a single mail account by ID. Password/token fields are redacted.

Parameters:
- id: Mail account ID

```typescript
get_mail_account({
  id: 1
})
```

#### process_mail_account
Manually trigger Paperless mail processing for one account. This can consume
matching mails according to the account's enabled mail rules.

Parameters:
- id: Mail account ID

```typescript
process_mail_account({
  id: 1
})
```

#### list_mail_rules
List mail rules with optional pagination.

Parameters:
- page (optional): Page number
- page_size (optional): Number of results per page

```typescript
list_mail_rules()
```

#### get_mail_rule
Get a single mail rule by ID.

Parameters:
- id: Mail rule ID

```typescript
get_mail_rule({
  id: 1
})
```

#### create_mail_rule
Create a mail rule. Use `list_mail_accounts` first to choose the account.

Required parameters:
- name: Rule name
- account: Mail account ID
- folder: IMAP folder to scan (e.g. "INBOX")

Common optional parameters:
- enabled (default true): Whether the rule is active
- filter_from / filter_to / filter_subject / filter_body: Match incoming mail
- maximum_age: Only process mail newer than this many days
- action: 1=Delete, 2=Move to folder, 3=Mark as read, 4=Flag, 5=Tag
- action_parameter: Target folder/tag for the chosen action
- assign_title_from: 1=Subject, 2=Attachment filename, 3=Do not assign
- assign_tags / assign_correspondent / assign_document_type: Metadata to apply
- assign_correspondent_from: 1=None, 2=Mail address, 3=Sender name, 4=Use assign_correspondent
- attachment_type: 1=Attachments only, 2=All files incl. inline
- consumption_scope: 1=Attachments only, 2=Full mail as .eml, 3=Both
- pdf_layout: 0=System default, 1=Text+HTML, 2=HTML+text, 3=HTML only, 4=Text only

```typescript
create_mail_rule({
  name: "Invoices",
  account: 1,
  folder: "INBOX",
  filter_subject: "invoice",
  action: 3,
  attachment_type: 1
})
```

#### update_mail_rule
Patch an existing mail rule. Only the fields you supply are changed.

Parameters:
- id: Mail rule ID
- ...any of the `create_mail_rule` fields to update

```typescript
update_mail_rule({
  id: 1,
  enabled: false
})
```

#### delete_mail_rule
Delete a mail rule. Requires an explicit confirmation flag. This changes future
mail ingestion behavior but does not delete any existing documents.

Parameters:
- id: Mail rule ID
- confirm: Must be `true` to confirm deletion

```typescript
delete_mail_rule({
  id: 1,
  confirm: true
})
```

## Error Handling

The server will show clear error messages if:
- The Paperless-NGX URL or API token is incorrect
- The Paperless-NGX server is unreachable
- The requested operation fails
- The provided parameters are invalid

## Testing

### Unit tests

Run the unit test suite (no external dependencies required):

```bash
npm test
```

### E2E tests

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`.

```bash
# 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.

## Development

Want to contribute or modify the server? Here's what you need to know:

1. Clone the repository
2. Install dependencies:
```bash
npm install
```

3. Make your changes to server.js
4. Test locally:
```bash
node server.js http://localhost:8000 your-test-token
```

The server is built with:
- [litemcp](https://github.com/wong2/litemcp): A TypeScript framework for building MCP servers
- [zod](https://github.com/colinhacks/zod): TypeScript-first schema validation

## API Documentation

This 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/).

## Running the MCP Server

The MCP server can be run in two modes:

### 1. stdio (default)

This is the default mode. The server communicates over stdio, suitable for CLI and direct integrations.

```
npm run start -- <baseUrl> <token>
```

### 2. HTTP (Streamable HTTP Transport)

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](https://expressjs.com/) to be installed (it is included as a dependency).

```
npm run start -- <baseUrl> <token> --http --port 3000
```

- The MCP API will be available at `POST /mcp` on the specified port.
- Each request is handled statelessly, following the [StreamableHTTPServerTransport](https://github.com/modelcontextprotocol/typescript-sdk) pattern.
- GET and DELETE requests to `/mcp` will return 405 Method Not Allowed.

#### Per-request API token (HTTP/Docker mode)

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 `Authorization` header silently fell back to the server-configured `PAPERLESS_API_KEY`, which left the HTTP endpoint open to anyone who could reach the port. As of v2.0.0, requests without a `Bearer` token are rejected with `401 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:

1. **Recommended:** have each client send `Authorization: Bearer <paperless-token>`.
2. **Restore the old behaviour** (trusted/local networks only): start the server with the `--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.

<details>
<summary>Docker Deployment</summary>

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.

### Docker Compose Configuration

Create a `docker-compose.yml` file:

```yaml
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:
```bash
docker-compose up -d
```

### Using with Continue VS Code Extension

If you're using the [Continue VS Code extension](https://continue.dev/), you can configure it to use the Dockerized MCP server via SSE.

Create or edit `.continue/mcpServers/paperless-mcp.yaml` at your workspace root:

```yaml
name: Paperless
version: 0.0.1
schema: v1
mcpServers:
  - name: Paperless
    type: sse
    url: http://localhost:3000/sse
```

**Notes:**
- Replace `localhost` with your Docker host's IP address or hostname if running on a remote server
- The Docker container handles authentication via environment variables, so no credentials are needed in the Continue config
- The SSE endpoint is available at `/sse` on the configured port (default: 3000)

</details>

# Credits

This project is a fork of [nloui/paperless-mcp](https://github.com/nloui/paperless-mcp). Many thanks to the original author for their work. Contributions and improvements may be returned upstream.

## Debugging

To debug the MCP server in VS Code, use the following launch configuration:

```json
{
    "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):

```typescript
// await new Promise((resolve) => setTimeout(resolve, 1000000));
```

This prevents the server from exiting immediately and allows you to set breakpoints and debug the code.
