Skip to main content
Glama
README.md
# tasks-mcp

A [Model Context Protocol](https://modelcontextprotocol.io) server for [Nextcloud Tasks](https://github.com/nextcloud/tasks) — exposes task lists, tasks, subtasks and tags to Claude and any MCP-compatible client.

## How it works

The Nextcloud Tasks app ships **no REST or OCS API**. It is a Vue front end over Nextcloud's existing calendar backend, so every task is an iCalendar `VTODO` and every operation is CalDAV: `PROPFIND` to discover the calendar home, `REPORT` to query, `PUT` and `DELETE` on individual `.ics` objects.

That makes this server structurally different from the Notes and Collectives ones. It carries its own namespace-aware XML reader for multistatus responses — prefixes are chosen per response by sabre/dav, `propstat` blocks are per-status, and `calendar-data` arrives as XML-escaped iCalendar, none of which survives a regex. [ical.js](https://github.com/kewisch/ical.js) handles the iCalendar layer.

Details and the behaviours that are not in the published specs are in [ENDPOINTS.md](ENDPOINTS.md).

## Tools exposed (11)

- **Tasks:** `list_tasks`, `get_task`, `create_task`, `update_task`, `delete_task`, `move_task`
- **Completion:** `complete_task`, `uncomplete_task`
- **Organisation:** `list_task_lists`, `list_tags`
- **Other:** `ping`

Every tool declares MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so clients can distinguish a read from an irreversible delete without parsing descriptions.

### Updates never clobber what they do not touch

`update_task` edits the parsed `VTODO` in place rather than rebuilding it. Recurrence rules, reminders, attendees, and any X-property written by DAVx5, Apple Reminders or Thunderbird pass through a write untouched — nothing removes what it does not recognise. This is what makes the server safe to point at a task list you also sync to a phone.

Only the fields you pass are changed. Passing `null` clears a field, which is a different request from omitting it: "no due date given" and "remove the due date" are both expressible.

### Safety rails

The server refuses rather than guesses where guessing would be silent:

- **An ambiguous uid.** UIDs are per-list, so searching every list can find more than one — an interrupted `move_task` deliberately leaves two copies. Rather than let list ordering decide which task an update or delete lands on, tools report the matching lists and ask you to name one.
- **Dates that do not exist.** `2026-02-30`, `2026-13-01` and `25:00:00` are rejected, not quietly normalised to a different date. So is a wall-clock time a zone skips for daylight saving.
- **Contradictory dates.** RFC 5545 requires `DUE` and `DTSTART` to share a value type with `DUE` later than `DTSTART`, and forbids `DUE` alongside `DURATION`. Combinations that break those rules are rejected before the write, when an edit touched a date — a task another client already wrote in an invalid state stays fully editable in every other respect.
- **Timezones with nothing to qualify.** `dueTimezone` without `due` would change nothing while still bumping the task's revision, so it is an error rather than a silent no-op.
- **Redirects off the configured origin.** Node's `fetch` follows redirects anywhere by default, which would let a compromised endpoint point this process at `localhost` or a metadata service and return the response through tool output. Only same-origin redirects are followed, plus an http→https upgrade on the same host.

### Concurrency

`get_task` returns the task's `etag`. Passing it back to `update_task`, `complete_task` or `delete_task` makes the write conditional — if the task changed on the server in the meantime, the write is refused. Unlike the Notes API, a CalDAV 412 carries no body, so the error says how to get the current state rather than pretending to carry it.

### Dates

iCalendar distinguishes a whole day from an instant, and both from a floating local time. Flattening them into one UTC timestamp is the standard way to move an all-day task onto the wrong day, so the distinction is preserved in both directions:

| You pass | Stored as |
| --- | --- |
| `"2026-03-01"` | a whole day (`VALUE=DATE`) |
| `"2026-03-01T14:30:00Z"` | a UTC instant |
| `"2026-03-01T14:30:00"` + `dueTimezone: "Europe/London"` | the equivalent UTC instant |
| `"2026-03-01T14:30:00"` alone | a floating time |

A zoned time is converted to UTC rather than written with a `TZID`, because a `TZID` is only valid alongside a matching `VTIMEZONE` and ical.js cannot generate one for an arbitrary zone. The UTC instant is exact and every client renders it in the reader's own zone.

### Subtasks

Subtasks are `RELATED-TO;RELTYPE=PARENT` links. Set `parentUid` on create or update; `list_tasks` with `nest: true` returns the tree. Two constraints come from the format rather than from this server:

- A parent must be in the **same list** as its subtask — `RELATED-TO` names a UID with no collection qualifier, so the link does not resolve across lists. `move_task` moves one task, so move a parent and its subtasks together if you want the hierarchy to survive.
- `delete_task` does **not** cascade. Subtasks are left in place and reported as `orphanedSubtasks`, so nothing is destroyed that was not named.

Re-parenting is checked for cycles, and a task cannot be its own parent.

### Repeating tasks

`complete_task` refuses a task that repeats — by `RRULE`, by explicit `RDATE`s, or both. Completing a repeating task has to advance it to its next occurrence; writing `STATUS:COMPLETED` onto the master component instead ends the series permanently, and the occurrences still to come cannot be recovered from it. Recurrence is reported on every task as `recurrenceRule` and preserved through every write — it is just not editable here. Complete or edit repeating tasks in the Tasks UI.

## Install

There is no published npm package. Install the release tarball, which puts the `tasks-mcp` command on your `PATH`:

```bash
# Download tasks-mcp-<version>.tgz from the latest release, then:
npm install -g ./tasks-mcp-<version>.tgz
```

The asset is attached to each [release](https://github.com/megamaced/nc_tasks-mcp/releases/latest).

To build it yourself instead, either pack the same tarball:

```bash
corepack pnpm install
corepack pnpm pack:tarball
npm install -g ./tasks-mcp-<version>.tgz
```

or skip the global install and point the client at the built entry point:

```bash
corepack pnpm install
corepack pnpm build
```

## Configuration

Add to your MCP client config (Claude Code shown). After a global install:

```json
{
  "mcpServers": {
    "tasks": {
      "command": "tasks-mcp",
      "args": [],
      "env": {
        "NEXTCLOUD_URL": "https://your-nextcloud.example.com",
        "NEXTCLOUD_USER": "your-username",
        "NEXTCLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx-xxxx"
      }
    }
  }
}
```

Or, running from the build directory, with an absolute path to `dist/index.js`:

```json
{
  "mcpServers": {
    "tasks": {
      "command": "node",
      "args": ["/absolute/path/to/nc_tasks-mcp/dist/index.js"],
      "env": {
        "NEXTCLOUD_URL": "https://your-nextcloud.example.com",
        "NEXTCLOUD_USER": "your-username",
        "NEXTCLOUD_APP_PASSWORD": "xxxx-xxxx-xxxx-xxxx-xxxx"
      }
    }
  }
}
```

**Generate the app-password** in Nextcloud under Settings > Security > Devices & sessions > "Create new app password". The MCP server only needs an app-password, never your real account password — and you can revoke it without affecting your main login.

## Development

```bash
corepack pnpm install
corepack pnpm dev        # stdio MCP server, point mcp inspector at it
corepack pnpm test       # deterministic unit tests, no Nextcloud required
corepack pnpm lint
corepack pnpm typecheck
corepack pnpm build      # tsc -> dist/
```

Required env vars: `NEXTCLOUD_URL`, `NEXTCLOUD_USER`, `NEXTCLOUD_APP_PASSWORD`.

Optional:

| Variable | Default | Purpose |
| --- | --- | --- |
| `NEXTCLOUD_DEFAULT_TASK_LIST` | unset | List used when a tool needs one and the caller omits it. Matched on uri, then display name. Unnecessary if the account has only one list. |
| `NEXTCLOUD_TIMEOUT_MS` | `60000` | Per-request deadline. Must be a whole number of milliseconds, at most 2147483647. |
| `NEXTCLOUD_MAX_RESPONSE_BYTES` | `20971520` | Largest response body buffered. A `calendar-query` returns every matching task's full iCalendar body in one document, so this scales with list size rather than page size. |
| `DEBUG` | unset | Log each request to stderr. |

## Disclosure

This project was 100% written by AI (Claude), including all source code, tests, CI configuration, and documentation.

## License

MIT — see [LICENSE](LICENSE).

## Related

- [Nextcloud Tasks](https://github.com/nextcloud/tasks)
- [RFC 5545](https://datatracker.ietf.org/doc/html/rfc5545) (iCalendar) and [RFC 4791](https://datatracker.ietf.org/doc/html/rfc4791) (CalDAV)
- [nc_notes-mcp](https://github.com/megamaced/nc_notes-mcp) — the same approach for Nextcloud Notes
- [nc_collectives-mcp](https://github.com/megamaced/nc_collectives-mcp) — the same approach for Nextcloud Collectives
- [Model Context Protocol](https://modelcontextprotocol.io)

TDQS

A4.5/5.0

Scored across 11 tools

Disambiguation5/5

Each tool targets a distinct action on tasks or task lists: ping for connectivity, CRUD operations, state changes (complete/uncomplete), moving, and tag listing. No two tools overlap in purpose, and descriptions clarify any potential confusion (e.g., complete_task vs. update_task).

Naming Consistency5/5

All tool names follow a consistent verb_noun snake_case pattern: list_task_lists, create_task, delete_task, etc. The only deviation is 'ping', which is a conventional standalone verb but fits the pattern of a simple connectivity check. No mixed conventions or vague verbs.

Tool Count5/5

With 11 tools, the server is well-scoped for task management: it covers connectivity, list retrieval, task CRUD, lifecycle transitions, moving, and tag utilities. The count is within the ideal range and each tool earns its place without redundancy.

Completeness5/5

The tool surface provides comprehensive coverage of task management: create, read, update, delete, complete/uncomplete, move, and list/filter. The inclusion of tags and the detailed handling of recurrence and subtasks in descriptions address edge cases, leaving no obvious dead ends for agents.

Maintenance

ActivityMaintained
ResponsivenessResponsive