Metadata-Version: 2.4
Name: nc-mcp-server
Version: 0.9.0
Summary: MCP server for Nextcloud — expose Nextcloud APIs as AI-usable tools
Author-email: Alexander Piskun <bigcat88@icloud.com>
License-Expression: MIT
Keywords: ai,mcp,model-context-protocol,nextcloud
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: icalendar>=7
Requires-Dist: mcp[cli]<2,>=1.20
Requires-Dist: niquests>=3
Provides-Extra: dev
Requires-Dist: black; extra == "dev"
Requires-Dist: isort; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Requires-Dist: pyright>=1.1; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: pytest-cov>=6; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Dynamic: license-file

# Nextcloud MCP Server

[![Lint](https://github.com/cloud-py-api/nc_mcp_server/actions/workflows/lint.yml/badge.svg)](https://github.com/cloud-py-api/nc_mcp_server/actions/workflows/lint.yml)
[![Unit Tests](https://github.com/cloud-py-api/nc_mcp_server/actions/workflows/tests-unit.yml/badge.svg)](https://github.com/cloud-py-api/nc_mcp_server/actions/workflows/tests-unit.yml)
[![Integration Tests](https://github.com/cloud-py-api/nc_mcp_server/actions/workflows/tests-integration.yml/badge.svg)](https://github.com/cloud-py-api/nc_mcp_server/actions/workflows/tests-integration.yml)
[![codecov](https://codecov.io/gh/cloud-py-api/nc_mcp_server/graph/badge.svg)](https://codecov.io/gh/cloud-py-api/nc_mcp_server)

![NextcloudVersion](https://img.shields.io/badge/Nextcloud-34%20%7C%2035-blue)
![PythonVersion](https://img.shields.io/badge/python-3.12%20%7C%203.13%20%7C%203.14-blue)
[![Python](https://img.shields.io/pypi/implementation/nc-mcp-server)](https://pypi.org/project/nc-mcp-server/)
[![PyPI](https://img.shields.io/pypi/v/nc-mcp-server.svg)](https://pypi.org/project/nc-mcp-server/)
[![License: MIT](https://img.shields.io/github/license/cloud-py-api/nc_mcp_server)](https://github.com/cloud-py-api/nc_mcp_server/blob/main/LICENSE)

> **Experimental** — This repository is fully maintained by AI (Claude). It serves as an experiment in autonomous AI-driven open-source development.

An [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server that exposes Nextcloud APIs as tools for AI assistants. Connect any MCP-compatible client (Claude Desktop, Claude Code, etc.) to your Nextcloud instance and let AI manage your files, calendar, contacts, conversations, and more.

## Quick Start

```bash
pip install nc-mcp-server
```

Set environment variables and connect:

```bash
export NEXTCLOUD_URL=https://your-nextcloud.example.com
export NEXTCLOUD_USER=your-username
export NEXTCLOUD_PASSWORD=your-app-password
nc-mcp-server
```

## 222 Tools Across 24 Nextcloud Apps

A 223rd tool, `upload_file_from_path`, is registered only when the operator sets
`NEXTCLOUD_MCP_UPLOAD_ROOT`. See [Files](#files) for details.

| Category | Tools | Protocol |
|----------|-------|----------|
| [Files](#files) | list, read, search, upload (text / binary / from path), copy, move, delete | WebDAV |
| [File Sharing](#file-sharing) | list, get, create, update, delete shares; accept, decline and leave shares from others | OCS |
| [Trashbin](#trashbin) | list, restore, delete item, empty trash | WebDAV |
| [File Versions](#file-versions) | list, restore versions | WebDAV |
| [File Comments](#file-comments) | list, add, edit, delete comments | WebDAV |
| [File Reminders](#file-reminders) | get, set, remove per-file reminders | OCS |
| [System Tags](#system-tags) | list, create, assign, unassign, delete tags | WebDAV |
| [Users](#users) | get current, list, get, create, update, enable/disable, delete users | OCS |
| [Groups](#groups) | list groups and members, create, delete groups | OCS |
| [User Status](#user-status) | get, set, clear status | OCS |
| [Notifications](#notifications) | list, dismiss one, dismiss all | OCS |
| [Activity](#activity) | activity feed with filters, search by file, time and user, daily counts | OCS |
| [Talk](#talk) | conversations, messages, threads, participants, edits, reactions, read state, shared items, pins, reminders, personal settings and tags, participant and conversation management | OCS |
| [Talk Polls](#talk-polls) | get, create, vote, close polls | OCS |
| [Announcements](#announcements) | list, create, delete announcements | OCS |
| [Calendar](#calendar) | list calendars, CRUD events | CalDAV |
| [Contacts](#contacts) | list address books, CRUD contacts | CardDAV |
| [Tasks](#tasks) | list lists, CRUD tasks, complete | CalDAV |
| [Mail](#mail) | accounts, mailboxes, messages, send, move, flags, tags | OCS + REST |
| [Collectives](#collectives) | list, pages, create, edit, move and copy, search, tags, attachments, public links, trash, restore | OCS |
| [Forms](#forms) | CRUD forms, questions, options, shares, submissions + export | OCS |
| [Circles (Teams)](#circles-teams) | list, CRUD, members (add/remove/promote), join/leave, search | OCS |
| [Cospend](#cospend) | shared expense tracking — projects, members, bills | OCS |
| [Unified Search](#unified-search) | list providers, search across apps | OCS |
| [App Management](#app-management) | list, info, enable, disable apps | OCS |
| [Flow](#flow) | list, create, update, delete automation rules; list what they can be built from | OCS |

## Security: Permission Model

Every tool has a required permission level. You control what the AI is allowed to do:

| Level | What it can do | Environment variable |
|-------|---------------|---------------------|
| `read` (default) | List files, read files, get users, view notifications | `NEXTCLOUD_MCP_PERMISSIONS=read` |
| `write` | Everything in `read` + upload files, send messages, create events | `NEXTCLOUD_MCP_PERMISSIONS=write` |
| `destructive` | Everything in `write` + delete files, remove shares, empty trash | `NEXTCLOUD_MCP_PERMISSIONS=destructive` |

If a tool is called without sufficient permission, it returns a clear error explaining what permission is needed — no silent failures, no accidental deletions.

## Installation

```bash
pip install nc-mcp-server
```

Or with `pipx` / `uvx` for isolated installation:
```bash
pipx install nc-mcp-server
# or
uvx nc-mcp-server
```

Or from source:
```bash
git clone https://github.com/cloud-py-api/nc_mcp_server.git
cd nc_mcp_server
pip install -e .
```

## Configuration

Set these environment variables:

```bash
# Required
export NEXTCLOUD_URL=https://your-nextcloud.example.com
export NEXTCLOUD_USER=your-username
export NEXTCLOUD_PASSWORD=your-app-password  # Use an app password, not your main password!

# Optional
export NEXTCLOUD_MCP_PERMISSIONS=read  # read (default), write, or destructive
export NEXTCLOUD_MCP_RETRY_MAX=3       # max retries on 429/503 (default: 3, 0 to disable)
export NEXTCLOUD_MCP_UPLOAD_ROOT=      # unset (default). If set to an absolute directory,
                                       # enables upload_file_from_path, restricted to files
                                       # inside that directory (symlinks resolved).
```

### Getting an App Password

1. Log into your Nextcloud instance
2. Go to **Settings** > **Security**
3. Under "Devices & sessions", create a new app password
4. Use this password for `NEXTCLOUD_PASSWORD`

Since Nextcloud 34.0.1 an app-password session never counts as password-confirmed, so with an app password the
admin tools Nextcloud guards with password confirmation (`create_user`, `update_user`, `set_user_enabled`,
`delete_user`, `create_group`, `delete_group`, `enable_app`, `disable_app`) fail with "Password confirmation is
required". To use them, allow the MCP server's IP address in `config.php` (Nextcloud 34.0.3 and newer), e.g.
`'allowed_no_password_confirmation_ranges' => ['192.0.2.10/32']`. With the account's login password they work
without that: when Nextcloud asks for a confirmation, the server repeats the request as a fresh login. Accounts
with two-factor authentication cannot log in with their password here, so they need an app password and the
exemption.

## Usage

### With Claude Desktop

Add to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "nextcloud": {
      "command": "nc-mcp-server",
      "env": {
        "NEXTCLOUD_URL": "https://your-nextcloud.example.com",
        "NEXTCLOUD_USER": "your-username",
        "NEXTCLOUD_PASSWORD": "your-app-password",
        "NEXTCLOUD_MCP_PERMISSIONS": "read"
      }
    }
  }
}
```

### With Claude Code

```bash
claude mcp add nextcloud \
  -e NEXTCLOUD_URL=https://your-nextcloud.example.com \
  -e NEXTCLOUD_USER=your-username \
  -e NEXTCLOUD_PASSWORD=your-app-password \
  -e NEXTCLOUD_MCP_PERMISSIONS=read \
  -- nc-mcp-server
```

### As HTTP Server (for containers/remote)

```bash
nc-mcp-server --transport http
# Listens on http://0.0.0.0:8100 by default
```

### Stdio Mode (default)

```bash
nc-mcp-server
# Communicates via stdin/stdout — used by MCP clients like Claude Desktop
```

## Available Tools

### Files

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_directory` | read | List files and folders in a directory |
| `get_file` | read | Read a file's content (returns images as MCP ImageContent) |
| `search_files` | read | Search files by name, MIME type, or path pattern |
| `upload_file` | write | Upload or overwrite a text file |
| `upload_file_binary` | write | Upload or overwrite a binary file (images, PDFs, archives) from base64-encoded content |
| `upload_file_from_path` | write | Stream a local file from the server's filesystem — only registered when `NEXTCLOUD_MCP_UPLOAD_ROOT` is set |
| `create_directory` | write | Create a new directory |
| `copy_file` | write | Copy a file or directory |
| `move_file` | destructive | Move or rename a file |
| `delete_file` | destructive | Delete a file or directory (moves to trash) |

`upload_file_from_path` is off by default because it gives the AI read access
to the local filesystem. To enable it, set `NEXTCLOUD_MCP_UPLOAD_ROOT` to an
absolute directory — only files resolving inside that directory (after symlink
resolution) can be uploaded. This is the right choice when you need to upload
multi-GB files that would blow past the size limit of an inline `base64` tool
call; the body is streamed in chunks rather than loaded into memory.

### File Sharing

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_shares` | read | List shares for a file/folder, all your shares, or the shares others gave you (federated included) |
| `get_share` | read | Get details of a specific share |
| `list_pending_shares` | read | List shares offered to you that wait to be accepted, from this server and federated |
| `create_share` | write | Share a file/folder (user, group, public link, email) |
| `update_share` | write | Update share permissions, expiration, password, etc. |
| `accept_share` | write | Accept a pending share |
| `delete_share` | destructive | Remove a share, or leave one you received |
| `decline_share` | destructive | Decline a pending share |

### Trashbin

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_trash` | read | List deleted files in the trash bin |
| `restore_trash_item` | write | Restore a file from trash to its original location |
| `delete_trash_item` | destructive | Permanently delete a single item from trash |
| `empty_trash` | destructive | Permanently delete all items in trash |

### File Versions

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_versions` | read | List version history of a file |
| `restore_version` | write | Restore a previous version of a file |

### File Comments

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_comments` | read | List comments on a file |
| `add_comment` | write | Add a comment to a file |
| `edit_comment` | write | Edit an existing comment |
| `delete_comment` | destructive | Delete a comment |

### File Reminders

| Tool | Permission | Description |
|------|-----------|-------------|
| `get_file_reminder` | read | Get the reminder set on a file (null if none) |
| `set_file_reminder` | write | Set or replace a reminder due date (ISO 8601, must be in the future) |
| `remove_file_reminder` | destructive | Remove the reminder from a file |

### System Tags

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_tags` | read | List all available tags |
| `get_file_tags` | read | Get tags assigned to a file |
| `create_tag` | write | Create a new tag |
| `assign_tag` | write | Assign a tag to a file |
| `unassign_tag` | destructive | Remove a tag from a file |
| `delete_tag` | destructive | Delete a tag |

### Users

| Tool | Permission | Description |
|------|-----------|-------------|
| `get_current_user` | read | Get current authenticated user info |
| `list_users` | read | List or search users |
| `get_user` | read | Get specific user details |
| `create_user` | write | Create a new user (admin only) |
| `update_user` | write | Change display name, email, password, quota, language, manager, groups and sub-admin groups in one call (Nextcloud 34+) |
| `set_user_enabled` | write | Enable or disable a user account (admin or sub-admin); disabling needs `destructive` |
| `delete_user` | destructive | Delete a user (admin only) |

`update_user` has Nextcloud validate every field before applying any of them. Users can change their own
display name, email, language and password with it; Nextcloud 34 and 35 only accept the underlying call from
admins and sub-admins, so for a regular user's own account the tool sets those fields one at a time instead,
without that all-or-nothing check. Passing `password`, `groups` or `subadmin_groups` needs the `destructive`
level, as does disabling an account with `set_user_enabled`.

### Groups

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_groups` | read | List or search groups with member counts (admin) |
| `list_group_members` | read | List the users in a group (admins, the group's sub-admins and members) |
| `create_group` | write | Create a group (admin only) |
| `delete_group` | destructive | Delete a group (admin only) |

### User Status

| Tool | Permission | Description |
|------|-----------|-------------|
| `get_user_status` | read | Get a user's status (online, away, dnd, etc.) |
| `set_user_status` | write | Set your status and custom message |
| `clear_user_status` | destructive | Clear your status |

### Notifications

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_notifications` | read | List all notifications |
| `dismiss_notification` | write | Dismiss a single notification |
| `dismiss_all_notifications` | write | Dismiss all notifications |

### Activity

| Tool | Permission | Description |
|------|-----------|-------------|
| `get_activity` | read | View recent activity with filtering, sorting, and pagination; search by file path, time range and user (Nextcloud 35) |
| `list_activity_filters` | read | List the activity filters this server offers |
| `get_activity_counts` | read | Count activities per day over the last days (Nextcloud 35) |

### Talk

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_conversations` | read | List all Talk conversations |
| `get_conversation` | read | Get conversation details |
| `get_messages` | read | Get messages from a conversation, or from one thread |
| `get_participants` | read | List participants in a conversation |
| `list_threads` | read | List the most recently active threads in a conversation |
| `get_thread` | read | Get a thread's title, reply count and first/last message |
| `list_subscribed_threads` | read | List the threads you follow across all conversations |
| `get_message_context` | read | Get the messages before and after one message |
| `get_reactions` | read | List who reacted to a message, and with what |
| `list_shared_items` | read | List files, media, polls, locations and more shared in a conversation |
| `search_mentions` | read | Find who can be mentioned, with the text to put in a message |
| `list_message_reminders` | read | List your upcoming message reminders |
| `list_conversation_tags` | read | List your personal conversation tags |
| `list_conversation_presets` | read | List the presets create_conversation can start from |
| `send_message` | write | Send a message; can start a thread or post into one |
| `create_conversation` | write | Create a one-to-one, group or public conversation, optionally from a preset |
| `update_conversation` | write | Rename, describe, lock (read-only) or open (public) a conversation (moderators); making it private needs `destructive`; owners can preserve it (Talk 25+) |
| `add_participant` | write | Add a user, group, team, email guest or federated user (moderators) |
| `set_participant_role` | write | Make a participant owner (Talk 25+), moderator or user |
| `rename_thread` | write | Rename a thread |
| `set_thread_notification_level` | write | Set your notification level for a thread |
| `edit_message` | write | Edit a message (own ones, or any as a moderator of a group conversation; within 24 hours) |
| `add_reaction` | write | React to a message with an emoji |
| `mark_conversation_read` | write | Mark a conversation read, fully or up to a message |
| `mark_conversation_unread` | write | Mark the last message unread again |
| `set_conversation_preferences` | write | Your own settings: favorite, archived, important, sensitive, message and call notifications |
| `pin_message` | write | Pin a message for everyone, optionally until a time (moderators) |
| `set_message_reminder` | write | Get a notification about a message later |
| `create_conversation_tag` | write | Create a personal conversation tag |
| `rename_conversation_tag` | write | Rename a conversation tag |
| `set_conversation_tags` | write | Set which of your tags a conversation has |
| `delete_message` | destructive | Delete a message |
| `leave_conversation` | destructive | Leave a conversation |
| `remove_participant` | destructive | Remove someone from a conversation (moderators) |
| `delete_conversation` | destructive | Delete a conversation for everyone (moderators; one-to-one ones can only be left) |
| `remove_reaction` | destructive | Take back your reaction to a message |
| `unpin_message` | destructive | Unpin a message for everyone, or hide it only for you |
| `remove_message_reminder` | destructive | Cancel a message reminder |
| `delete_conversation_tag` | destructive | Delete a conversation tag |

The thread tools need a Talk version that advertises the `threads` capability (Talk 22, which
ships with Nextcloud 32, and newer), so every Nextcloud release supported here has them.
A thread ID is the message ID of the thread's first message.

Messages read back with their mentions and shared objects filled in ("@Jane Doe", "report.pdf")
instead of the placeholders Talk stores (`{mention-user1}`, `{file}`).

### Talk Polls

| Tool | Permission | Description |
|------|-----------|-------------|
| `get_poll` | read | Get poll details and results |
| `create_poll` | write | Create a poll in a conversation |
| `vote_poll` | write | Vote on a poll |
| `close_poll` | write | Close a poll |

### Announcements

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_announcements` | read | List announcements |
| `create_announcement` | write | Create an announcement |
| `delete_announcement` | destructive | Delete an announcement |

### Calendar

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_calendars` | read | List user's calendars |
| `get_events` | read | Get events from a calendar (with date filtering) |
| `get_event` | read | Get a single event by UID |
| `create_event` | write | Create a calendar event |
| `update_event` | write | Update an event (partial updates supported) |
| `delete_event` | destructive | Delete a calendar event |

### Contacts

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_addressbooks` | read | List user's address books |
| `get_contacts` | read | Get contacts with pagination |
| `get_contact` | read | Get a single contact by UID |
| `create_contact` | write | Create a contact (multi-value email/phone supported) |
| `update_contact` | write | Update a contact (ETag concurrency control) |
| `delete_contact` | destructive | Delete a contact |

### Tasks

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_task_lists` | read | List task lists (CalDAV VTODO collections) |
| `get_tasks` | read | List tasks in a list (with status/completed filters) |
| `get_task` | read | Get a single task by UID |
| `create_task` | write | Create a task (due date, priority, categories, etc.) |
| `update_task` | write | Update a task (partial updates supported) |
| `complete_task` | write | Mark a task as completed |
| `delete_task` | destructive | Delete a task |

### Mail

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_mail_accounts` | read | List mail accounts |
| `list_mailboxes` | read | List mailboxes (folders) for an account |
| `list_mail_messages` | read | List messages in a mailbox |
| `get_mail_message` | read | Get full message content |
| `send_mail` | write | Send an email |
| `move_mail_message` | write | Move a message to another mailbox of the same account (its ID changes) |
| `set_mail_message_flags` | write | Mark as read/unread, starred, answered |
| `create_mail_tag` | write | Create a tag, or get the existing one with the same label |
| `add_mail_message_tag` | write | Tag a message |
| `remove_mail_message_tag` | write | Remove a tag from a message |

### Collectives

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_collectives` | read | List all collectives |
| `get_collective_pages` | read | List pages in a collective |
| `get_collective_page` | read | Get a page's content |
| `search_collective_pages` | read | Search the text of a collective's pages |
| `list_recent_collective_pages` | read | List the most recently changed pages across collectives |
| `list_collective_tags` | read | List a collective's page tags |
| `list_collective_page_attachments` | read | List the files attached to a page |
| `list_collective_shares` | read | List your public links to a collective and its pages |
| `create_collective` | write | Create a new collective |
| `create_collective_page` | write | Create a page in a collective, optionally with its text |
| `update_collective_page` | write | Change a page's text, title or emoji |
| `move_collective_page` | write | Move or copy a page under another page, also into another collective |
| `create_collective_tag` | write | Create a page tag |
| `update_collective_tag` | write | Rename a tag or change its color |
| `set_collective_page_tags` | write | Set which tags a page has |
| `share_collective` | write | Create a public link to a collective or one page, optionally editable and with a password |
| `update_collective_share` | write | Change a public link's editing and password |
| `trash_collective` | destructive | Move a collective to trash |
| `delete_collective` | destructive | Permanently delete a trashed collective, optionally with its team |
| `trash_collective_page` | destructive | Move a page to trash |
| `delete_collective_page` | destructive | Permanently delete a trashed page |
| `delete_collective_tag` | destructive | Delete a tag, taking it off its pages |
| `delete_collective_share` | destructive | Remove a public link |
| `restore_collective` | write | Restore a collective from trash |
| `restore_collective_page` | write | Restore a page from trash |

### Forms

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_forms` | read | List forms (filter by ownership: "owned" or "shared"; omit to merge both) |
| `get_form` | read | Get a form with questions, options, shares |
| `list_questions` | read | List questions on a form |
| `get_question` | read | Get a single question |
| `list_submissions` | read | List submissions (owner only), with pagination and text filter |
| `get_submission` | read | Get a single submission with answers |
| `create_form` | write | Create an empty form or clone from an existing form |
| `update_form` | write | Update form properties (title, access, state, maxSubmissions, etc.) |
| `create_question` | write | Add a question (short, long, multiple, dropdown, date, file, grid, …) |
| `update_question` | write | Update question properties |
| `reorder_questions` | write | Reorder all questions on a form |
| `create_options` | write | Add answer options to a choice question |
| `update_option` | write | Update option text |
| `reorder_options` | write | Reorder options within a question |
| `create_form_share` | write | Share a form with user, group, circle, or link |
| `update_form_share` | write | Update share permissions |
| `submit_form` | write | Submit answers to a form |
| `update_submission` | write | Edit an existing submission (requires allowEditSubmissions) |
| `export_submissions` | write | Export submissions as a spreadsheet to a Nextcloud folder |
| `delete_form` | destructive | Delete a form and all its content |
| `delete_question` | destructive | Delete a question |
| `delete_option` | destructive | Delete an option |
| `delete_form_share` | destructive | Revoke a share |
| `delete_submission` | destructive | Delete one submission |
| `delete_all_submissions` | destructive | Delete every submission on a form |

### Circles (Teams)

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_circles` | read | List circles the current user can see |
| `get_circle` | read | Get a single circle including the current user's membership |
| `list_circle_members` | read | List members of a circle |
| `search_circles` | read | Search circles and candidate members (users/groups/mail) by term |
| `create_circle` | write | Create a circle; caller becomes owner |
| `update_circle_name` | write | Rename a circle |
| `update_circle_description` | write | Update description |
| `update_circle_config` | write | Update config bitmask (VISIBLE, OPEN, INVITE, HIDDEN, etc.) |
| `add_circle_member` | write | Add a user, group, email, or nested circle as a member |
| `update_circle_member_level` | write | Promote/demote a member (member/moderator/admin/owner) |
| `join_circle` | write | Join an open circle |
| `leave_circle` | write | Leave a circle |
| `delete_circle` | destructive | Delete a circle |
| `remove_circle_member` | destructive | Kick a member |

### Cospend

Shared expense tracking ("who paid for what"). Requires the [Cospend](https://apps.nextcloud.com/apps/cospend) app to be installed and enabled. All routes are OCS at `/ocs/v2.php/apps/cospend/api/v1/`.

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_cospend_projects` | read | List projects the user can access |
| `get_cospend_project` | read | Get full project info (members, balance, shares, settings) |
| `get_cospend_project_statistics` | read | Per-member spending stats (paid/spent/balance) with filters |
| `get_cospend_project_settlement` | read | Suggested reimbursement transactions to settle a project |
| `list_cospend_members` | read | List members of a project |
| `list_cospend_bills` | read | List bills with filters (payer, category, search, pagination) |
| `get_cospend_bill` | read | Get a single bill |
| `create_cospend_project` | write | Create a project (caller becomes ADMIN) |
| `update_cospend_project` | write | Update project name, currency, sort, archive, etc. |
| `create_cospend_member` | write | Add a member (free-form name or linked to a Nextcloud user) |
| `update_cospend_member` | write | Update name/weight/color/activated/userid |
| `create_cospend_bill` | write | Create a bill (defaults date to today if neither date nor timestamp set) |
| `update_cospend_bill` | write | Update any bill field |
| `delete_cospend_project` | destructive | Delete a project and all its data |
| `delete_cospend_member` | destructive | Delete (or soft-disable if member has bills) |
| `delete_cospend_bill` | destructive | Delete a bill (default: trash; pass `move_to_trash=False` to purge) |

### Unified Search

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_search_providers` | read | List available search providers (files, mail, talk, etc.) |
| `unified_search` | read | Search across one or more providers with pagination |

### App Management

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_apps` | read | List installed apps |
| `get_app_info` | read | Get detailed app information |
| `enable_app` | write | Enable an app (admin only) |
| `disable_app` | destructive | Disable an app (admin only) |

### Flow

| Tool | Permission | Description |
|------|-----------|-------------|
| `list_flows` | read | List Flow rules of the user or (admin) global scope |
| `get_flow_options` | read | List the operations, entities, events and checks a rule can use, with operators and value formats for the built-in checks |
| `create_flow` | write | Create a rule; global rules need `destructive` |
| `update_flow` | write | Change a rule's name, checks, settings or events; global rules need `destructive` |
| `delete_flow` | destructive | Delete a rule |

The available operations depend on the installed apps (Talk adds "Write to conversation", for example), and
Nextcloud has no API that lists them, so `get_flow_options` reads them from the Flow settings page. Global
rules act on every user's files, which is why they need the `destructive` level. Rules that would run a command
or command-line arguments of the agent's choosing on the server (the workflow_script app's operation, or
workflow_ocr with custom ocrmypdf arguments) are refused at any level.

## Development

```bash
# Clone and install
git clone https://github.com/cloud-py-api/nc_mcp_server.git
cd nc_mcp_server
python3 -m venv venv && source venv/bin/activate
pip install -e ".[dev]"

# Run tests
pytest                              # Unit tests
pytest tests/integration/ -v        # Integration tests (needs running Nextcloud)

# Lint & type check
ruff check . && ruff format --check .
pyright
```

### Integration Tests

Integration tests run against a real Nextcloud instance. Set the environment variables and run:

```bash
export NEXTCLOUD_URL=http://localhost:8080
export NEXTCLOUD_USER=admin
export NEXTCLOUD_PASSWORD=admin
pytest tests/integration/ -v
```

CI runs the integration tests against Nextcloud 34 and 35 using the official Docker images.

## About This Project

This project is an experiment in AI-autonomous open-source development. The entire codebase — including this README — is written and maintained by Claude (Anthropic's AI assistant). Human oversight is limited to:

- High-level design decisions
- Code review of pull requests
- Resolving architectural questions

The goal is to explore how far autonomous AI development can go in building production-quality, well-tested software.
