Skip to main content
Glama

ms-to-do-mcp

An MCP server for Microsoft To Do, in two deployments sharing the same eleven tools and the same Azure app registration:

  • Local Docker (this README, Python) — streamable HTTP on http://127.0.0.1:8787/mcp, unauthenticated, loopback only. Any local MCP client — Claude Code, Codex, Claude Desktop — points at that one URL and shares the same container and the same Microsoft sign-in.

  • Remote Cloudflare Worker (worker/, TypeScript) — for hosted clients like ChatGPT web that cannot reach 127.0.0.1. Fronted by OAuth 2.1 + PKCE, with Microsoft as the identity provider.

Both talk to Microsoft Graph as a public client against the /consumers tenant, with the Tasks.ReadWrite scope. The local version signs in with the device-code flow; the Worker with the auth-code flow.

Tools

Tool

list_task_lists

every list, with its active-task count

list_tasks

tasks across one or all lists, sorted by due date; include_completed, due_before, limit

get_task

one task in full, including checklist steps

search_tasks

substring match over titles and notes, all lists

list_checklist_items

a task's steps

create_task

new task with due, reminder, notes, importance

update_task

change any field; empty string clears a due date or reminder

complete_task

mark done (recurring tasks roll forward)

delete_task

permanent — annotated destructiveHint

create_task_list

new list

add_checklist_item

add a step to a task

Lists and tasks are addressed by display name / title, not by Graph's opaque ids: exact match first, then a unique case-insensitive substring, and an error naming the candidates when a reference is ambiguous. Raw ids are accepted too.

Dates use YYYY-MM-DD or YYYY-MM-DD HH:MM in TODO_TIMEZONE. To Do stores a due date as a plain date, so a time passed in due is dropped by Microsoft — use reminder for a time-of-day alert.

Related MCP server: microsoft-todo-mcp-server

Setup (local Docker)

colima start                          # or Docker Desktop — the daemon must be up
docker compose run --rm ms-todo auth  # first run only — Microsoft sign-in (see below)
docker compose up -d
docker compose logs -f ms-todo

Everything else is in compose.yaml. The port is published as 127.0.0.1:8787 on purpose: the MCP endpoint is unauthenticated, so anything that can reach it can drive the To Do account. Never publish it as bare 8787.

For the remote deployment, follow worker/README.md instead — Cloudflare login, two KV namespaces, an Azure redirect URI, and npx wrangler deploy.

The sign-in

The Microsoft token lives at ~/.ms-todo-mcp/token.json (dir 700, file 600), outside this repo, bind-mounted into the container at /data. The container refreshes it in place and persists the rotated refresh token; never copy this file elsewhere, since two copies race each other's rotation and both eventually break.

The same auth command handles both the first sign-in and the re-sign-in Microsoft occasionally forces:

docker compose run --rm ms-todo auth

It prints a code to enter at microsoft.com/devicelogin on the host — no redirect URI or in-container browser needed, which is what makes this containerise cleanly.

Client registration

Claude Code:

claude mcp add --transport http --scope user ms-todo http://127.0.0.1:8787/mcp

Codex — in ~/.codex/config.toml:

[mcp_servers.microsoft_todo]
url = "http://127.0.0.1:8787/mcp"
startup_timeout_sec = 20
tool_timeout_sec = 120

For the Worker, register https://<worker>/mcp instead — the client then runs the OAuth flow on first use (details in worker/README.md).

Checking it works

# auth + Graph reachability, independent of MCP
docker compose run --rm --entrypoint python ms-todo -c \
  "from ms_todo_mcp.graph import get_all; print(len(get_all('me/todo/lists')), 'lists')"

# MCP handshake
curl -sS http://127.0.0.1:8787/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

A 404 means streamable_http_path is wrong; a 421 means the MCP_ALLOWED_HOSTS allowlist (DNS-rebinding protection) doesn't cover the Host header you sent.

Layout

src/ms_todo_mcp/     the local Docker server (Python)
├── graph.py         token lifecycle + Graph HTTP (stdlib urllib only, no deps)
├── auth.py          device-code sign-in
├── server.py        MCPServer + the 11 tools
└── __main__.py      `serve` (default) | `auth`

worker/              the remote Cloudflare Worker (TypeScript) — see worker/README.md

Built against mcp 2.x (MCPServer, not the removed 1.x FastMCP).

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