Skip to main content
Glama
sjvadrevu
by sjvadrevu
README.md
# todoist-mcp-server

A custom Node.js [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that connects Claude to Todoist using a permanent API token. No OAuth, no re-authentication, works identically on Claude web, Claude Code, and Claude mobile.

## Why this exists

### The problem

Todoist needs to work across three Claude surfaces:
- Claude web (claude.ai) on PC
- Claude Code on PC and Linux
- Claude mobile on iPhone

Every existing solution fails at least one of these. The core issue is **authentication**. Todoist's official MCP server ([ai.todoist.net/mcp](https://ai.todoist.net/mcp)) uses OAuth, but Todoist does not issue OAuth refresh tokens. When the access token expires, Claude cannot silently refresh it and forces manual re-authentication. On Claude mobile this is impossible to fix: you cannot re-authenticate from the iOS app. This is a confirmed bug tracked in the Claude Code issue tracker: [#5706](https://github.com/anthropics/claude-code/issues/5706), [#12447](https://github.com/anthropics/claude-code/issues/12447), [#19456](https://github.com/anthropics/claude-code/issues/19456), [#28256](https://github.com/anthropics/claude-code/issues/28256).

### What we evaluated

| Option | Why it failed |
|--------|--------------|
| **Todoist official MCP** | OAuth with no refresh token. Re-auths daily. Broken on mobile. |
| **ClickUp** | Official MCP in Claude directory but free tier caps at 300 MCP calls/day. |
| **Microsoft To Do** | M365 connector requires a business Entra tenant. Personal Microsoft accounts explicitly blocked. |
| **Trello** | No hosted MCP URL. All implementations are local stdio only, which don't work on Claude web or mobile. |
| **TickTick** | Local stdio only, no hosted URL. |
| **Apple Reminders via CalDAV** | iOS 13+ broke all third-party CalDAV write access to the native Reminders app. Items written via CalDAV do not appear in the app. Not fixable. |
| **Zapier MCP** | Free tier: 100 tasks/month. Each MCP call costs 2 tasks = 50 calls/month total. |
| **n8n cloud** | No meaningful free tier. $24/month plan exhausted in ~9 days by polling. |
| **Composio** | Works for Claude Code only via local config. Cannot be used as a remote connector for Claude web or mobile. |
| **Bindify** | Unverified indie credential proxy. No GitHub presence, no security audit. |
| **Asana** | Team-focused. Missing personal task richness, no Todoist import. |

### Why a custom server works

Todoist issues a **permanent API token** (Settings > Integrations > Developer). This token never expires unless manually revoked. It is not an OAuth token and requires no refresh.

A custom MCP server that authenticates to Todoist using this token (stored as an Azure environment variable, never in code) gives a hosted remote MCP URL that:

- Never requires re-authentication on any surface
- Works on Claude web, Claude Code, and Claude mobile identically
- Has no rate limits from a middleware layer
- Is fully under the owner's control
- Updates by editing one file in GitHub (Azure redeploys automatically)

### API version note

Todoist REST API v2 and Sync API v9 are deprecated as of early 2026. This server uses the unified **Todoist API v1** at `https://api.todoist.com/api/v1`.

---

## Architecture

```mermaid
flowchart TD
    A["๐Ÿ–ฅ๏ธ Claude web (claude.ai)"] --> D
    B["๐Ÿ’ป Claude Code (PC / Linux)"] --> D
    C["๐Ÿ“ฑ Claude mobile (iOS)"] --> D

    D["todoist-mcp-server\nNode.js ยท Azure App Service"]

    D -->|"Todoist API v1\npermanent Bearer token, never expires"| E["api.todoist.com"]

    style D fill:#0078d4,color:#fff,stroke:none
    style E fill:#db4035,color:#fff,stroke:none
```

**Key design decisions:**

- **Stateless HTTP transport:** `StreamableHTTPServerTransport` with no session ID generator. Each request is self-contained. Safe on any platform that recycles processes (Azure App Service, Cloud Run, etc.).
- **Native fetch:** Node 20+ has built-in `fetch`. No axios, no node-fetch at runtime.
- **`X-Request-Id` on mutations:** Todoist uses this for idempotency on POST/DELETE. Generated per request with `crypto.randomUUID()`.
- **Tool-per-operation:** Each API endpoint is its own MCP tool with typed Zod parameters. Gives Claude precise descriptions and significantly improves how reliably it picks the right tool.
- **No Express:** Node's built-in `http` module keeps dependencies minimal and startup fast.

---

## Tools (33 total)

### Tasks (8)

| Tool | Description |
|------|-------------|
| `get_tasks` | List active tasks. Filter by project, section, label, or Todoist filter query (e.g. `today`, `overdue`, `p1`, `#ProjectName`) |
| `get_task` | Get a single task by ID |
| `create_task` | Create a task with content, description, project, section, parent, labels, priority (1-4), due date, assignee |
| `update_task` | Update any field of an existing task |
| `complete_task` | Mark a task as done |
| `reopen_task` | Reopen a completed task |
| `delete_task` | Permanently delete a task |
| `move_task` | Move a task to a different project, section, or parent task |

Priority scale: `4` = urgent (red), `3` = high (orange), `2` = medium (blue), `1` = normal.

### Projects (5)

| Tool | Description |
|------|-------------|
| `get_projects` | List all projects |
| `get_project` | Get a single project by ID |
| `create_project` | Create a project with optional color, favorite flag, and view style (list or board) |
| `update_project` | Update project name, color, favorite status, or view style |
| `delete_project` | Delete a project and all its tasks |

### Sections (5)

| Tool | Description |
|------|-------------|
| `get_sections` | List all sections, optionally filtered by project |
| `get_section` | Get a single section by ID |
| `create_section` | Create a section inside a project |
| `update_section` | Rename a section |
| `delete_section` | Delete a section and all its tasks |

### Labels (5)

| Tool | Description |
|------|-------------|
| `get_labels` | List all personal labels |
| `get_label` | Get a single label by ID |
| `create_label` | Create a label with optional color, order, and favorite flag |
| `update_label` | Update a label's name, color, or favorite status |
| `delete_label` | Delete a label |

### Comments (5)

| Tool | Description |
|------|-------------|
| `get_comments` | Get all comments on a task or project (one of `task_id` or `project_id` required) |
| `get_comment` | Get a single comment by ID |
| `create_comment` | Add a comment to a task or project |
| `update_comment` | Edit the text of a comment |
| `delete_comment` | Delete a comment |

### Reminders (4)

Requires Todoist Pro. Reminders fire a notification at a specific time or relative to a task's due date.

| Tool | Description |
|------|-------------|
| `get_reminders` | Get all reminders, optionally filtered by task |
| `create_reminder` | Create a reminder: `absolute` (specific datetime) or `relative` (N minutes before due) |
| `update_reminder` | Update an existing reminder's time |
| `delete_reminder` | Delete a reminder |

### Productivity stats (1)

| Tool | Description |
|------|-------------|
| `get_productivity_stats` | Karma score, task completion counts, streaks, and daily/weekly goal progress |

---

## Adding to Claude

### claude.ai (web and mobile)

1. Go to claude.ai and sign in.
2. Click your profile icon, then **Customize**.
3. Go to **Connectors** and click **+**.
4. Select **Add custom connector** and fill in:
   - **Name**: Todoist
   - **Remote MCP server URL**: `https://your-deployed-app-url/mcp`
   - Leave all OAuth fields blank.
5. Click **Add**.

Syncs automatically to the Claude mobile app and Claude Code. No re-authentication ever.

### Claude Code (.mcp.json)

```json
{
  "mcpServers": {
    "todoist": {
      "type": "http",
      "url": "https://your-deployed-app-url/mcp"
    }
  }
}
```

---

## Local development

```bash
npm install
export TODOIST_API_TOKEN=your_token_here
npm start
```

Health check: `http://localhost:8080/health`
MCP endpoint: `http://localhost:8080/mcp`

Get your API token from: todoist.com/app/settings/integrations/developer

---

## A note

This server exists because I love both Claude and Todoist, and the broken OAuth re-authentication flow was ruining the experience of using them together. Hopefully this workaround becomes unnecessary someday. If Anthropic fixes Claude's MCP OAuth token refresh ([#5706](https://github.com/anthropics/claude-code/issues/5706), [#12447](https://github.com/anthropics/claude-code/issues/12447), [#19456](https://github.com/anthropics/claude-code/issues/19456), [#28256](https://github.com/anthropics/claude-code/issues/28256)) or Todoist issues proper refresh tokens, the official MCP server at [ai.todoist.net/mcp](https://ai.todoist.net/mcp) would make this redundant. Until then, this works.

---

## License

MIT. Copyright (c) 2026 Shashank Vadrevu. See [LICENSE](LICENSE).

Maintenance

ActivityInactive
ResponsivenessNo issues