@kjanat/paperless-mcp 1.0.1-dev.1 → 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.
Files changed (3) hide show
  1. package/README.md +59 -59
  2. package/package.json +6 -5
  3. package/src/index.ts +144 -0
package/README.md CHANGED
@@ -16,16 +16,16 @@ An MCP (Model Context Protocol) server for interacting with a Paperless-ngx API
16
16
 
17
17
  ```jsonc
18
18
  {
19
- "mcpServers": {
20
- "paperless": {
21
- "command": "bunx", // or npx
22
- "args": [
23
- "@kjanat/paperless-mcp",
24
- "http://your-paperless-instance:8000",
25
- "your-api-token",
26
- ],
27
- },
28
- },
19
+ "mcpServers": {
20
+ "paperless": {
21
+ "command": "bunx", // or npx
22
+ "args": [
23
+ "@kjanat/paperless-mcp",
24
+ "http://your-paperless-instance:8000",
25
+ "your-api-token",
26
+ ],
27
+ },
28
+ },
29
29
  }
30
30
  ```
31
31
 
@@ -66,7 +66,7 @@ Parameters:
66
66
 
67
67
  ```typescript
68
68
  get_document({
69
- id: 123,
69
+ id: 123,
70
70
  });
71
71
  ```
72
72
 
@@ -80,7 +80,7 @@ Parameters:
80
80
 
81
81
  ```typescript
82
82
  search_documents({
83
- query: 'invoice 2024',
83
+ query: "invoice 2024",
84
84
  });
85
85
  ```
86
86
 
@@ -95,8 +95,8 @@ Parameters:
95
95
 
96
96
  ```typescript
97
97
  download_document({
98
- id: 123,
99
- original: false,
98
+ id: 123,
99
+ original: false,
100
100
  });
101
101
  ```
102
102
 
@@ -139,39 +139,39 @@ Examples:
139
139
  ```typescript
140
140
  // Add a tag to multiple documents
141
141
  bulk_edit_documents({
142
- documents: [1, 2, 3],
143
- method: 'add_tag',
144
- tag: 5,
142
+ documents: [1, 2, 3],
143
+ method: "add_tag",
144
+ tag: 5,
145
145
  });
146
146
 
147
147
  // Set correspondent and document type
148
148
  bulk_edit_documents({
149
- documents: [4, 5],
150
- method: 'set_correspondent',
151
- correspondent: 2,
149
+ documents: [4, 5],
150
+ method: "set_correspondent",
151
+ correspondent: 2,
152
152
  });
153
153
 
154
154
  // Merge documents
155
155
  bulk_edit_documents({
156
- documents: [6, 7, 8],
157
- method: 'merge',
158
- metadata_document_id: 6,
159
- delete_originals: true,
156
+ documents: [6, 7, 8],
157
+ method: "merge",
158
+ metadata_document_id: 6,
159
+ delete_originals: true,
160
160
  });
161
161
 
162
162
  // Split document into parts
163
163
  bulk_edit_documents({
164
- documents: [9],
165
- method: 'split',
166
- pages: '[1-2,3-4,5]',
164
+ documents: [9],
165
+ method: "split",
166
+ pages: "[1-2,3-4,5]",
167
167
  });
168
168
 
169
169
  // Modify multiple tags at once
170
170
  bulk_edit_documents({
171
- documents: [10, 11],
172
- method: 'modify_tags',
173
- add_tags: [1, 2],
174
- remove_tags: [3, 4],
171
+ documents: [10, 11],
172
+ method: "modify_tags",
173
+ add_tags: [1, 2],
174
+ remove_tags: [3, 4],
175
175
  });
176
176
  ```
177
177
 
@@ -194,14 +194,14 @@ Parameters:
194
194
 
195
195
  ```typescript
196
196
  post_document({
197
- file: 'base64_encoded_content',
198
- filename: 'invoice.pdf',
199
- title: 'January Invoice',
200
- created: '2024-01-19',
201
- correspondent: 1,
202
- document_type: 2,
203
- tags: [1, 3],
204
- archive_serial_number: '2024-001',
197
+ file: "base64_encoded_content",
198
+ filename: "invoice.pdf",
199
+ title: "January Invoice",
200
+ created: "2024-01-19",
201
+ correspondent: 1,
202
+ document_type: 2,
203
+ tags: [1, 3],
204
+ archive_serial_number: "2024-001",
205
205
  });
206
206
  ```
207
207
 
@@ -228,10 +228,10 @@ Parameters:
228
228
 
229
229
  ```typescript
230
230
  create_tag({
231
- name: 'Invoice',
232
- color: '#ff0000',
233
- match: 'invoice',
234
- matching_algorithm: 5,
231
+ name: "Invoice",
232
+ color: "#ff0000",
233
+ match: "invoice",
234
+ matching_algorithm: 5,
235
235
  });
236
236
  ```
237
237
 
@@ -249,9 +249,9 @@ Parameters:
249
249
 
250
250
  ```typescript
251
251
  update_tag({
252
- id: 5,
253
- name: 'Invoices',
254
- color: '#00ff00',
252
+ id: 5,
253
+ name: "Invoices",
254
+ color: "#00ff00",
255
255
  });
256
256
  ```
257
257
 
@@ -265,7 +265,7 @@ Parameters:
265
265
 
266
266
  ```typescript
267
267
  delete_tag({
268
- id: 5,
268
+ id: 5,
269
269
  });
270
270
  ```
271
271
 
@@ -283,8 +283,8 @@ Parameters:
283
283
 
284
284
  ```typescript
285
285
  bulk_edit_tags({
286
- tag_ids: [1, 2, 3],
287
- operation: 'delete',
286
+ tag_ids: [1, 2, 3],
287
+ operation: "delete",
288
288
  });
289
289
  ```
290
290
 
@@ -310,9 +310,9 @@ Parameters:
310
310
 
311
311
  ```typescript
312
312
  create_correspondent({
313
- name: 'ACME Corp',
314
- match: 'ACME',
315
- matching_algorithm: 5,
313
+ name: "ACME Corp",
314
+ match: "ACME",
315
+ matching_algorithm: 5,
316
316
  });
317
317
  ```
318
318
 
@@ -330,8 +330,8 @@ Parameters:
330
330
 
331
331
  ```typescript
332
332
  bulk_edit_correspondents({
333
- correspondent_ids: [1, 2],
334
- operation: 'delete',
333
+ correspondent_ids: [1, 2],
334
+ operation: "delete",
335
335
  });
336
336
  ```
337
337
 
@@ -357,9 +357,9 @@ Parameters:
357
357
 
358
358
  ```typescript
359
359
  create_document_type({
360
- name: 'Invoice',
361
- match: 'invoice total amount due',
362
- matching_algorithm: 1,
360
+ name: "Invoice",
361
+ match: "invoice total amount due",
362
+ matching_algorithm: 1,
363
363
  });
364
364
  ```
365
365
 
@@ -377,8 +377,8 @@ Parameters:
377
377
 
378
378
  ```typescript
379
379
  bulk_edit_document_types({
380
- document_type_ids: [1, 2],
381
- operation: 'delete',
380
+ document_type_ids: [1, 2],
381
+ operation: "delete",
382
382
  });
383
383
  ```
384
384
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kjanat/paperless-mcp",
3
- "version": "1.0.1-dev.1",
3
+ "version": "2.0.0",
4
4
  "description": "MCP server for interacting with Paperless-ngx document management system.",
5
5
  "keywords": [
6
6
  "mcp",
@@ -27,10 +27,10 @@
27
27
  ],
28
28
  "scripts": {
29
29
  "bd": "bun build src/index.ts --minify --outdir=dist --target=node --banner='#!/usr/bin/env node'",
30
- "fmt": "dprint fmt .",
31
- "fmt:check": "dprint check .",
30
+ "fmt": "dprint fmt",
31
+ "fmt:check": "dprint check",
32
32
  "inspect": "bunx @modelcontextprotocol/inspector bun src/index.ts",
33
- "prepack": "bun bd",
33
+ "prepack": "bun bd && bunx prettier README.md --write",
34
34
  "start": "bun src/index.ts",
35
35
  "typecheck": "tsgo --noEmit"
36
36
  },
@@ -40,7 +40,8 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@types/bun": "^1.3.9",
43
- "@typescript/native-preview": "^7.0.0-dev.20260220.1",
43
+ "@typescript/native-preview": "^7.0.0-dev.20260221.1",
44
+ "dprint": "^0.51.1",
44
45
  "typescript": "^5.9.3"
45
46
  },
46
47
  "optionalDependencies": {
package/src/index.ts ADDED
@@ -0,0 +1,144 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+
3
+ import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
4
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
5
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
7
+
8
+ import { PaperlessAPI } from './api/paperless-api';
9
+ import { registerCorrespondentTools } from './tools/correspondents';
10
+ import { registerDocumentTools } from './tools/documents';
11
+ import { registerDocumentTypeTools } from './tools/documentTypes';
12
+ import { registerTagTools } from './tools/tags';
13
+
14
+ // CLI argument parsing
15
+ const args = process.argv.slice(2);
16
+ const useHttp = args.includes('--http');
17
+
18
+ function parsePort(): number {
19
+ const portIndex = args.indexOf('--port');
20
+ if (portIndex !== -1) {
21
+ const raw = args[portIndex + 1];
22
+ if (raw == null) {
23
+ console.warn('--port flag provided without a value, using default 3000');
24
+ return 3000;
25
+ }
26
+ const parsed = parseInt(raw, 10);
27
+ if (isNaN(parsed)) {
28
+ console.warn(`--port value "${raw}" is not a valid number, using default 3000`);
29
+ return 3000;
30
+ }
31
+ return parsed;
32
+ }
33
+ return 3000;
34
+ }
35
+
36
+ const port = parsePort();
37
+
38
+ /** Express request with parsed body attached by express.json() middleware. */
39
+ interface ParsedRequest extends IncomingMessage {
40
+ body?: unknown;
41
+ }
42
+
43
+ /** JSON-RPC error response shape. */
44
+ interface JsonRpcError {
45
+ readonly jsonrpc: '2.0';
46
+ readonly error: { readonly code: number; readonly message: string };
47
+ readonly id: null;
48
+ }
49
+
50
+ function jsonRpcError(code: number, message: string): JsonRpcError {
51
+ return { jsonrpc: '2.0', error: { code, message }, id: null };
52
+ }
53
+
54
+ async function main(): Promise<void> {
55
+ let baseUrl: string | undefined;
56
+ let token: string | undefined;
57
+
58
+ if (useHttp) {
59
+ baseUrl = process.env['PAPERLESS_URL'];
60
+ token = process.env['API_KEY'];
61
+ if (!baseUrl || !token) {
62
+ console.error(
63
+ 'When using --http, PAPERLESS_URL and API_KEY environment variables must be set.',
64
+ );
65
+ process.exit(1);
66
+ }
67
+ } else {
68
+ baseUrl = args[0];
69
+ token = args[1];
70
+ if (!baseUrl || !token) {
71
+ console.error(
72
+ 'Usage: paperless-mcp <baseUrl> <token> [--http] [--port <port>]',
73
+ );
74
+ console.error(
75
+ 'Example: paperless-mcp http://localhost:8000 your-api-token --http --port 3000',
76
+ );
77
+ console.error(
78
+ 'When using --http, PAPERLESS_URL and API_KEY environment variables must be set.',
79
+ );
80
+ process.exit(1);
81
+ }
82
+ }
83
+
84
+ const api = new PaperlessAPI(baseUrl, token);
85
+
86
+ /** Create a fresh McpServer with all tools registered. */
87
+ function createServer(): McpServer {
88
+ const server = new McpServer({ name: 'paperless-ngx', version: '1.0.0' });
89
+ registerDocumentTools(server, api);
90
+ registerTagTools(server, api);
91
+ registerCorrespondentTools(server, api);
92
+ registerDocumentTypeTools(server, api);
93
+ return server;
94
+ }
95
+
96
+ if (useHttp) {
97
+ const app = createMcpExpressApp({ host: '0.0.0.0' });
98
+
99
+ app.post('/mcp', async (req: ParsedRequest, res: ServerResponse) => {
100
+ try {
101
+ const transport = new StreamableHTTPServerTransport({
102
+ sessionIdGenerator: undefined,
103
+ });
104
+ res.on('close', () => {
105
+ void transport.close();
106
+ });
107
+ const server = createServer();
108
+ await server.connect(transport);
109
+ await transport.handleRequest(req, res, req.body);
110
+ } catch (error: unknown) {
111
+ console.error('Error handling MCP request:', error);
112
+ if (!res.headersSent) {
113
+ res.writeHead(500, { 'Content-Type': 'application/json' });
114
+ res.end(JSON.stringify(jsonRpcError(-32603, 'Internal server error')));
115
+ }
116
+ }
117
+ });
118
+
119
+ app.get('/mcp', (_req: ParsedRequest, res: ServerResponse) => {
120
+ res.writeHead(405, { 'Content-Type': 'application/json' });
121
+ res.end(JSON.stringify(jsonRpcError(-32000, 'Method not allowed.')));
122
+ });
123
+
124
+ app.delete('/mcp', (_req: ParsedRequest, res: ServerResponse) => {
125
+ res.writeHead(405, { 'Content-Type': 'application/json' });
126
+ res.end(JSON.stringify(jsonRpcError(-32000, 'Method not allowed.')));
127
+ });
128
+
129
+ app.listen(port, () => {
130
+ console.log(
131
+ `MCP Stateless Streamable HTTP Server listening on port ${port}`,
132
+ );
133
+ });
134
+ } else {
135
+ const server = createServer();
136
+ const transport = new StdioServerTransport();
137
+ await server.connect(transport);
138
+ }
139
+ }
140
+
141
+ main().catch((e: unknown) => {
142
+ const message = e instanceof Error ? e.message : String(e);
143
+ console.error(message);
144
+ });