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

195 lines
8.1 KiB
Markdown

---
name: ms-todo
description: >-
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):
```bash
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](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):
```bash
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:
```bash
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:
```bash
node scripts/todo.mjs lists
```
## Reading tasks
```bash
# 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](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.
```bash
# 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
```bash
# 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](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](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.