bulk_edit_documents
Edit multiple documents simultaneously on Paperless-MCP by setting correspondents, document types, storage paths, tags, permissions, or applying actions like merge, split, rotate, or delete.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| add_tags | No | ||
| correspondent | No | ||
| degrees | No | ||
| delete_originals | No | ||
| document_type | No | ||
| documents | Yes | ||
| metadata_document_id | No | ||
| method | Yes | ||
| pages | No | ||
| permissions | No | ||
| remove_tags | No | ||
| storage_path | No | ||
| tag | No |
Implementation Reference
- src/tools/documents.ts:79-110 (handler)The handler function for the 'bulk_edit_documents' MCP tool. It performs input validation (e.g., confirmation for delete), transforms custom fields parameters, calls the PaperlessAPI.bulkEditDocuments method, and formats the response as MCP content.withErrorHandling(async (args, extra) => { if (!api) throw new Error("Please configure API connection first"); if (args.method === "delete" && !args.confirm) { throw new Error( "Confirmation required for destructive operation. Set confirm: true to proceed." ); } const { documents, method, add_custom_fields, ...parameters } = args; // Transform add_custom_fields into the two separate API parameters const apiParameters = { ...parameters }; if (add_custom_fields && add_custom_fields.length > 0) { apiParameters.assign_custom_fields = add_custom_fields.map( (cf) => cf.field ); apiParameters.assign_custom_fields_values = add_custom_fields; } const response = await api.bulkEditDocuments( documents, method, apiParameters ); return { content: [ { type: "text", text: JSON.stringify({ result: response.result || response }), }, ], }; })
- src/tools/documents.ts:12-78 (schema)Zod schema defining the input parameters for the 'bulk_edit_documents' tool, including required 'documents' and 'method', optional fields for various operations, and validation transforms.{ documents: z.array(z.number()), method: z.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", ]), correspondent: z.number().optional(), document_type: z.number().optional(), storage_path: z.number().optional(), tag: z.number().optional(), add_tags: z.array(z.number()).optional().transform(arrayNotEmpty), remove_tags: z.array(z.number()).optional().transform(arrayNotEmpty), add_custom_fields: z .array( z.object({ field: z.number(), value: z.union([z.string(), z.number(), z.boolean(), z.null()]), }) ) .optional() .transform(arrayNotEmpty), remove_custom_fields: z .array(z.number()) .optional() .transform(arrayNotEmpty), permissions: z .object({ owner: z.number().nullable().optional(), set_permissions: z .object({ view: z.object({ users: z.array(z.number()), groups: z.array(z.number()), }), change: z.object({ users: z.array(z.number()), groups: z.array(z.number()), }), }) .optional(), merge: z.boolean().optional(), }) .optional() .transform(objectNotEmpty), metadata_document_id: z.number().optional(), delete_originals: z.boolean().optional(), pages: z.string().optional(), degrees: z.number().optional(), confirm: z .boolean() .optional() .describe( "Must be true when method is 'delete' to confirm destructive operation" ), },
- src/tools/documents.ts:9-111 (registration)The server.tool() call that registers the 'bulk_edit_documents' tool on the MCP server, specifying name, description, input schema, and handler function.server.tool( "bulk_edit_documents", "Perform bulk operations on multiple documents. Note: 'remove_tag' removes a tag from specific documents (tag remains in system), while 'delete_tag' permanently deletes a tag from the entire system. ⚠️ WARNING: 'delete' method permanently deletes documents and requires confirmation.", { documents: z.array(z.number()), method: z.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", ]), correspondent: z.number().optional(), document_type: z.number().optional(), storage_path: z.number().optional(), tag: z.number().optional(), add_tags: z.array(z.number()).optional().transform(arrayNotEmpty), remove_tags: z.array(z.number()).optional().transform(arrayNotEmpty), add_custom_fields: z .array( z.object({ field: z.number(), value: z.union([z.string(), z.number(), z.boolean(), z.null()]), }) ) .optional() .transform(arrayNotEmpty), remove_custom_fields: z .array(z.number()) .optional() .transform(arrayNotEmpty), permissions: z .object({ owner: z.number().nullable().optional(), set_permissions: z .object({ view: z.object({ users: z.array(z.number()), groups: z.array(z.number()), }), change: z.object({ users: z.array(z.number()), groups: z.array(z.number()), }), }) .optional(), merge: z.boolean().optional(), }) .optional() .transform(objectNotEmpty), metadata_document_id: z.number().optional(), delete_originals: z.boolean().optional(), pages: z.string().optional(), degrees: z.number().optional(), confirm: z .boolean() .optional() .describe( "Must be true when method is 'delete' to confirm destructive operation" ), }, withErrorHandling(async (args, extra) => { if (!api) throw new Error("Please configure API connection first"); if (args.method === "delete" && !args.confirm) { throw new Error( "Confirmation required for destructive operation. Set confirm: true to proceed." ); } const { documents, method, add_custom_fields, ...parameters } = args; // Transform add_custom_fields into the two separate API parameters const apiParameters = { ...parameters }; if (add_custom_fields && add_custom_fields.length > 0) { apiParameters.assign_custom_fields = add_custom_fields.map( (cf) => cf.field ); apiParameters.assign_custom_fields_values = add_custom_fields; } const response = await api.bulkEditDocuments( documents, method, apiParameters ); return { content: [ { type: "text", text: JSON.stringify({ result: response.result || response }), }, ], }; }) );
- src/api/PaperlessAPI.ts:80-93 (helper)Helper method in PaperlessAPI class that sends the bulk edit request to the Paperless-ngx API endpoint '/documents/bulk_edit/'.async bulkEditDocuments( documents: number[], method: string, parameters: BulkEditParameters = {} ): Promise<BulkEditDocumentsResult> { return this.request<BulkEditDocumentsResult>("/documents/bulk_edit/", { method: "POST", body: JSON.stringify({ documents, method, parameters, }), }); }
- src/api/types.ts:148-174 (schema)TypeScript interfaces for BulkEditDocumentsResult (output) and BulkEditParameters (input parameters for the API call).export interface BulkEditDocumentsResult { result: string; } export interface BulkEditParameters { assign_custom_fields?: number[]; assign_custom_fields_values?: CustomFieldInstanceRequest[]; remove_custom_fields?: number[]; add_tags?: number[]; remove_tags?: number[]; degrees?: number; pages?: string; metadata_document_id?: number; delete_originals?: boolean; correspondent?: number; document_type?: number; storage_path?: number; tag?: number; permissions?: { owner?: number | null; set_permissions?: { view: { users: number[]; groups: number[] }; change: { users: number[]; groups: number[] }; }; merge?: boolean; }; }