@kjanat/paperless-mcp 2.1.1 → 2.1.2
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 +69 -69
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,16 +18,16 @@ An MCP (Model Context Protocol) server for interacting with a Paperless-ngx API
|
|
|
18
18
|
|
|
19
19
|
```jsonc
|
|
20
20
|
{
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
21
|
+
"mcpServers": {
|
|
22
|
+
"paperless": {
|
|
23
|
+
"command": "bunx", // or npx
|
|
24
|
+
"args": ["@kjanat/paperless-mcp"],
|
|
25
|
+
"env": {
|
|
26
|
+
"PAPERLESS_URL": "http://your-paperless-instance:8000",
|
|
27
|
+
"PAPERLESS_API_KEY": "your-api-token",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
31
|
}
|
|
32
32
|
```
|
|
33
33
|
|
|
@@ -35,16 +35,16 @@ An MCP (Model Context Protocol) server for interacting with a Paperless-ngx API
|
|
|
35
35
|
|
|
36
36
|
```jsonc
|
|
37
37
|
{
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
38
|
+
"mcpServers": {
|
|
39
|
+
"paperless": {
|
|
40
|
+
"command": "bunx", // or npx
|
|
41
|
+
"args": [
|
|
42
|
+
"@kjanat/paperless-mcp",
|
|
43
|
+
"http://your-paperless-instance:8000",
|
|
44
|
+
"your-api-token",
|
|
45
|
+
],
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
48
|
}
|
|
49
49
|
```
|
|
50
50
|
|
|
@@ -96,7 +96,7 @@ Parameters:
|
|
|
96
96
|
|
|
97
97
|
```typescript
|
|
98
98
|
get_document({
|
|
99
|
-
|
|
99
|
+
id: 123,
|
|
100
100
|
});
|
|
101
101
|
```
|
|
102
102
|
|
|
@@ -110,7 +110,7 @@ Parameters:
|
|
|
110
110
|
|
|
111
111
|
```typescript
|
|
112
112
|
search_documents({
|
|
113
|
-
|
|
113
|
+
query: "invoice 2024",
|
|
114
114
|
});
|
|
115
115
|
```
|
|
116
116
|
|
|
@@ -125,8 +125,8 @@ Parameters:
|
|
|
125
125
|
|
|
126
126
|
```typescript
|
|
127
127
|
download_document({
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
id: 123,
|
|
129
|
+
original: false,
|
|
130
130
|
});
|
|
131
131
|
```
|
|
132
132
|
|
|
@@ -169,39 +169,39 @@ Examples:
|
|
|
169
169
|
```typescript
|
|
170
170
|
// Add a tag to multiple documents
|
|
171
171
|
bulk_edit_documents({
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
documents: [1, 2, 3],
|
|
173
|
+
method: "add_tag",
|
|
174
|
+
tag: 5,
|
|
175
175
|
});
|
|
176
176
|
|
|
177
177
|
// Set correspondent and document type
|
|
178
178
|
bulk_edit_documents({
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
179
|
+
documents: [4, 5],
|
|
180
|
+
method: "set_correspondent",
|
|
181
|
+
correspondent: 2,
|
|
182
182
|
});
|
|
183
183
|
|
|
184
184
|
// Merge documents
|
|
185
185
|
bulk_edit_documents({
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
186
|
+
documents: [6, 7, 8],
|
|
187
|
+
method: "merge",
|
|
188
|
+
metadata_document_id: 6,
|
|
189
|
+
delete_originals: true,
|
|
190
190
|
});
|
|
191
191
|
|
|
192
192
|
// Split document into parts
|
|
193
193
|
bulk_edit_documents({
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
194
|
+
documents: [9],
|
|
195
|
+
method: "split",
|
|
196
|
+
pages: "[1-2,3-4,5]",
|
|
197
197
|
});
|
|
198
198
|
|
|
199
199
|
// Modify multiple tags at once
|
|
200
200
|
bulk_edit_documents({
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
201
|
+
documents: [10, 11],
|
|
202
|
+
method: "modify_tags",
|
|
203
|
+
add_tags: [1, 2],
|
|
204
|
+
remove_tags: [3, 4],
|
|
205
205
|
});
|
|
206
206
|
```
|
|
207
207
|
|
|
@@ -224,14 +224,14 @@ Parameters:
|
|
|
224
224
|
|
|
225
225
|
```typescript
|
|
226
226
|
post_document({
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
227
|
+
file: "base64_encoded_content",
|
|
228
|
+
filename: "invoice.pdf",
|
|
229
|
+
title: "January Invoice",
|
|
230
|
+
created: "2024-01-19",
|
|
231
|
+
correspondent: 1,
|
|
232
|
+
document_type: 2,
|
|
233
|
+
tags: [1, 3],
|
|
234
|
+
archive_serial_number: "2024-001",
|
|
235
235
|
});
|
|
236
236
|
```
|
|
237
237
|
|
|
@@ -262,10 +262,10 @@ Parameters:
|
|
|
262
262
|
|
|
263
263
|
```typescript
|
|
264
264
|
create_tag({
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
265
|
+
name: "Invoice",
|
|
266
|
+
color: "#ff0000",
|
|
267
|
+
match: "invoice",
|
|
268
|
+
matching_algorithm: 5,
|
|
269
269
|
});
|
|
270
270
|
```
|
|
271
271
|
|
|
@@ -283,9 +283,9 @@ Parameters:
|
|
|
283
283
|
|
|
284
284
|
```typescript
|
|
285
285
|
update_tag({
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
id: 5,
|
|
287
|
+
name: "Invoices",
|
|
288
|
+
color: "#00ff00",
|
|
289
289
|
});
|
|
290
290
|
```
|
|
291
291
|
|
|
@@ -299,7 +299,7 @@ Parameters:
|
|
|
299
299
|
|
|
300
300
|
```typescript
|
|
301
301
|
delete_tag({
|
|
302
|
-
|
|
302
|
+
id: 5,
|
|
303
303
|
});
|
|
304
304
|
```
|
|
305
305
|
|
|
@@ -317,8 +317,8 @@ Parameters:
|
|
|
317
317
|
|
|
318
318
|
```typescript
|
|
319
319
|
bulk_edit_tags({
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
tag_ids: [1, 2, 3],
|
|
321
|
+
operation: "delete",
|
|
322
322
|
});
|
|
323
323
|
```
|
|
324
324
|
|
|
@@ -348,9 +348,9 @@ Parameters:
|
|
|
348
348
|
|
|
349
349
|
```typescript
|
|
350
350
|
create_correspondent({
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
name: "ACME Corp",
|
|
352
|
+
match: "ACME",
|
|
353
|
+
matching_algorithm: 5,
|
|
354
354
|
});
|
|
355
355
|
```
|
|
356
356
|
|
|
@@ -368,8 +368,8 @@ Parameters:
|
|
|
368
368
|
|
|
369
369
|
```typescript
|
|
370
370
|
bulk_edit_correspondents({
|
|
371
|
-
|
|
372
|
-
|
|
371
|
+
correspondent_ids: [1, 2],
|
|
372
|
+
operation: "delete",
|
|
373
373
|
});
|
|
374
374
|
```
|
|
375
375
|
|
|
@@ -399,9 +399,9 @@ Parameters:
|
|
|
399
399
|
|
|
400
400
|
```typescript
|
|
401
401
|
create_document_type({
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
402
|
+
name: "Invoice",
|
|
403
|
+
match: "invoice total amount due",
|
|
404
|
+
matching_algorithm: 1,
|
|
405
405
|
});
|
|
406
406
|
```
|
|
407
407
|
|
|
@@ -419,8 +419,8 @@ Parameters:
|
|
|
419
419
|
|
|
420
420
|
```typescript
|
|
421
421
|
bulk_edit_document_types({
|
|
422
|
-
|
|
423
|
-
|
|
422
|
+
document_type_ids: [1, 2],
|
|
423
|
+
operation: "delete",
|
|
424
424
|
});
|
|
425
425
|
```
|
|
426
426
|
|
package/dist/index.js
CHANGED
|
@@ -347,6 +347,6 @@ data:
|
|
|
347
347
|
`;if($)u+=`id: ${$}
|
|
348
348
|
`;return u+=`data: ${JSON.stringify(v)}
|
|
349
349
|
|
|
350
|
-
`,i.enqueue(n.encode(u)),!0}catch(u){return this.onerror?.(u),!1}}handleUnsupportedRequest(){return this.onerror?.(Error("Method not allowed.")),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32000,message:"Method not allowed."},id:null}),{status:405,headers:{Allow:"GET, POST, DELETE","Content-Type":"application/json"}})}async handlePostRequest(i,n){try{let v=i.headers.get("accept");if(!v?.includes("application/json")||!v.includes("text/event-stream"))return this.onerror?.(Error("Not Acceptable: Client must accept both application/json and text/event-stream")),this.createJsonErrorResponse(406,-32000,"Not Acceptable: Client must accept both application/json and text/event-stream");let $=i.headers.get("content-type");if(!$||!$.includes("application/json"))return this.onerror?.(Error("Unsupported Media Type: Content-Type must be application/json")),this.createJsonErrorResponse(415,-32000,"Unsupported Media Type: Content-Type must be application/json");let u={headers:Object.fromEntries(i.headers.entries()),url:new URL(i.url)},c;if(n?.parsedBody!==void 0)c=n.parsedBody;else try{c=await i.json()}catch{return this.onerror?.(Error("Parse error: Invalid JSON")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON")}let l;try{if(Array.isArray(c))l=c.map((Q)=>G0.parse(Q));else l=[G0.parse(c)]}catch{return this.onerror?.(Error("Parse error: Invalid JSON-RPC message")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON-RPC message")}let b=l.some(pz);if(b){if(this._initialized&&this.sessionId!==void 0)return this.onerror?.(Error("Invalid Request: Server already initialized")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Server already initialized");if(l.length>1)return this.onerror?.(Error("Invalid Request: Only one initialization request is allowed")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Only one initialization request is allowed");if(this.sessionId=this.sessionIdGenerator?.(),this._initialized=!0,this.sessionId&&this._onsessioninitialized)await Promise.resolve(this._onsessioninitialized(this.sessionId))}if(!b){let Q=this.validateSession(i);if(Q)return Q;let F=this.validateProtocolVersion(i);if(F)return F}if(!l.some(ev)){for(let Q of l)this.onmessage?.(Q,{authInfo:n?.authInfo,requestInfo:u});return new Response(null,{status:202})}let x=crypto.randomUUID(),z=l.find((Q)=>pz(Q)),U=z?z.params.protocolVersion:i.headers.get("mcp-protocol-version")??mF;if(this._enableJsonResponse)return new Promise((Q)=>{this._streamMapping.set(x,{resolveJson:Q,cleanup:()=>{this._streamMapping.delete(x)}});for(let F of l)if(ev(F))this._requestToStreamMapping.set(F.id,x);for(let F of l)this.onmessage?.(F,{authInfo:n?.authInfo,requestInfo:u})});let J=new TextEncoder,W,Y=new ReadableStream({start:(Q)=>{W=Q},cancel:()=>{this._streamMapping.delete(x)}}),X={"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"};if(this.sessionId!==void 0)X["mcp-session-id"]=this.sessionId;for(let Q of l)if(ev(Q))this._streamMapping.set(x,{controller:W,encoder:J,cleanup:()=>{this._streamMapping.delete(x);try{W.close()}catch{}}}),this._requestToStreamMapping.set(Q.id,x);await this.writePrimingEvent(W,J,x,U);for(let Q of l){let F,M;if(ev(Q)&&this._eventStore&&U>="2025-11-25")F=()=>{this.closeSSEStream(Q.id)},M=()=>{this.closeStandaloneSSEStream()};this.onmessage?.(Q,{authInfo:n?.authInfo,requestInfo:u,closeSSEStream:F,closeStandaloneSSEStream:M})}return new Response(Y,{status:200,headers:X})}catch(v){return this.onerror?.(v),this.createJsonErrorResponse(400,-32700,"Parse error",{data:String(v)})}}async handleDeleteRequest(i){let n=this.validateSession(i);if(n)return n;let v=this.validateProtocolVersion(i);if(v)return v;return await Promise.resolve(this._onsessionclosed?.(this.sessionId)),await this.close(),new Response(null,{status:200})}validateSession(i){if(this.sessionIdGenerator===void 0)return;if(!this._initialized)return this.onerror?.(Error("Bad Request: Server not initialized")),this.createJsonErrorResponse(400,-32000,"Bad Request: Server not initialized");let n=i.headers.get("mcp-session-id");if(!n)return this.onerror?.(Error("Bad Request: Mcp-Session-Id header is required")),this.createJsonErrorResponse(400,-32000,"Bad Request: Mcp-Session-Id header is required");if(n!==this.sessionId)return this.onerror?.(Error("Session not found")),this.createJsonErrorResponse(404,-32001,"Session not found");return}validateProtocolVersion(i){let n=i.headers.get("mcp-protocol-version");if(n!==null&&!$6.includes(n))return this.onerror?.(Error(`Bad Request: Unsupported protocol version: ${n} (supported versions: ${$6.join(", ")})`)),this.createJsonErrorResponse(400,-32000,`Bad Request: Unsupported protocol version: ${n} (supported versions: ${$6.join(", ")})`);return}async close(){this._streamMapping.forEach(({cleanup:i})=>{i()}),this._streamMapping.clear(),this._requestResponseMap.clear(),this.onclose?.()}closeSSEStream(i){let n=this._requestToStreamMapping.get(i);if(!n)return;let v=this._streamMapping.get(n);if(v)v.cleanup()}closeStandaloneSSEStream(){let i=this._streamMapping.get(this._standaloneSseStreamId);if(i)i.cleanup()}async send(i,n){let v=n?.relatedRequestId;if(Kv(i)||c6(i))v=i.id;if(v===void 0){if(Kv(i)||c6(i))throw Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");let c;if(this._eventStore)c=await this._eventStore.storeEvent(this._standaloneSseStreamId,i);let l=this._streamMapping.get(this._standaloneSseStreamId);if(l===void 0)return;if(l.controller&&l.encoder)this.writeSSEEvent(l.controller,l.encoder,i,c);return}let $=this._requestToStreamMapping.get(v);if(!$)throw Error(`No connection established for request ID: ${String(v)}`);let u=this._streamMapping.get($);if(!this._enableJsonResponse&&u?.controller&&u?.encoder){let c;if(this._eventStore)c=await this._eventStore.storeEvent($,i);this.writeSSEEvent(u.controller,u.encoder,i,c)}if(Kv(i)||c6(i)){this._requestResponseMap.set(v,i);let c=Array.from(this._requestToStreamMapping.entries()).filter(([b,w])=>w===$).map(([b])=>b);if(c.every((b)=>this._requestResponseMap.has(b))){if(!u)throw Error(`No connection established for request ID: ${String(v)}`);if(this._enableJsonResponse&&u.resolveJson){let b={"Content-Type":"application/json"};if(this.sessionId!==void 0)b["mcp-session-id"]=this.sessionId;let w=c.map((x)=>this._requestResponseMap.get(x));if(w.length===1)u.resolveJson(new Response(JSON.stringify(w[0]),{status:200,headers:b}));else u.resolveJson(new Response(JSON.stringify(w),{status:200,headers:b}))}else u.cleanup();for(let b of c)this._requestResponseMap.delete(b),this._requestToStreamMapping.delete(b)}}}}class MJ{constructor(i={}){this._requestContext=new WeakMap,this._webStandardTransport=new oJ(i),this._requestListener=AJ(async(n)=>{let v=this._requestContext.get(n);return this._webStandardTransport.handleRequest(n,{authInfo:v?.authInfo,parsedBody:v?.parsedBody})},{overrideGlobalObjects:!1})}get sessionId(){return this._webStandardTransport.sessionId}set onclose(i){this._webStandardTransport.onclose=i}get onclose(){return this._webStandardTransport.onclose}set onerror(i){this._webStandardTransport.onerror=i}get onerror(){return this._webStandardTransport.onerror}set onmessage(i){this._webStandardTransport.onmessage=i}get onmessage(){return this._webStandardTransport.onmessage}async start(){return this._webStandardTransport.start()}async close(){return this._webStandardTransport.close()}async send(i,n){return this._webStandardTransport.send(i,n)}async handleRequest(i,n,v){let $=i.auth;await AJ(async(c)=>{return this._webStandardTransport.handleRequest(c,{authInfo:$,parsedBody:v})},{overrideGlobalObjects:!1})(i,n)}closeSSEStream(i){this._webStandardTransport.closeSSEStream(i)}closeStandaloneSSEStream(){this._webStandardTransport.closeStandaloneSSEStream()}}var EB={name:"@kjanat/paperless-mcp",version:"2.1.1",description:"MCP server for interacting with Paperless-ngx document management system.",keywords:["paperless-ngx","paperless","ai","mcp","model-context-protocol","document-management"],repository:{type:"git",url:"git+https://github.com/kjanat/paperless-mcp.git"},license:"MIT",author:"Kaj Kowalski",type:"module",imports:{"#*":"./src/*.ts"},bin:{"paperless-mcp":"dist/index.js"},files:["dist","skills"],scripts:{bd:"bun build src/index.ts --minify --outdir=dist --target=node --banner='#!/usr/bin/env node'",meta:"bash scripts/meta.sh",fmt:"dprint fmt","fmt:check":"dprint check",mcp:"bun src/index.ts",inspect:"bunx @modelcontextprotocol/inspector run mcp",prepack:"bun bd",start:"run mcp",typecheck:"tsgo --noEmit"},devDependencies:{"@modelcontextprotocol/sdk":"^1.29.0","@types/bun":"^1.3.14","@types/express":"^5.0.6","@typescript/native-preview":"^7.0.0-dev.20260606.1",dprint:"0.51.1",express:"^5.2.1","runner-run":"^0.12.1",typescript:"^6.0.3",zod:"^4.4.3"},packageManager:"bun@1.3.14",engines:{node:">=22"},publishConfig:{access:"public",tag:"dev"},volta:{node:"26.3.0",npm:"11.16.0"}};class jJ{baseUrl;token;constructor(i,n){this.baseUrl=i;this.token=n}async request(i,n={}){let v=`${this.baseUrl}/api${i}`,$={Authorization:`Token ${this.token}`,Accept:"application/json; version=6","Content-Type":"application/json","Accept-Language":"en-US,en;q=0.9"},u=await fetch(v,{...n,headers:{...$,...n.headers}});if(!u.ok){let c=await u.json().catch(()=>null);throw Error(`HTTP ${u.status} from ${i}: ${JSON.stringify(c)}`)}if(u.status===204||u.status===205)return;return u.json()}async bulkEditDocuments(i,n,v={}){return this.request("/documents/bulk_edit/",{method:"POST",body:JSON.stringify({documents:i,method:n,...Xd(v)})})}async postDocument(i,n={}){let v=new FormData;if(v.append("document",i),n.title!=null)v.append("title",n.title);if(n.created!=null)v.append("created",n.created);if(n.correspondent!=null)v.append("correspondent",String(n.correspondent));if(n.document_type!=null)v.append("document_type",String(n.document_type));if(n.storage_path!=null)v.append("storage_path",String(n.storage_path));if(n.tags!=null)for(let u of n.tags)v.append("tags",String(u));if(n.archive_serial_number!=null)v.append("archive_serial_number",String(n.archive_serial_number));if(n.custom_fields!=null)for(let u of n.custom_fields)v.append("custom_fields",String(u));let $=await fetch(`${this.baseUrl}/api/documents/post_document/`,{method:"POST",headers:{Authorization:`Token ${this.token}`,Accept:"application/json; version=6"},body:v});if(!$.ok){let u=await $.json().catch(()=>null);throw Error(`HTTP ${$.status} from /documents/post_document/: ${JSON.stringify(u)}`)}return $.json()}async getDocuments(i=""){return this.request(`/documents/${i}`)}async getDocument(i){return this.request(`/documents/${i}/`)}async searchDocuments(i,n,v){let $=new URLSearchParams;if($.set("query",i),n!=null)$.set("page",n.toString());if(v!=null)$.set("page_size",v.toString());let u=await this.request(`/documents/?${$.toString()}`);return{...u,results:u.results.map((c)=>{let{content:l,download_url:b,thumbnail_url:w,...x}=c;return x})}}async downloadDocument(i,n=!1){let v=n?"?original=true":"",$=`/documents/${i}/download/`,u=await fetch(`${this.baseUrl}/api${$}${v}`,{headers:{Authorization:`Token ${this.token}`,Accept:"application/json; version=6"}});if(!u.ok){let c=await u.text().catch(()=>"");throw Error(`HTTP ${u.status} from ${$}: ${c}`)}return u}async getTags(){return this.getAllPages("/tags/")}async createTag(i){return this.request("/tags/",{method:"POST",body:JSON.stringify(i)})}async updateTag(i,n){return this.request(`/tags/${i}/`,{method:"PATCH",body:JSON.stringify(n)})}async deleteTag(i){await this.request(`/tags/${i}/`,{method:"DELETE"})}async getCorrespondents(){return this.getAllPages("/correspondents/")}async createCorrespondent(i){return this.request("/correspondents/",{method:"POST",body:JSON.stringify(i)})}async getDocumentTypes(){return this.getAllPages("/document_types/")}async createDocumentType(i){return this.request("/document_types/",{method:"POST",body:JSON.stringify(i)})}async bulkEditObjects(i,n,v,$={}){return this.request("/bulk_edit_objects/",{method:"POST",body:JSON.stringify({objects:i,object_type:n,operation:v,...$})})}async getAllPages(i){let v=[],$=[],u=0,c=1;while(!0){let l=i.includes("?")?"&":"?",b=await this.request(`${i}${l}page=${c}&page_size=100`);if(u=b.count,c===1)$=b.all;if(v.push(...b.results),b.next==null||v.length>=b.count)return{count:u,next:null,previous:null,all:$,results:v};c+=1}}}function Xd(i){let n={};for(let[v,$]of Object.entries(i))if($!==void 0)n[v]=$;return n}function ji(i){return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}function kB(i,n){i.registerTool("list_correspondents",{description:"Retrieve all available correspondents (people, companies, organizations that send/receive documents). Returns names and automatic matching patterns for document assignment."},async(v)=>{return ji(await n.getCorrespondents())}),i.registerTool("create_correspondent",{description:"Create a new correspondent (person, company, or organization) for tracking document senders and receivers. Can include automatic matching patterns for smart assignment to incoming documents.",inputSchema:{name:K.string().describe("Name of the correspondent (person, company, or organization that sends/receives documents). Examples: 'Bank of America', 'John Smith', 'Electric Company'."),match:K.string().optional().describe("Text pattern to automatically assign this correspondent to matching documents. Use names, email addresses, or keywords that appear in documents from this correspondent."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("How to match text patterns: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic. Default is 0."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive. Default is true.")}},async(v,$)=>{return ji(await n.createCorrespondent(v))}),i.registerTool("bulk_edit_correspondents",{description:"Perform bulk operations on multiple correspondents: set permissions to control who can assign them to documents, or permanently delete multiple correspondents. Use with caution as deletion affects all associated documents.",inputSchema:{correspondent_ids:K.array(K.number()).describe("Array of correspondent IDs to perform bulk operations on. Use list_correspondents to get valid correspondent IDs."),operation:K.enum(["set_permissions","delete"]).describe("Bulk operation: 'set_permissions' to control who can assign these correspondents to documents, 'delete' to permanently remove correspondents from the system. Warning: Deleting correspondents will remove them from all associated documents."),owner:K.number().optional().describe("User ID to set as owner when operation is 'set_permissions'. The owner has full control over these correspondents."),permissions:K.object({view:K.object({users:K.array(K.number()).optional().describe("User IDs who can see and assign these correspondents to documents"),groups:K.array(K.number()).optional().describe("Group IDs who can see and assign these correspondents to documents")}).describe("Users and groups with permission to view and use these correspondents"),change:K.object({users:K.array(K.number()).optional().describe("User IDs who can modify correspondent details (name, matching rules)"),groups:K.array(K.number()).optional().describe("Group IDs who can modify correspondent details")}).describe("Users and groups with permission to edit these correspondent settings")}).optional().describe("Permission settings when operation is 'set_permissions'. Defines who can view/assign and modify these correspondents."),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them entirely (false). Default is false.")}},async({correspondent_ids:v,operation:$,...u},c)=>{let l=await n.bulkEditObjects(v,"correspondents",$,$==="set_permissions"?u:{});return ji(l)})}function ZB(i,n){i.registerTool("bulk_edit_documents",{description:"Perform bulk operations on multiple documents simultaneously: set correspondent/type/tags, delete, reprocess, merge, split, rotate, or manage permissions. Efficient for managing large document collections.",inputSchema:{documents:K.array(K.number()).describe("Array of document IDs to perform bulk operations on. Get document IDs from search_documents first."),method:K.enum(["set_correspondent","set_document_type","set_storage_path","add_tag","remove_tag","modify_tags","modify_custom_fields","delete","reprocess","set_permissions","merge","split","rotate","delete_pages","edit_pdf","remove_password"]).describe("The bulk operation to perform."),correspondent:K.number().optional().describe("ID of correspondent to assign when method is 'set_correspondent'. Use list_correspondents to get valid IDs."),document_type:K.number().optional().describe("ID of document type to assign when method is 'set_document_type'. Use list_document_types to get valid IDs."),storage_path:K.number().optional().describe("ID of storage path to assign when method is 'set_storage_path'. Storage paths organize documents in folder hierarchies."),tag:K.number().optional().describe("Single tag ID to add or remove when method is 'add_tag' or 'remove_tag'. Use list_tags to get valid IDs."),add_tags:K.array(K.number()).optional().describe("Array of tag IDs to add when method is 'modify_tags'. Use list_tags to get valid IDs."),remove_tags:K.array(K.number()).optional().describe("Array of tag IDs to remove when method is 'modify_tags'. Use list_tags to get valid IDs."),permissions:K.object({owner:K.number().nullable().optional().describe("User ID to set as document owner, or null to remove ownership"),set_permissions:K.object({view:K.object({users:K.array(K.number()).describe("User IDs granted view permission"),groups:K.array(K.number()).describe("Group IDs granted view permission")}).describe("Users and groups who can view these documents"),change:K.object({users:K.array(K.number()).describe("User IDs granted edit permission"),groups:K.array(K.number()).describe("Group IDs granted edit permission")}).describe("Users and groups who can edit these documents")}).optional().describe("Specific permission settings for users and groups"),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them (false)")}).optional().describe("Permission settings when method is 'set_permissions'. Controls who can view and edit the documents."),metadata_document_id:K.number().optional().describe("Source document ID when merging documents. The metadata from this document will be preserved."),delete_originals:K.boolean().optional().describe("Whether to delete original documents after merge/split operations. Use with caution."),pages:K.string().optional().describe("Page specification for delete_pages method. Format: '1,3,5-7' to delete pages 1, 3, and 5 through 7."),degrees:K.number().optional().describe("Rotation angle in degrees when method is 'rotate'. Use 90, 180, or 270 for standard rotations.")}},async({documents:v,method:$,...u},c)=>{return ji(await n.bulkEditDocuments(v,$,Qd($,u)))}),i.registerTool("post_document",{description:"Upload a new document to Paperless-ngx with metadata. Supports PDF, images (PNG/JPG/TIFF), and text files. Automatically processes for OCR and indexing.",inputSchema:{file:K.string().describe("Base64 encoded file content. Convert your file to base64 before uploading. Supports PDF, images (PNG, JPG, TIFF), and text files."),filename:K.string().describe("Original filename with extension (e.g., 'invoice.pdf', 'receipt.png'). This helps Paperless determine file type and initial document title."),title:K.string().optional().describe("Custom document title. If not provided, Paperless will extract title from filename or document content."),created:K.string().optional().describe("Document creation date in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss). If not provided, uses current date."),correspondent:K.number().optional().describe("ID of the correspondent (sender/receiver) for this document. Use list_correspondents to find or create_correspondent to add new ones."),document_type:K.number().optional().describe("ID of document type for categorization (e.g., Invoice, Receipt, Letter). Use list_document_types to find or create_document_type to add new ones."),storage_path:K.number().optional().describe("ID of storage path to organize document location in folder hierarchy. Leave empty for default storage."),tags:K.array(K.number()).optional().describe("Array of tag IDs to label this document. Use list_tags to find existing tags or create_tag to add new ones."),archive_serial_number:K.number().int().min(0).optional().describe("Archive serial number for document organization and reference. Useful for maintaining external filing systems."),custom_fields:K.array(K.number()).optional().describe("Array of custom field IDs to associate with this document. Custom fields store additional metadata.")}},async(v,$)=>{let u=Buffer.from(v.file,"base64"),c=new Blob([u]),l=new File([c],v.filename),b={title:v.title,created:v.created,correspondent:v.correspondent,document_type:v.document_type,storage_path:v.storage_path,tags:v.tags,archive_serial_number:v.archive_serial_number,custom_fields:v.custom_fields};return ji(await n.postDocument(l,b))}),i.registerTool("get_document",{description:"Get complete details for a specific document including full metadata, content preview, tags, correspondent, and document type information.",inputSchema:{id:K.number().describe("Unique document ID. Get this from search_documents results. Returns full document metadata, content preview, and associated tags/correspondent/type.")}},async({id:v},$)=>{return ji(await n.getDocument(v))}),i.registerTool("search_documents",{description:"Search through documents using full-text search across content, titles, tags, and metadata. Returns document metadata WITHOUT the full OCR content field to prevent token overflow. Use get_document to retrieve full details for specific documents of interest. Supports Paperless-ngx advanced query syntax.",inputSchema:{query:K.string().describe("Search query using Paperless-ngx syntax. By default, matches documents containing ALL words. Advanced syntax: Field searches: 'tag:unpaid', 'type:invoice', 'correspondent:university'. Logical operators: 'term1 AND (term2 OR term3)'. Date ranges: 'created:[2020 to 2024]', 'added:yesterday', 'modified:today'. Wildcards: 'prod*name'. Combine multiple criteria as needed. Search looks through document content, title, correspondent, type, and tags."),page:K.number().optional().describe("Page number for pagination (starts at 1). Use to browse through large result sets without hitting token limits."),page_size:K.number().optional().describe("Number of documents per page (default 25, max 100). Smaller page sizes help avoid token limits when many documents match.")}},async({query:v,page:$,page_size:u},c)=>{return ji(await n.searchDocuments(v,$,u))}),i.registerTool("download_document",{description:"Download a document file as base64-encoded data. Choose between original uploaded file or processed/archived version with OCR improvements.",inputSchema:{id:K.number().describe("Document ID to download. Get this from search_documents or get_document results."),original:K.boolean().optional().describe("Whether to download the original uploaded file (true) or the processed/archived version (false, default). Original files preserve exact formatting but may not include OCR improvements.")}},async({id:v,original:$},u)=>{let c=await n.downloadDocument(v,$),l=c.headers.get("content-disposition")?.split("filename=")[1]?.replace(/"/g,"")??`document-${v}`;return ji({blob:Buffer.from(await c.arrayBuffer()).toString("base64"),filename:l})})}function Qd(i,n){switch(i){case"set_correspondent":return Av(n,["correspondent"]);case"set_document_type":return Av(n,["document_type"]);case"set_storage_path":return Av(n,["storage_path"]);case"add_tag":case"remove_tag":return Av(n,["tag"]);case"modify_tags":return Av(n,["add_tags","remove_tags"]);case"set_permissions":return Av(n,["permissions"]);case"merge":case"split":return Av(n,["metadata_document_id","delete_originals"]);case"rotate":return Av(n,["degrees"]);case"delete_pages":return Av(n,["pages"]);default:return{}}}function Av(i,n){let v={};for(let $ of n){let u=i[$];if(u!==void 0)v[$]=u}return v}function aB(i,n){i.registerTool("list_document_types",{description:"Retrieve all available document types for categorizing documents by purpose or format (Invoice, Receipt, Contract, etc.). Returns names and automatic matching rules."},async(v)=>{return ji(await n.getDocumentTypes())}),i.registerTool("create_document_type",{description:"Create a new document type for categorizing documents by their purpose or format (e.g., Invoice, Receipt, Contract). Can include automatic matching rules for smart classification.",inputSchema:{name:K.string().describe("Name of the document type for categorizing documents by their purpose or format. Examples: 'Invoice', 'Receipt', 'Contract', 'Letter', 'Bank Statement', 'Tax Document'."),match:K.string().optional().describe("Text pattern to automatically assign this document type to matching documents. Use keywords that commonly appear in this type of document (e.g., 'invoice', 'receipt', 'contract terms')."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("How to match text patterns: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic. Default is 0."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive. Default is true.")}},async(v,$)=>{return ji(await n.createDocumentType(v))}),i.registerTool("bulk_edit_document_types",{description:"Perform bulk operations on multiple document types: set permissions to control who can assign them to documents, or permanently delete multiple types. Use with caution as deletion affects all associated documents.",inputSchema:{document_type_ids:K.array(K.number()).describe("Array of document type IDs to perform bulk operations on. Use list_document_types to get valid document type IDs."),operation:K.enum(["set_permissions","delete"]).describe("Bulk operation: 'set_permissions' to control who can assign these document types to documents, 'delete' to permanently remove document types from the system. Warning: Deleting document types will remove the classification from all associated documents."),owner:K.number().optional().describe("User ID to set as owner when operation is 'set_permissions'. The owner has full control over these document types."),permissions:K.object({view:K.object({users:K.array(K.number()).optional().describe("User IDs who can see and assign these document types to documents"),groups:K.array(K.number()).optional().describe("Group IDs who can see and assign these document types to documents")}).describe("Users and groups with permission to view and use these document types for categorization"),change:K.object({users:K.array(K.number()).optional().describe("User IDs who can modify document type details (name, matching rules)"),groups:K.array(K.number()).optional().describe("Group IDs who can modify document type details")}).describe("Users and groups with permission to edit these document type settings")}).optional().describe("Permission settings when operation is 'set_permissions'. Defines who can view/assign and modify these document types."),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them entirely (false). Default is false.")}},async({document_type_ids:v,operation:$,...u},c)=>{let l=await n.bulkEditObjects(v,"document_types",$,$==="set_permissions"?u:{});return ji(l)})}function fB(i,n){i.registerTool("list_tags",{description:"Retrieve all available tags for labeling and organizing documents. Returns tag names, colors, and matching rules for automatic assignment."},async(v)=>{return ji(await n.getTags())}),i.registerTool("create_tag",{description:"Create a new tag for labeling and organizing documents. Tags can have colors for visual identification and automatic matching rules for smart assignment.",inputSchema:{name:K.string().describe("Tag name for labeling and organizing documents (e.g., 'important', 'taxes', 'receipts'). Must be unique and descriptive."),color:K.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Hex color code for visual identification (e.g., '#FF0000' for red, '#00FF00' for green). If not provided, Paperless assigns a random color."),match:K.string().optional().describe("Text pattern to automatically assign this tag to matching documents. Use keywords, phrases, or regular expressions depending on matching_algorithm."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("How to match text patterns: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic. Default is 0."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive. Default is true."),is_inbox_tag:K.boolean().optional().describe("Whether this is an inbox tag. Documents with inbox tags appear in the inbox."),parent:K.number().nullable().optional().describe("ID of the parent tag for hierarchical tag organization. Null for top-level tags.")}},async(v,$)=>{return ji(await n.createTag(v))}),i.registerTool("update_tag",{description:"Modify an existing tag's name, color, or automatic matching rules. Useful for refining tag organization and improving automatic document classification.",inputSchema:{id:K.number().describe("ID of the tag to update. Use list_tags to find existing tag IDs."),name:K.string().optional().describe("New tag name. Must be unique among all tags."),color:K.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("New hex color code for visual identification (e.g., '#FF0000' for red). Leave empty to keep current color."),match:K.string().optional().describe("Text pattern for automatic tag assignment. Empty string removes auto-matching. Use keywords, phrases, or regex depending on matching_algorithm."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("Algorithm for pattern matching: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive."),is_inbox_tag:K.boolean().optional().describe("Whether this is an inbox tag."),parent:K.number().nullable().optional().describe("ID of the parent tag. Null for top-level tags.")}},async({id:v,...$},u)=>{return ji(await n.updateTag(v,$))}),i.registerTool("delete_tag",{description:"Permanently delete a tag from the system. This removes the tag from all documents that currently use it. Use with caution as this action cannot be undone.",inputSchema:{id:K.number().describe("ID of the tag to permanently delete. This will remove the tag from all documents that currently use it. Use list_tags to find tag IDs.")}},async({id:v},$)=>{return ji(await n.deleteTag(v))}),i.registerTool("bulk_edit_tags",{description:"Perform bulk operations on multiple tags: set permissions to control access or permanently delete multiple tags at once. Efficient for managing large tag collections.",inputSchema:{tag_ids:K.array(K.number()).describe("Array of tag IDs to perform bulk operations on. Use list_tags to get valid tag IDs."),operation:K.enum(["set_permissions","delete"]).describe("Bulk operation: 'set_permissions' to control who can use these tags, 'delete' to permanently remove all specified tags from the system."),owner:K.number().optional().describe("User ID to set as owner when operation is 'set_permissions'. Owner has full control over the tags."),permissions:K.object({view:K.object({users:K.array(K.number()).optional().describe("User IDs who can see and use these tags"),groups:K.array(K.number()).optional().describe("Group IDs who can see and use these tags")}).describe("Users and groups with view/use permissions for these tags"),change:K.object({users:K.array(K.number()).optional().describe("User IDs who can modify these tags (name, color, matching rules)"),groups:K.array(K.number()).optional().describe("Group IDs who can modify these tags")}).describe("Users and groups with edit permissions for these tags")}).optional().describe("Permission settings when operation is 'set_permissions'. Defines who can view/use and modify these tags."),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them entirely (false). Default is false.")}},async({tag_ids:v,operation:$,...u},c)=>{let l=await n.bulkEditObjects(v,"tags",$,$==="set_permissions"?u:{});return ji(l)})}var Gd=3000,Hd="paperless-ngx";function Fd(i,n){return{jsonrpc:"2.0",error:{code:i,message:n}}}function TB(i,n,v,$){i.writeHead(n,{"Content-Type":"application/json"}),i.end(JSON.stringify(Fd(v,$)))}function Vd(){console.error(`Usage: paperless-mcp [baseUrl] [token] [--http] [--port <1-65535>]
|
|
350
|
+
`,i.enqueue(n.encode(u)),!0}catch(u){return this.onerror?.(u),!1}}handleUnsupportedRequest(){return this.onerror?.(Error("Method not allowed.")),new Response(JSON.stringify({jsonrpc:"2.0",error:{code:-32000,message:"Method not allowed."},id:null}),{status:405,headers:{Allow:"GET, POST, DELETE","Content-Type":"application/json"}})}async handlePostRequest(i,n){try{let v=i.headers.get("accept");if(!v?.includes("application/json")||!v.includes("text/event-stream"))return this.onerror?.(Error("Not Acceptable: Client must accept both application/json and text/event-stream")),this.createJsonErrorResponse(406,-32000,"Not Acceptable: Client must accept both application/json and text/event-stream");let $=i.headers.get("content-type");if(!$||!$.includes("application/json"))return this.onerror?.(Error("Unsupported Media Type: Content-Type must be application/json")),this.createJsonErrorResponse(415,-32000,"Unsupported Media Type: Content-Type must be application/json");let u={headers:Object.fromEntries(i.headers.entries()),url:new URL(i.url)},c;if(n?.parsedBody!==void 0)c=n.parsedBody;else try{c=await i.json()}catch{return this.onerror?.(Error("Parse error: Invalid JSON")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON")}let l;try{if(Array.isArray(c))l=c.map((Q)=>G0.parse(Q));else l=[G0.parse(c)]}catch{return this.onerror?.(Error("Parse error: Invalid JSON-RPC message")),this.createJsonErrorResponse(400,-32700,"Parse error: Invalid JSON-RPC message")}let b=l.some(pz);if(b){if(this._initialized&&this.sessionId!==void 0)return this.onerror?.(Error("Invalid Request: Server already initialized")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Server already initialized");if(l.length>1)return this.onerror?.(Error("Invalid Request: Only one initialization request is allowed")),this.createJsonErrorResponse(400,-32600,"Invalid Request: Only one initialization request is allowed");if(this.sessionId=this.sessionIdGenerator?.(),this._initialized=!0,this.sessionId&&this._onsessioninitialized)await Promise.resolve(this._onsessioninitialized(this.sessionId))}if(!b){let Q=this.validateSession(i);if(Q)return Q;let F=this.validateProtocolVersion(i);if(F)return F}if(!l.some(ev)){for(let Q of l)this.onmessage?.(Q,{authInfo:n?.authInfo,requestInfo:u});return new Response(null,{status:202})}let x=crypto.randomUUID(),z=l.find((Q)=>pz(Q)),U=z?z.params.protocolVersion:i.headers.get("mcp-protocol-version")??mF;if(this._enableJsonResponse)return new Promise((Q)=>{this._streamMapping.set(x,{resolveJson:Q,cleanup:()=>{this._streamMapping.delete(x)}});for(let F of l)if(ev(F))this._requestToStreamMapping.set(F.id,x);for(let F of l)this.onmessage?.(F,{authInfo:n?.authInfo,requestInfo:u})});let J=new TextEncoder,W,Y=new ReadableStream({start:(Q)=>{W=Q},cancel:()=>{this._streamMapping.delete(x)}}),X={"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"};if(this.sessionId!==void 0)X["mcp-session-id"]=this.sessionId;for(let Q of l)if(ev(Q))this._streamMapping.set(x,{controller:W,encoder:J,cleanup:()=>{this._streamMapping.delete(x);try{W.close()}catch{}}}),this._requestToStreamMapping.set(Q.id,x);await this.writePrimingEvent(W,J,x,U);for(let Q of l){let F,M;if(ev(Q)&&this._eventStore&&U>="2025-11-25")F=()=>{this.closeSSEStream(Q.id)},M=()=>{this.closeStandaloneSSEStream()};this.onmessage?.(Q,{authInfo:n?.authInfo,requestInfo:u,closeSSEStream:F,closeStandaloneSSEStream:M})}return new Response(Y,{status:200,headers:X})}catch(v){return this.onerror?.(v),this.createJsonErrorResponse(400,-32700,"Parse error",{data:String(v)})}}async handleDeleteRequest(i){let n=this.validateSession(i);if(n)return n;let v=this.validateProtocolVersion(i);if(v)return v;return await Promise.resolve(this._onsessionclosed?.(this.sessionId)),await this.close(),new Response(null,{status:200})}validateSession(i){if(this.sessionIdGenerator===void 0)return;if(!this._initialized)return this.onerror?.(Error("Bad Request: Server not initialized")),this.createJsonErrorResponse(400,-32000,"Bad Request: Server not initialized");let n=i.headers.get("mcp-session-id");if(!n)return this.onerror?.(Error("Bad Request: Mcp-Session-Id header is required")),this.createJsonErrorResponse(400,-32000,"Bad Request: Mcp-Session-Id header is required");if(n!==this.sessionId)return this.onerror?.(Error("Session not found")),this.createJsonErrorResponse(404,-32001,"Session not found");return}validateProtocolVersion(i){let n=i.headers.get("mcp-protocol-version");if(n!==null&&!$6.includes(n))return this.onerror?.(Error(`Bad Request: Unsupported protocol version: ${n} (supported versions: ${$6.join(", ")})`)),this.createJsonErrorResponse(400,-32000,`Bad Request: Unsupported protocol version: ${n} (supported versions: ${$6.join(", ")})`);return}async close(){this._streamMapping.forEach(({cleanup:i})=>{i()}),this._streamMapping.clear(),this._requestResponseMap.clear(),this.onclose?.()}closeSSEStream(i){let n=this._requestToStreamMapping.get(i);if(!n)return;let v=this._streamMapping.get(n);if(v)v.cleanup()}closeStandaloneSSEStream(){let i=this._streamMapping.get(this._standaloneSseStreamId);if(i)i.cleanup()}async send(i,n){let v=n?.relatedRequestId;if(Kv(i)||c6(i))v=i.id;if(v===void 0){if(Kv(i)||c6(i))throw Error("Cannot send a response on a standalone SSE stream unless resuming a previous client request");let c;if(this._eventStore)c=await this._eventStore.storeEvent(this._standaloneSseStreamId,i);let l=this._streamMapping.get(this._standaloneSseStreamId);if(l===void 0)return;if(l.controller&&l.encoder)this.writeSSEEvent(l.controller,l.encoder,i,c);return}let $=this._requestToStreamMapping.get(v);if(!$)throw Error(`No connection established for request ID: ${String(v)}`);let u=this._streamMapping.get($);if(!this._enableJsonResponse&&u?.controller&&u?.encoder){let c;if(this._eventStore)c=await this._eventStore.storeEvent($,i);this.writeSSEEvent(u.controller,u.encoder,i,c)}if(Kv(i)||c6(i)){this._requestResponseMap.set(v,i);let c=Array.from(this._requestToStreamMapping.entries()).filter(([b,w])=>w===$).map(([b])=>b);if(c.every((b)=>this._requestResponseMap.has(b))){if(!u)throw Error(`No connection established for request ID: ${String(v)}`);if(this._enableJsonResponse&&u.resolveJson){let b={"Content-Type":"application/json"};if(this.sessionId!==void 0)b["mcp-session-id"]=this.sessionId;let w=c.map((x)=>this._requestResponseMap.get(x));if(w.length===1)u.resolveJson(new Response(JSON.stringify(w[0]),{status:200,headers:b}));else u.resolveJson(new Response(JSON.stringify(w),{status:200,headers:b}))}else u.cleanup();for(let b of c)this._requestResponseMap.delete(b),this._requestToStreamMapping.delete(b)}}}}class MJ{constructor(i={}){this._requestContext=new WeakMap,this._webStandardTransport=new oJ(i),this._requestListener=AJ(async(n)=>{let v=this._requestContext.get(n);return this._webStandardTransport.handleRequest(n,{authInfo:v?.authInfo,parsedBody:v?.parsedBody})},{overrideGlobalObjects:!1})}get sessionId(){return this._webStandardTransport.sessionId}set onclose(i){this._webStandardTransport.onclose=i}get onclose(){return this._webStandardTransport.onclose}set onerror(i){this._webStandardTransport.onerror=i}get onerror(){return this._webStandardTransport.onerror}set onmessage(i){this._webStandardTransport.onmessage=i}get onmessage(){return this._webStandardTransport.onmessage}async start(){return this._webStandardTransport.start()}async close(){return this._webStandardTransport.close()}async send(i,n){return this._webStandardTransport.send(i,n)}async handleRequest(i,n,v){let $=i.auth;await AJ(async(c)=>{return this._webStandardTransport.handleRequest(c,{authInfo:$,parsedBody:v})},{overrideGlobalObjects:!1})(i,n)}closeSSEStream(i){this._webStandardTransport.closeSSEStream(i)}closeStandaloneSSEStream(){this._webStandardTransport.closeStandaloneSSEStream()}}var EB={name:"@kjanat/paperless-mcp",version:"2.1.2",description:"MCP server for interacting with Paperless-ngx document management system.",keywords:["paperless-ngx","paperless","ai","mcp","model-context-protocol","document-management"],repository:{type:"git",url:"git+https://github.com/kjanat/paperless-mcp.git"},license:"MIT",author:"Kaj Kowalski",type:"module",imports:{"#*":"./src/*.ts"},bin:{"paperless-mcp":"dist/index.js"},files:["dist","skills"],scripts:{bd:"bun build src/index.ts --minify --outdir=dist --target=node --banner='#!/usr/bin/env node'",meta:"bash scripts/meta.sh",fmt:"dprint fmt","fmt:check":"dprint check",mcp:"bun src/index.ts",inspect:"bunx @modelcontextprotocol/inspector run mcp",prepack:"bun bd",start:"run mcp",typecheck:"tsgo --noEmit"},devDependencies:{"@modelcontextprotocol/sdk":"^1.29.0","@types/bun":"^1.3.14","@types/express":"^5.0.6","@typescript/native-preview":"^7.0.0-dev.20260606.1",dprint:"0.51.1",express:"^5.2.1","runner-run":"^0.12.1",typescript:"^6.0.3",zod:"^4.4.3"},packageManager:"bun@1.3.14",engines:{node:">=22"},publishConfig:{access:"public",tag:"dev"},volta:{node:"26.3.0",npm:"11.16.0"}};class jJ{baseUrl;token;constructor(i,n){this.baseUrl=i;this.token=n}async request(i,n={}){let v=`${this.baseUrl}/api${i}`,$={Authorization:`Token ${this.token}`,Accept:"application/json; version=6","Content-Type":"application/json","Accept-Language":"en-US,en;q=0.9"},u=await fetch(v,{...n,headers:{...$,...n.headers}});if(!u.ok){let c=await u.json().catch(()=>null);throw Error(`HTTP ${u.status} from ${i}: ${JSON.stringify(c)}`)}if(u.status===204||u.status===205)return;return u.json()}async bulkEditDocuments(i,n,v={}){return this.request("/documents/bulk_edit/",{method:"POST",body:JSON.stringify({documents:i,method:n,...Xd(v)})})}async postDocument(i,n={}){let v=new FormData;if(v.append("document",i),n.title!=null)v.append("title",n.title);if(n.created!=null)v.append("created",n.created);if(n.correspondent!=null)v.append("correspondent",String(n.correspondent));if(n.document_type!=null)v.append("document_type",String(n.document_type));if(n.storage_path!=null)v.append("storage_path",String(n.storage_path));if(n.tags!=null)for(let u of n.tags)v.append("tags",String(u));if(n.archive_serial_number!=null)v.append("archive_serial_number",String(n.archive_serial_number));if(n.custom_fields!=null)for(let u of n.custom_fields)v.append("custom_fields",String(u));let $=await fetch(`${this.baseUrl}/api/documents/post_document/`,{method:"POST",headers:{Authorization:`Token ${this.token}`,Accept:"application/json; version=6"},body:v});if(!$.ok){let u=await $.json().catch(()=>null);throw Error(`HTTP ${$.status} from /documents/post_document/: ${JSON.stringify(u)}`)}return $.json()}async getDocuments(i=""){return this.request(`/documents/${i}`)}async getDocument(i){return this.request(`/documents/${i}/`)}async searchDocuments(i,n,v){let $=new URLSearchParams;if($.set("query",i),n!=null)$.set("page",n.toString());if(v!=null)$.set("page_size",v.toString());let u=await this.request(`/documents/?${$.toString()}`);return{...u,results:u.results.map((c)=>{let{content:l,download_url:b,thumbnail_url:w,...x}=c;return x})}}async downloadDocument(i,n=!1){let v=n?"?original=true":"",$=`/documents/${i}/download/`,u=await fetch(`${this.baseUrl}/api${$}${v}`,{headers:{Authorization:`Token ${this.token}`,Accept:"application/json; version=6"}});if(!u.ok){let c=await u.text().catch(()=>"");throw Error(`HTTP ${u.status} from ${$}: ${c}`)}return u}async getTags(){return this.getAllPages("/tags/")}async createTag(i){return this.request("/tags/",{method:"POST",body:JSON.stringify(i)})}async updateTag(i,n){return this.request(`/tags/${i}/`,{method:"PATCH",body:JSON.stringify(n)})}async deleteTag(i){await this.request(`/tags/${i}/`,{method:"DELETE"})}async getCorrespondents(){return this.getAllPages("/correspondents/")}async createCorrespondent(i){return this.request("/correspondents/",{method:"POST",body:JSON.stringify(i)})}async getDocumentTypes(){return this.getAllPages("/document_types/")}async createDocumentType(i){return this.request("/document_types/",{method:"POST",body:JSON.stringify(i)})}async bulkEditObjects(i,n,v,$={}){return this.request("/bulk_edit_objects/",{method:"POST",body:JSON.stringify({objects:i,object_type:n,operation:v,...$})})}async getAllPages(i){let v=[],$=[],u=0,c=1;while(!0){let l=i.includes("?")?"&":"?",b=await this.request(`${i}${l}page=${c}&page_size=100`);if(u=b.count,c===1)$=b.all;if(v.push(...b.results),b.next==null||v.length>=b.count)return{count:u,next:null,previous:null,all:$,results:v};c+=1}}}function Xd(i){let n={};for(let[v,$]of Object.entries(i))if($!==void 0)n[v]=$;return n}function ji(i){return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}function kB(i,n){i.registerTool("list_correspondents",{description:"Retrieve all available correspondents (people, companies, organizations that send/receive documents). Returns names and automatic matching patterns for document assignment."},async(v)=>{return ji(await n.getCorrespondents())}),i.registerTool("create_correspondent",{description:"Create a new correspondent (person, company, or organization) for tracking document senders and receivers. Can include automatic matching patterns for smart assignment to incoming documents.",inputSchema:{name:K.string().describe("Name of the correspondent (person, company, or organization that sends/receives documents). Examples: 'Bank of America', 'John Smith', 'Electric Company'."),match:K.string().optional().describe("Text pattern to automatically assign this correspondent to matching documents. Use names, email addresses, or keywords that appear in documents from this correspondent."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("How to match text patterns: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic. Default is 0."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive. Default is true.")}},async(v,$)=>{return ji(await n.createCorrespondent(v))}),i.registerTool("bulk_edit_correspondents",{description:"Perform bulk operations on multiple correspondents: set permissions to control who can assign them to documents, or permanently delete multiple correspondents. Use with caution as deletion affects all associated documents.",inputSchema:{correspondent_ids:K.array(K.number()).describe("Array of correspondent IDs to perform bulk operations on. Use list_correspondents to get valid correspondent IDs."),operation:K.enum(["set_permissions","delete"]).describe("Bulk operation: 'set_permissions' to control who can assign these correspondents to documents, 'delete' to permanently remove correspondents from the system. Warning: Deleting correspondents will remove them from all associated documents."),owner:K.number().optional().describe("User ID to set as owner when operation is 'set_permissions'. The owner has full control over these correspondents."),permissions:K.object({view:K.object({users:K.array(K.number()).optional().describe("User IDs who can see and assign these correspondents to documents"),groups:K.array(K.number()).optional().describe("Group IDs who can see and assign these correspondents to documents")}).describe("Users and groups with permission to view and use these correspondents"),change:K.object({users:K.array(K.number()).optional().describe("User IDs who can modify correspondent details (name, matching rules)"),groups:K.array(K.number()).optional().describe("Group IDs who can modify correspondent details")}).describe("Users and groups with permission to edit these correspondent settings")}).optional().describe("Permission settings when operation is 'set_permissions'. Defines who can view/assign and modify these correspondents."),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them entirely (false). Default is false.")}},async({correspondent_ids:v,operation:$,...u},c)=>{let l=await n.bulkEditObjects(v,"correspondents",$,$==="set_permissions"?u:{});return ji(l)})}function ZB(i,n){i.registerTool("bulk_edit_documents",{description:"Perform bulk operations on multiple documents simultaneously: set correspondent/type/tags, delete, reprocess, merge, split, rotate, or manage permissions. Efficient for managing large document collections.",inputSchema:{documents:K.array(K.number()).describe("Array of document IDs to perform bulk operations on. Get document IDs from search_documents first."),method:K.enum(["set_correspondent","set_document_type","set_storage_path","add_tag","remove_tag","modify_tags","modify_custom_fields","delete","reprocess","set_permissions","merge","split","rotate","delete_pages","edit_pdf","remove_password"]).describe("The bulk operation to perform."),correspondent:K.number().optional().describe("ID of correspondent to assign when method is 'set_correspondent'. Use list_correspondents to get valid IDs."),document_type:K.number().optional().describe("ID of document type to assign when method is 'set_document_type'. Use list_document_types to get valid IDs."),storage_path:K.number().optional().describe("ID of storage path to assign when method is 'set_storage_path'. Storage paths organize documents in folder hierarchies."),tag:K.number().optional().describe("Single tag ID to add or remove when method is 'add_tag' or 'remove_tag'. Use list_tags to get valid IDs."),add_tags:K.array(K.number()).optional().describe("Array of tag IDs to add when method is 'modify_tags'. Use list_tags to get valid IDs."),remove_tags:K.array(K.number()).optional().describe("Array of tag IDs to remove when method is 'modify_tags'. Use list_tags to get valid IDs."),permissions:K.object({owner:K.number().nullable().optional().describe("User ID to set as document owner, or null to remove ownership"),set_permissions:K.object({view:K.object({users:K.array(K.number()).describe("User IDs granted view permission"),groups:K.array(K.number()).describe("Group IDs granted view permission")}).describe("Users and groups who can view these documents"),change:K.object({users:K.array(K.number()).describe("User IDs granted edit permission"),groups:K.array(K.number()).describe("Group IDs granted edit permission")}).describe("Users and groups who can edit these documents")}).optional().describe("Specific permission settings for users and groups"),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them (false)")}).optional().describe("Permission settings when method is 'set_permissions'. Controls who can view and edit the documents."),metadata_document_id:K.number().optional().describe("Source document ID when merging documents. The metadata from this document will be preserved."),delete_originals:K.boolean().optional().describe("Whether to delete original documents after merge/split operations. Use with caution."),pages:K.string().optional().describe("Page specification for delete_pages method. Format: '1,3,5-7' to delete pages 1, 3, and 5 through 7."),degrees:K.number().optional().describe("Rotation angle in degrees when method is 'rotate'. Use 90, 180, or 270 for standard rotations.")}},async({documents:v,method:$,...u},c)=>{return ji(await n.bulkEditDocuments(v,$,Qd($,u)))}),i.registerTool("post_document",{description:"Upload a new document to Paperless-ngx with metadata. Supports PDF, images (PNG/JPG/TIFF), and text files. Automatically processes for OCR and indexing.",inputSchema:{file:K.string().describe("Base64 encoded file content. Convert your file to base64 before uploading. Supports PDF, images (PNG, JPG, TIFF), and text files."),filename:K.string().describe("Original filename with extension (e.g., 'invoice.pdf', 'receipt.png'). This helps Paperless determine file type and initial document title."),title:K.string().optional().describe("Custom document title. If not provided, Paperless will extract title from filename or document content."),created:K.string().optional().describe("Document creation date in ISO format (YYYY-MM-DD or YYYY-MM-DDTHH:mm:ss). If not provided, uses current date."),correspondent:K.number().optional().describe("ID of the correspondent (sender/receiver) for this document. Use list_correspondents to find or create_correspondent to add new ones."),document_type:K.number().optional().describe("ID of document type for categorization (e.g., Invoice, Receipt, Letter). Use list_document_types to find or create_document_type to add new ones."),storage_path:K.number().optional().describe("ID of storage path to organize document location in folder hierarchy. Leave empty for default storage."),tags:K.array(K.number()).optional().describe("Array of tag IDs to label this document. Use list_tags to find existing tags or create_tag to add new ones."),archive_serial_number:K.number().int().min(0).optional().describe("Archive serial number for document organization and reference. Useful for maintaining external filing systems."),custom_fields:K.array(K.number()).optional().describe("Array of custom field IDs to associate with this document. Custom fields store additional metadata.")}},async(v,$)=>{let u=Buffer.from(v.file,"base64"),c=new Blob([u]),l=new File([c],v.filename),b={title:v.title,created:v.created,correspondent:v.correspondent,document_type:v.document_type,storage_path:v.storage_path,tags:v.tags,archive_serial_number:v.archive_serial_number,custom_fields:v.custom_fields};return ji(await n.postDocument(l,b))}),i.registerTool("get_document",{description:"Get complete details for a specific document including full metadata, content preview, tags, correspondent, and document type information.",inputSchema:{id:K.number().describe("Unique document ID. Get this from search_documents results. Returns full document metadata, content preview, and associated tags/correspondent/type.")}},async({id:v},$)=>{return ji(await n.getDocument(v))}),i.registerTool("search_documents",{description:"Search through documents using full-text search across content, titles, tags, and metadata. Returns document metadata WITHOUT the full OCR content field to prevent token overflow. Use get_document to retrieve full details for specific documents of interest. Supports Paperless-ngx advanced query syntax.",inputSchema:{query:K.string().describe("Search query using Paperless-ngx syntax. By default, matches documents containing ALL words. Advanced syntax: Field searches: 'tag:unpaid', 'type:invoice', 'correspondent:university'. Logical operators: 'term1 AND (term2 OR term3)'. Date ranges: 'created:[2020 to 2024]', 'added:yesterday', 'modified:today'. Wildcards: 'prod*name'. Combine multiple criteria as needed. Search looks through document content, title, correspondent, type, and tags."),page:K.number().optional().describe("Page number for pagination (starts at 1). Use to browse through large result sets without hitting token limits."),page_size:K.number().optional().describe("Number of documents per page (default 25, max 100). Smaller page sizes help avoid token limits when many documents match.")}},async({query:v,page:$,page_size:u},c)=>{return ji(await n.searchDocuments(v,$,u))}),i.registerTool("download_document",{description:"Download a document file as base64-encoded data. Choose between original uploaded file or processed/archived version with OCR improvements.",inputSchema:{id:K.number().describe("Document ID to download. Get this from search_documents or get_document results."),original:K.boolean().optional().describe("Whether to download the original uploaded file (true) or the processed/archived version (false, default). Original files preserve exact formatting but may not include OCR improvements.")}},async({id:v,original:$},u)=>{let c=await n.downloadDocument(v,$),l=c.headers.get("content-disposition")?.split("filename=")[1]?.replace(/"/g,"")??`document-${v}`;return ji({blob:Buffer.from(await c.arrayBuffer()).toString("base64"),filename:l})})}function Qd(i,n){switch(i){case"set_correspondent":return Av(n,["correspondent"]);case"set_document_type":return Av(n,["document_type"]);case"set_storage_path":return Av(n,["storage_path"]);case"add_tag":case"remove_tag":return Av(n,["tag"]);case"modify_tags":return Av(n,["add_tags","remove_tags"]);case"set_permissions":return Av(n,["permissions"]);case"merge":case"split":return Av(n,["metadata_document_id","delete_originals"]);case"rotate":return Av(n,["degrees"]);case"delete_pages":return Av(n,["pages"]);default:return{}}}function Av(i,n){let v={};for(let $ of n){let u=i[$];if(u!==void 0)v[$]=u}return v}function aB(i,n){i.registerTool("list_document_types",{description:"Retrieve all available document types for categorizing documents by purpose or format (Invoice, Receipt, Contract, etc.). Returns names and automatic matching rules."},async(v)=>{return ji(await n.getDocumentTypes())}),i.registerTool("create_document_type",{description:"Create a new document type for categorizing documents by their purpose or format (e.g., Invoice, Receipt, Contract). Can include automatic matching rules for smart classification.",inputSchema:{name:K.string().describe("Name of the document type for categorizing documents by their purpose or format. Examples: 'Invoice', 'Receipt', 'Contract', 'Letter', 'Bank Statement', 'Tax Document'."),match:K.string().optional().describe("Text pattern to automatically assign this document type to matching documents. Use keywords that commonly appear in this type of document (e.g., 'invoice', 'receipt', 'contract terms')."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("How to match text patterns: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic. Default is 0."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive. Default is true.")}},async(v,$)=>{return ji(await n.createDocumentType(v))}),i.registerTool("bulk_edit_document_types",{description:"Perform bulk operations on multiple document types: set permissions to control who can assign them to documents, or permanently delete multiple types. Use with caution as deletion affects all associated documents.",inputSchema:{document_type_ids:K.array(K.number()).describe("Array of document type IDs to perform bulk operations on. Use list_document_types to get valid document type IDs."),operation:K.enum(["set_permissions","delete"]).describe("Bulk operation: 'set_permissions' to control who can assign these document types to documents, 'delete' to permanently remove document types from the system. Warning: Deleting document types will remove the classification from all associated documents."),owner:K.number().optional().describe("User ID to set as owner when operation is 'set_permissions'. The owner has full control over these document types."),permissions:K.object({view:K.object({users:K.array(K.number()).optional().describe("User IDs who can see and assign these document types to documents"),groups:K.array(K.number()).optional().describe("Group IDs who can see and assign these document types to documents")}).describe("Users and groups with permission to view and use these document types for categorization"),change:K.object({users:K.array(K.number()).optional().describe("User IDs who can modify document type details (name, matching rules)"),groups:K.array(K.number()).optional().describe("Group IDs who can modify document type details")}).describe("Users and groups with permission to edit these document type settings")}).optional().describe("Permission settings when operation is 'set_permissions'. Defines who can view/assign and modify these document types."),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them entirely (false). Default is false.")}},async({document_type_ids:v,operation:$,...u},c)=>{let l=await n.bulkEditObjects(v,"document_types",$,$==="set_permissions"?u:{});return ji(l)})}function fB(i,n){i.registerTool("list_tags",{description:"Retrieve all available tags for labeling and organizing documents. Returns tag names, colors, and matching rules for automatic assignment."},async(v)=>{return ji(await n.getTags())}),i.registerTool("create_tag",{description:"Create a new tag for labeling and organizing documents. Tags can have colors for visual identification and automatic matching rules for smart assignment.",inputSchema:{name:K.string().describe("Tag name for labeling and organizing documents (e.g., 'important', 'taxes', 'receipts'). Must be unique and descriptive."),color:K.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Hex color code for visual identification (e.g., '#FF0000' for red, '#00FF00' for green). If not provided, Paperless assigns a random color."),match:K.string().optional().describe("Text pattern to automatically assign this tag to matching documents. Use keywords, phrases, or regular expressions depending on matching_algorithm."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("How to match text patterns: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic. Default is 0."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive. Default is true."),is_inbox_tag:K.boolean().optional().describe("Whether this is an inbox tag. Documents with inbox tags appear in the inbox."),parent:K.number().nullable().optional().describe("ID of the parent tag for hierarchical tag organization. Null for top-level tags.")}},async(v,$)=>{return ji(await n.createTag(v))}),i.registerTool("update_tag",{description:"Modify an existing tag's name, color, or automatic matching rules. Useful for refining tag organization and improving automatic document classification.",inputSchema:{id:K.number().describe("ID of the tag to update. Use list_tags to find existing tag IDs."),name:K.string().optional().describe("New tag name. Must be unique among all tags."),color:K.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("New hex color code for visual identification (e.g., '#FF0000' for red). Leave empty to keep current color."),match:K.string().optional().describe("Text pattern for automatic tag assignment. Empty string removes auto-matching. Use keywords, phrases, or regex depending on matching_algorithm."),matching_algorithm:K.number().int().min(0).max(6).optional().describe("Algorithm for pattern matching: 0=none, 1=any word, 2=all words, 3=exact match, 4=regular expression, 5=fuzzy word, 6=automatic."),is_insensitive:K.boolean().optional().describe("Whether matching is case-insensitive."),is_inbox_tag:K.boolean().optional().describe("Whether this is an inbox tag."),parent:K.number().nullable().optional().describe("ID of the parent tag. Null for top-level tags.")}},async({id:v,...$},u)=>{return ji(await n.updateTag(v,$))}),i.registerTool("delete_tag",{description:"Permanently delete a tag from the system. This removes the tag from all documents that currently use it. Use with caution as this action cannot be undone.",inputSchema:{id:K.number().describe("ID of the tag to permanently delete. This will remove the tag from all documents that currently use it. Use list_tags to find tag IDs.")}},async({id:v},$)=>{return ji(await n.deleteTag(v))}),i.registerTool("bulk_edit_tags",{description:"Perform bulk operations on multiple tags: set permissions to control access or permanently delete multiple tags at once. Efficient for managing large tag collections.",inputSchema:{tag_ids:K.array(K.number()).describe("Array of tag IDs to perform bulk operations on. Use list_tags to get valid tag IDs."),operation:K.enum(["set_permissions","delete"]).describe("Bulk operation: 'set_permissions' to control who can use these tags, 'delete' to permanently remove all specified tags from the system."),owner:K.number().optional().describe("User ID to set as owner when operation is 'set_permissions'. Owner has full control over the tags."),permissions:K.object({view:K.object({users:K.array(K.number()).optional().describe("User IDs who can see and use these tags"),groups:K.array(K.number()).optional().describe("Group IDs who can see and use these tags")}).describe("Users and groups with view/use permissions for these tags"),change:K.object({users:K.array(K.number()).optional().describe("User IDs who can modify these tags (name, color, matching rules)"),groups:K.array(K.number()).optional().describe("Group IDs who can modify these tags")}).describe("Users and groups with edit permissions for these tags")}).optional().describe("Permission settings when operation is 'set_permissions'. Defines who can view/use and modify these tags."),merge:K.boolean().optional().describe("Whether to merge with existing permissions (true) or replace them entirely (false). Default is false.")}},async({tag_ids:v,operation:$,...u},c)=>{let l=await n.bulkEditObjects(v,"tags",$,$==="set_permissions"?u:{});return ji(l)})}var Gd=3000,Hd="paperless-ngx";function Fd(i,n){return{jsonrpc:"2.0",error:{code:i,message:n}}}function TB(i,n,v,$){i.writeHead(n,{"Content-Type":"application/json"}),i.end(JSON.stringify(Fd(v,$)))}function Vd(){console.error(`Usage: paperless-mcp [baseUrl] [token] [--http] [--port <1-65535>]
|
|
351
351
|
Args: paperless-mcp http://localhost:8000 your-api-token
|
|
352
352
|
Env: PAPERLESS_URL + PAPERLESS_API_KEY (or legacy API_KEY)`)}function Kd(i){let n=Number(i);if(!Number.isInteger(n)||n<1||n>65535)throw Error(`Invalid --port value "${i}". Expected an integer 1-65535.`);return n}function Ld(i){let{values:n,positionals:v}=Dd({args:[...i],options:{http:{type:"boolean",default:!1},port:{type:"string"}},strict:!0,allowPositionals:!0});return{useHttp:n.http,port:n.port!=null?Kd(n.port):Gd,positional:v}}function Id(){return process.env.PAPERLESS_API_KEY??process.env.API_KEY}function Nd(i){return new URL(i).href.replace(/\/+$/,"")}function mB(i){let n=new VJ({name:Hd,version:EB.version});return ZB(n,i),fB(n,i),kB(n,i),aB(n,i),n}async function Bd(i,n,v){let $=new MJ({sessionIdGenerator:void 0});n.on("close",()=>{$.close()});try{await mB(v).connect($),await $.handleRequest(i,n,i.body)}catch(u){if(console.error("Error handling MCP request:",u),!n.headersSent)TB(n,500,-32603,"Internal server error")}}async function Od(){let i=Ld(process.argv.slice(2)),n=i.positional[0]??process.env.PAPERLESS_URL,v=i.positional[1]??Id();if(!n||!v){Vd(),process.exitCode=1;return}let $=Nd(n),u=new jJ($,v);if(i.useHttp){let b=IG({host:"0.0.0.0"});b.post("/mcp",(w,x)=>{Bd(w,x,u)}),b.all("/mcp",(w,x)=>{TB(x,405,-32000,"Method not allowed.")}),b.listen(i.port,()=>{console.log(`MCP Stateless Streamable HTTP Server listening on port ${i.port}`)});return}let c=mB(u),l=new LJ;await c.connect(l)}Od().catch((i)=>{if(i instanceof Error)console.error(i.stack??i.message);else console.error(String(i));process.exitCode=1});
|