ms-to-do-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@ms-to-do-mcpshow my tasks due today"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
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 reach127.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 | |
| every list, with its active-task count |
| tasks across one or all lists, sorted by due date; |
| one task in full, including checklist steps |
| substring match over titles and notes, all lists |
| a task's steps |
| new task with due, reminder, notes, importance |
| change any field; empty string clears a due date or reminder |
| mark done (recurring tasks roll forward) |
| permanent — annotated |
| new list |
| 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: Todoist 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-todoEverything 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 authIt 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/mcpCodex — in ~/.codex/config.toml:
[mcp_servers.microsoft_todo]
url = "http://127.0.0.1:8787/mcp"
startup_timeout_sec = 20
tool_timeout_sec = 120For 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.mdBuilt against mcp 2.x (MCPServer, not the removed 1.x FastMCP).
This server cannot be deployed
Maintenance
Related MCP Connectors
Create, list, and complete todo items through MCP.
Local-first task manager: create, edit, and complete tasks, projects, and checklists via MCP.
Microsoft To Do (Microsoft 365) MCP Pack
- mcpOAuthnet.todoist
Official Todoist MCP server for AI assistants to manage tasks, projects, and workflows.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables comprehensive Microsoft To Do task management through Microsoft Graph API with Entra ID OAuth authentication. Supports creating, updating, and managing todo lists, tasks, reminders, and due dates through natural language.-
- AlicenseNot gradedqualityDmaintenanceEnables interaction with Todoist through the MCP interface, providing full CRUD operations for tasks and projects including creating, updating, completing, and filtering tasks with natural language commands.9 npmMIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage Microsoft To Do tasks via the Graph API, supporting multiple accounts, encrypted auth, and full CRUD operations.5 npm2MIT
- AlicenseAqualityCmaintenanceMCP server for Microsoft To Do that enables managing task lists, tasks, and checklist items via the Microsoft Graph API with zero-config authentication.132Apache 2.0