Skip to main content
Glama

Deferno MCP Server

An MCP server that exposes the Deferno task-manager backend to AI agents.

MCP is the open standard used by Claude Desktop / Claude Code, Cursor, Windsurf, Zed, VS Code Copilot agents, Continue, OpenAI Agents, and others, so this server works with any of them — you configure it once in your client and every tool and resource below becomes available.

What the agent can do

Reading items (kind-neutral)

Reads are kind-neutral: one set of tools spans Tasks, Habits, Chores, and Events. Every read returns a Compact projection by default — a small fixed field set chosen so reads don't flood the agent's context — and accepts full=true to get the complete record instead.

Tool

Purpose

get_item

Fetch ONE item (any kind) by any Ref input form

list_items

Bounded, kind-neutral list backed by GET /items (filters + limit + window)

search_items

Compact full-text search over items

Removed: the old task-list tools list_tasks, get_task, and search_tasks are gone. Use the kind-neutral list_items / get_item / search_items above instead. (Their defernowork://tasks and defernowork://task/{task_id} resources were removed too — see the Resources table.)

get_item(item, full=False, as_alias=False) — fetch a single item by any Ref input form. Compact projection by default (single-item compact keeps description); full=true returns the complete record (action history, comments, children, mood, attachments…). as_alias=true forces the by-alias lookup for ambiguous external strings (e.g. ABC-223) that the classifier deliberately won't auto-route.

list_items(kind=, status=, from_date=, to_date=, limit=, full=False, window=None) — the canonical bounded list. kind / status / from_date / to_date compose into an OData $filter; limit maps to $top (the backend caps it at 500 by rejecting larger values with a 400 — it does not silently clamp); full=true returns full rows; window="all" opts out of the default done-visibility window for full history. Compact list rows are narrower than get_item — roughly ref, kind, title, status, complete_by, parent_id, labels, with the body dropped.

search_items(query, status=, label=, from_date=, to_date=, parent_id=, full=False) — compact full-text search; output is the same narrow Compact projection as list_items (full=true for full rows). Note: full-text is Tasks-only in the backend today (a kind-neutral /items/search is a known backend follow-on); use list_items to enumerate non-Task kinds.

Creating items (behavioral capture)

Creation is caller-categorized: the agent answers a few jargon-free, behavioral questions and the server deterministically derives the item kind (Task / Habit / Chore / Event) and builds the kind-specific payload. The agent never names a Deferno kind — it describes how the thing behaves, and the derivation does the rest. There is no inference and no model call on this path; the kind is read straight off the discriminators.

Two rules keep the discriminators honest:

  • Date and time-of-day are orthogonal operands, never kind signals. Every kind carries a time-of-day — a deadline for Task/Chore/Habit, a start for Event — so "has a set time" cannot decide the kind. (See the unified-WHEN model in the Deferno backend: every kind gained an explicit time-of-day field.)

  • Source never votes on kind. An item synced from GitHub, Microsoft, or Google Calendar can be any of the four kinds; external provenance is orthogonal to the kind decision.

Kind-derivation tree

flowchart TD
    Start(["New item"]) --> Q1{"Do you attend it?"}
    Q1 -->|yes| Event(["Event"])
    Q1 -->|no| Q2{"Repeats on a schedule?"}
    Q2 -->|no| Task(["Task"])
    Q2 -->|yes| Q3{"Need it, or just want it?"}
    Q3 -->|need| Chore(["Chore"])
    Q3 -->|want| Habit(["Habit"])

Event short-circuits first: a thing you attend is an Event whether or not it repeats (a weekly stand-up is still an Event). need vs want is only asked once we know it recurs — every one-off (want or need) is a Task. The need/want split is the same obligation-vs-aspiration distinction the backend encodes as carries-forward (Chore) vs lapses (Habit).

Truth table

Example

Attend?

Repeats?

Need / want

→ Kind

Weekly team stand-up

yes

—

—

Event

Dentist appointment Tue

yes

—

—

Event

Pay rent every month

no

yes

need

Chore

Meditate daily

no

yes

want

Habit

File taxes by Apr 15

no

no

—

Task

Read this novel (one-off)

no

no

—

Task

Other tools

Tool

Purpose

whoami

Return the currently authenticated user

capture_item

Create any item by behavior → Task/Chore/Habit/Event (the create front door; see Creating items)

update_item

Patch any item (Task/Chore/Habit/Event); kind-validated fields

delete_item

Delete any item (Task hard-delete; Chore/Habit/Event archive)

move_item

Reparent or reorder any item (Task/Chore/Habit/Event)

convert_item

Convert an item to a different kind (Task/Chore/Habit/Event)

get_daily_plan

Today's curated daily plan (recurring + carried forward): kind-tagged rows across all four kinds, recurring rows carrying today_occurrence

add_to_items_plan / remove_from_items_plan / reorder_items_plan

Manage the daily plan ordering

get_items_calendar

Calendar view across all item kinds

Item mutations are kind-neutral: capture_item creates, update_item edits, delete_item removes, move_item reparents, and convert_item changes kind — each resolves the item's kind from the reference, so the agent never picks a per-kind tool. These and every other id-taking tool (occurrence, attachment, plan, …) accept any Ref input form for their item-id arguments — Transparent resolution resolves the ref to a UUID before the backend call runs.

Ref input forms

Anywhere a tool names a single item (and the defernowork://item/{ref} resource), the MCP accepts any Ref input form and resolves it to a UUID before acting — the agent never has to know which form it holds (Transparent resolution). The recognised forms are:

Form

Example

Notes

UUID

b1c2…

passed straight through (no lookup)

Sequence shorthand

#123 or bare 123

resolves against your personal org only

Canonical ref

acme-123, u-1y0e2v-123

resolves across orgs

App URL

https://app.defernowork.com/o/{org_slug}/items/{seq-or-id}

paste verbatim; resolves across orgs

GitHub alias

owner/repo#N

auto-routes to by-alias (External tasks feature)

A bare #N always means a Deferno Sequence shorthand here — it is not inferred as a GitHub issue. Ambiguous strings like ABC-223 collide with a Canonical ref and are not auto-routed; use get_item(item, as_alias=true) to force the alias path. (Resolving the Deferno-# vs GitHub-# ambiguity from conversation is the job of a future context-adaptive classifier — see CONTEXT.md and docs/adr/0001-transparent-ref-resolution.md.)

Resources

(readable by MCP clients that index resources)

URI

Content

defernowork://tasks/plan

Today's curated daily plan (all four kinds, despite the tasks/ URI)

defernowork://tasks/mood-history

Mood log for finished tasks

defernowork://item/{ref}

A single item by any Ref input form (Compact)

The unbounded defernowork://tasks (all-tasks) and UUID-only defernowork://task/{task_id} resources were removed (per ADR-0002): unbounded reads flood agent context, and the any-ref defernowork://item/{ref} above supersedes the single-task resource.

Related MCP server: JJ Planner MCP Server

Install

The easiest way is uvx — it runs the package in an isolated environment without a manual install step:

uvx defernowork-mcp

Or install permanently:

pip install defernowork-mcp
# or with uv:
uv pip install defernowork-mcp

Authenticate

Run the one-time auth command:

defernowork-mcp auth --base-url https://app.defernowork.com/api

This opens a browser-based login flow:

  1. A URL is printed — open it in your browser

  2. Sign in (or approve if already signed in)

  3. A short code is shown — paste it back into the terminal

Your token is saved to ~/.config/defernowork/credentials.json and loaded automatically on future runs. No env vars needed.

Alternatively, set DEFERNO_TOKEN as an environment variable to skip the interactive flow (useful for CI or containers).

Authentication flow

The CLI auth flow (defernowork-mcp auth, for stdio transport) is coordinated by three backend endpoints. (Remote HTTP clients authenticate via the OAuth 2.0 discovery flow instead — see Install; the old in-band start_auth / complete_auth MCP tools were retired.) The handshake:

MCP / CLI                  Backend                     Browser
  |                          |                           |
  |-- POST /auth/cli/init -->|                           |
  |<-- {session_id, url} ----|                           |
  |                          |                           |
  |  (user opens url)        |                           |
  |                          |<--- GET /cli-auth?s=...---|
  |                          |                           |
  |                          |   (user logs in if needed)|
  |                          |                           |
  |                          |<- POST /auth/cli/approve -|
  |                          |   {session_id}            |
  |                          |-- {code} ---------------->|
  |                          |   (browser shows code)    |
  |                          |                           |
  |  (user pastes code)      |                           |
  |                          |                           |
  |-- POST /auth/cli/verify->|                           |
  |   {session_id, code}     |                           |
  |<-- {token, user} --------|                           |
  |                          |                           |
  |  (token saved to disk)   |                           |

Backend endpoints

Endpoint

Auth

Request

Response

POST /auth/cli/init

none

{}

{session_id: string, auth_url: string}

POST /auth/cli/approve

Bearer

{session_id: string}

{code: string}

POST /auth/cli/verify

none

{session_id: string, code: string}

{token: string, user: {id, username, …}}

cli/init creates a pending CLI session in Redis with a short TTL (~10 minutes) and returns a URL the user should open in their browser.

cli/approve is called by the frontend after the user is logged in. It creates a new backend session for the CLI (including the cached DEK so encrypted task data remains accessible), generates a short one-time code, and stores both in the CLI session record. The browser session and CLI session are independent — logging out of one does not affect the other.

cli/verify is called by the MCP server / CLI. It looks up the CLI session, verifies the code, returns the session token and user info, and deletes the CLI session record from Redis.

Token resolution order

When the MCP server needs a token it checks, in order:

  1. Per-request Authorization: Bearer header (HTTP transport only)

  2. DEFERNO_TOKEN environment variable

  3. Saved credentials at ~/.config/defernowork/credentials.json

Agent-driven flow

When an agent (Claude Code, Cursor, etc.) calls any tool and gets a 401:

  • Remote / HTTP clients follow the OAuth 2.0 discovery flow the server instructions point them to (RFC 9728 PRM → RFC 8414 AS metadata → Authorization Code + PKCE).

  • Local / stdio clients need a token on disk — the user runs defernowork-mcp auth (the CLI handshake above) once, or sets DEFERNO_TOKEN. The in-band start_auth / complete_auth tools were retired, so an agent cannot self-authenticate over stdio.

All subsequent tool calls work automatically, including across restarts.

Configure

Environment variables:

Variable

Default

Purpose

DEFERNO_BASE_URL

http://127.0.0.1:3000/api

URL of the Deferno backend HTTP API (must include /api prefix)

DEFERNO_TOKEN

(unset)

Pre-existing bearer token; skips browser login

DEFERNO_LOG_LEVEL

WARNING

Python logging level

API envelope versions

The MCP server intentionally speaks both 0.1 and 0.2 of the Deferno API envelope. This is forward-prep: the backend has not cut over yet — it still emits 0.1 today — and accepting 0.2 now means the MCP keeps working unchanged the moment an imminent 0.2 cutover lands (and during any partial rollout where both shapes are in flight). No client-side configuration is needed — DefernoClient accepts either envelope and unwraps data the same way. The set is defined as SUPPORTED_API_VERSIONS in src/defernowork_mcp/client.py; once the backend has settled on "0.2" and rollback to "0.1" is no longer plausible, drop "0.1" from the frozenset.

Client configuration snippets

Claude Desktop / Claude Code (Interactive method)

Add Deferno's MCP server to Claude's MCP configuration using the command line:

claude mcp add --transport http deferno https://app.defernowork.com/mcp

Or add to your MCP client settings (claude_desktop_config.json on Claude Desktop, or Claude Code's mcpServers config):

{
  "mcpServers": {
    "deferno": {
      "command": "uvx",
      "args": ["defernowork-mcp"],
      "env": {
        "DEFERNO_BASE_URL": "https://app.defernowork.com/api"
      }
    }
  }
}

Headless or mcporter (Token method)

If you prefer to skip the interactive flow, or you are running in a headless/SSH environment, provide a token directly. First, generate the MCP Personal access tokens through Deferno's Settings/Interactions page, then paste it into the config:

{
  "mcpServers": {
    "deferno": {
      "command": "uvx",
      "args": ["defernowork-mcp"],
      "env": {
        "DEFERNO_BASE_URL": "https://app.defernowork.com/api",
        "DEFERNO_TOKEN": "..."
      }
    }
  }
}

Development

Syntax / import sanity check:

python -c "from defernowork_mcp.server import create_server; create_server()"

src/defernowork_mcp/server.py wires the server (auth, OAuth, resources) and delegates the tool surface to per-area modules under src/defernowork_mcp/tools/ (e.g. items.py, tasks.py, chores.py, habits.py, events.py, …), each exposing a register(mcp, get_client, format_error, …) entry point. A thin async HTTP client (src/defernowork_mcp/client.py), credential storage (src/defernowork_mcp/credentials.py), and the shared Ref classifier/resolver (src/defernowork_mcp/refs.py) round it out. Adding a new tool is a matter of wrapping a new client method in an @mcp.tool() inside the relevant tools/ module; any id argument should be run through resolve_ref so it accepts every Ref input form.

Available Tools

81 tools
add_to_items_planC

Add an item (any kind) to the daily plan.

Inputs: task_id (required), date. Returns: result

add_to_planA

Add a task to the daily plan.

task_id is the UUID of an existing task. date defaults to today.

Inputs: task_id (required), date. Returns: result

batch_tasksA

Execute multiple task operations atomically in a single call.

operations is a list of operation objects. Each must have an op field ("update" or "move") and a task_id.

Update operations accept the same fields as update_task (title, description, status, labels, etc.) at the top level alongside op and task_id.

Move operations accept new_parent_id (UUID or null for root) and an optional position (insertion index).

All operations succeed or none do (all-or-nothing). On success returns {"tasks": [...]}, the list of all modified tasks.

Inputs: operations (required). Returns: result

clear_habit_occurrenceB

Clear an explicitly-marked habit occurrence at date (YYYY-MM-DD).

habit_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before clearing the occurrence.

Inputs: habit_id (required), date (required). Returns: result

commit_event_occurrence_attachmentsA

Commit intents and/or url-provider entries to an event occurrence.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the commit.

intents are attachment ids returned by a prior presign call whose files have been PUT to S3. urls are url-provider entries {url, filename?}. 400 if both lists are empty.

Inputs: event_id (required), date (required), intents, urls. Returns: result

commit_task_attachmentsA

Commit presigned intents and/or url-provider entries to a task.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the commit.

intents is a list of attachment_ids returned by a prior presign call. urls is a list of {url, filename?} records for the url-provider (no upload). At least one must be non-empty. Returns the full attachments Vec post-commit.

Inputs: task_id (required), intents, urls. Returns: result

complete_authA

Finish authentication by exchanging the browser code for a token.

session_id comes from the start_auth response. code is the short code the user copied from their browser after signing in via the OIDC provider.

NOTE: In HTTP transport with OAuth enabled, authentication is handled automatically. This tool is only needed for stdio/CLI.

Inputs: session_id (required), code (required). Returns: result

convert_itemA

Convert an item to a different kind (Task / Chore / Habit / Event).

item_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the conversion.

to is one of "task", "chore", "habit", "event" -- this is the backend wire field name (ConvertItemPayload.to). complete_by (RFC3339) is required when to is Event/Chore/Habit; recurrence is required when to is Habit/Chore (and optional for Event); end_time is Event-only. Returns the updated item view -- the backend uses 201 on a real conversion, 200 when to equals the current kind (idempotent).

Inputs: item_id (required), to (required), complete_by, end_time, +1 more. Returns: result

create_choreA

Create a recurring chore that lingers until done.

Chores differ from habits in that an unfinished occurrence carries forward (Missed/InProgress) rather than resetting each period. complete_by should be the first scheduled date as ISO-8601. recurrence follows the same shape as Task: {"type": "daily"}, {"type": "every_n_days", "n": 3}, or {"type": "weekly", "days": ["Mon", "Wed"]}.

v0.2 optional fields:

  • cadence_mode: "rolling" (default; the next occurrence is computed from the actual completion time) or "fixed" (the next occurrence is anchored to the original schedule, ignoring completion delay).

  • deadline_time_of_day: "HH:MM" time-of-day deadline within scheduled_date (user's TZ). Defaults to end-of-day.

  • subtask_template: a list of subtask shapes that materialize as child Tasks on each occurrence. Empty list (default) means no template.

Inputs: title (required), description, complete_by, recurrence, +2 more. Returns: result

create_eventC

Create a time-bound event.

complete_by is the start time (ISO-8601). end_time (if provided) must be at or after complete_by.

v0.2 optional fields:

  • subtask_template: list of subtask shapes materialized per occurrence.

Inputs: title (required), complete_by (required), end_time, description, +3 more. Returns: result

create_habitA

Create a recurring habit that resets each period.

Habits differ from chores in that an unfinished occurrence does not carry forward — each period gets a fresh start. recurrence follows the same shape as Task: {"type": "daily"}, {"type": "every_n_days", "n": 3}, or {"type": "weekly", "days": ["Mon", "Wed"]}.

v0.2 optional fields:

  • deadline_time_of_day: "HH:MM" time-of-day deadline (user's TZ).

  • subtask_template: list of subtask shapes materialized per occurrence.

Inputs: title (required), description, complete_by, recurrence, +2 more. Returns: result

create_taskA

Create a new task.

complete_by must be an ISO-8601 UTC timestamp. parent_id attaches the new task as a child of an existing task. productive and desire are floats in [0, 1] representing how productive this task feels and how much the user wants to do it. recurrence sets a repeat schedule. Use {"type": "daily"}, {"type": "every_n_days", "n": 3}, or {"type": "weekly", "days": ["Mon", "Wed", "Fri"]}. recurring_type controls behavior when a recurring task is missed. Must be one of "chore" (lingers until done, default), "habit" (fresh start each day), or "event" (time-bound, can't be made up). Only meaningful when recurrence is set.

v0.2 optional field:

  • occurrence_id: when this Task is a materialized subtask of a recurring entity's occurrence, the Occurrence id it belongs to. Normal tasks omit this field.

Inputs: title (required), description (required), labels, parent_id, +6 more. Returns: result

delete_choreA

Archive (soft-delete) a chore.

chore_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the delete.

Inputs: chore_id (required). Returns: result

delete_commentB

Delete a comment owned by the caller.

Inputs: comment_id (required). Returns: result

delete_eventA

Archive (soft-delete) an event.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the delete.

Inputs: event_id (required). Returns: result

delete_event_occurrenceA

Clear an event occurrence row entirely (undo a prior mark).

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the clear.

date is YYYY-MM-DD. Returns {"ok": true} on success.

Inputs: event_id (required), date (required). Returns: result

delete_event_occurrence_attachmentA

Delete a single attachment from an event occurrence.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the delete. att_id is the attachment id returned in the AttachmentView (not an item reference) and is passed through unresolved.

Returns {"ok": true} on success.

Inputs: event_id (required), date (required), att_id (required). Returns: result

delete_event_occurrence_commentA

Soft-delete the latest comment on an event occurrence (date).

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the delete.

Inputs: event_id (required), date (required). Returns: result

delete_habitA

Archive (soft-delete) a habit.

habit_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the delete.

Inputs: habit_id (required). Returns: result

delete_taskA

Hard-delete a task by id.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL. The returned task_id is the resolved UUID the deletion ran against.

Inputs: task_id (required). Returns: result

delete_task_attachmentA

Delete a single attachment from a task.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the delete. att_id is an attachment id (not an item reference) and is passed through unresolved.

Inputs: task_id (required), att_id (required). Returns: result

export_dataA

Export all user data as JSON.

Returns a complete backup of all tasks (with full history, mood vectors, recurrence rules), root ordering, and daily plans. The export can be imported via the Deferno web UI settings page.

Inputs: no named parameters. Returns: result

feedback_statsA

Return aggregate feedback statistics (admin only).

Inputs: no named parameters. Returns: result

fold_taskA

Insert a new next-step task directly after task_id in the sequence.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL.

Preserves any existing downstream chain. Returns the original task and the newly created next task.

Inputs: task_id (required), title (required), description (required), labels, +3 more. Returns: result

get_calendar_eventsA

Query calendar events for a date range.

Returns recurring task instances (expanded from RRULE schedules) plus one-off tasks with due dates in the range. start and end are YYYY-MM-DD strings.

Parameters

start : YYYY-MM-DD start of the window (inclusive). end : YYYY-MM-DD end of the window (exclusive). tz : optional IANA timezone (e.g. "America/Los_Angeles"). Supply if you know the user's local timezone — the backend uses it to align the day window boundaries with local midnight rather than UTC midnight.

Inputs: start (required), end (required), tz. Returns: result

get_daily_planA

Return today's curated daily plan.

The plan auto-seeds from recurring tasks, carries forward incomplete items from yesterday, and includes any task/chore/event with a due date falling on the target date in the user's timezone.

Parameters

date : optional YYYY-MM-DD. Defaults to today in the user's timezone. If no timezone is known, defaults to UTC. tz : optional IANA timezone (e.g. "America/Los_Angeles"). Supply if you know the user's local timezone — Claude Desktop / Claude Code typically have this in the system prompt as locale info. Once supplied for the first time, the backend persists it as the user's preference, so future calls don't need to repeat it.

Inputs: date, tz. Returns: result

get_itemA

Fetch a single item (Task / Habit / Chore / Event) by any reference.

item accepts any Ref input form and is resolved transparently:

  • a UUID (GET /items/{id});

  • a Sequence shorthand -- #123 or bare 123. This resolves against your personal org only, by design. For an item in a shared org, name it by its Canonical ref (acme-123) or its App URL instead -- both resolve across orgs;

  • a Canonical ref (slug-123, e.g. u-1y0e2v-123);

  • an App URL (https://app.defernowork.com/o/{org_slug}/items/{seq-or-id});

  • the unambiguous GitHub Alias owner/repo#N (it carries a /, so it can't be confused with a Canonical ref) -- auto-routed to the by-alias endpoint.

Deferno-# vs GitHub-# ambiguity. A bare #N always means a Deferno Sequence shorthand here; it is NOT inferred as a GitHub issue. Likewise an ambiguous string like ABC-223 collides with a Canonical ref and is therefore NOT auto-routed to alias resolution. Inferring which a user means from conversation is the job of a future context-adaptive classifier (see CONTEXT.md "Flagged ambiguities"), not this tool. Until then, use as_alias=true to force the alias path.

Args: item: Any Ref input form (or, with as_alias=true, a raw alias). full: When true, return the complete record (action history, comments, children, mood, attachments, ...) instead of the default compact projection. as_alias: When true, BYPASS the Ref classifier and look item up directly via GET /items/by-alias/{item}. This is the explicit escape-hatch for ambiguous external aliases (e.g. ABC-223) that the classifier deliberately will not auto-route. The unambiguous GitHub form owner/repo#N already routes to by-alias WITHOUT this flag.

Returns a compact projection by default (a small whitelist of fields, including description). Pass full=true for the complete record (action history, comments, children, mood, attachments, ...).

Inputs: item (required), full, as_alias. Returns: result

get_item_historyA

Return the change-history list for any item kind (Task/Habit/Chore/Event).

item_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the history lookup.

Inputs: item_id (required). Returns: result

get_items_calendarA

Calendar view across all item kinds (Task, Habit, Chore, Event).

start and end are YYYY-MM-DD; end is exclusive. tz is an optional IANA timezone for local-midnight alignment.

Inputs: start (required), end (required), tz. Returns: result

get_items_planB

Daily plan across all item kinds (Task, Habit, Chore, Event).

Returns a polymorphic array — each entry has a kind discriminator. date defaults to today; tz is an optional IANA timezone.

Inputs: date, tz. Returns: result

get_mood_historyA

Return the user's historical mood-per-task log for finished tasks.

Inputs: no named parameters. Returns: result

get_settingsA

Return the user's settings blob (theme, done-visibility, etc.).

Inputs: no named parameters. Returns: result

get_tasks_calendarA

Calendar view of tasks (recurring expansions + due dates).

start and end are YYYY-MM-DD strings; end is exclusive. tz is an optional IANA timezone for local-midnight alignment.

Inputs: start (required), end (required), tz. Returns: result

import_dataA

Import an ExportData blob produced by export_data.

data should be the full ExportData object (with keys tasks, events, habits, chores, root_order, daily_plans). Pass an empty dict to dry-run.

Inputs: data. Returns: result

list_chore_occurrencesA

List derived occurrences for a chore in the given date window.

chore_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the lookup.

Each occurrence has a status: Scheduled, Missed, InProgress, Skipped, DoneOnTime, or DoneLate. Dates use YYYY-MM-DD; range is inclusive on both ends.

Inputs: chore_id (required), from_date, to_date. Returns: result

list_event_occurrence_attachmentsA

List attachments on a specific event occurrence (date).

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the lookup.

Returns the AttachmentView shape: {id, provider, filename, mime, size, created_at, created_by, url}. url is a freshly signed GET for s3-backed entries.

Inputs: event_id (required), date (required). Returns: result

list_event_occurrencesA

List occurrences for an Event in a date range.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the lookup.

from_date / to_date are YYYY-MM-DD; both optional. Returns the unified-Occurrence shape (id, parent_id, scheduled_date, status, comment, attachments). Events never produce DoneLate.

Inputs: event_id (required), from_date, to_date. Returns: result

list_feedbackA

List submitted feedback (admin only).

Inputs: no named parameters. Returns: result

list_habit_occurrencesA

List occurrences for a habit in a date window.

habit_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the lookup.

Dates use YYYY-MM-DD; range is inclusive on both ends.

Inputs: habit_id (required), from_date, to_date. Returns: result

list_itemsA

List items of any kind (Task / Habit / Chore / Event), windowed.

The canonical, bounded list view. Returns a Compact projection by default -- a small fixed field set per row (ref, kind, title, status, complete_by, parent_id, labels) with the heavy body (description) and raw id dropped -- so a query returns a trimmed set, not the entire working set in full detail.

Filters (composed into an OData $filter with and):

  • kind -- one of "task", "habit", "chore", "event".

  • status -- the item status (e.g. "open", "done").

  • from_date / to_date -- YYYY-MM-DD; filter on complete_by widened to RFC3339 day boundaries (start-of-day for from_date, end-of-day for to_date).

An unknown / unfilterable field returns a backend 400, surfaced clearly (not swallowed).

  • limit -- maps to OData $top. The backend caps $top at 500 by REJECTING larger values with a 400 (it does NOT clamp); the number is passed through verbatim.

  • full=true -- return every field on each row (drops the projection).

  • window="all" -- opt out of the default done-visibility window for full history (the default window applies only to the unfiltered call).

Regardless of projection, the backend always injects ref, org_slug, type and sequence into every row.

Inputs: kind, status, from_date, to_date, +3 more. Returns: result

list_pinned_tasksA

List the user's sidebar-pinned items in display order.

Returns a JSON array of {task: TaskSummary, label: str | null} objects. The backend reconciles inconsistencies on every call — list entries whose underlying task is unpinned or deleted are dropped — so the result is always self-consistent and safe to render directly in the sidebar.

Inputs: no named parameters. Returns: result

list_saved_searchesA

List the caller's saved searches in their explicit order.

Inputs: no named parameters. Returns: result

list_task_attachmentsA

List a task's attachments. Returns the AttachmentView wire shape: {id, provider, filename, mime, size, created_at, created_by, url}. For provider=s3 records, url is a freshly-signed GET URL.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the lookup.

Inputs: task_id (required). Returns: result

logoutA

Log out and remove saved credentials.

Inputs: no named parameters. Returns: result

mark_habit_occurrenceA

Mark a habit occurrence as done or not-done.

habit_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before marking the occurrence.

date is YYYY-MM-DD; defaults to today on the server side.

Inputs: habit_id (required), done (required), date. Returns: result

mark_next_chore_doneA

Apply status to the earliest unresolved occurrence of a chore.

chore_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before marking the occurrence.

Useful for the common "I just did the dishes" case where the user doesn't want to look up which date is overdue. 404 if no unresolved occurrence exists.

status is the action to apply: one of "in_progress", "done", or "dropped" (alias: "skipped" for legacy callers).

Note: Done resolves on the server to either DoneOnTime or DoneLate based on the occurrence's complete_by.

Inputs: chore_id (required), status. Returns: result

merge_taskA

Roll the active children of a task back into the parent.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL.

Child content is appended to the parent description; the children are marked as pruned but remain recoverable. Pass the id of any child whose parent should receive the merge.

Inputs: task_id (required). Returns: result

move_taskA

Move a task to a different parent or reorder within its current parent.

task_id and new_parent_id each accept any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and are resolved to UUIDs before the move.

new_parent_id=None detaches the task to root level (kept as-is, not resolved). position is the insertion index in the target's children list (0 = first). Omit to append at end.

Inputs: task_id (required), new_parent_id, position. Returns: result

patch_event_occurrence_commentB

Edit the latest comment on an event occurrence (date).

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the edit.

Inputs: event_id (required), date (required), body, is_private. Returns: result

post_event_occurrence_commentA

Append a new comment to an event occurrence (date).

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the comment is posted.

Multiple comments per occurrence are supported (PR-F). Returns the persisted Comment with id + created_at.

Inputs: event_id (required), date (required), body (required), is_private. Returns: result

presign_event_occurrence_attachmentsA

Batch-presign attachments for a specific event occurrence (date).

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before presigning.

Each entry in files is {filename, content_type, size_bytes}. Server enforces 25 MB per-file cap, blocked-MIME list, and a max-attachments cap. Returns presigned PUT URLs with intent ids that commit_event_occurrence_attachments later consumes.

Inputs: event_id (required), date (required), files (required). Returns: result

presign_task_attachmentsA

Batch-presign S3 PUT URLs for files to attach to a task.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before presigning.

files is a list of {filename, content_type, size_bytes} records (the wire keys match the backend PresignRequest struct — no serde renames). The server enforces a 25 MB per-file cap and a blocked-MIME list; violations return 400. Returns a list of {attachment_id, put_url, expires_at} records — the caller PUTs each blob to its url before invoking commit_task_attachments.

Inputs: task_id (required), files (required). Returns: result

promote_taskA

Promote a personal-org task into a target org.

Moves the task from the caller's personal org into target_org_id, re-encrypting it under the target org's data-encryption key. The caller must own the task in their personal org AND be a member of target_org_id. Returns JSON null on success (the backend returns no body).

Inputs: task_id (required), target_org_id (required). Returns: result

remove_from_items_planC

Remove an item from the daily plan.

Inputs: task_id (required), date. Returns: result

remove_from_planA

Remove a task from the daily plan.

task_id is the UUID of the task to remove. date defaults to today.

Inputs: task_id (required), date. Returns: result

reorder_items_planC

Replace the daily plan ordering with the given full list of IDs.

Inputs: task_ids (required), date. Returns: result

reorder_pinned_tasksA

Replace the pinned-list ordering with task_ids.

task_ids must be an exact permutation of the user's current pinned set: extra ids, missing ids, or duplicates all 400. To add or remove an item, use set_item_pinned first, then reorder. Returns {"reordered": True, "count": N} on success.

Inputs: task_ids (required). Returns: result

reorder_planC

Replace the daily plan ordering with the given task ID list.

task_ids is the full ordered list of task UUIDs for the plan. date defaults to today.

Inputs: task_ids (required), date. Returns: result

reorder_saved_searchesA

Replace the saved-search ordering with the given full list of IDs.

Inputs: ids (required). Returns: result

reschedule_chore_occurrenceA

Move a single chore occurrence to new_date without touching the cadence.

chore_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the reschedule. date and new_date are YYYY-MM-DD occurrence dates, not item references.

NOTE (v0.2): the backend returns 501 today for chores (legacy storage); the tool is exposed for forward compatibility. Once the chore storage is migrated, this becomes the SCOPE-010 path.

Inputs: chore_id (required), date (required), new_date (required). Returns: result

reschedule_event_occurrenceA

Move a single event occurrence to new_date without touching the RRULE.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the reschedule.

The origin date's row is marked Dropped (with rescheduled_to=new_date); a fresh Scheduled row lands on the target date (with rescheduled_from=origin_date). 400 if new_date equals the origin date.

Inputs: event_id (required), date (required), new_date (required). Returns: result

reschedule_habit_occurrenceA

Move a single habit occurrence to new_date without touching the cadence.

habit_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the reschedule. date and new_date are YYYY-MM-DD occurrence dates, not item references.

NOTE (v0.2): the backend returns 501 today for habits (legacy storage); the tool is exposed for forward compatibility.

Inputs: habit_id (required), date (required), new_date (required). Returns: result

search_itemsA

Full-text search over items, returning a Compact projection.

The compact, kind-neutral full-text search over items. Returns a Compact projection by default -- the same small fixed field set per row as list_items (ref, kind, title, status, complete_by, parent_id, labels) -- so the heavy body (description) and the raw id are dropped. Pass full=true for the rows verbatim.

Scope: full-text search currently covers Tasks only. This tool is backed by the Tasks search path (GET /tasks/search) because the backend has no kind-neutral /items/search endpoint today; a kind-neutral full-text search is a known backend follow-on (to be filed in the Deferno backend repo, out of scope for the MCP). Non-Task kinds (Habits / Chores / Events) are therefore not reached by query yet -- use list_items to enumerate those.

Args: query: Search query (min 2 characters). Searches title and description. status: Filter by status (open, in-progress, in-review, done, dropped). label: Filter by label tag. from_date: Filter items due on or after this ISO 8601 date. to_date: Filter items due on or before this ISO 8601 date. parent_id: Scope search to children of this item (UUID). full: When true, return every field on each row (no projection).

Inputs: query (required), status, label, from_date, +3 more. Returns: result

set_chore_occurrence_statusA

Set the status of a single chore occurrence.

chore_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the status change.

status is the action to apply: one of "in_progress", "done", or "dropped" (alias: "skipped" for legacy callers). date is YYYY-MM-DD.

Note: Done resolves on the server to either DoneOnTime or DoneLate based on the occurrence's complete_by.

Inputs: chore_id (required), date (required), status (required). Returns: result

set_event_occurrenceA

Mark a single event occurrence with an action.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the mark.

action is one of "in_progress", "done", "dropped" (alias: "skipped"). date is YYYY-MM-DD.

When the occurrence has materialized subtasks, cascade_subtasks=false (the default) causes a 409 if any subtask is non-terminal (SUBTASK-003). Pass cascade_subtasks=true to sweep them to the matching terminal status.

Inputs: event_id (required), date (required), action (required), cascade_subtasks. Returns: result

set_item_pinnedA

Pin or unpin a sidebar item (Task/Habit/Chore/Event).

item_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the pin toggle.

Backend body is {pinned: bool} -- the gap-closure plan's optional label argument is not part of this endpoint (custom pin labels live on PATCH /tasks/pinned/{id}). Returns {"ok": true} on success (backend response is 204 NO_CONTENT).

Inputs: item_id (required), pinned (required). Returns: result

set_task_statusA

Convenience wrapper around update_task for status changes.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL.

Accepts open, in-progress, in-review, done, dropped, pruned.

Inputs: task_id (required), status (required). Returns: result

split_taskB

Decompose a task into two child tasks while preserving the parent.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL.

Returns the updated parent and both new children.

Inputs: task_id (required), first_title (required), first_description (required), second_title (required), +1 more. Returns: result

start_authA

Begin the Deferno authentication flow.

Returns a URL for the user to open in their browser. The user authenticates via the OIDC provider, then sees a short code to paste back here.

NOTE: In HTTP transport with OAuth enabled, authentication is handled automatically by the transport layer. This tool is only needed for stdio/CLI transport.

Inputs: no named parameters. Returns: result

update_choreA

Patch mutable fields on a chore. Omitted fields stay untouched.

chore_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the patch.

complete_by cannot be cleared on chores. Pass new value to shift the schedule. Updating recurrence rotates the chore's series ID so prior occurrences remain attached to the old definition.

v0.2 optional fields:

  • cadence_mode: "rolling" (default; the next occurrence is computed from the actual completion time) or "fixed" (the next occurrence is anchored to the original schedule, ignoring completion delay).

  • deadline_time_of_day: "HH:MM" time-of-day deadline within scheduled_date (user's TZ). Defaults to end-of-day.

  • subtask_template: a list of subtask shapes that materialize as child Tasks on each occurrence. Empty list (default) means no template.

Inputs: chore_id (required), title, description, complete_by, +2 more. Returns: result

update_commentB

Patch a comment's body or visibility.

Empty payload (no fields supplied) → backend returns 422.

Inputs: comment_id (required), body, is_private. Returns: result

update_eventA

Patch mutable fields on an event. Backend rejects end_time < complete_by.

event_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the patch.

v0.2 optional fields:

  • subtask_template: list of subtask shapes materialized per occurrence.

Inputs: event_id (required), title, complete_by, end_time, +3 more. Returns: result

update_feedbackB

Update a feedback item's status / admin notes (admin only).

Inputs: feedback_id (required), status (required), admin_notes. Returns: result

update_habitA

Patch mutable fields on a habit. Omitted fields stay untouched.

habit_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the patch.

v0.2 optional fields:

  • deadline_time_of_day: "HH:MM" time-of-day deadline (user's TZ).

  • subtask_template: list of subtask shapes materialized per occurrence.

Inputs: habit_id (required), title, description, complete_by, +2 more. Returns: result

update_pinned_labelA

Set or clear the custom sidebar label for a pinned task.

pinned_id is the underlying task UUID (the pinned list keys on task id; there is no separate pin-record id, hence the route /tasks/pinned/{id}). Pass label=None (or omit) to clear the label — this is the only way to clear it, so the body is always sent as {"label": label} including the JSON null. 404 if the task is not in the pinned list.

Inputs: pinned_id (required), label. Returns: result

update_settingsB

Patch user settings. Omitted fields stay unchanged.

Inputs: done_visibility, theme. Returns: result

update_taskA

Patch mutable fields on a task.

task_id accepts any reference form — UUID, sequence shorthand (#123, personal-org only), canonical ref (acme-123), or app URL — and is resolved to a UUID before the update.

status must be one of open, in-progress, in-review, done, dropped, pruned. The backend rejects completing a task while any of its children are still active.

Pass None explicitly to clear a field (e.g. complete_by=None removes the deadline). Omitting a parameter leaves it unchanged.

recurrence sets or clears a repeat schedule (see create_task).

recurring_type can be "chore", "habit", or "event" (see create_task for details). Pass None to clear.

For recurring tasks, if you change title, description, labels, or complete_by, you MUST also provide recurring_scope: "this" (single instance), "following" (this and future), or "all" (entire series). "this" and "following" also require recurrence_id (the ISO start time of the instance). If the task is recurring and scope is missing, the call will fail with a message asking you to specify the scope — ask the user which option they prefer.

v0.2 optional field:

  • occurrence_id: when this Task is a materialized subtask of a recurring entity's occurrence, the Occurrence id it belongs to. Normal tasks omit this field.

Inputs: task_id (required), title, description, status, +9 more. Returns: result

whoamiA

Return the currently authenticated Deferno user.

Call this first to confirm that the Authorization header is valid before issuing task operations.

Inputs: no named parameters. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.3/5.0

    Scored across 81 tools

    Disambiguation3/5

    Several tools have overlapping purposes, such as 'add_to_plan' vs 'add_to_items_plan' and 'get_calendar_events' vs 'get_items_calendar'. While descriptions clarify differences, the large number of tools increases selection difficulty.

    Naming Consistency3/5

    Naming is mostly verb_noun snake_case, but there are inconsistencies like 'mark_habit_occurrence' vs 'set_chore_occurrence_status', and 'remove_from_items_plan' vs 'remove_from_plan'. Some patterns are not uniformly applied.

    Tool Count2/5

    With 81 tools, the server is over-scoped for typical MCP usage. Many specialized operations (e.g., separate occurrence tools for each item type) could be consolidated into fewer, more general tools.

    Completeness4/5

    The tool set covers CRUD for tasks, chores, habits, events, daily plans, attachments, comments, and settings. A notable gap is that search only covers tasks, not other item types, but overall coverage is robust.

    Maintenance

    ActivityStale
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers