Create/update operations were sending empty request bodies to Paperless,
producing two visible failure modes:
1. POST creates (correspondents, tags, document types, storage paths,
custom fields) returned `400 {"name":["This field is required."]}`
even when the caller passed a valid name.
2. PATCH updates (documents, correspondents, tags, etc.) returned 200
and bumped the row's `modified` timestamp, but no fields actually
changed — server-side this looked like a successful no-op PATCH.
3. Bulk edit operations (`api/documents/bulk_edit/`,
`api/bulk_edit_objects/`) failed because the inner `parameters` field
went out as `{}`, so add_tag/remove_tag/etc. arrived without a tag id.
Reproducer: `correspondents.create(name="ACME Corp")` against a real
Paperless instance returns 400 with the body above.
Root causes are two related serialization patterns where the
compile-time type of the value to serialize is `object`:
- `PostWithResultAsync` / `PatchWithResultAsync` accept the request as
`object` and pass it to `PostAsJsonAsync<TValue>` /
`JsonContent.Create<T>(value, ...)`. The generic `T` is inferred as
`object`, and through the configured DI HttpClient pipeline
(DelegatingHandler + Polly retry policy) the body that reaches the
wire is empty, even though the same JsonContent's
`ReadAsStringAsync()` returns the expected JSON.
- `BulkEditDocumentsAsync` / `BulkEditObjectsAsync` wrap the call args
in an anonymous type whose `parameters` field has compile-time type
`object?`. System.Text.Json serializes that property against
`object`, producing `"parameters":{}` regardless of the runtime value.
Fix: serialize against the runtime type explicitly and materialize the
JSON into a `StringContent` before the request leaves this client.
For bulk edits, build the body via `JsonObject` and serialize the
`parameters` payload against its runtime type.
Adds wire-format pinning tests for create / update / bulk_edit /
bulk_edit_objects that capture the outbound request body and assert the
expected payload shape. These do not reproduce the original bug under
MockHttp (the test helper bypasses the DI handler chain), but they pin
the new serialization for future refactors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reflection-based tests verify all McpServerTool names match
^[a-zA-Z0-9_-]{1,64}$ and contain no dots, preventing regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add FUNDING.yml for sponsor button on repo
- Add support section to README with badges
Time to get paid for doing jack shit
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Lead with the problem: you have docs, no time to organize them
- Clear "Is this for you?" section
- Installation options: Docker first, then Claude Desktop, k8s, source
- Full tool reference with collapsible sections
- No more `latest` tag - link to releases badge instead
- Acknowledge this works with any MCP-compatible AI, not just Claude
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Show pod status, events, describe, and logs when k8s rollout fails
instead of just timing out with no context.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add cog.toml with commit type configs and version bump hooks
- Add .woodpecker.yml pipeline for automated version bumping on main
- Add Version element to csproj for cog to update
- Document cog workflow in CLAUDE.md
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The 24-hour timeout was still not being respected - sessions were being
killed after ~14 minutes. Using Timeout.InfiniteTimeSpan completely
disables idle-based session pruning in the SDK's IdleTrackingBackgroundService.
This makes the MCP server bulletproof against session drops during long
periods of inactivity (e.g., when users are discussing/planning before
making tool calls).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Woodpecker was interpreting ${VAR} as CI variables and replacing
them with empty strings. Changed to $VAR syntax which gets passed
through to the shell correctly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add Directory.Build.props for consistent version across all projects
- Add version.json to track current version in source control
- Update release pipeline to:
- Calculate version bumps from conventional commits
- Pass /p:Version to all build/test/pack commands
- Commit version.json back before tagging
- Generate changelog in GitHub releases
- Version bump rules:
- feat!: or BREAKING CHANGE: → major bump
- feat: → minor bump
- fix/perf/refactor/build/ci/docs/style/test: → patch bump
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add ApiResult<T> type to carry success/failure with error details
- Add UpdateDocumentWithResultAsync and CreateTagWithResultAsync methods
- Update DocumentTools.Update and TagTools.Create to return actual
HTTP status codes and response bodies in error responses
- Add comprehensive tests for error handling (18 new tests)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Validates all commit messages in a PR follow the conventional commits
format before allowing build/test to proceed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The /api/ endpoint returns HTML (Swagger UI), not JSON.
Changed ping to use /api/status/ which returns JSON with version info.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
MCP endpoints return 406 for plain GET requests since they use SSE.
TCP socket probes check if the port is open without protocol issues.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
HTTP transport mode was missing the required WithHttpTransport() call,
causing MapMcp() to fail. Also adds Kubernetes manifests for deployment.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Provides build commands, architecture overview, and development
conventions for AI-assisted development.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The old plugin only works on tag events. Using curl to call
GitHub API directly to create releases.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Woodpecker evaluates ${VAR} before the shell runs. Using $$ escaping
so variables are passed to the shell properly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Multiline backslash continuation may have shell expansion issues.
Using single line with quoted arguments instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each command runs in separate shell - variables don't persist.
All steps with variable dependencies now use single script blocks.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Add IsPackable and package metadata to csproj for NuGet package
generation in CI pipeline.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each command runs in separate shell - variables don't persist.
Moving everything into one script block.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The multiline script runs in sh, not bash. Using sed instead of
bash parameter expansion for stripping the v prefix.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Each step runs in a fresh container, so NuGet packages restored in one
step aren't available in the next. Consolidating restore/build/test
into single steps.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Use woodpeckerci/plugin-git with lfs: false setting instead of
skipping clone entirely.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Woodpecker's default clone step fails when git-lfs isn't installed.
Using manual clone with alpine/git instead.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The LFS warning is non-fatal. Removing the incorrect clone config
that was causing linter errors.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>