Deferno MCP Server
The Deferno MCP Server exposes the Deferno task-manager backend to AI agents, enabling full lifecycle management of Tasks, Habits, Chores, and Events through a unified interface.
Authentication & User
Browser-based CLI auth flow (
start_auth,complete_auth) or pre-set token (DEFERNO_TOKEN)logout,whoami,get_settings,update_settings(theme, done-visibility)
Reading Items (Kind-Neutral)
get_item,list_items,search_items— filter by kind/status/date; accepts UUID, sequence shorthand (#123), canonical ref, app URL, or GitHub aliasget_item_historyfor change history
Creating & Mutating Items
capture_item— derives kind automatically from behavioral questions; or use per-kind:create_task,create_chore,create_habit,create_eventUpdate:
update_item(kind-neutral) or per-kind variantsDelete, move/reparent (
move_item), convert between kinds (convert_item)Task-specific:
set_task_status,split_task,fold_task,merge_task,promote_task,batch_tasks(atomic batch operations)
Recurring Item Occurrences
Chores: list, set status, mark next done, reschedule
Habits: list, mark, clear, reschedule
Events: list, set, delete, reschedule occurrences
Daily Plan
get_daily_plan/get_items_planAdd, remove, and reorder items in the plan
Calendar
get_items_calendar(all kinds),get_calendar_events/get_tasks_calendar(tasks with recurring expansions)
Attachments
Presign, commit, list, and delete attachments on Tasks and Event occurrences
Comments
Post, edit, delete comments on Event occurrences; update/delete any comment by ID
Pinned Items & Saved Searches
Pin/unpin, reorder, and label sidebar items
Create, update, delete, and reorder saved searches
Data Management
export_data,import_data,get_mood_history
Admin
list_feedback,feedback_stats,update_feedback
MCP Resources
defernowork://tasks/plan(daily plan),defernowork://tasks/mood-history,defernowork://item/{ref}
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Deferno MCP Server show today's daily plan"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 |
| Fetch ONE item (any kind) by any Ref input form |
| Bounded, kind-neutral list backed by |
| Compact full-text search over items |
Removed: the old task-list tools
list_tasks,get_task, andsearch_tasksare gone. Use the kind-neutrallist_items/get_item/search_itemsabove instead. (Theirdefernowork://tasksanddefernowork://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 |
| Return the currently authenticated user |
| Create any item by behavior → Task/Chore/Habit/Event (the create front door; see Creating items) |
| Patch any item (Task/Chore/Habit/Event); kind-validated fields |
| Delete any item (Task hard-delete; Chore/Habit/Event archive) |
| Reparent or reorder any item (Task/Chore/Habit/Event) |
| Convert an item to a different kind (Task/Chore/Habit/Event) |
| Today's curated daily plan (recurring + carried forward): kind-tagged rows across all four kinds, recurring rows carrying |
| Manage the daily plan ordering |
| 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 |
| passed straight through (no lookup) |
Sequence shorthand |
| resolves against your personal org only |
Canonical ref |
| resolves across orgs |
App URL |
| paste verbatim; resolves across orgs |
GitHub alias |
| 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 |
| Today's curated daily plan (all four kinds, despite the |
| Mood log for finished tasks |
| 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-mcpOr install permanently:
pip install defernowork-mcp
# or with uv:
uv pip install defernowork-mcpAuthenticate
Run the one-time auth command:
defernowork-mcp auth --base-url https://app.defernowork.com/apiThis opens a browser-based login flow:
A URL is printed — open it in your browser
Sign in (or approve if already signed in)
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 |
| none |
|
|
| Bearer |
|
|
| none |
|
|
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:
Per-request
Authorization: Bearerheader (HTTP transport only)DEFERNO_TOKENenvironment variableSaved 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 setsDEFERNO_TOKEN. The in-bandstart_auth/complete_authtools 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 |
|
| URL of the Deferno backend HTTP API (must include |
| (unset) | Pre-existing bearer token; skips browser login |
|
| 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/mcpOr 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 toolsadd_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 withinscheduled_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_saved_searchC
Save a search. query_string is the same syntax as search_items.
Inputs: name (required), query_string (required). 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_saved_searchB
Delete a saved search.
Inputs: saved_search_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 --
#123or bare123. 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 oncomplete_bywidened to RFC3339 day boundaries (start-of-day forfrom_date, end-of-day forto_date).
An unknown / unfilterable field returns a backend 400, surfaced clearly (not swallowed).
limit-- maps to OData$top. The backend caps$topat 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 withinscheduled_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_saved_searchA
Patch a saved search's name or query string.
Inputs: saved_search_id (required), name, query_string. 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
Scored across 81 tools
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 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.
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.
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
Related MCP Connectors
Manage tasks, Focus Zone, notes, projects, and task history from compatible AI assistants.
Manage a family's Plannie calendar, tasks, lists, files and meal plan as an agent.
AI-native task management: list, create, update and archive tasks with rich context for AI agents
Lets Claude, ChatGPT, and Muse read and update your beckett tasks, notes, calendar, and lists.
Related MCP Servers
- AlicenseBqualityBmaintenanceEnables AI assistants to access and manage Streamline tasks, notes, tags, and workspaces via a Supabase-powered backend. It supports full CRUD operations, allowing users to search, create, update, and organize their productivity data through natural language.1712 npmMIT
- FlicenseNot gradedqualityDmaintenanceEnables AI agents to manage projects, tasks, and schedules in JJ Planner through CRUD operations.24 npm-
- FlicenseNot gradedqualityBmaintenanceEnables external clients to manage tasks, memories, daily plans, and productivity reports via HTTP tools, including a natural-language productivity assistant.-
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to create, read, and manage tasks and projects in a personal todo system through MCP tools.1MIT