# MCP

init.Tasks speaks [MCP](https://modelcontextprotocol.io) at `https://mcp.inittasks.com/mcp`, so an AI client can work with a user's tasks directly — the same data, the same scopes, the same revocation.

// It is the same server and the same encryption as the REST API. An MCP client is just another OAuth app.

## Connect

Most clients take a URL. In Claude Code:

```bash
claude mcp add --transport http init-tasks https://mcp.inittasks.com/mcp
```

In a client that reads a JSON config:

```json
{
  "mcpServers": {
    "init-tasks": {
      "type": "http",
      "url": "https://mcp.inittasks.com/mcp"
    }
  }
}
```

Then the client opens the approval flow. **You approve on your phone**, not in the client's browser window: init.Tasks shows a nine-digit code, you open the app on a device you already trust, and confirm there. No password is typed into anything but the app itself.

## Read-only by default

A connection asks for read access unless you grant more. 12 of the 39 tools work read-only; the rest need write access, and the server refuses them without it — the refusal is enforced server-side, not left to the client to respect.

Revoke any time in **Settings → account → encryption & devices → connected apps**. A password change or an encryption-key change disconnects everything at once.

## The tools

| tool | read-only | what it does |
|---|---|---|
| `archive_inbox_item` | [ ] | Archive an inbox item (hides it from the inbox). |
| `capture_inbox` | [ ] | Quick-add capture into the inbox (v1.2). Kind (text/url) is auto-detected. For a text capture the trailing date phrase is parsed via the canonical quick-add grammar ("call maria tomorrow", "renew passport 7 oct") — the remainder becomes the item text and the parsed date is PERSISTED on the item, ready to ride onto the to-do when it is later moved into a project. A url fires the title fetcher best-effort. |
| `complete_todo` | [ ] | Mark a to-do done (idempotent). A recurring active occurrence runs the spec/60 transition — a done copy is created and the active row advances to its next occurrence. Resolve by id or exact title. |
| `create_area` | [ ] | Create a top-level area. |
| `create_filter` | [ ] | Create a saved filter (v1.4). `criteria` is { v:1, all:[ …criterion ] }, AND-composed. A criterion is one of: {kind:"tag",name} · {kind:"priority",min:"low"\|"medium"\|"high"} (matches priority ≥ min) · {kind:"deadline",withinDays:0..365} (deadline ≤ today+N, overdue included) · {kind:"deadline",overdue:true} · {kind:"scheduled",state:"dated"\|"anytime"\|"someday"} · {kind:"where",containerId} (the to-do’s ancestor chain contains that area/project/subproject id). An unknown/newer-version criterion is rejected here (only stored filters may be inert). Returns the filter and its match count. |
| `create_note` | [ ] | v1.6: create a standalone NOTE in the inbox — content the user means to KEEP, as opposed to a scrap awaiting triage. Stored as tasks_inbox.kind='note' with the body in `text` and an optional heading in `title` (spec/10 "Notes"), so it lives alongside attachments and inherits the whole inbox pipeline. ⛔ A note is NOT a to-do: it has no done state, never appears in a date view, and counts only toward `inbox`. Kind is never auto-detected for notes — use capture_inbox for ordinary text/url captures. |
| `create_project` | [ ] | Create a project. area is resolved by name or id; omit for an area-less project. color = #RRGGBB. priority = low\|medium\|high (display-only ! mark, v1.2). |
| `create_subproject` | [ ] | Create a subproject inside a project (resolved by name or id). priority = low\|medium\|high (display-only ! mark, v1.2). |
| `create_todo` | [ ] | Create a to-do. Parent (project or subproject) is resolved by exact name (case-insensitive) or id; omit for a standalone to-do. when/deadline accept natural language ("tomorrow", "fri", "7 oct") or yyyy-MM-dd. Pass `repeat` to make it recurring (spec/60 — clears deadline, stamps whenDate/anchor). A repeat rule takes an optional `mode` ("schedule", the default fixed grid, or "afterCompletion", where the next date counts from when you complete it — afterCompletion carries ONLY freq+interval), an optional `until` (strict yyyy-MM-dd, the last date an occurrence may fall on, inclusive) and/or an optional `count` (occurrences remaining incl. the active one, 1…999, decremented on each completion). Setting a repeat with no when in schedule mode seeds the first occurrence date; in afterCompletion mode it seeds when=today. `reminder` is a local `yyyy-MM-ddTHH:mm`. |
| `delete_container` | [ ] | Alias of trash_container — moves a container subtree to trash (v1.2; recoverable). For an irreversible hard delete use permanent_delete. |
| `delete_filter` | [ ] | Delete a saved filter (v1.4) — HARD delete, no trash, no confirm (a config row like a tag; cheap to recreate, and the to-dos it matched are untouched). Resolve by id or exact name. |
| `delete_todo` | [ ] | Alias of trash_todo — moves a to-do to trash (v1.2; recoverable). For an irreversible hard delete use permanent_delete. |
| `empty_trash` | [ ] | Permanently delete EVERY trashed root and its subtree (v1.2) — the irreversible hard-delete cascade for the whole trash. Destructive — requires confirm: true. |
| `get_anytime` | [✓] | Anytime (v1.2, narrowed in v1.6): open to-dos with no scheduled date (whenDate null) that ARE filed under a project/subproject. A deadline may exist and the row still lists here (deadline alone never schedules a to-do). someday to-dos are excluded. v1.6: an UNFILED to-do (no parent AND no date) is NOT here — it belongs to the inbox; see get_inbox. |
| `get_filter` | [✓] | A saved filter’s VIEW (v1.4): the matched to-dos with their count, in the anytime view’s order (tree-position-then-sortKey, standalone last). Universe = open + someday visible to-dos matching ALL criteria; done never matches (the logbook owns history). An INERT filter (criteria from a newer version) returns count 0, no matches, and a muted note. Resolve by id or exact name. |
| `get_inbox` | [✓] | The inbox = the triage queue (v1.6): UNFILED to-dos first (open, no parent AND no date), then open capture rows in capture order. `openCount` is the nav count over both. A to-do leaves the inbox the moment it gets a parent or a date — there is no inbox flag to set. |
| `get_logbook` | [✓] | The completed-history logbook (v1.4): every done to-do (non-trashed, with a completedAt), grouped by the LOCAL calendar day it was completed, newest day first, and completedAt-descending within a day. Each day carries its date, a label, a count and the to-dos. Recurring done copies appear like any done to-do; inbox items never appear. Unbounded — no nav count. |
| `get_next_7_days` | [✓] | The next 7 days (a rolling window: today through today+6): overdue plus one bucket per day, each with its to-dos. Always 7 buckets — it is not bounded by the calendar week and does not read the weekStart setting. |
| `get_overview` | [✓] | The full task tree: areas → projects → subprojects with recursive open-todo counts and progress, their open to-dos (with dates/tags), standalone to-dos, and an inbox summary. The one call to understand everything. |
| `get_someday` | [✓] | Someday (v1.2 phase 2 — GROUPED): parked to-dos and parked inbox items. Parking is timeline-free (it strips dates), so no date meta renders here. Top to bottom: `groups` — every project/subproject that has DIRECT parked to-dos, in overview-tree order, each `{ container, todos }`; `standalone` — standalone parked to-dos (no project) then parked TEXT inbox captures; `attachments` — parked url/image/file inbox items, last. `containerSomeday` is the raw `{containerId: [todoIds]}` map that also feeds each container’s own collapsible someday section. |
| `get_today` | [✓] | Today view: overdue to-dos (missed deadlines), today’s to-dos, and what was completed today. |
| `get_upcoming` | [✓] | Upcoming: open to-dos dated after today, grouped by their effective date ascending. |
| `inbox_to_someday` | [ ] | Park an inbox item in someday (stays an inbox item; v1.2 parking strips its captured date — contrast archive, which keeps it). |
| `list_filters` | [✓] | The saved filters (v1.4), in nav order (sortKey). Each row reports its parsed criteria, a live match COUNT (open + someday visible to-dos matching all criteria; done never matches), a derived priorityMin when the filter leads with a priority criterion, and inert=true for a filter whose criteria this client can’t parse (an unknown/newer-version shape — it matches nothing and renders a muted note). The three defaults low/medium/high are ordinary rows. |
| `list_trash` | [✓] | The trash view (v1.2): trashed ROOTS only (a row trashed individually inside a container that was later trashed is hidden here but still purges by its own stamp), newest first. Each entry reports its kind, label, when it was trashed, and — for containers — the subtree it will take with it on permanent delete. |
| `move_container` | [ ] | Move an area/project/subproject to a new parent, and/or reorder it among its new siblings. parent = the destination container (name or id), or the literal "top" for the top level. ⭐ A move can RE-KIND the row, exactly as dragging does: move a project onto a project and it becomes a SUBPROJECT of it; move a subproject to "top" and it is PROMOTED to a project. The hierarchy is closed (area → project → subproject), so illegal moves are refused with the reason — nothing may nest under a subproject, an area is always top level, and nothing may move into its own contents. position = "start" \| "end" (default) \| the id of a sibling to place it after. |
| `move_inbox_item` | [ ] | Sort an inbox item onto a target (resolved by name or id). A text item MOVES into a container (becomes a to-do, carrying its captured date); a url item ATTACHES to a container or to-do. Pass target "anytime" to turn a text item into a standalone, unscheduled to-do (v1.2). |
| `move_to_anytime` | [ ] | Move an existing to-do to anytime (v1.2): detach it from its project and clear its scheduled date, keeping its deadline and tags. It becomes a standalone, unscheduled to-do that lists in the anytime view. Resolve by id or exact title. |
| `move_todo` | [ ] | Reparent and/or reorder a to-do. position = "start" \| "end" \| a sibling to-do id to place it after. Omit `parent` (or pass "none") to reorder within the STANDALONE band — the project-less to-dos that list after the projects on the overview. |
| `permanent_delete` | [ ] | Permanently delete a trashed row by id (v1.2) — the irreversible hard-delete cascade (a container takes its whole subtree: child containers, to-dos and attachments). Destructive — requires confirm: true. Pass an id from list_trash. |
| `restore_trashed` | [ ] | Restore a trashed root by id (v1.2): clears its trash stamp; the row — and, for a container, its whole subtree — returns to its views untouched. |
| `search` | [✓] | Search everything (v1.2): case-insensitive substring match across to-do titles/subtitles/notes/tags, container names/subtitles/notes, inbox text/urls/titles, and attachment titles/urls/text. Excludes trashed rows and archived/trashed subtrees; someday rows are included. Each hit reports which field matched and its direct-parent crumb. |
| `set_week_start` | [ ] | Set the first day of the week (shared setting): 1 = Monday (the default), 7 = Sunday, null = Monday. v1.5: this is presentation-only — it orders the weekday columns of the date-picker calendar in the apps. It changes NO view, no bucket and no count (next-7-days is always today…today+6), and weekly recurrence stays Monday-anchored. |
| `trash_container` | [ ] | Move an area/project/subproject (and, by exclusion, its whole subtree) to trash (v1.2). Only this root row is stamped; restore_trashed brings the subtree back untouched. Auto-purged after 30 days. No confirm — trash is the safety net. |
| `trash_todo` | [ ] | Move a to-do to trash (v1.2). Recoverable via restore_trashed; auto-purged after 30 days. No confirm — trash is the safety net. Resolve by id or exact title. |
| `uncomplete_todo` | [ ] | Reopen a done to-do (clears completedAt). Resolve by id or exact title. |
| `update_container` | [ ] | Edit an area/project/subproject. Pass only fields to change. notes = the detail-pane note (v1.2), or null to clear. deadline accepts a date phrase / yyyy-MM-dd, or null to clear (areas have no deadline). priority = low\|medium\|high (display-only ! mark, v1.2; projects/subprojects only — areas never carry it), or null to clear. subtitle/name/color (#RRGGBB) editable too. |
| `update_filter` | [ ] | Edit a saved filter (v1.4): pass `name` and/or `criteria` (the same { v:1, all:[…] } shape as create_filter — replaces the whole criteria set). An unknown/newer-version criterion is rejected. Resolve by id or exact name. Returns the filter and its new match count. |
| `update_todo` | [ ] | Edit a to-do. Pass only fields to change. status = open\|someday\|done. Parking (status→someday) is timeline-free for a PLAIN to-do: it STRIPS whenDate + deadline to null but KEEPS the parent (a project idea parks with its project); a RECURRING to-do instead keeps its whenDate + rule DORMANT while parked (spec/60). Un-park a plain parked to-do by setting any when or deadline (assigning only a parent leaves it parked); un-park a recurring parked to-do with status=open, which re-aligns its dormant whenDate to today-or-later (schedule mode) or leaves it (afterCompletion). parent = project/subproject name or id, or null to make standalone. when/deadline = a date phrase / yyyy-MM-dd, or null to clear. priority = low\|medium\|high (display-only ! mark), or null to clear. tags replaces the tag set. repeat = a rule to make it recurring (clears deadline, re-anchors to its whenDate; takes optional mode "schedule"\|"afterCompletion" + optional until yyyy-MM-dd + optional count 1…999), or null to clear the rule. reminder = a local `yyyy-MM-ddTHH:mm`, or null to clear. |

## What an AI client cannot do

- **Read anything outside the granted scopes.** Scopes are enforced per request.
- **Write on a read-only grant.** The server refuses.
- **See your data after you revoke.** The grant carried the key; revoking ends it.
- **Search your notes on the server.** Everything is decrypted per request, for that request only — see [encryption](/encryption).

## Building an MCP client

The server implements the standard discovery documents — [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) protected-resource metadata and [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) authorization-server metadata — so a compliant client needs no special-casing.

One thing is stricter than the baseline: `resource` ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)) is **mandatory** on `/authorize` and `/token`. A token minted for the MCP server is refused at the REST API and vice versa. Send `https://mcp.inittasks.com/mcp` as the resource when connecting here.

See [authentication](/authentication) for the rest of the flow — it is the same one.
