Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,20 @@ bulk_edit_documents({
bulk_edit_documents({
documents: [12, 13],
method: "modify_custom_fields",
add_custom_fields: {
"2": "שנה"
}
add_custom_fields: [
{ field: 2, value: "שנה" }
],
remove_custom_fields: []
})

// Set an empty custom field value, e.g. a date field used as a pending marker
bulk_edit_documents({
documents: [14],
method: "modify_custom_fields",
add_custom_fields: [
{ field: 9, value: "" }
],
remove_custom_fields: []
})
```

Expand Down
3 changes: 1 addition & 2 deletions src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,7 @@ export interface BulkEditDocumentsResult {
}

export interface BulkEditParameters {
assign_custom_fields?: number[];
assign_custom_fields_values?: CustomFieldInstanceRequest[];
add_custom_fields?: Record<string, CustomFieldInstanceRequest["value"]>;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
remove_custom_fields?: number[];
add_tags?: number[];
remove_tags?: number[];
Expand Down
43 changes: 43 additions & 0 deletions src/tools/documents.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import { buildBulkEditParameters } from "./documents";

test("buildBulkEditParameters sends Paperless bulk custom fields as id:value map", () => {
const parameters = buildBulkEditParameters(
{ remove_custom_fields: [] as number[] },
[
{ field: 9, value: "" },
{ field: 10, value: "2026-05-14" },
]
);

assert.deepEqual(parameters, {
remove_custom_fields: [],
add_custom_fields: {
"9": "",
"10": "2026-05-14",
},
});
assert.ok(!("assign_custom_fields" in parameters));
assert.ok(!("assign_custom_fields_values" in parameters));
});

test("buildBulkEditParameters preserves null custom field values", () => {
const parameters = buildBulkEditParameters({}, [{ field: 9, value: null }]);

assert.deepEqual(parameters, {
add_custom_fields: {
"9": null,
},
});
});


test("buildBulkEditParameters includes Paperless-required empty custom field keys", () => {
const parameters = buildBulkEditParameters({}, undefined, true);

assert.deepEqual(parameters, {
add_custom_fields: {},
remove_custom_fields: [],
});
});
53 changes: 43 additions & 10 deletions src/tools/documents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,44 @@ import { withErrorHandling } from "./utils/middlewares";
import { validateCustomFields } from "./utils/monetary";
import { CUSTOM_FIELD_VALUE_DESCRIPTION } from "./utils/descriptions";

export type BulkCustomFieldValue = string | number | boolean | number[] | null;

export type BulkCustomFieldUpdate = {
field: number;
value: BulkCustomFieldValue;
};

export type BulkCustomFieldParameters = {
add_custom_fields?: Record<string, BulkCustomFieldValue>;
remove_custom_fields?: number[];
};

export function buildBulkEditParameters<T extends Record<string, unknown>>(
parameters: T,
addCustomFields?: BulkCustomFieldUpdate[],
includeCustomFieldDefaults = false
): T & BulkCustomFieldParameters {
const apiParameters: T & BulkCustomFieldParameters = {
...parameters,
};

if (addCustomFields && addCustomFields.length > 0) {
apiParameters.add_custom_fields = Object.fromEntries(
addCustomFields.map((customField) => [
String(customField.field),
customField.value,
])
);
}

if (includeCustomFieldDefaults) {
apiParameters.add_custom_fields ??= {};
apiParameters.remove_custom_fields ??= [];
}

return apiParameters;
}

export function registerDocumentTools(server: McpServer, api: PaperlessAPI) {
server.tool(
"bulk_edit_documents",
Expand Down Expand Up @@ -95,19 +133,14 @@ export function registerDocumentTools(server: McpServer, api: PaperlessAPI) {

validateCustomFields(add_custom_fields);

// 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
buildBulkEditParameters(
parameters,
add_custom_fields,
method === "modify_custom_fields"
)
);
return {
content: [
Expand Down