Skip to content

Fix bulk custom field parameters#89

Merged
baruchiro merged 5 commits into
baruchiro:mainfrom
peshay:fix-bulk-custom-field-params
May 18, 2026
Merged

Fix bulk custom field parameters#89
baruchiro merged 5 commits into
baruchiro:mainfrom
peshay:fix-bulk-custom-field-params

Conversation

@peshay
Copy link
Copy Markdown

@peshay peshay commented May 14, 2026

Summary

  • Send modify_custom_fields bulk edits using Paperless-NGX's expected add_custom_fields id/value map instead of unsupported assign_custom_fields fields.
  • Include Paperless-required empty add_custom_fields / remove_custom_fields defaults for modify_custom_fields operations.
  • Preserve empty string and null custom field values so workflows can create intentionally empty fields, e.g. pending markers on date custom fields.
  • Update README examples and add unit tests for the parameter transformation.

Why

Paperless-NGX validates bulk custom field edits with parameters.add_custom_fields and parameters.remove_custom_fields. The current MCP transformation sends assign_custom_fields and assign_custom_fields_values, which Paperless rejects with errors like add_custom_fields not specified.

This also prevents setting an intentionally empty custom field value used by workflows such as "date field is present but empty = pending".

Tests

  • npm test
  • npm run build

Note: local environment is Node 22, while the package declares Node >=24, so npm prints an EBADENGINE warning during install. Tests and TypeScript build pass.

Summary by CodeRabbit

  • Breaking Changes

    • Bulk document custom-field edits now use a string-key → value mapping for added fields; previous bulk-field parameters removed.
  • Documentation

    • Updated examples for bulk modify operations, including how to add/remove custom fields and how to set fields to empty values.
  • Tests

    • Added coverage verifying bulk-edit parameter shape, preservation of empty/null values, and supported value types.

Review Change Stack

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 14, 2026

🦋 Changeset detected

Latest commit: 91e5cd8

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@baruchiro/paperless-mcp Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 14, 2026

Warning

Rate limit exceeded

@baruchiro has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 56 minutes and 36 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 08f4741d-169e-4939-8ef4-41cf70bd3063

📥 Commits

Reviewing files that changed from the base of the PR and between 19cd9ad and 91e5cd8.

📒 Files selected for processing (1)
  • README.md
📝 Walkthrough

Walkthrough

This PR refactors the custom field bulk edit parameter handling from an array-based assignment model (assign_custom_fields, assign_custom_fields_values) to a key-value mapping model (add_custom_fields). It introduces a shared CustomFieldValue type, updates BulkEditParameters, adds tests for buildBulkEditParameters() that validate payload shapes and edge cases, and updates the document tools to use the new builder.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • baruchiro
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and precisely describes the main change: fixing bulk custom field parameters to use Paperless-NGX's expected format.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/tools/documents.test.ts (3)

35-35: 💤 Low value

Extra blank line.

There's an additional blank line here that creates inconsistent spacing between test cases.

♻️ Proposed fix
-
-
 test("buildBulkEditParameters includes Paperless-required empty custom field keys", () => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/documents.test.ts` at line 35, Remove the extra blank line in
src/tools/documents.test.ts that sits between the test cases (the stray empty
line around line 35), so spacing between the adjacent describe/it or test blocks
is consistent; simply delete that empty line to restore the file's expected test
spacing.

5-43: ⚡ Quick win

Consider adding edge case tests for comprehensive coverage.

The current tests cover the core requirements well. For improved robustness, consider adding tests for:

  • Empty custom fields array (vs undefined)
  • Combination of base parameters, custom fields, and includeEmptyCustomFields flag
  • Different value types (numbers, booleans) to verify type preservation
💡 Example additional test cases
test("buildBulkEditParameters handles empty custom fields array", () => {
  const parameters = buildBulkEditParameters({}, []);
  
  assert.deepEqual(parameters, {
    add_custom_fields: {},
  });
});

test("buildBulkEditParameters combines base params with custom fields", () => {
  const parameters = buildBulkEditParameters(
    { remove_tags: [1, 2], add_tags: [3] },
    [{ field: 9, value: "test" }]
  );
  
  assert.deepEqual(parameters, {
    remove_tags: [1, 2],
    add_tags: [3],
    add_custom_fields: {
      "9": "test",
    },
  });
});

test("buildBulkEditParameters preserves different value types", () => {
  const parameters = buildBulkEditParameters({}, [
    { field: 1, value: 42 },
    { field: 2, value: true },
    { field: 3, value: "" },
  ]);
  
  assert.strictEqual(parameters.add_custom_fields["1"], 42);
  assert.strictEqual(parameters.add_custom_fields["2"], true);
  assert.strictEqual(parameters.add_custom_fields["3"], "");
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/documents.test.ts` around lines 5 - 43, Add unit tests around
buildBulkEditParameters to cover the suggested edge cases: (1) call
buildBulkEditParameters({}, []) and assert it returns add_custom_fields: {} (and
no unexpected keys), (2) call buildBulkEditParameters with a non-empty base
params object (e.g., remove_tags/add_tags) plus a custom fields array and assert
the returned object merges base params and adds add_custom_fields with the
correct id->value mapping, and (3) call buildBulkEditParameters with custom
field values of different types (number, boolean, empty string, null) and assert
the values are preserved as-is in parameters.add_custom_fields; reference the
buildBulkEditParameters function in tests and assert presence/absence of keys as
in existing tests.

7-7: 💤 Low value

Consider removing the unnecessary type assertion.

The type assertion as number[] appears redundant since TypeScript should infer the type from the object literal and the function signature.

♻️ Proposed simplification
-    { remove_custom_fields: [] as number[] },
+    { remove_custom_fields: [] },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/documents.test.ts` at line 7, The inline type assertion "as
number[]" is unnecessary on the object literal property remove_custom_fields;
remove the "as number[]" so the line becomes { remove_custom_fields: [] },
letting TypeScript infer the type from the literal and the surrounding function
signature or test helper (look for the test helper/fixture that consumes
remove_custom_fields in this file).
src/tools/documents.ts (1)

22-46: ⚡ Quick win

Add JSDoc documentation for the exported utility function.

The buildBulkEditParameters function is exported and has non-trivial logic (conditional defaults, array-to-record transformation). As per coding guidelines, complex functions should have clear JSDoc comments, especially exported functions.

📝 Proposed JSDoc documentation
+/**
+ * Builds bulk edit parameters by transforming custom field updates into the format expected by Paperless-NGX.
+ * Converts an array of custom field updates into a Record mapping field IDs to values.
+ * 
+ * `@template` T - The base parameters type
+ * `@param` parameters - Base parameters to include in the result
+ * `@param` addCustomFields - Optional array of custom field updates to transform
+ * `@param` includeCustomFieldDefaults - Whether to include empty add_custom_fields and remove_custom_fields defaults (required for modify_custom_fields method)
+ * `@returns` Combined parameters with transformed custom fields
+ */
 export function buildBulkEditParameters<T extends Record<string, unknown>>(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tools/documents.ts` around lines 22 - 46, Add JSDoc for the exported
utility buildBulkEditParameters describing its purpose, parameters, return type,
and behavior: document parameters (parameters: T, addCustomFields?:
BulkCustomFieldUpdate[], includeCustomFieldDefaults?: boolean), explain that
addCustomFields transforms an array into the add_custom_fields record (keys from
customField.field and values from customField.value), note that
includeCustomFieldDefaults will initialize add_custom_fields and
remove_custom_fields defaults, and specify the returned type T &
BulkCustomFieldParameters; include examples or edge-case notes about empty
addCustomFields and the use of nullish coalescing
(apiParameters.add_custom_fields ??= {}).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/api/types.ts`:
- Line 153: CustomFieldInstanceRequest["value"] currently allows a broad object
type which mismatches the runtime tools that use number[] (see
BulkCustomFieldValue and the Zod schema); update the union for
CustomFieldInstanceRequest["value"] to explicitly include number[] (or replace
the generic object with number[]) so add_custom_fields?: Record<string,
CustomFieldInstanceRequest["value"]> accepts the actual array shape, and ensure
the Zod schema and BulkCustomFieldValue definition remain consistent with this
change.

---

Nitpick comments:
In `@src/tools/documents.test.ts`:
- Line 35: Remove the extra blank line in src/tools/documents.test.ts that sits
between the test cases (the stray empty line around line 35), so spacing between
the adjacent describe/it or test blocks is consistent; simply delete that empty
line to restore the file's expected test spacing.
- Around line 5-43: Add unit tests around buildBulkEditParameters to cover the
suggested edge cases: (1) call buildBulkEditParameters({}, []) and assert it
returns add_custom_fields: {} (and no unexpected keys), (2) call
buildBulkEditParameters with a non-empty base params object (e.g.,
remove_tags/add_tags) plus a custom fields array and assert the returned object
merges base params and adds add_custom_fields with the correct id->value
mapping, and (3) call buildBulkEditParameters with custom field values of
different types (number, boolean, empty string, null) and assert the values are
preserved as-is in parameters.add_custom_fields; reference the
buildBulkEditParameters function in tests and assert presence/absence of keys as
in existing tests.
- Line 7: The inline type assertion "as number[]" is unnecessary on the object
literal property remove_custom_fields; remove the "as number[]" so the line
becomes { remove_custom_fields: [] }, letting TypeScript infer the type from the
literal and the surrounding function signature or test helper (look for the test
helper/fixture that consumes remove_custom_fields in this file).

In `@src/tools/documents.ts`:
- Around line 22-46: Add JSDoc for the exported utility buildBulkEditParameters
describing its purpose, parameters, return type, and behavior: document
parameters (parameters: T, addCustomFields?: BulkCustomFieldUpdate[],
includeCustomFieldDefaults?: boolean), explain that addCustomFields transforms
an array into the add_custom_fields record (keys from customField.field and
values from customField.value), note that includeCustomFieldDefaults will
initialize add_custom_fields and remove_custom_fields defaults, and specify the
returned type T & BulkCustomFieldParameters; include examples or edge-case notes
about empty addCustomFields and the use of nullish coalescing
(apiParameters.add_custom_fields ??= {}).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3bd52ba4-f115-4f17-b3d4-5669be35adfb

📥 Commits

Reviewing files that changed from the base of the PR and between 2bdd779 and df2d977.

📒 Files selected for processing (4)
  • README.md
  • src/api/types.ts
  • src/tools/documents.test.ts
  • src/tools/documents.ts

Comment thread src/api/types.ts
peshay and others added 3 commits May 14, 2026 19:43
Tightens the custom field value type, documents buildBulkEditParameters, and adds edge-case coverage for empty custom field arrays and type preservation.\n\nValidation:\n- npm test\n- npm run build
@baruchiro baruchiro enabled auto-merge (squash) May 18, 2026 05:41
@baruchiro baruchiro merged commit 5927777 into baruchiro:main May 18, 2026
3 checks passed
baruchiro added a commit that referenced this pull request May 24, 2026
Closes #101

## Summary

- **`docker-compose.e2e.yml`** — Paperless-ngx 2.14 + Redis fixture
stack with auto-created admin user
- **`e2e/e2e.test.ts`** — Deterministic `tools/call` tests (no LLM)
using Node's native `node:test` covering all tools from the issue:
`list_tags`, `create_tag`, `list_correspondents`,
`create_correspondent`, `list_document_types`, `create_document_type`,
`list_documents`, `get_document`, `search_documents`,
`download_document` (regression for #87), `get_document_thumbnail`,
`bulk_edit_documents` (regression for #100, #89), `post_document`
- **`e2e/client.ts`** — SDK `StreamableHTTPClientTransport` factory
- **`e2e/paperless.ts`** — Direct Paperless REST client for seeding test
data in `before()` hooks
- **`.github/workflows/e2e.yml`** — Matrix job (CLI + Docker) triggered
on every PR and push to `main`
- **`README.md`** — Testing section with unit and E2E run instructions
- **`package.json`** — `npm run test:e2e` script

## Test plan

- [ ] Verify CI passes on this PR (E2E matrix job: cli + docker)
- [ ] Check that `list_tags` finds the seeded tag
- [ ] Check that `download_document` returns a `paperless://` URI
(regression #87)
- [ ] Check that `bulk_edit_documents` modify_tags round-trip works
(regression #100)
- [ ] Confirm Docker image job also passes (builds image then runs same
test file)
- [ ] Review README Testing section renders correctly

https://claude.ai/code/session_01LActHt6HMhygRppaphFRzQ

---
_Generated by [Claude
Code](https://claude.ai/code/session_01LActHt6HMhygRppaphFRzQ)_

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Tests**
* Added a comprehensive E2E test suite that runs locally and in CI
against a real Paperless-ngx instance, exercising
tag/correspondent/document-type management, document operations
(list/get/search/download/thumbnail), bulk tag edits, and uploads.

* **Documentation**
* Added a “Testing” section with prerequisites and instructions for
running unit and E2E tests and cleanup steps.

* **Chores**
* Added CI workflow, Docker Compose for E2E, and an npm script to run
E2E tests; excluded E2E from regular TypeScript compilation.

* **Bug Fixes**
* Bulk-edit behavior now initializes tag fields by default for
tag-modify operations to ensure robust payloads.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/baruchiro/paperless-mcp/pull/104?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants