Skip to main content
Glama

toodledo-mcp

A small, open-source remote MCP server that connects Claude (claude.ai web, desktop, mobile and scheduled tasks) to your Toodledo account through the official Toodledo API v3.

  • Runs on Cloudflare Workers (free tier is plenty for one person).

  • OAuth on both sides: Claude → this server (OAuth 2.1 + PKCE, dynamic client registration), and this server → Toodledo (OAuth2 authorization code). No authless mode.

  • Toodledo tokens are encrypted at rest (AES-256-GCM) in a per-user Durable Object that serialises refreshes, so Toodledo's single-use refresh tokens are never used twice.

  • Human-friendly I/O: YYYY-MM-DD dates, HH:MM times, and folder/context/goal names instead of numeric IDs.

  • Five small runtime dependencies (the Cloudflare agents MCP helpers need both MCP SDK packages), no UI framework. MIT licensed.

You deploy your own instance and connect your own Toodledo account. Nothing is shared with the author.


Architecture

 ┌──────────────┐  OAuth 2.1 (PKCE, DCR)   ┌───────────────────────── Cloudflare Worker ─────────────────────────┐
 │  claude.ai   │ ───────────────────────► │  OAuthProvider (@cloudflare/workers-oauth-provider)                  │
 │ web/desktop/ │   Bearer token           │   /authorize /token /register  ── grants & tokens ──► KV (OAUTH_KV)  │
 │ mobile/sched │ ───────────────────────► │   /mcp  ──► ToodledoMCP (McpAgent Durable Object, Streamable HTTP)   │
 └──────────────┘                          │               │ tools (zod) ─► ToodledoClient ── retries/backoff ──┐ │
                                           │               │ name lookups cached 5 min                          │ │
        browser (once) ──► consent page ──►│   /callback ◄─┘                                                    │ │
              │                            │       │ exchange code                                              │ │
              ▼                            │       ▼                                                            │ │
   ┌────────────────────┐ OAuth2 code      │  ToodledoTokenStore (Durable Object, one per Toodledo user)        │ │
   │ Toodledo sign-in   │ ◄────────────────│   AES-GCM encrypted {access, refresh, expiry, call count}          │ │
   └────────────────────┘                  │   single in-flight refresh · persists rotated refresh token first  │ │
                                           └────────────────────────────────────────────────────────────────────┼─┘
                                                                                                                ▼
                                                                                       https://api.toodledo.com/3/*
src/
  core/                 platform-agnostic (no Cloudflare imports)
    dates.ts            ISO <-> Toodledo timestamps (GMT-noon dates, floating times)
    token-manager.ts    refresh/rotation/serialisation/encryption logic
    toodledo-oauth.ts   authorize URL + token endpoint
    toodledo-client.ts  API calls, batching (50), pagination (1000), retry/backoff
    lookups.ts          folder/context/goal/location cache + name resolution
    schemas.ts          zod input schemas
    format.ts           raw task <-> tool I/O
    tools.ts            MCP tool definitions
  worker/               Cloudflare glue
    auth-handler.ts     /authorize, /callback, consent page
    token-store.ts      ToodledoTokenStore Durable Object
    mcp-agent.ts        ToodledoMCP (McpAgent)
  index.ts              Worker entry (OAuthProvider)
test/                   vitest unit tests (Toodledo is mocked)
scripts/smoke-test.mjs  end-to-end test against a deployed instance

The core only uses fetch and WebCrypto, so it can be reused on another runtime by swapping src/worker/.


Related MCP server: ticktick-mcp

Self-hosting (≈30 minutes)

Prerequisites

  • Node.js 20+ and npm

  • A free Cloudflare account

  • A Toodledo account

  • A Claude plan that supports custom connectors

1. Get the code

git clone https://github.com/<you>/toodledo-mcp.git
cd toodledo-mcp
npm install
npm run check        # typecheck + lint + tests

2. Log in to Cloudflare and create the KV namespace

npx wrangler login
npx wrangler kv namespace create OAUTH_KV

Copy the printed id into wrangler.toml (replace REPLACE_WITH_YOUR_KV_NAMESPACE_ID). The KV id is not a secret.

Durable Objects need no manual setup: the [[migrations]] block in wrangler.toml creates them on first deploy (SQLite-backed, available on the free plan).

3. First deploy (to learn your URL)

npx wrangler deploy

Note the URL, e.g. https://toodledo-mcp.<your-subdomain>.workers.dev. Visiting it shows a small "toodledo-mcp is running" page. It can't authorize anyone yet because the secrets aren't set.

4. Register a Toodledo app

  1. Sign in to Toodledo, then open https://api.toodledo.com/3/account/doc_register.php.

  2. Create a new application:

    • Redirect URI: https://toodledo-mcp.<your-subdomain>.workers.dev/callback (it must match exactly)

    • Name/description/website: anything you like.

  3. Keep the Client ID and Client Secret handy for the next step. Don't paste them anywhere else.

5. Set the secrets

Every secret goes in through Wrangler's interactive prompt or a pipe, so it never lands in a file or your shell history:

npx wrangler secret put TOODLEDO_CLIENT_ID        # paste when prompted
npx wrangler secret put TOODLEDO_CLIENT_SECRET    # paste when prompted
openssl rand -base64 32 | npx wrangler secret put TOKEN_ENCRYPTION_KEY
openssl rand -base64 32 | npx wrangler secret put COOKIE_ENCRYPTION_KEY

Optional: restrict the instance to your own Toodledo account (recommended). Leave this unset for the first connection. Afterwards, toodledo_get_account shows your userid:

npx wrangler secret put ALLOWED_TOODLEDO_USERIDS   # e.g. "td1234567890abc"

⚠️ Don't rotate TOKEN_ENCRYPTION_KEY casually. Stored Toodledo tokens become unreadable and you'll have to reconnect.

npm run smoke -- https://toodledo-mcp.<your-subdomain>.workers.dev

This opens your browser for consent and Toodledo sign-in, then runs list_folders, list_tasks, add_tasks, edit_tasks, get_task, complete_tasks and delete_tasks on a throwaway task titled [toodledo-mcp smoke test] …. Add --keep to leave the task in Toodledo. See Verification for MCP Inspector instructions.

7. Add the connector in Claude

  1. In claude.ai (web or desktop): Settings → Connectors → Add custom connector.

  2. Name: Toodledo. URL: https://toodledo-mcp.<your-subdomain>.workers.dev/mcp.

  3. Click Add, then Connect. Approve the consent page, then sign in to Toodledo and allow access. You only do this once.

  4. In a chat, turn the connector on from the tools menu if it isn't already. The connector syncs to Claude on iPhone/Android and works in scheduled tasks.

Try: "List my open Toodledo tasks due this week, grouped by folder."

Local development

cp .dev.vars.example .dev.vars   # fill in values (never commit .dev.vars)
npm run dev                      # http://localhost:8787

For a local Toodledo login you'd need a second Toodledo app whose redirect URI is http://localhost:8787/callback.


Tool reference

All dates are YYYY-MM-DD and all times are HH:MM (24h). Folder, context, goal and location inputs accept a name (case-insensitive) or an id. 0 or "none" clears them. Outputs include { id, name } objects.

Tool

Kind

What it does

toodledo_get_account

read

Alias, plan (subtasks need a paid plan), date format, last-edit timestamps

toodledo_list_tasks

read

Filters: completed (false default / true / "any"), modified_after, folder, context, goal, location, due_before, due_after (inclusive), star, priority, min_priority, status (one or list), search (title/tags/note); sort (due, priority, modified, title); fields; limit/offset; max_scan

toodledo_get_task

read

One task by id, all fields

toodledo_add_tasks

write

Batch (≤200): title, note, folder, context, goal, location, duedate, duetime, duedatemod, startdate, starttime, priority, status, star, tag (string or list), repeat (RRULE), length (min), remind, parent

toodledo_edit_tasks

write

Batch partial update by id. null clears dates/times. completed: "YYYY-MM-DD" or null (reopen)

toodledo_complete_tasks

write

Batch complete. Repeating tasks roll to the next occurrence

toodledo_delete_tasks

destructive

Batch permanent delete. Requires confirm: true

toodledo_list_folders

read

include_archived

toodledo_list_contexts

read

toodledo_list_goals

read

Level (lifetime/long_term/short_term), contributes_to relation

toodledo_list_locations

read

toodledo_add_folder / toodledo_add_context

write

Create by name

toodledo_add_goal

write

name, level, contributes_to, note

Enum values: priority negative, low, medium, high, top. status none, next_action, active, planning, delegated, waiting, hold, postponed, someday, canceled, reference. duedatemod due_by, due_on, due_after, optionally.

Read tools have readOnlyHint: true. Delete has destructiveHint: true.

Toodledo date conventions (how conversion works)

  • Dates (duedate, startdate, completed, added) are stored by Toodledo as a unix timestamp at 12:00 GMT of that day. Noon keeps the calendar day the same in nearly every timezone. This server converts 2026-11-08 ⇄ 1794139200 and ignores the time part on the way back.

  • Times (duetime, starttime) are floating: the GMT clock time is the time you chose (18:30 stays 18:30 anywhere). When you change only the time on an existing task, the server reads the task's current date first so the time stays on the right day.

  • modified is a real instant, returned as ISO 8601 UTC.

  • toodledo_complete_tasks defaults to today in UTC. Claude is told to pass your local date when it knows it.


Security notes

  • No authless mode. /mcp requires an OAuth access token issued by this server. Tokens are issued only after a person approves the consent page and signs in to Toodledo.

  • Consent page and CSRF. Dynamic client registration is open, as MCP clients expect. The consent page shows the client name and redirect host, so an unknown app can't silently get a token. The flow uses a signed CSRF cookie, and the Toodledo state is bound to the browser with a signed cookie.

  • Restrict accounts. Set ALLOWED_TOODLEDO_USERIDS so only your Toodledo account can use your instance and your Workers quota.

  • Encryption at rest. Toodledo access and refresh tokens are encrypted with AES-256-GCM using TOKEN_ENCRYPTION_KEY. The ciphertext is bound to the user id, so blobs can't be swapped between users. Claude-side OAuth grants are handled by workers-oauth-provider, which stores only hashes of its tokens.

  • Secrets only live in Cloudflare (wrangler secret put). .dev.vars is git-ignored. CI uses no secrets.

  • Scopes. The server requests the Toodledo scopes basic tasks write, and nothing for notes, outlines, lists or sharing.

  • Revoking access. Disconnect the connector in Claude, revoke the app in Toodledo's account settings, or delete the worker.

See SECURITY.md for reporting vulnerabilities.

Token lifecycle (why it survives > 2 hours)

Toodledo rule

How it's handled

Access token lasts 2h

Refreshed automatically 5 minutes before expiry

~100 API calls per access token

Calls are counted per token. A refresh happens at 90, or on a rate-limit error after heavy use

Refresh token is single-use

Every refresh goes through one Durable Object per user with a single in-flight promise. The new refresh token is persisted before the access token is handed out

Refresh token dies after 30 days unused

Tools return: "Toodledo authorization has expired… Reconnect the Toodledo connector in Claude…"

~10 token requests per hour

If the token endpoint is rate limited, the current access token keeps being used while it's still valid

Errors 1/2 (invalid token)

One forced refresh and retry, then a re-authorize error

Error 3 / HTTP 429, 5xx, network

Exponential backoff (0.5s, 1s, 2s)

Batch writes go to Toodledo in chunks of 50 (the API maximum). Task listing reads pages of 1000.

Limitations

  • Filtering is client-side for everything except completion and modified_after, because Toodledo's tasks/get has no folder or due-date filters. max_scan (default 2000, max 5000) caps how many tasks are read per call, and each 1000 tasks costs one API call.

  • If you don't use the connector for 30 days, the Toodledo refresh token expires and you'll need to reconnect once.

  • Subtasks (parent) need a paid Toodledo plan.

  • Not in v1: notes, outlines, lists, habits, collaboration/sharing, webhooks, editing or deleting folders, contexts and goals.

  • "Today" defaults to UTC when completing tasks without a date.


Verification

Option A: script. npm run smoke -- <your worker URL> (see step 6).

Option B: MCP Inspector.

npx @modelcontextprotocol/inspector
  1. Transport Streamable HTTP, URL https://toodledo-mcp.<your-subdomain>.workers.dev/mcp.

  2. Open Auth settings → Quick OAuth flow (or Connect) and complete the consent page and Toodledo sign-in.

  3. Tools → List tools, then run:

    • toodledo_list_folders with {}

    • toodledo_list_tasks with {"limit": 5, "sort": "due"}

    • toodledo_add_tasks with {"tasks":[{"title":"Inspector test","duedate":"2026-11-15","priority":"high"}]} (note the returned id)

    • toodledo_edit_tasks with {"tasks":[{"id": <id>, "title":"Inspector test (edited)","duetime":"18:30"}]}

    • toodledo_complete_tasks with {"ids":[<id>]}

Token refresh > 2h. Make one call, wait more than 2 hours, then make another. It should succeed with no re-login. To watch rotations, run npx wrangler tail (tokens are never logged).

Contributing

See CONTRIBUTING.md. npm run check must pass.

License

MIT. Not affiliated with Toodledo or Anthropic.

Tool Schema Changelog

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

Loading tool schema changelog…

    Related MCP Connectors

    Related MCP Servers