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>
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) IDTODO_TENANT_ID— optional; defaults tocommon(use a tenant id for work/school-only apps, orconsumersfor 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. Runnode scripts/todo.mjs login.401/403right after login → the app is missing the delegatedTasks.ReadWritepermission, or consent wasn't granted — see references/setup.md.AADSTS53003during 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) → registerhttp://localhostandhttp://127.0.0.1under the app's Mobile-and-desktop platform. See setup.400on a write → usually a bad property name or value format (e.g. a date not indateTimeTimeZoneshape, or an invalidstatus/importanceenum). Check references/graph-api.md.404on a list → wrong name; runliststo 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.