Skip to content

Commit cf5a339

Browse files
authored
Add Forms tools — full OCS v3 coverage (25 tools) (#47)
1 parent 9f39947 commit cf5a339

9 files changed

Lines changed: 1107 additions & 5 deletions

File tree

.github/workflows/tests-integration.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ jobs:
7272
$OCC "php occ app:install announcementcenter" || echo "announcementcenter already installed"
7373
$OCC "php occ app:install collectives" || echo "collectives already installed"
7474
$OCC "php occ app:install mail"
75+
$OCC "php occ app:install forms" || echo "forms already installed"
7576
SMTP4DEV_IP=$(docker inspect ${{ job.services.smtp4dev.id }} --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}')
7677
echo "smtp4dev IP: $SMTP4DEV_IP"
7778
$OCC "php occ mail:account:create admin 'Test Mail' test@localhost $SMTP4DEV_IP 143 none test test $SMTP4DEV_IP 25 none test test"

PROGRESS.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,15 @@
4040
- [x] upload_file_binary tool: base64-encoded binary upload with MIME inference (2026-04-21)
4141
- [x] upload_file_from_path tool: stream a local file to Nextcloud. Off by default; enabled via NEXTCLOUD_MCP_UPLOAD_ROOT, restricted to files inside that root (symlinks resolved) (2026-04-21)
4242
- [x] File Reminders tools: get_file_reminder, set_file_reminder, remove_file_reminder (2026-04-22)
43+
- [x] Forms tools: 25 tools covering forms, questions, options, shares, submissions CRUD + export (2026-04-23)
4344

4445
### In Progress
4546

4647
### Blocked
4748
(none)
4849

4950
### Next Up
50-
- Tables, Forms, Weather Status (all with full OCS coverage)
51+
- Weather Status (fully OCS). Tables skipped for now — OCS v2 API is incomplete (rows/columns/views require v1 REST).
5152

5253
## Phases
5354

@@ -94,7 +95,8 @@
9495
| State || 2 |
9596
| File Helpers || 26 |
9697
| File Reminders | 3 | 20 |
97-
| **Total** | **102** | **771** |
98+
| Forms | 25 | 34 |
99+
| **Total** | **127** | **805** |
98100

99101
Files shows 10, but one (`upload_file_from_path`) is only registered when
100-
`NEXTCLOUD_MCP_UPLOAD_ROOT` is configured. Default deployments expose 101 tools.
102+
`NEXTCLOUD_MCP_UPLOAD_ROOT` is configured. Default deployments expose 126 tools.

README.md

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ export NEXTCLOUD_PASSWORD=your-app-password
3030
nc-mcp-server
3131
```
3232

33-
## 101 Tools Across 21 Nextcloud Apps
33+
## 126 Tools Across 22 Nextcloud Apps
3434

35-
A 102nd tool, `upload_file_from_path`, is registered only when the operator sets
35+
A 127th tool, `upload_file_from_path`, is registered only when the operator sets
3636
`NEXTCLOUD_MCP_UPLOAD_ROOT`. See [Files](#files) for details.
3737

3838
| Category | Tools | Protocol |
@@ -56,6 +56,7 @@ A 102nd tool, `upload_file_from_path`, is registered only when the operator sets
5656
| [Tasks](#tasks) | list lists, CRUD tasks, complete | CalDAV |
5757
| [Mail](#mail) | accounts, mailboxes, messages, send | REST |
5858
| [Collectives](#collectives) | list, pages, create, trash, restore | REST |
59+
| [Forms](#forms) | CRUD forms, questions, options, shares, submissions + export | OCS |
5960
| [Unified Search](#unified-search) | list providers, search across apps | OCS |
6061
| [App Management](#app-management) | list, info, enable, disable apps | OCS |
6162

@@ -363,6 +364,36 @@ call; the body is streamed in chunks rather than loaded into memory.
363364
| `restore_collective` | write | Restore a collective from trash |
364365
| `restore_collective_page` | write | Restore a page from trash |
365366

367+
### Forms
368+
369+
| Tool | Permission | Description |
370+
|------|-----------|-------------|
371+
| `list_forms` | read | List forms (filter by ownership: "owned" or "shared"; omit to merge both) |
372+
| `get_form` | read | Get a form with questions, options, shares |
373+
| `list_questions` | read | List questions on a form |
374+
| `get_question` | read | Get a single question |
375+
| `list_submissions` | read | List submissions (owner only), with pagination and text filter |
376+
| `get_submission` | read | Get a single submission with answers |
377+
| `create_form` | write | Create an empty form or clone from an existing form |
378+
| `update_form` | write | Update form properties (title, access, state, maxSubmissions, etc.) |
379+
| `create_question` | write | Add a question (short, long, multiple, dropdown, date, file, grid, …) |
380+
| `update_question` | write | Update question properties |
381+
| `reorder_questions` | write | Reorder all questions on a form |
382+
| `create_options` | write | Add answer options to a choice question |
383+
| `update_option` | write | Update option text |
384+
| `reorder_options` | write | Reorder options within a question |
385+
| `create_form_share` | write | Share a form with user, group, circle, or link |
386+
| `update_form_share` | write | Update share permissions |
387+
| `submit_form` | write | Submit answers to a form |
388+
| `update_submission` | write | Edit an existing submission (requires allowEditSubmissions) |
389+
| `export_submissions` | write | Export submissions as a spreadsheet to a Nextcloud folder |
390+
| `delete_form` | destructive | Delete a form and all its content |
391+
| `delete_question` | destructive | Delete a question |
392+
| `delete_option` | destructive | Delete an option |
393+
| `delete_form_share` | destructive | Revoke a share |
394+
| `delete_submission` | destructive | Delete one submission |
395+
| `delete_all_submissions` | destructive | Delete every submission on a form |
396+
366397
### Unified Search
367398

368399
| Tool | Permission | Description |

src/nc_mcp_server/client.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,22 @@ async def ocs_patch(self, path: str, data: dict[str, Any] | None = None) -> Any:
271271
result: dict[str, Any] = response.json() # type: ignore[assignment]
272272
return result["ocs"]["data"]
273273

274+
async def ocs_patch_json(self, path: str, json_data: dict[str, Any] | None = None) -> Any:
275+
"""Make an OCS PATCH request with a JSON body and return the data portion."""
276+
url = f"{self._base_url}/ocs/v2.php/{path}"
277+
response = await self._do_request("PATCH", url, json=json_data or {})
278+
_raise_for_ocs_status(response, f"OCS PATCH {path}")
279+
result: dict[str, Any] = response.json() # type: ignore[assignment]
280+
return result["ocs"]["data"]
281+
282+
async def ocs_put_json(self, path: str, json_data: dict[str, Any] | None = None) -> Any:
283+
"""Make an OCS PUT request with a JSON body and return the data portion."""
284+
url = f"{self._base_url}/ocs/v2.php/{path}"
285+
response = await self._do_request("PUT", url, json=json_data or {})
286+
_raise_for_ocs_status(response, f"OCS PUT {path}")
287+
result: dict[str, Any] = response.json() # type: ignore[assignment]
288+
return result["ocs"]["data"]
289+
274290
# --- WebDAV ---
275291

276292
async def dav_propfind(self, path: str, depth: int = 1) -> list[dict[str, Any]]:

src/nc_mcp_server/server.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
comments,
1616
contacts,
1717
files,
18+
forms,
1819
mail,
1920
notifications,
2021
reminders,
@@ -63,6 +64,7 @@ def create_server(config: Config | None = None) -> FastMCP:
6364
comments.register(mcp)
6465
contacts.register(mcp)
6566
files.register(mcp)
67+
forms.register(mcp)
6668
mail.register(mcp)
6769
notifications.register(mcp)
6870
reminders.register(mcp)

0 commit comments

Comments
 (0)