Skip to content

Add Cospend tools — 16 tools for projects, members, bills (OCS)#52

Merged
oleksandr-nc merged 6 commits intomainfrom
feature/cospend-projects-members-bills
Apr 28, 2026
Merged

Add Cospend tools — 16 tools for projects, members, bills (OCS)#52
oleksandr-nc merged 6 commits intomainfrom
feature/cospend-projects-members-bills

Conversation

@oleksandr-nc
Copy link
Copy Markdown
Contributor

@oleksandr-nc oleksandr-nc commented Apr 26, 2026

Summary

Adds Cospend (shared expense tracking) tool support to the MCP server. Cospend is an extension app — not bundled with Nextcloud — and exposes a clean OCS v1 API at /ocs/v2.php/apps/cospend/api/v1/. This PR covers the foundation: enough tools to use Cospend end-to-end (create a project, add members, log expenses, see balances).

Scope (this PR)

Projects (7): list_cospend_projects, get_cospend_project, create_cospend_project, update_cospend_project, delete_cospend_project, get_cospend_project_statistics, get_cospend_project_settlement

Members (4): list_cospend_members, create_cospend_member, update_cospend_member, delete_cospend_member

Bills (5): list_cospend_bills, get_cospend_bill, create_cospend_bill, update_cospend_bill, delete_cospend_bill

Total: 16 tools, 35 integration tests (all green on master + stable33).

Out of scope (future PRs)

  • Shares (5 share types: user/group/circle/public/federated, ~11 endpoints)
  • Categories / Payment modes / Currencies (12 endpoints)
  • Federation (4 endpoints)
  • Statistics CSV export, project CSV export/import, auto-settlement

Notable design decisions

  • create_cospend_bill defaults date to today (UTC) when neither date nor timestamp is provided. The controller signature marks both as optional, but the service layer raises 400 unless one is given — defaulting saves users from a guaranteed-fail call.
  • delete_cospend_bill defaults move_to_trash=True, matching the API default. Pass move_to_trash=False for an irreversible purge.
  • Documented Cospend quirks in tool docstrings:
    • list_cospend_bills: search_term is silently ignored unless limit is also set (Cospend uses different code paths for limit vs no-limit).
    • list_cospend_bills: nb_bills is the project-wide count under filters but is not affected by search_term, even when search_term filters the returned bills.
    • update_cospend_member: Setting activated=False on a member with no bills deletes them rather than disabling.
    • delete_cospend_member: Members with bills are soft-disabled (kept for bill history); bill-less members are removed.
  • Permission mapping: read for list/get/stats/settlement, write for create/update, destructive for all delete operations.

Test plan

  • Unit + integration test suite green on NC34 (master)
  • Integration test suite green on NC33 (stable33)
  • ruff check . clean
  • pyright clean
  • All 16 tools registered (verified by TestToolRegistration)
  • CI green

Summary by CodeRabbit

  • New Features

    • Cospend shared-expense integration: create/list/get/update/delete projects, members, bills; view statistics and settlement suggestions; bill trash vs purge behavior; bill creation defaults date to today when absent.
  • Documentation

    • README documents Cospend routes, available tools, permission levels, and behavioral notes.
  • Progress

    • Progress updated to mark Cospend complete and adjust phase/tool totals and “Next Up”.
  • Tests

    • End-to-end integration tests and cleanup added; server tool inventory expectations updated.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 26, 2026

Warning

Rate limit exceeded

@oleksandr-nc has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 51 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2e27b9ca-cc32-473f-9a63-1e02e0290b68

📥 Commits

Reviewing files that changed from the base of the PR and between 4b80b58 and ccd320b.

📒 Files selected for processing (2)
  • src/nc_mcp_server/tools/cospend.py
  • tests/integration/test_cospend.py
📝 Walkthrough

Walkthrough

Adds a new Cospend MCP tool module (projects, members, bills, statistics, settlement), registers it with the MCP server, updates docs, and includes integration tests and cleanup for Cospend against a real Nextcloud instance.

Changes

Cohort / File(s) Summary
Documentation
PROGRESS.md, README.md
Mark Cospend as completed (16 tools, 35 tests), update totals and default tool counts; add ### Cospend doc with OCS route base and MCP tool listing + permission notes.
Server Registration
src/nc_mcp_server/server.py
Import and register the new Cospend tool via cospend.register(mcp).
Cospend Tool Implementation
src/nc_mcp_server/tools/cospend.py
New MCP module registering read/write/destructive tools for projects, members, bills, statistics, and settlement; builds request bodies (omitting nulls), URL-encodes project ids, enforces validations (e.g., non-empty payed_for), applies defaults (bill date), gates by permissions, makes OCS HTTP calls, and returns JSON responses.
Tests & Fixtures
tests/integration/conftest.py, tests/integration/test_cospend.py, tests/integration/test_server.py
Add cospend cleanup helper to fixtures; add comprehensive integration tests exercising lifecycle, filters, permission gating, and error cases; extend EXPECTED_TOOLS in server tests to include Cospend tool names.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant MCP as FastMCP Server
  participant Cospend as apps/cospend OCS
  participant NC as Nextcloud Core

  Client->>MCP: call MCP tool (e.g., create_bill)
  MCP->>Cospend: HTTP request to /ocs/v2.php/apps/cospend/api/v1/... (body filtered)
  Cospend->>NC: read/write DB (projects/members/bills)
  NC-->>Cospend: operation result (JSON)
  Cospend-->>MCP: OCS JSON response
  MCP-->>Client: JSON result (tool response)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 A little rabbit hops with cheer,
I bundled Cospend tools right here,
Projects, members, bills in tow,
Settlements balance to and fro,
Hop, click, share — let's split the carrots!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.67% 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 PR title accurately summarizes the main change: adding Cospend tools for projects, members, and bills with the OCS API.
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
  • Commit unit tests in branch feature/cospend-projects-members-bills

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.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 26, 2026

Codecov Report

❌ Patch coverage is 51.72414% with 70 lines in your changes missing coverage. Please review.
✅ Project coverage is 94.08%. Comparing base (99470be) to head (ccd320b).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/nc_mcp_server/tools/cospend.py 51.38% 70 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #52      +/-   ##
==========================================
- Coverage   95.99%   94.08%   -1.91%     
==========================================
  Files          30       31       +1     
  Lines        3222     3367     +145     
==========================================
+ Hits         3093     3168      +75     
- Misses        129      199      +70     
Flag Coverage Δ
integration 92.90% <51.72%> (-1.86%) ⬇️
nc32 92.90% <51.72%> (-1.86%) ⬇️
nc33 92.87% <51.72%> (-1.86%) ⬇️
py3.12 9.47% <0.00%> (-0.43%) ⬇️
py3.13 9.47% <0.00%> (-0.43%) ⬇️
py3.14 9.47% <0.00%> (-0.43%) ⬇️
session-cache 19.89% <13.10%> (-0.31%) ⬇️
unit 9.47% <0.00%> (-0.43%) ⬇️
user-permissions 40.42% <51.72%> (+0.50%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@oleksandr-nc oleksandr-nc marked this pull request as ready for review April 26, 2026 06:38
@oleksandr-nc
Copy link
Copy Markdown
Contributor Author

(AI) Ready for review. CI green on Lint, Type Check, Python 3.12-3.14, NC 32 (skips Cospend tests since min-version=33), NC 33, plus Session cache and User permissions on both NC versions.

Summary:

  • 16 tools across projects (7), members (4), bills (5)
  • 35 integration tests, all green on both master and stable33
  • Cospend's min-version=33 means tests properly skip on NC 32 via the _skip_if_no_cospend autouse fixture
  • Initial CI failure was the hardcoded EXPECTED_TOOLS list in tests/integration/test_server.py — fixed in c56ec86

Notable design notes captured in tool docstrings:

  • create_cospend_bill defaults date to today (UTC) when neither date nor timestamp is given (server requires one despite the controller marking both optional)
  • list_cospend_bills: documented Cospend's quirk that search_term is silently ignored unless limit is also set, and that nb_bills is always pre-search even when bills is search-filtered
  • update_cospend_member/delete_cospend_member: documented that members with bills are soft-disabled; bill-less members are hard-deleted

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

33-36: ⚠️ Potential issue | 🟡 Minor

Stale tool / app counts in the heading and callout.

After adding 16 Cospend tools and 1 new app (Cospend), the totals here should track PROGRESS.md:

  • "140 Tools Across 23 Nextcloud Apps" → 156 Tools Across 24 Nextcloud Apps
  • "A 141st tool, upload_file_from_path" → 157th tool
📝 Proposed update
-## 140 Tools Across 23 Nextcloud Apps
+## 156 Tools Across 24 Nextcloud Apps

-A 141st tool, `upload_file_from_path`, is registered only when the operator sets
+A 157th tool, `upload_file_from_path`, is registered only when the operator sets
 `NEXTCLOUD_MCP_UPLOAD_ROOT`. See [Files](`#files`) for details.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 33 - 36, Update the stale counts in the README
heading and callout: change the section title string "140 Tools Across 23
Nextcloud Apps" to "156 Tools Across 24 Nextcloud Apps" and update the callout
sentence "A 141st tool, `upload_file_from_path`" to "A 157th tool,
`upload_file_from_path`" so the numbers match PROGRESS.md; ensure the
surrounding text and anchor [Files](`#files`) remain intact.
🧹 Nitpick comments (2)
tests/integration/conftest.py (1)

221-228: Optional: short-circuit when Cospend isn't installed.

_cleanup_cospend is invoked by the autouse _clean_test_data fixture for every integration test. On instances without the Cospend app, this still issues a 404'd apps/cospend/api/v1/projects request per test (silenced by the outer contextlib.suppress). Consider gating it on the same _cospend_available probe used in tests/integration/test_cospend.py, or caching a one-shot "cospend installed" flag at session scope to skip the call entirely.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integration/conftest.py` around lines 221 - 228, The _cleanup_cospend
function currently issues a 404 on instances without Cospend; update it to
short-circuit by checking the existing _cospend_available probe (as used in
test_cospend) or a cached session-scoped boolean before calling client.ocs_get,
e.g. consult the _cospend_available check (or initialize a one-shot session
flag) and skip the API call entirely when false to avoid per-test 404 requests;
keep the existing contextlib.suppress around delete calls for safety.
PROGRESS.md (1)

53-53: Optional: split "Next Up" back into bullets.

The single sentence packs Cospend follow-ups, Weather Status, and the skip list together; bulleting these (as before) reads better and makes the actual next item obvious at a glance.

📝 Suggested layout
-- Cospend follow-ups (Shares: 5 share types; Categories/Payment modes/Currencies; Statistics export). Weather Status (fully OCS, bundled). Tables, Polls, Notes, Deck, Bookmarks, Photos skipped — API not OCS or OCS incomplete.
+- Cospend follow-ups: Shares (5 share types), Categories/Payment modes/Currencies, Statistics export
+- Weather Status (fully OCS, bundled)
+- Skipped — API not OCS or OCS incomplete: Tables, Polls, Notes, Deck, Bookmarks, Photos
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@PROGRESS.md` at line 53, The "Next Up" single-line entry containing "Cospend
follow-ups (Shares: 5 share types; Categories/Payment modes/Currencies;
Statistics export). Weather Status (fully OCS, bundled). Tables, Polls, Notes,
Deck, Bookmarks, Photos skipped — API not OCS or OCS incomplete." should be
split back into separate bullet items so each next-task is visible at a glance;
replace that single sentence with three bullets (1) Cospend follow-ups — list
the subitems "Shares: 5 share types; Categories/Payment modes/Currencies;
Statistics export", (2) Weather Status — note "fully OCS, bundled", and (3)
Skipped items — "Tables, Polls, Notes, Deck, Bookmarks, Photos" with the note
"API not OCS or OCS incomplete" so each item and its status are clear.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nc_mcp_server/tools/cospend.py`:
- Around line 471-537: The update_cospend_bill handler currently maps
payed_for=[] to payedFor="" which can unintentionally clear owers; change the
payed_for handling in update_cospend_bill so that when payed_for is an empty
list it uses None (i.e., omit the payedFor key) instead of ",". Concretely,
update the payedFor argument passed to _body (in update_cospend_bill) to be
",".join(str(m) for m in payed_for) if payed_for is not None and payed_for != []
else None; optionally add a one-line docstring note that passing [] does not
clear owers on update (or raise ValueError if you prefer explicit rejection).
Ensure references: update_cospend_bill, payed_for, payedFor, and _body.

In `@tests/integration/test_cospend.py`:
- Around line 139-220: Add a new async test that exercises update_cospend_member
with activated=False and then activated=True to cover the soft-disable lifecycle
via the update path: create a project (use _make_project), create a member
(_make_member), call nc_mcp.call("update_cospend_member", project_id=...,
member_id=..., activated=False) and assert the returned member and subsequent
list_cospend_members show activated is False (member kept but disabled), then
call update_cospend_member with activated=True and assert it is re-enabled;
ensure the test uses the same patterns as other tests and targets the
update_cospend_member code path noted in tools/cospend.py to catch the
create/update type/key asymmetry.

---

Outside diff comments:
In `@README.md`:
- Around line 33-36: Update the stale counts in the README heading and callout:
change the section title string "140 Tools Across 23 Nextcloud Apps" to "156
Tools Across 24 Nextcloud Apps" and update the callout sentence "A 141st tool,
`upload_file_from_path`" to "A 157th tool, `upload_file_from_path`" so the
numbers match PROGRESS.md; ensure the surrounding text and anchor
[Files](`#files`) remain intact.

---

Nitpick comments:
In `@PROGRESS.md`:
- Line 53: The "Next Up" single-line entry containing "Cospend follow-ups
(Shares: 5 share types; Categories/Payment modes/Currencies; Statistics export).
Weather Status (fully OCS, bundled). Tables, Polls, Notes, Deck, Bookmarks,
Photos skipped — API not OCS or OCS incomplete." should be split back into
separate bullet items so each next-task is visible at a glance; replace that
single sentence with three bullets (1) Cospend follow-ups — list the subitems
"Shares: 5 share types; Categories/Payment modes/Currencies; Statistics export",
(2) Weather Status — note "fully OCS, bundled", and (3) Skipped items — "Tables,
Polls, Notes, Deck, Bookmarks, Photos" with the note "API not OCS or OCS
incomplete" so each item and its status are clear.

In `@tests/integration/conftest.py`:
- Around line 221-228: The _cleanup_cospend function currently issues a 404 on
instances without Cospend; update it to short-circuit by checking the existing
_cospend_available probe (as used in test_cospend) or a cached session-scoped
boolean before calling client.ocs_get, e.g. consult the _cospend_available check
(or initialize a one-shot session flag) and skip the API call entirely when
false to avoid per-test 404 requests; keep the existing contextlib.suppress
around delete calls for safety.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a22a2f73-2a3f-4420-a3ad-14c833f7ebe4

📥 Commits

Reviewing files that changed from the base of the PR and between 99470be and c56ec86.

📒 Files selected for processing (7)
  • PROGRESS.md
  • README.md
  • src/nc_mcp_server/server.py
  • src/nc_mcp_server/tools/cospend.py
  • tests/integration/conftest.py
  • tests/integration/test_cospend.py
  • tests/integration/test_server.py

Comment thread src/nc_mcp_server/tools/cospend.py
Comment thread tests/integration/test_cospend.py
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.

🧹 Nitpick comments (1)
src/nc_mcp_server/tools/cospend.py (1)

105-105: Inconsistent boolean query parameter serialization across endpoints requires clarification.

get_cospend_project_statistics encodes showDisabled as "1"/"0" (line 105), while list_cospend_bills encodes reverse as "true"/"false" (line 225), and deleted is passed as an integer (line 224). Integration tests pass but do not exercise these boolean parameters with true values, so the formats are not actually validated. If this inconsistency mirrors upstream Cospend API expectations (as suggested by the intentional create/update field-name asymmetry for member activation), document this assumption. Otherwise, standardize to a single format.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/nc_mcp_server/tools/cospend.py` at line 105,
get_cospend_project_statistics currently serializes showDisabled as "1"/"0"
while list_cospend_bills uses "true"/"false" for reverse and an integer for
deleted; pick a single boolean serialization (either JSON-style "true"/"false"
or numeric "1"/"0") or explicitly document that these formats are intentionally
different to match upstream Cospend behavior. Update the serialization in
get_cospend_project_statistics (params key showDisabled), list_cospend_bills
(params keys reverse and deleted) to use the chosen consistent format, add a
short comment in each function (get_cospend_project_statistics,
list_cospend_bills) stating the expected API format if it must differ, and
add/adjust unit or integration tests to exercise true/active values so the
behavior is validated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/nc_mcp_server/tools/cospend.py`:
- Line 105: get_cospend_project_statistics currently serializes showDisabled as
"1"/"0" while list_cospend_bills uses "true"/"false" for reverse and an integer
for deleted; pick a single boolean serialization (either JSON-style
"true"/"false" or numeric "1"/"0") or explicitly document that these formats are
intentionally different to match upstream Cospend behavior. Update the
serialization in get_cospend_project_statistics (params key showDisabled),
list_cospend_bills (params keys reverse and deleted) to use the chosen
consistent format, add a short comment in each function
(get_cospend_project_statistics, list_cospend_bills) stating the expected API
format if it must differ, and add/adjust unit or integration tests to exercise
true/active values so the behavior is validated.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4d03a344-437a-4354-8a67-bfe8a573c172

📥 Commits

Reviewing files that changed from the base of the PR and between c56ec86 and 5431d1e.

📒 Files selected for processing (2)
  • src/nc_mcp_server/tools/cospend.py
  • tests/integration/test_cospend.py

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/nc_mcp_server/tools/cospend.py`:
- Around line 192-214: The function that lists bills (the one accepting
search_term and limit) can silently ignore search when search_term is provided
but limit is None; modify the function (referencing the search_term and limit
parameters in the bills-listing function) to fail fast: when search_term is not
None and limit is None, raise a clear ValueError (or TypeError) telling callers
to provide a limit (or set a default limit), so the search behavior is enforced;
apply the same guard to the similar bills-listing variant referenced in lines
~225-237.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 379e21f9-e9b1-45d4-a6d3-6926f69651f3

📥 Commits

Reviewing files that changed from the base of the PR and between 5431d1e and 4b80b58.

📒 Files selected for processing (3)
  • src/nc_mcp_server/tools/cospend.py
  • tests/integration/conftest.py
  • tests/integration/test_cospend.py
✅ Files skipped from review due to trivial changes (1)
  • tests/integration/test_cospend.py

Comment thread src/nc_mcp_server/tools/cospend.py Outdated
@oleksandr-nc oleksandr-nc merged commit 735c4c5 into main Apr 28, 2026
12 checks passed
@oleksandr-nc oleksandr-nc deleted the feature/cospend-projects-members-bills branch April 28, 2026 12:41
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.

1 participant