Files
Spencer McGuire 2162aac515 Initial commit: Microsoft To Do for Claude (skill + .mcpb extension)
Claude Code skill (CLI over Microsoft Graph, delegated auth-code + PKCE) and a
Claude Desktop .mcpb extension sharing one dependency-free core client.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 17:31:48 -06:00

8.1 KiB

name, description
name description
ms-todo Read and write Microsoft To Do tasks via the Microsoft Graph API using delegated (browser, auth-code + PKCE) authentication. Use this skill whenever the user wants to view, list, search, add, create, update, complete, check off, or delete their Microsoft To Do tasks, task lists, or subtasks/checklist items — even if they just say "my to-dos", "my tasks", "my task list", "add a reminder", "what's on my list", or name a specific To Do list rather than saying "Microsoft To Do" explicitly. Also use it for bulk operations over tasks (export, reporting, batch complete/clean-up) and for sign-in/connectivity troubleshooting against To Do. This is for Microsoft *To Do* (personal tasks in a user's mailbox), not Planner, Project, or SharePoint task lists.

Microsoft To Do

Connect to Microsoft To Do and perform full CRUD (create, read, update, delete) on task lists, tasks, and checklist items (subtasks) through the Microsoft Graph API.

Authentication is delegated, not app-only. Microsoft To Do has no application permission — the Graph /me/todo endpoints only accept a signed-in user's token. This skill uses the OAuth2 authorization-code + PKCE flow: login opens your browser, you sign in, and the refresh token is cached locally so later runs refresh silently. This is the key difference from an app-only integration like SharePoint Lists — there are no *_CLIENT_SECRET credentials and a human must sign in the first time. (A browser flow is used rather than device-code because device-code is often blocked by Conditional Access — AADSTS53003.)

All operations go through one CLI, scripts/todo.mjs, which wraps the reusable core client in scripts/lib/graph.mjs. Run it with Node (already available):

node scripts/todo.mjs <command> [flags]

Paths above are relative to this skill's directory. From elsewhere, use the absolute path, e.g. node "<skill-dir>/scripts/todo.mjs" ....

Prerequisites (one-time)

You need an Entra (Azure AD) public client app registration with the delegated Tasks.ReadWrite permission. It requires only one value:

  • TODO_CLIENT_ID — Application (client) ID
  • TODO_TENANT_ID — optional; defaults to common (use a tenant id for work/school-only apps, or consumers for personal Microsoft accounts only)

Set them as environment variables, or copy .env.example to .env in this skill's directory and fill it in (the CLI loads .env automatically; .env is git-ignored).

If the app registration is not yet set up, do not guess — point the user to references/setup.md, a step-by-step walkthrough of creating the public-client app, adding the loopback redirect URI, adding the delegated Tasks.ReadWrite permission, and signing in.

First step in any task: sign in, then verify

Sign in once (interactive — the user must complete it in a browser):

node scripts/todo.mjs login

This opens the user's browser to the Microsoft sign-in page (and prints the URL as a fallback). The user signs in and approves; the browser redirects back to a temporary local server and the refresh token is cached to scripts/.token-cache.json (git-ignored). Run login on the same machine as the browser. After that, confirm everything works:

node scripts/todo.mjs test

A successful response shows who is signed in and the names of their task lists. If any command returns "needsLogin": true, the cached sign-in is missing or expired — run login again.

Identifying a list

  • A task list can be given by display name (e.g. "Groceries") or by its opaque list id. The client resolves names to ids and, on a typo, lists the available names so you can correct it.
  • To Do list ids are long opaque strings (not GUIDs), so prefer the display name unless you already have the id from a previous call.

When unsure what lists exist, discover first:

node scripts/todo.mjs lists

Reading tasks

# Everything in a list (follows pagination):
node scripts/todo.mjs tasks --list "Groceries" --all

# Open tasks only, soonest due first, projected:
node scripts/todo.mjs tasks --list "Work" \
  --filter "status ne 'completed'" \
  --select "title,status,dueDateTime,importance" \
  --orderby "dueDateTime/dateTime asc" --top 50

# One task by id:
node scripts/todo.mjs get --list "Work" --id "<TASK_ID>"

Tasks come back simplified (id, title, status, importance, due/start/reminder date-times, body, categories, timestamps). See references/graph-api.md for the full property set and OData query details.

Writing tasks

Convenience flags cover the common properties; --fields (inline JSON, @file.json, or - for stdin) merges on top for anything else.

# Create
node scripts/todo.mjs create --list "Work" \
  --title "Renew SSL cert" --due 2026-07-01 --importance high \
  --body "Use the prod ACME account"

# Update (partial — only what you pass changes)
node scripts/todo.mjs update --list "Work" --id "<TASK_ID>" --status inProgress

# Complete (shorthand for --status completed)
node scripts/todo.mjs done --list "Work" --id "<TASK_ID>"

# Delete
node scripts/todo.mjs delete --list "Work" --id "<TASK_ID>"

--due, --start, and --reminder accept 2026-07-01 or a full date-time and are converted to Graph's dateTimeTimeZone shape using TODO_TIMEZONE (default UTC). Setting --reminder also turns the reminder on.

Deletes are irreversible. Confirm the task id with the user before deleting, and prefer showing them the task via get first. For bulk deletes, confirm the full set once, then proceed.

Task lists and checklist items

# Lists
node scripts/todo.mjs list-create --name "Trip planning"
node scripts/todo.mjs list-update --list "Trip planning" --name "Italy trip"
node scripts/todo.mjs list-delete --list "Italy trip"      # deletes its tasks too — confirm first

# Checklist items (subtasks) on a task
node scripts/todo.mjs checklist        --list "Work" --id "<TASK_ID>"
node scripts/todo.mjs checklist-add    --list "Work" --id "<TASK_ID>" --name "Draft email"
node scripts/todo.mjs checklist-check  --list "Work" --id "<TASK_ID>" --item "<ITEM_ID>"   # --uncheck to undo
node scripts/todo.mjs checklist-delete --list "Work" --id "<TASK_ID>" --item "<ITEM_ID>"

Bulk operations

For exports or batch edits, fetch with --all, transform the JSON however the task needs, then loop the relevant create/update/done/delete calls. Keep concurrency modest — Graph throttles with HTTP 429; the client already retries with backoff, but don't fan out hundreds of parallel writes.

Handling errors

The CLI prints a JSON error to stderr and exits non-zero. Common cases:

  • "needsLogin": true → no cached sign-in or the refresh token expired/was revoked. Run node scripts/todo.mjs login.
  • 401/403 right after login → the app is missing the delegated Tasks.ReadWrite permission, or consent wasn't granted — see references/setup.md.
  • AADSTS53003 during sign-in → a Conditional Access policy blocked it. This browser flow avoids device-code-specific blocks; if the policy requires a compliant/managed device, the device must be enrolled first. See setup.
  • AADSTS50011 (redirect mismatch) → register http://localhost and http://127.0.0.1 under the app's Mobile-and-desktop platform. See setup.
  • 400 on a write → usually a bad property name or value format (e.g. a date not in dateTimeTimeZone shape, or an invalid status/importance enum). Check references/graph-api.md.
  • 404 on a list → wrong name; run lists to see valid names.

Note on graduating to an MCP server

The core logic is isolated in scripts/lib/graph.mjs (the MSTodoClient class, zero dependencies). To expose this as a standalone MCP server later, import that class and expose its methods (listTasks, getTask, createTask, updateTask, deleteTask, list/checklist methods) as MCP tools — no rewrite of the Graph or auth logic needed. The only wrinkle vs. an app-only server: the browser login must be run once on the host to seed the token cache the server reads.