@orellbuehler/paperless-mcp 1.0.1 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -96,7 +96,7 @@ This fetches `GET /api/schema/` and overwrites `paperless-openapi.yaml`. Run it
96
96
  | Search | `search_documents`, `search_autocomplete` |
97
97
  | Documents | `list_documents`, `get_document`, `get_documents`, `download_document`, `update_document`, `delete_document`, `upload_document` |
98
98
  | Document details | `get_document_metadata`, `get_document_suggestions`, `get_document_notes`, `add_document_note`, `delete_document_note` |
99
- | Bulk operations | `bulk_edit_documents`, `get_next_asn` |
99
+ | Bulk operations | `bulk_edit_documents`, `bulk_set_object_permissions`, `get_next_asn` |
100
100
  | Correspondents | `list_correspondents`, `get_correspondent`, `create_correspondent`, `update_correspondent`, `delete_correspondent` |
101
101
  | Document types | `list_document_types`, `get_document_type`, `create_document_type`, `update_document_type`, `delete_document_type` |
102
102
  | Tags | `list_tags`, `get_tag`, `create_tag`, `update_tag`, `delete_tag` |
@@ -111,6 +111,8 @@ This fetches `GET /api/schema/` and overwrites `paperless-openapi.yaml`. Run it
111
111
  > **Note:** `list_documents` and `search_documents` return document metadata only (no OCR text) to keep responses small. Use `get_document` (single) or `get_documents` (batch) to retrieve full content.
112
112
  >
113
113
  > Saved views, users/groups, and workflows support read + create + update only — no delete tools (use the Paperless web UI to delete). User management covers accounts and group membership; it does not set per-document permissions. Notes support add and delete only (no edit), so there is no note-editing tool.
114
+ >
115
+ > The `update_*` tools for tags, correspondents, document types, storage paths, saved views, and custom fields accept `owner` and `set_permissions` (`{ view, change }` → `{ users, groups }`) to share objects. `bulk_set_object_permissions` sets owner/permissions on many tags, correspondents, document types, or storage paths in one call (saved views and custom fields are not supported by the bulk endpoint — share those individually).
114
116
 
115
117
  ### Extended Tools
116
118
 
@@ -6,10 +6,7 @@ export function buildQS(params) {
6
6
  if (Array.isArray(v)) {
7
7
  if (v.length === 0)
8
8
  continue;
9
- if (k === "id__in")
10
- sp.set(k, v.join(","));
11
- else
12
- v.forEach((item) => sp.append(k, String(item)));
9
+ sp.set(k, v.join(","));
13
10
  }
14
11
  else {
15
12
  sp.set(k, String(v));
@@ -1,6 +1,22 @@
1
1
  import { z } from "zod";
2
2
  import { readFile } from "node:fs/promises";
3
3
  import { buildQS, ok, err, summarizeDocs } from "../paperless/format.js";
4
+ const permissionGrant = z.object({
5
+ users: z.array(z.number()).optional().describe("User IDs"),
6
+ groups: z.array(z.number()).optional().describe("Group IDs"),
7
+ });
8
+ const permissionMap = z.object({
9
+ view: permissionGrant.optional(),
10
+ change: permissionGrant.optional(),
11
+ });
12
+ const ownerField = z
13
+ .number()
14
+ .nullable()
15
+ .optional()
16
+ .describe("Owner user ID, or null to make it unowned (visible to everyone)");
17
+ const setPermissionsField = permissionMap
18
+ .optional()
19
+ .describe("Grant view (read) and change (edit) access to users and groups");
4
20
  export function registerCoreTools(server, client) {
5
21
  // --- System ---
6
22
  server.tool("get_status", "Get Paperless-ngx server status", {}, async () => {
@@ -243,6 +259,37 @@ export function registerCoreTools(server, client) {
243
259
  return err(e);
244
260
  }
245
261
  });
262
+ server.tool("bulk_set_object_permissions", "Set owner and/or view/change permissions on many tags, correspondents, document types or storage paths at once (e.g. share a whole taxonomy with a household group). Saved views and custom fields are not supported here — update those individually with set_permissions.", {
263
+ object_type: z.enum(["tags", "correspondents", "document_types", "storage_paths"]),
264
+ objects: z.array(z.number()).describe("IDs of the objects to update"),
265
+ owner: ownerField,
266
+ permissions: setPermissionsField,
267
+ merge: z
268
+ .boolean()
269
+ .optional()
270
+ .describe("Merge with existing permissions instead of replacing them (default false)"),
271
+ }, async ({ object_type, objects, owner, permissions, merge }) => {
272
+ try {
273
+ const body = {
274
+ objects,
275
+ object_type,
276
+ operation: "set_permissions",
277
+ };
278
+ if (owner !== undefined)
279
+ body.owner = owner;
280
+ if (permissions !== undefined)
281
+ body.permissions = permissions;
282
+ if (merge !== undefined)
283
+ body.merge = merge;
284
+ return ok(await client.fetch("/api/bulk_edit_objects/", {
285
+ method: "POST",
286
+ body: JSON.stringify(body),
287
+ }));
288
+ }
289
+ catch (e) {
290
+ return err(e);
291
+ }
292
+ });
246
293
  server.tool("get_next_asn", "Get the next available archive serial number", {}, async () => {
247
294
  try {
248
295
  return ok(await client.fetch("/api/documents/next_asn/"));
@@ -301,6 +348,8 @@ export function registerCoreTools(server, client) {
301
348
  .optional()
302
349
  .describe("1=any, 2=all, 3=literal, 4=regex, 5=fuzzy, 6=auto"),
303
350
  is_insensitive: z.boolean().optional(),
351
+ owner: ownerField,
352
+ set_permissions: setPermissionsField,
304
353
  }, async ({ id, ...body }) => {
305
354
  try {
306
355
  return ok(await client.fetch(`/api/correspondents/${id}/`, {
@@ -370,6 +419,8 @@ export function registerCoreTools(server, client) {
370
419
  .optional()
371
420
  .describe("1=any, 2=all, 3=literal, 4=regex, 5=fuzzy, 6=auto"),
372
421
  is_insensitive: z.boolean().optional(),
422
+ owner: ownerField,
423
+ set_permissions: setPermissionsField,
373
424
  }, async ({ id, ...body }) => {
374
425
  try {
375
426
  return ok(await client.fetch(`/api/document_types/${id}/`, {
@@ -444,6 +495,8 @@ export function registerCoreTools(server, client) {
444
495
  .optional()
445
496
  .describe("1=any, 2=all, 3=literal, 4=regex, 5=fuzzy, 6=auto"),
446
497
  is_insensitive: z.boolean().optional(),
498
+ owner: ownerField,
499
+ set_permissions: setPermissionsField,
447
500
  }, async ({ id, ...body }) => {
448
501
  try {
449
502
  return ok(await client.fetch(`/api/tags/${id}/`, {
@@ -513,6 +566,8 @@ export function registerCoreTools(server, client) {
513
566
  sort_field: z.string().optional(),
514
567
  sort_reverse: z.boolean().optional(),
515
568
  page_size: z.number().optional(),
569
+ owner: ownerField,
570
+ set_permissions: setPermissionsField,
516
571
  }, async ({ id, ...body }) => {
517
572
  try {
518
573
  return ok(await client.fetch(`/api/saved_views/${id}/`, {
@@ -575,6 +630,8 @@ export function registerCoreTools(server, client) {
575
630
  .optional()
576
631
  .describe("1=any, 2=all, 3=literal, 4=regex, 5=fuzzy, 6=auto"),
577
632
  is_insensitive: z.boolean().optional(),
633
+ owner: ownerField,
634
+ set_permissions: setPermissionsField,
578
635
  }, async ({ id, ...body }) => {
579
636
  try {
580
637
  return ok(await client.fetch(`/api/storage_paths/${id}/`, {
@@ -640,6 +697,8 @@ export function registerCoreTools(server, client) {
640
697
  id: z.number(),
641
698
  name: z.string().optional(),
642
699
  extra_data: z.record(z.unknown()).optional(),
700
+ owner: ownerField,
701
+ set_permissions: setPermissionsField,
643
702
  }, async ({ id, ...body }) => {
644
703
  try {
645
704
  return ok(await client.fetch(`/api/custom_fields/${id}/`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orellbuehler/paperless-mcp",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Model Context Protocol server for Paperless-ngx: documents, organization, saved views, users, workflows, and optional semantic search.",
5
5
  "type": "module",
6
6
  "bin": {