@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.
- package/README.md +59 -59
- package/package.json +6 -5
- 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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
69
|
+
id: 123,
|
|
70
70
|
});
|
|
71
71
|
```
|
|
72
72
|
|
|
@@ -80,7 +80,7 @@ Parameters:
|
|
|
80
80
|
|
|
81
81
|
```typescript
|
|
82
82
|
search_documents({
|
|
83
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
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
|
-
|
|
287
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
-
|
|
334
|
-
|
|
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
|
-
|
|
361
|
-
|
|
362
|
-
|
|
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
|
-
|
|
381
|
-
|
|
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": "
|
|
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.
|
|
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
|
+
});
|