@doist/automations-mcp
v1.3.0
Published
MCP server and importable AI tools for Todoist Automations
Downloads
1,027
Readme
@doist/automations-mcp
The MCP server for Todoist Automations: the tools that give an agent the same access to a user's
automations that the tda CLI gives, packaged for the hosted MCP server at ai.todoist.net. The
design and the rollout it is part of are in
the spec.
What it exports
getMcpServer({ token, baseUrl }) builds an McpServer over one user's Automations, bound to the
bearer token it is given: a delegated Todoist OAuth token with the automations audience, or a
Todoist personal token.
validateAutomationsToken(token, baseUrl) classifies a token before any tool runs, with one
status-only call to the automations list: valid, invalid (401) or forbidden (403 with an
insufficient_scope challenge). A 403 without that challenge counts as valid on purpose: it is a
refusal consent cannot lift, such as the waiting list, so each tool surfaces the backend's own
message instead of the client looping through OAuth. Any other status throws
AutomationsTokenValidationError with the status on it, which the hosted server treats as
transient for 5xx.
That is the whole published surface. The tools themselves are typed against
@doist/automations-sdk, which is not published, so they are reached through the server rather than
exported, and the build refuses to emit a declaration that would leave a consumer with an import it
cannot resolve.
Chat turns
start-conversation and continue-conversation each run one durable server turn inside the tool
call, the way tda chat does in a process: the call blocks until the agent stops, which can take
minutes, and reports liveness every ten seconds through notifications/progress when the client
sent a progress token, or a debug log message otherwise. Cancelling the call aborts the upstream
turn; the partial reply is persisted and continue-conversation resumes it. The shared logic is
@doist/automations-sdk/chat-turn, so the CLI and this package cannot drift on how a pending
action is read or a connection resolved.
Running it locally
Everything here can run against a real account without the hosted server. AUTOMATIONS_API_KEY is
a Todoist personal token or the output of tda auth token view; AUTOMATIONS_BASE_URL points at
staging or a local backend and defaults to production.
npm run build:core # once, from the monorepo root
npm run build:mcp # produces apps/mcp/dist
npm --workspace=apps/mcp run inspect # the MCP inspector over stdio
npm --workspace=apps/mcp run tool -- find-automations '{"limit": 5}'
npm --workspace=apps/mcp run tool -- start-conversation '{"message": "Draft an automation that..."}'
npm --workspace=apps/mcp run tool:listAn MCP client can point at the stdio build directly, with the same environment:
{
"mcpServers": {
"automations": {
"command": "node",
"args": ["/path/to/automations/apps/mcp/dist/main.js"],
"env": { "AUTOMATIONS_API_KEY": "..." }
}
}
}Releases
@doist/automations-mcp is published from main by
.github/workflows/release-mcp.yml, which runs
semantic-release with the configuration in release.config.js. The version
comes from the conventional-commit messages that touched this directory, so nothing is bumped by
hand and the repository holds no version number of its own. Releases go to the latest dist tag:
the consumer is the hosted server, which pins an exact version. After publishing, the workflow sends
an automations-mcp-updated dispatch to Doist/todoist-ai-integrations, whose update workflow
installs the version, runs its checks, commits and redeploys, so a release reaches ai.todoist.net
without waiting for a dependency PR.
The workspace libraries the package uses are not published, so npm run build compiles them into
dist/ and only the packages under dependencies stay external. Because a release is scoped to
commits under apps/mcp, a change confined to one of those libraries does not trigger a release
even though it changes what gets published; it ships with the next release instead. Publish it
sooner by landing it together with a change here.
Adding a tool
src/tool-registry.ts is the single source of truth for the tool surface; the server, run-tool
and the token-footprint baseline derive from it, and src/tool-registry.test.ts fails when the
views drift.
src/utils/tool-names.ts: add the name.src/tools/<tool-name>.ts: the definition,satisfies AutomationsTool<...>. Copyfind-automations.ts.src/tool-registry.ts: add it to the map, in the section it belongs to.src/tools/tool-annotations.test.ts: add its annotation expectation.src/tools/<tool-name>.test.ts: tests against a fakedAutomationsApi.src/mcp-server.ts: add toinstructionsonly when the tool needs cross-tool routing guidance; per-tool detail belongs in its own description.
Output schemas never use .nullable(): nulls are stripped before the SDK validates the output, so
a nullable field is a validation failure waiting to happen. Use .optional() and leave the key
out. src/tools/output-schema-nullability.test.ts enforces it.
src/token-footprint.test.ts prints what the tool surface costs a model on every tools/list. If a
change pushes the combined fixed cost over its budget, raise the budget in the same PR and say so.
