todoist-mcp
Click on "Install 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., "@todoist-mcpshow 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.
todoist-mcp
A purpose-built, local-stdio Model Context Protocol server for a single personal Todoist (GTD) account. It exposes a deliberately narrow tool surface, an env-gated read-only mode, and in-server prompt-injection mitigations.
It was built fresh against the Todoist API v1. It is not a fork of any existing server; two proven patterns (SSRF allowlist, token redaction) were reimplemented from MadLlama25/fastmail-mcp as reference, everything else is written clean.
Why this exists (and the one hard limitation)
Off-the-shelf Todoist MCP servers were rejected because none offers a credential-level read-only tier reachable from local stdio, and all expose far more surface than personal GTD needs. This server gives a controlled tool set, in-server injection defenses, an env-gated read-only mode, and full auditability.
Credential constraint — read this first
Todoist's personal API token (TODOIST_API_KEY, from Settings → Integrations → Developer) is always full read + write, account-wide. Todoist has no scope parameter, flag, or token tier that narrows a personal token to read-only. Scoped OAuth exists, but only via a registered OAuth app + browser consent flow, which does not fit the per-subagent local-stdio isolation pattern this connection uses.
Therefore the token can always write. All read/write separation in this server is enforced in the server process, not at the credential layer. On the connection-architecture "enforcement ladder" this is the server layer (strong: holds regardless of harness behavior) — but it is softer than a credential-layer guarantee, because the same token, used by a different process, could still write. This is stated plainly, not hidden.
Related MCP server: Todoist MCP Server
Env-gated read-only mode
The server reads TODOIST_READONLY once, at process startup, when it registers tools:
| Mode | Write tools |
unset / empty | read-only (fail-safe default) | not registered — do not exist in the process |
| read-only | not registered |
| read/write | registered |
When read-only, the write tools are not registered at all — they are absent from the process, not merely hidden or rejected. This is not a runtime toggle; nothing flips it mid-session. Which mode you get is decided entirely by the launching environment (i.e. which subagent frontmatter launched the process).
What read-only mode does and does NOT protect against
Does: cap the blast radius of a data-borne injection during a read session. If a task or comment contains "delete everything in project X" and the reading process has no write tools registered, the injected instruction has nothing to call.
Does NOT: protect against a compromised orchestrator that can choose to invoke a write-capable process. Read-only mode is not a defense against orchestrator compromise, and this README does not claim it is.
Tool set
Read tools are registered in all modes. Write tools are registered only when TODOIST_READONLY=false.
Read tools (all modes)
Tool | Todoist API v1 endpoint(s) |
|
|
|
|
|
|
|
|
|
|
|
|
| Aggregates |
Write tools (only when TODOIST_READONLY=false)
Tool | Todoist API v1 endpoint(s) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Never implemented / never registered (any mode)
delete-object (deletion is a manual, human-only operation), manage-assignments, list-workspaces, find-project-collaborators, get-workspace-insights, analyze-project-health, get-productivity-stats, get-project-activity-stats, project-health, reorder-objects, project-move, add-reminders, add-filters, update-filters.
There is no delete tool of any kind. Removing a task, project, section, or label is done by a human in Todoist directly. Reminders and filters may be added later if adopted; they are deliberately out of scope for now.
In-server injection mitigations
Ingress is the read path: untrusted content enters agent context when a tool reads task/description/comment/project/section/label text. All of the following are built in:
Content framing — every untrusted field value is wrapped in explicit
‹UNTRUSTED›…‹/UNTRUSTED›delimiters marking it as data, not instructions, with a leading notice on every read result. A field cannot forge a closing fence (any embedded fence marker is neutralized).HTML/markdown stripping — HTML tags/comments are removed, markdown links/images/emphasis/code markers are defanged, and control characters are stripped, before framing.
Output-size caps — total text returned by any read tool is bounded (
TODOIST_MAX_OUTPUT_CHARS, default 50000) and each field is capped (TODOIST_MAX_FIELD_CHARS, default 2000); pagination is bounded (TODOIST_MAX_ITEMS, default 200). A huge list cannot flood context.SSRF allowlist — every outbound request is pinned to
https://api.todoist.com. Any other host, or non-HTTPS scheme, is rejected before a socket opens.Token redaction — the API token is registered as a secret at startup and scrubbed from all logs and error messages;
Bearer …/Authorization:material is also redacted generically. Logs go to stderr only (stdout is the MCP transport).Env-gated read-only registration — as described above.
A full prompt-injection threat model for this connection is deliberately deferred (a task list is a narrower, lower-stakes blast radius than a mailbox). The six mitigations above are the agreed baseline.
Configuration
All configuration is environment-based and read once at startup. See .env.example.
Variable | Default | Meaning |
| — | The Todoist personal API token. Full read+write (see constraint above). |
| — | Alternative to the above: path to a file whose contents are the token. Used if |
| (unset → read-only) |
|
|
| Whole-response output-size cap per read tool. |
|
| Per-field truncation cap for untrusted text. |
|
| Max items fetched across pagination per read call. |
Credential storage (deployment)
Follow the M365 / Fastmail file precedent on the host (LegioNix):
Store the token in a file owned by
claudecode, mode0600, inside a directory mode0700— e.g./home/claudecode/.todoist-mcp/token.Point the subagent at it with
TODOIST_API_KEY_FILE=/home/claudecode/.todoist-mcp/token.Never commit a real token.
.gitignoreexcludes.env,*.token, andsecrets/.
install -d -m 0700 -o claudecode -g claudecode /home/claudecode/.todoist-mcp
printf '%s' 'YOUR_TODOIST_TOKEN' > /home/claudecode/.todoist-mcp/token
chmod 0600 /home/claudecode/.todoist-mcp/tokenRunning
npm install
# read-only (default)
TODOIST_API_KEY=... node src/index.js
# read/write
TODOIST_API_KEY=... TODOIST_READONLY=false node src/index.jsThe server speaks MCP over stdio. It is intended to be launched by a Claude Code subagent via inline mcpServers frontmatter — see .claude/agents/todoist.md, which follows the m365-briefing isolation pattern.
Tests
npm test # offline suite (registration, config, sanitize, redact, client/SSRF, MCP e2e)
# real spawned-process checks (fake token; tools/list makes no network call)
TODOIST_READONLY=true node scripts/stdio-check.js
TODOIST_READONLY=false node scripts/stdio-check.js
# LIVE write round-trip — needs a real token, hits the real account
TODOIST_API_KEY=... TODOIST_READONLY=false npm run smokeThe offline suite covers: excluded tools absent in every mode; read-only vs. read/write registration split (verified both in-process and against a real spawned stdio process); content framing + markup stripping; forged-fence neutralization; per-field and whole-output size caps; token redaction (including on a deliberately triggered error); and the SSRF allowlist rejecting non-api.todoist.com hosts and non-HTTPS schemes.
The one acceptance test that cannot run offline is the live write round-trip (add + read-back + complete a throwaway task) — run npm run smoke with a real token to exercise it.
Layout
src/
index.js stdio bootstrap; reads config once, connects transport
server.js builds the MCP server; env-gated tool registration
config.js env parsing; read-only default; token resolution (env or file)
client.js Todoist API v1 client; SSRF allowlist; pagination
sanitize.js framing + markup stripping + size caps (read-path ingress)
shape.js raw API object -> compact, sanitized result object
redact.js token/secret redaction
logger.js stderr logging (redacted)
tools/read.js the 7 read tools
tools/write.js the 9 write tools (registered only when writes enabled)
scripts/
stdio-check.js list tools from a real spawned process
live-smoke.js live write round-trip (needs a real token)
test/ offline test suite
.claude/agents/todoist.md the read/write subagent (inline mcpServers frontmatter)This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/kevlasher/todoist-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server