@baruchiro/paperless-mcp 1.0.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -67,6 +67,7 @@ Add these to your MCP config file:
67
67
  | `PAPERLESS_API_KEY` | Yes | — | API token from your Paperless-NGX profile |
68
68
  | `PAPERLESS_PUBLIC_URL` | No | `PAPERLESS_URL` | Public-facing URL for document links |
69
69
  | `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`. |
70
+ | `PAPERLESS_MCP_UPLOAD_PATHS` | No | — | Colon-separated list of allowed directories for `file_path` uploads. **Recommended for security.** Example: `/var/uploads:/tmp/scans` |
70
71
 
71
72
  That's it! Now you can ask Claude to help you manage your Paperless-NGX documents.
72
73
 
@@ -86,11 +87,23 @@ Here are some things you can ask Claude to do:
86
87
  ### Document Operations
87
88
 
88
89
  #### list_documents
89
- Get a paginated list of all documents.
90
+ 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`.
90
91
 
91
92
  Parameters:
92
93
  - page (optional): Page number
93
94
  - page_size (optional): Number of documents per page
95
+ - search (optional): Simple Paperless search term
96
+ - correspondent (optional): Correspondent ID
97
+ - document_type (optional): Document type ID
98
+ - tag (optional): Tag ID
99
+ - storage_path (optional): Storage path ID
100
+ - created__date__gte (optional): Created date on or after YYYY-MM-DD
101
+ - created__date__lte (optional): Created date on or before YYYY-MM-DD
102
+ - ordering (optional): Paperless ordering field
103
+ - archive_serial_number (optional): Archive serial number
104
+ - archive_serial_number__isnull (optional): Whether the archive serial number is empty
105
+ - custom_field_query (optional): Raw JSON-encoded Paperless custom field query string
106
+ - custom_fields__icontains (optional): Case-insensitive substring match across custom field values
94
107
 
95
108
  ```typescript
96
109
  list_documents({
@@ -99,6 +112,70 @@ list_documents({
99
112
  })
100
113
  ```
101
114
 
115
+ #### query_documents
116
+ Canonical document query tool. Supports full-text querying, simple Paperless search, custom field filters, and documented `/api/documents/` Paperless query parameters.
117
+
118
+ Parameters:
119
+ - page (optional): Page number
120
+ - page_size (optional): Number of documents per page
121
+ - ordering (optional): Paperless ordering field
122
+ - query (optional): Full-text query string
123
+ - search (optional): Simple Paperless search term
124
+ - more_like_id (optional): Find documents similar to this document ID
125
+ - correspondent (optional): Correspondent ID
126
+ - document_type (optional): Document type ID
127
+ - tag (optional): Tag ID
128
+ - storage_path (optional): Storage path ID
129
+ - created__date__gte (optional): Created date on or after YYYY-MM-DD
130
+ - created__date__lte (optional): Created date on or before YYYY-MM-DD
131
+ - custom_field_query (optional): Structured Paperless custom field query using `[field_name_or_id, operator, value]` leaves or `["AND" | "OR", [clause1, clause2]]` groups
132
+ - paperless_filters (optional): Additional documented `/api/documents/` Paperless query parameters, passed as key/value pairs
133
+
134
+ ```typescript
135
+ // Full-text query
136
+ query_documents({
137
+ query: "invoice 2024"
138
+ })
139
+
140
+ // Simple search term
141
+ query_documents({
142
+ search: "acme"
143
+ })
144
+
145
+ // Custom field exact match
146
+ query_documents({
147
+ custom_field_query: ["Invoice Number", "exact", "12345"]
148
+ })
149
+
150
+ // Custom field empty
151
+ query_documents({
152
+ custom_field_query: ["OR", [
153
+ ["Invoice Number", "isnull", true],
154
+ ["Invoice Number", "exact", ""]
155
+ ]]
156
+ })
157
+
158
+ // Custom field missing
159
+ query_documents({
160
+ custom_field_query: ["Invoice Number", "exists", false]
161
+ })
162
+
163
+ // Combined filters
164
+ query_documents({
165
+ query: "invoice",
166
+ tag: 5,
167
+ created__date__gte: "2024-01-01",
168
+ custom_field_query: ["Invoice Number", "exists", true]
169
+ })
170
+
171
+ // One documented Paperless filter that is not a first-class argument
172
+ query_documents({
173
+ paperless_filters: {
174
+ id__in: [101, 202, 303]
175
+ }
176
+ })
177
+ ```
178
+
102
179
  #### get_document
103
180
  Get a specific document by ID.
104
181
 
@@ -112,7 +189,7 @@ get_document({
112
189
  ```
113
190
 
114
191
  #### search_documents
115
- Full-text search across documents.
192
+ Deprecated compatibility wrapper for full-text search. Prefer `query_documents({ query: ... })` for new integrations.
116
193
 
117
194
  Parameters:
118
195
  - query: Search query string
@@ -244,9 +321,17 @@ bulk_edit_documents({
244
321
  #### post_document
245
322
  Upload a new document to Paperless-NGX.
246
323
 
324
+ **Two upload modes:**
325
+
326
+ 1. **Base64 mode** (traditional): Provide `file` (base64-encoded content) + `filename`
327
+ 2. **Filesystem mode** (efficient): Provide `file_path` (absolute path on server)
328
+
329
+ **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.
330
+
247
331
  Parameters:
248
- - file: Base64 encoded file content
249
- - filename: Name of the file
332
+ - file (optional): Base64 encoded file content. Either `file` or `file_path` required.
333
+ - file_path (optional): Absolute path to file on server's filesystem. Either `file` or `file_path` required.
334
+ - filename (optional): Name of the file. Required with `file`, optional with `file_path` (derives from path).
250
335
  - title (optional): Title for the document
251
336
  - created (optional): DateTime when the document was created (e.g. "2024-01-19" or "2024-01-19 06:15:00+02:00")
252
337
  - correspondent (optional): ID of a correspondent
@@ -256,7 +341,10 @@ Parameters:
256
341
  - archive_serial_number (optional): Archive serial number
257
342
  - custom_fields (optional): Array of custom field IDs
258
343
 
344
+ **File size limit:** 100MB for both modes
345
+
259
346
  ```typescript
347
+ // Base64 mode (traditional)
260
348
  post_document({
261
349
  file: "base64_encoded_content",
262
350
  filename: "invoice.pdf",
@@ -268,6 +356,59 @@ post_document({
268
356
  archive_serial_number: "2024-001",
269
357
  custom_fields: [1, 2]
270
358
  })
359
+
360
+ // Filesystem mode (more efficient for large files)
361
+ post_document({
362
+ file_path: "/var/uploads/invoice.pdf",
363
+ title: "January Invoice",
364
+ correspondent: 1,
365
+ document_type: 2,
366
+ tags: [1, 3]
367
+ })
368
+ ```
369
+
370
+ ### Document Notes
371
+
372
+ #### list_document_notes
373
+ List all notes attached to a document.
374
+
375
+ Parameters:
376
+ - id: Document ID
377
+
378
+ ```typescript
379
+ list_document_notes({
380
+ id: 123
381
+ })
382
+ ```
383
+
384
+ #### create_document_note
385
+ Add a note to a document. Returns the document's full list of notes.
386
+
387
+ Parameters:
388
+ - id: Document ID
389
+ - note: The note text to add
390
+
391
+ ```typescript
392
+ create_document_note({
393
+ id: 123,
394
+ note: "Invoice paid on 2026-06-30 from Commerzbank account."
395
+ })
396
+ ```
397
+
398
+ #### delete_document_note
399
+ ⚠️ Delete a single note from a document by its note ID. This operation is irreversible.
400
+
401
+ Parameters:
402
+ - id: Document ID
403
+ - note_id: The ID of the note to delete
404
+ - confirm: Must be `true` to confirm this destructive operation
405
+
406
+ ```typescript
407
+ delete_document_note({
408
+ id: 123,
409
+ note_id: 5,
410
+ confirm: true
411
+ })
271
412
  ```
272
413
 
273
414
  ### Tag Operations
@@ -447,6 +588,133 @@ bulk_edit_custom_fields({
447
588
  })
448
589
  ```
449
590
 
591
+ ### Mail Operations
592
+
593
+ Tools for managing Paperless mail accounts and the mail rules that drive
594
+ automatic email ingestion. Account passwords/tokens are never exposed: they are
595
+ redacted from every tool response.
596
+
597
+ #### list_mail_accounts
598
+ List mail accounts so you can pick the account ID needed when creating a mail
599
+ rule. Passwords are redacted.
600
+
601
+ Parameters:
602
+ - page (optional): Page number
603
+ - page_size (optional): Number of results per page
604
+
605
+ ```typescript
606
+ list_mail_accounts()
607
+ ```
608
+
609
+ #### get_mail_account
610
+ Get a single mail account by ID. Password/token fields are redacted.
611
+
612
+ Parameters:
613
+ - id: Mail account ID
614
+
615
+ ```typescript
616
+ get_mail_account({
617
+ id: 1
618
+ })
619
+ ```
620
+
621
+ #### process_mail_account
622
+ Manually trigger Paperless mail processing for one account. This can consume
623
+ matching mails according to the account's enabled mail rules.
624
+
625
+ Parameters:
626
+ - id: Mail account ID
627
+
628
+ ```typescript
629
+ process_mail_account({
630
+ id: 1
631
+ })
632
+ ```
633
+
634
+ #### list_mail_rules
635
+ List mail rules with optional pagination.
636
+
637
+ Parameters:
638
+ - page (optional): Page number
639
+ - page_size (optional): Number of results per page
640
+
641
+ ```typescript
642
+ list_mail_rules()
643
+ ```
644
+
645
+ #### get_mail_rule
646
+ Get a single mail rule by ID.
647
+
648
+ Parameters:
649
+ - id: Mail rule ID
650
+
651
+ ```typescript
652
+ get_mail_rule({
653
+ id: 1
654
+ })
655
+ ```
656
+
657
+ #### create_mail_rule
658
+ Create a mail rule. Use `list_mail_accounts` first to choose the account.
659
+
660
+ Required parameters:
661
+ - name: Rule name
662
+ - account: Mail account ID
663
+ - folder: IMAP folder to scan (e.g. "INBOX")
664
+
665
+ Common optional parameters:
666
+ - enabled (default true): Whether the rule is active
667
+ - filter_from / filter_to / filter_subject / filter_body: Match incoming mail
668
+ - maximum_age: Only process mail newer than this many days
669
+ - action: 1=Delete, 2=Move to folder, 3=Mark as read, 4=Flag, 5=Tag
670
+ - action_parameter: Target folder/tag for the chosen action
671
+ - assign_title_from: 1=Subject, 2=Attachment filename, 3=Do not assign
672
+ - assign_tags / assign_correspondent / assign_document_type: Metadata to apply
673
+ - assign_correspondent_from: 1=None, 2=Mail address, 3=Sender name, 4=Use assign_correspondent
674
+ - attachment_type: 1=Attachments only, 2=All files incl. inline
675
+ - consumption_scope: 1=Attachments only, 2=Full mail as .eml, 3=Both
676
+ - pdf_layout: 0=System default, 1=Text+HTML, 2=HTML+text, 3=HTML only, 4=Text only
677
+
678
+ ```typescript
679
+ create_mail_rule({
680
+ name: "Invoices",
681
+ account: 1,
682
+ folder: "INBOX",
683
+ filter_subject: "invoice",
684
+ action: 3,
685
+ attachment_type: 1
686
+ })
687
+ ```
688
+
689
+ #### update_mail_rule
690
+ Patch an existing mail rule. Only the fields you supply are changed.
691
+
692
+ Parameters:
693
+ - id: Mail rule ID
694
+ - ...any of the `create_mail_rule` fields to update
695
+
696
+ ```typescript
697
+ update_mail_rule({
698
+ id: 1,
699
+ enabled: false
700
+ })
701
+ ```
702
+
703
+ #### delete_mail_rule
704
+ Delete a mail rule. Requires an explicit confirmation flag. This changes future
705
+ mail ingestion behavior but does not delete any existing documents.
706
+
707
+ Parameters:
708
+ - id: Mail rule ID
709
+ - confirm: Must be `true` to confirm deletion
710
+
711
+ ```typescript
712
+ delete_mail_rule({
713
+ id: 1,
714
+ confirm: true
715
+ })
716
+ ```
717
+
450
718
  ## Error Handling
451
719
 
452
720
  The server will show clear error messages if:
@@ -551,19 +819,28 @@ npm run start -- <baseUrl> <token> --http --port 3000
551
819
 
552
820
  #### Per-request API token (HTTP/Docker mode)
553
821
 
554
- In HTTP mode, clients can supply their own Paperless-NGX API token via the standard `Authorization` header instead of (or in addition to) the server-configured `PAPERLESS_API_KEY`. The client-supplied token takes precedence.
822
+ In HTTP mode, clients authenticate by supplying a Paperless-NGX API token via the standard `Authorization` header:
555
823
 
556
824
  ```
557
825
  Authorization: Bearer <paperless-ngx-api-token>
558
826
  ```
559
827
 
560
- | Scenario | Token used |
561
- |---|---|
562
- | Client sends `Authorization: Bearer <tok>` | `<tok>` (client-supplied, takes precedence) |
563
- | No header, `PAPERLESS_API_KEY` env var set | `PAPERLESS_API_KEY` from env |
564
- | No header, no env var | `401 Unauthorized` |
828
+ 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.
829
+
830
+ > **⚠️ Breaking change in v2.0.0 HTTP mode is now authenticated by default.**
831
+ >
832
+ > 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`.
833
+
834
+ | Scenario | `--no-auth` off (default) | `--no-auth` on |
835
+ |---|---|---|
836
+ | Client sends `Authorization: Bearer <tok>` | `<tok>` (client-supplied) | `<tok>` (client-supplied) |
837
+ | No header, `PAPERLESS_API_KEY` / `--token` set | `401 Unauthorized` | server token |
838
+ | No header, no server token | `401 Unauthorized` | `401 Unauthorized` |
839
+
840
+ **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:
565
841
 
566
- This allows a single server instance to serve multiple users, each authenticating with their own Paperless-NGX token. The same behaviour applies to both `/mcp` and `/sse` endpoints.
842
+ 1. **Recommended:** have each client send `Authorization: Bearer <paperless-token>`.
843
+ 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.
567
844
 
568
845
  <details>
569
846
  <summary>Docker Deployment</summary>
@@ -1,5 +1,5 @@
1
1
  import { AxiosResponse } from "axios";
2
- import { BulkEditDocumentsResult, BulkEditParameters, Correspondent, CustomField, Document, DocumentsResponse, DocumentType, GetCorrespondentsResponse, GetCustomFieldsResponse, GetDocumentTypesResponse, GetTagsResponse, Tag } from "./types";
2
+ import { BulkEditDocumentsResult, BulkEditParameters, Correspondent, CustomField, Document, DocumentsResponse, DocumentType, GetCorrespondentsResponse, GetCustomFieldsResponse, GetDocumentTypesResponse, GetMailAccountsResponse, GetMailRulesResponse, MailAccount, MailRule, GetTagsResponse, Note, Tag } from "./types";
3
3
  export declare class PaperlessAPI {
4
4
  private readonly baseUrl;
5
5
  private readonly token;
@@ -11,9 +11,28 @@ export declare class PaperlessAPI {
11
11
  getDocuments(query?: string): Promise<DocumentsResponse>;
12
12
  getDocument(id: number): Promise<Document>;
13
13
  updateDocument(id: number, data: Partial<Document>): Promise<Document>;
14
- searchDocuments(query: string): Promise<DocumentsResponse>;
15
14
  downloadDocument(id: number, asOriginal?: boolean): Promise<AxiosResponse<ArrayBuffer>>;
16
15
  getThumbnail(id: number): Promise<AxiosResponse<ArrayBuffer>>;
16
+ /**
17
+ * Retrieve all notes attached to a document.
18
+ * @param documentId - The document ID.
19
+ * @returns The document's notes.
20
+ */
21
+ getDocumentNotes(documentId: number): Promise<Note[]>;
22
+ /**
23
+ * Create a note on a document.
24
+ * @param documentId - The document ID.
25
+ * @param note - The note text to add.
26
+ * @returns The document's full notes list after creation.
27
+ */
28
+ createDocumentNote(documentId: number, note: string): Promise<Note[]>;
29
+ /**
30
+ * Delete a note from a document by its note ID.
31
+ * @param documentId - The document ID.
32
+ * @param noteId - The ID of the note to delete.
33
+ * @returns The document's remaining notes after deletion.
34
+ */
35
+ deleteDocumentNote(documentId: number, noteId: number): Promise<Note[]>;
17
36
  getTags(): Promise<GetTagsResponse>;
18
37
  createTag(data: Partial<Tag>): Promise<Tag>;
19
38
  updateTag(id: number, data: Partial<Tag>): Promise<Tag>;
@@ -27,6 +46,14 @@ export declare class PaperlessAPI {
27
46
  createDocumentType(data: Partial<DocumentType>): Promise<DocumentType>;
28
47
  updateDocumentType(id: number, data: Partial<DocumentType>): Promise<DocumentType>;
29
48
  deleteDocumentType(id: number): Promise<void>;
49
+ getMailAccounts(queryString?: string): Promise<GetMailAccountsResponse>;
50
+ getMailAccount(id: number): Promise<MailAccount>;
51
+ processMailAccount(id: number): Promise<void>;
52
+ getMailRules(queryString?: string): Promise<GetMailRulesResponse>;
53
+ getMailRule(id: number): Promise<MailRule>;
54
+ createMailRule(data: Partial<MailRule>): Promise<MailRule>;
55
+ updateMailRule(id: number, data: Partial<MailRule>): Promise<MailRule>;
56
+ deleteMailRule(id: number): Promise<void>;
30
57
  getCustomFields(): Promise<GetCustomFieldsResponse>;
31
58
  getCustomField(id: number): Promise<CustomField>;
32
59
  createCustomField(data: Partial<CustomField>): Promise<CustomField>;
@@ -42,7 +42,7 @@ class PaperlessAPI {
42
42
  console.error({
43
43
  error: "Error executing request",
44
44
  url,
45
- options,
45
+ method: options.method || "GET",
46
46
  status: response.status,
47
47
  response: body,
48
48
  });
@@ -63,7 +63,7 @@ class PaperlessAPI {
63
63
  error: "Error executing request",
64
64
  message: error instanceof Error ? error.message : String(error),
65
65
  url,
66
- options,
66
+ method: options.method || "GET",
67
67
  responseData: axios_1.default.isAxiosError(error) ? (_b = error.response) === null || _b === void 0 ? void 0 : _b.data : undefined,
68
68
  status: axios_1.default.isAxiosError(error) ? (_c = error.response) === null || _c === void 0 ? void 0 : _c.status : undefined,
69
69
  });
@@ -145,12 +145,6 @@ class PaperlessAPI {
145
145
  });
146
146
  });
147
147
  }
148
- searchDocuments(query) {
149
- return __awaiter(this, void 0, void 0, function* () {
150
- const response = yield this.request(`/documents/?query=${encodeURIComponent(query)}`);
151
- return response;
152
- });
153
- }
154
148
  downloadDocument(id_1) {
155
149
  return __awaiter(this, arguments, void 0, function* (id, asOriginal = false) {
156
150
  const query = asOriginal ? "?original=true" : "";
@@ -174,6 +168,44 @@ class PaperlessAPI {
174
168
  return response;
175
169
  });
176
170
  }
171
+ // Document note operations
172
+ /**
173
+ * Retrieve all notes attached to a document.
174
+ * @param documentId - The document ID.
175
+ * @returns The document's notes.
176
+ */
177
+ getDocumentNotes(documentId) {
178
+ return __awaiter(this, void 0, void 0, function* () {
179
+ return this.request(`/documents/${documentId}/notes/`);
180
+ });
181
+ }
182
+ /**
183
+ * Create a note on a document.
184
+ * @param documentId - The document ID.
185
+ * @param note - The note text to add.
186
+ * @returns The document's full notes list after creation.
187
+ */
188
+ createDocumentNote(documentId, note) {
189
+ return __awaiter(this, void 0, void 0, function* () {
190
+ return this.request(`/documents/${documentId}/notes/`, {
191
+ method: "POST",
192
+ body: JSON.stringify({ note }),
193
+ });
194
+ });
195
+ }
196
+ /**
197
+ * Delete a note from a document by its note ID.
198
+ * @param documentId - The document ID.
199
+ * @param noteId - The ID of the note to delete.
200
+ * @returns The document's remaining notes after deletion.
201
+ */
202
+ deleteDocumentNote(documentId, noteId) {
203
+ return __awaiter(this, void 0, void 0, function* () {
204
+ return this.request(`/documents/${documentId}/notes/?id=${noteId}`, {
205
+ method: "DELETE",
206
+ });
207
+ });
208
+ }
177
209
  // Tag operations
178
210
  getTags() {
179
211
  return __awaiter(this, void 0, void 0, function* () {
@@ -269,6 +301,63 @@ class PaperlessAPI {
269
301
  });
270
302
  });
271
303
  }
304
+ // Mail account operations
305
+ getMailAccounts(queryString) {
306
+ return __awaiter(this, void 0, void 0, function* () {
307
+ const url = queryString
308
+ ? `/mail_accounts/?${queryString}`
309
+ : "/mail_accounts/";
310
+ return this.request(url);
311
+ });
312
+ }
313
+ getMailAccount(id) {
314
+ return __awaiter(this, void 0, void 0, function* () {
315
+ return this.request(`/mail_accounts/${id}/`);
316
+ });
317
+ }
318
+ processMailAccount(id) {
319
+ return __awaiter(this, void 0, void 0, function* () {
320
+ return this.request(`/mail_accounts/${id}/process/`, {
321
+ method: "POST",
322
+ body: JSON.stringify({}),
323
+ });
324
+ });
325
+ }
326
+ // Mail rule operations
327
+ getMailRules(queryString) {
328
+ return __awaiter(this, void 0, void 0, function* () {
329
+ const url = queryString ? `/mail_rules/?${queryString}` : "/mail_rules/";
330
+ return this.request(url);
331
+ });
332
+ }
333
+ getMailRule(id) {
334
+ return __awaiter(this, void 0, void 0, function* () {
335
+ return this.request(`/mail_rules/${id}/`);
336
+ });
337
+ }
338
+ createMailRule(data) {
339
+ return __awaiter(this, void 0, void 0, function* () {
340
+ return this.request("/mail_rules/", {
341
+ method: "POST",
342
+ body: JSON.stringify(data),
343
+ });
344
+ });
345
+ }
346
+ updateMailRule(id, data) {
347
+ return __awaiter(this, void 0, void 0, function* () {
348
+ return this.request(`/mail_rules/${id}/`, {
349
+ method: "PATCH",
350
+ body: JSON.stringify(data),
351
+ });
352
+ });
353
+ }
354
+ deleteMailRule(id) {
355
+ return __awaiter(this, void 0, void 0, function* () {
356
+ return this.request(`/mail_rules/${id}/`, {
357
+ method: "DELETE",
358
+ });
359
+ });
360
+ }
272
361
  // Custom field operations
273
362
  getCustomFields() {
274
363
  return __awaiter(this, void 0, void 0, function* () {
@@ -125,6 +125,53 @@ export interface DocumentType {
125
125
  }
126
126
  export interface GetDocumentTypesResponse extends PaginationResponse<DocumentType> {
127
127
  }
128
+ export interface MailAccount {
129
+ id: number;
130
+ name: string;
131
+ imap_server: string;
132
+ imap_port: number | null;
133
+ imap_security: number;
134
+ username: string;
135
+ password?: string;
136
+ character_set: string;
137
+ is_token: boolean;
138
+ owner: number | null;
139
+ user_can_change: boolean;
140
+ account_type: number;
141
+ expiration: string | null;
142
+ }
143
+ export interface GetMailAccountsResponse extends PaginationResponse<MailAccount> {
144
+ }
145
+ export interface MailRule {
146
+ id: number;
147
+ name: string;
148
+ account: number;
149
+ enabled: boolean;
150
+ folder: string;
151
+ filter_from: string | null;
152
+ filter_to: string | null;
153
+ filter_subject: string | null;
154
+ filter_body: string | null;
155
+ filter_attachment_filename_include: string | null;
156
+ filter_attachment_filename_exclude: string | null;
157
+ maximum_age: number;
158
+ action: number;
159
+ action_parameter: string | null;
160
+ assign_title_from: number;
161
+ assign_tags: Array<number | null>;
162
+ assign_correspondent_from: number;
163
+ assign_correspondent: number | null;
164
+ assign_document_type: number | null;
165
+ assign_owner_from_rule: boolean;
166
+ order: number;
167
+ attachment_type: number;
168
+ consumption_scope: number;
169
+ pdf_layout: number;
170
+ owner: number | null;
171
+ user_can_change: boolean;
172
+ }
173
+ export interface GetMailRulesResponse extends PaginationResponse<MailRule> {
174
+ }
128
175
  export interface BulkEditDocumentsResult {
129
176
  result: string;
130
177
  }
package/build/index.js CHANGED
@@ -20,13 +20,14 @@ const express_1 = __importDefault(require("express"));
20
20
  const node_util_1 = require("node:util");
21
21
  const server_1 = require("./server");
22
22
  const { version } = require("../package.json");
23
- const { values: { baseUrl, token, http: useHttp, port, publicUrl }, } = (0, node_util_1.parseArgs)({
23
+ const { values: { baseUrl, token, http: useHttp, port, publicUrl, "no-auth": noAuth }, } = (0, node_util_1.parseArgs)({
24
24
  options: {
25
25
  baseUrl: { type: "string" },
26
26
  token: { type: "string" },
27
27
  http: { type: "boolean", default: false },
28
28
  port: { type: "string" },
29
29
  publicUrl: { type: "string", default: "" },
30
+ "no-auth": { type: "boolean", default: false },
30
31
  },
31
32
  allowPositionals: true,
32
33
  });
@@ -35,15 +36,21 @@ const resolvedToken = token || process.env.PAPERLESS_API_KEY;
35
36
  const resolvedPublicUrl = publicUrl || process.env.PAPERLESS_PUBLIC_URL || resolvedBaseUrl;
36
37
  const resolvedPort = port ? parseInt(port, 10) : 3000;
37
38
  if (!resolvedBaseUrl) {
38
- console.error("Usage: paperless-mcp --baseUrl <url> --token <token> [--http] [--port <port>] [--publicUrl <url>]");
39
+ console.error("Usage: paperless-mcp --baseUrl <url> --token <token> [--http] [--port <port>] [--publicUrl <url>] [--no-auth]");
39
40
  console.error("Or set PAPERLESS_URL and PAPERLESS_API_KEY environment variables.");
40
41
  process.exit(1);
41
42
  }
42
43
  if (!useHttp && !resolvedToken) {
43
- console.error("Usage: paperless-mcp --baseUrl <url> --token <token> [--http] [--port <port>] [--publicUrl <url>]");
44
+ console.error("Usage: paperless-mcp --baseUrl <url> --token <token> [--http] [--port <port>] [--publicUrl <url>] [--no-auth]");
44
45
  console.error("Or set PAPERLESS_URL and PAPERLESS_API_KEY environment variables.");
45
46
  process.exit(1);
46
47
  }
48
+ if (noAuth && !resolvedToken) {
49
+ console.error("--no-auth allows unauthenticated requests to use the server's Paperless token, " +
50
+ "but no server token is configured. Provide --token <token> or set PAPERLESS_API_KEY, " +
51
+ "or drop --no-auth and have clients authenticate with 'Authorization: Bearer <token>'.");
52
+ process.exit(1);
53
+ }
47
54
  function buildServer(requestToken) {
48
55
  return (0, server_1.createMcpServer)({
49
56
  baseUrl: resolvedBaseUrl,
@@ -55,12 +62,25 @@ function buildServer(requestToken) {
55
62
  function main() {
56
63
  return __awaiter(this, void 0, void 0, function* () {
57
64
  if (useHttp) {
65
+ if (noAuth) {
66
+ console.log("[paperless-mcp] --no-auth is enabled: requests without an 'Authorization: Bearer' header " +
67
+ "will use the server's Paperless token. Only use this on a trusted/local network.");
68
+ }
69
+ else if (resolvedToken) {
70
+ console.log("[paperless-mcp] A server token is configured, but unauthenticated requests are rejected. " +
71
+ "Clients must send 'Authorization: Bearer <paperless-token>'. " +
72
+ "To use the server token for unauthenticated requests instead, restart with the --no-auth flag " +
73
+ "(trusted/local networks only).");
74
+ }
58
75
  const app = (0, express_1.default)();
59
76
  app.use(express_1.default.json());
60
77
  // Store transports for each session
61
78
  const sseTransports = {};
62
79
  app.post("/mcp", (req, res) => __awaiter(this, void 0, void 0, function* () {
63
- const requestToken = (0, server_1.getBearerToken)(req, resolvedToken);
80
+ const requestToken = (0, server_1.getBearerToken)(req, {
81
+ fallbackToken: resolvedToken,
82
+ allowAnonymous: noAuth,
83
+ });
64
84
  if (!requestToken) {
65
85
  (0, server_1.sendUnauthorized)(res);
66
86
  return;
@@ -112,7 +132,10 @@ function main() {
112
132
  }));
113
133
  app.get("/sse", (req, res) => __awaiter(this, void 0, void 0, function* () {
114
134
  console.log("SSE request received");
115
- const requestToken = (0, server_1.getBearerToken)(req, resolvedToken);
135
+ const requestToken = (0, server_1.getBearerToken)(req, {
136
+ fallbackToken: resolvedToken,
137
+ allowAnonymous: noAuth,
138
+ });
116
139
  if (!requestToken) {
117
140
  (0, server_1.sendUnauthorized)(res);
118
141
  return;