Hermes Agent guidance and a local, personal-token Todoist integration.
Noah/Hermes default: use the local wrapper at
/root/.local/bin/todoistand keep the official hosted Todoist MCP disabled unless explicitly needed. The hosted MCP is richer, but it can trigger repeated OAuth prompts at Hermes startup. The local path usesTODOIST_API_TOKENfrom env or~/.config/todoist/env, avoids startup OAuth entirely, and includes deterministic safety rails.
# 1. Store a personal Todoist API token locally (one-time)
/root/.local/bin/set-todoist-token
# 2. Use the OAuth-free local wrapper
/root/.local/bin/todoist ping
/root/.local/bin/todoist list --filter "today | overdue" --limit 10The official hosted MCP remains useful for broad Todoist coverage, but keep it disabled by default on this host to avoid startup OAuth prompts:
hermes config set mcp_servers.todoist.enabled false# 1. Add the official Todoist MCP server to Hermes (one-time, OAuth in browser)
hermes mcp add todoist --url https://ai.todoist.net/mcp --auth oauth
# 2. Install the Hermes skill that teaches the agent how to drive it safely
hermes skills install \
https://raw.githubusercontent.com/mogilventures/hermes-todoist/main/skills/todoist-official-mcp/SKILL.md \
--category productivityThat's it. Hermes will surface Todoist tools (typically prefixed mcp_todoist_*) and the skill teaches it to look up before acting, dedupe before creating, and confirm destructive operations.
Full setup, OAuth flow, tool-naming notes, and troubleshooting are in docs/official-mcp-hermes.md.
| Official Todoist MCP (recommended) | Local plugin (this repo's Python package) | |
|---|---|---|
| Auth | OAuth via Hermes MCP host | TODOIST_API_TOKEN env var |
| Runtime | Hosted by Doist | Local Python process / stdio MCP |
| Surface | Broad — whatever Doist exposes & maintains | 14 curated tools, intentionally small |
| Tool names | Host-prefixed (e.g. mcp_todoist_*) |
Native Hermes (todoist_*) |
| Delete safety | Whatever Doist ships | Hard refuse without confirm: true |
| Idempotent create | Not guaranteed | todoist_create_or_update_task upserts by normalized content |
| Name resolution | Per Doist | Project/section names resolved case-insensitively client-side |
| Network requirement | Internet + OAuth | Internet to api.todoist.com only |
| Dependencies | None (host handles it) | stdlib urllib only |
| Best for | Day-to-day use | Air-gapped envs, scripts that need deterministic upsert, tests, automation pipelines that own their own token |
Rule of thumb: start with the official MCP. Reach for this plugin only if you specifically need one of the local-fallback properties on the right.
docs/
official-mcp-hermes.md # Setup guide for the recommended path
skills/
todoist/ # Skill that ships with the local plugin (todoist_* tools)
todoist-official-mcp/ # Skill for the official hosted MCP (mcp_todoist_* tools)
hermes_todoist/ # Local fallback plugin (Python, stdlib-only)
tests/ # Unit tests for the local plugin
You can install either skill on its own — skills/todoist-official-mcp/ is the new "primary" skill and does not depend on this Python package being installed.
Everything below documents the Python plugin in hermes_todoist/. Use it only if you specifically need a Hermes-native, local, personal-token integration with deterministic safety rails.
Choose it when you want:
- Native Hermes tools + bundled skill — tool names are direct (
todoist_*) and the skill teaches Hermes the safe operating pattern. - Zero runtime dependencies — stdlib
urllibonly. - Local personal-token auth — read from
TODOIST_API_TOKEN, never logged, never written to disk by this package. - Safety rails —
todoist_delete_taskrefuses withoutconfirm: true;todoist_create_or_update_taskdeduplicates by normalized content. - Resolves names — projects and sections can be passed by exact (case-insensitive) name or by ID; labels are passed by Todoist label name.
| Tool | What it does |
|---|---|
todoist_list_tasks |
List open tasks; filter by project / section / label / Todoist filter query |
todoist_get_task |
Fetch a single task by ID |
todoist_create_task |
Create a task (project / labels accepted as names) |
todoist_update_task |
Patch any subset of fields on a task |
todoist_complete_task |
Mark complete (recurring tasks advance) |
todoist_reopen_task |
Un-complete a task |
todoist_delete_task |
Requires confirm: true — irreversible |
todoist_list_projects |
List all projects |
todoist_list_sections |
List sections, optionally scoped to a project |
todoist_list_labels |
List personal labels |
todoist_add_comment |
Comment on a task or a project |
todoist_list_comments |
List comments on a task or a project |
todoist_find_duplicate_tasks |
Group open tasks with identical normalized content |
todoist_create_or_update_task |
Idempotent upsert by normalized content |
Every handler returns a JSON string of the form {"success": true, ...} or {"success": false, "error": "...", "code": "..."}. Errors never raise — the wrapper guarantees a JSON response. List endpoints preserve next_cursor for pagination.
hermes plugins install mogilventures/hermes-todoist --enableThe installer prompts for TODOIST_API_TOKEN (Todoist → Settings → Integrations → Developer → API token) and stores it via the standard Hermes plugin install flow.
pip install hermes-todoist
hermes plugins enable todoistThe package registers under the hermes_agent.plugins entry-point group, so Hermes auto-discovers it on next start.
git clone https://github.com/mogilventures/hermes-todoist ~/.hermes/plugins/todoist
hermes plugins enable todoist
export TODOIST_API_TOKEN=...The plugin reads exactly one environment variable:
| Var | Required | Description |
|---|---|---|
TODOIST_API_TOKEN |
yes | Your personal Todoist API token. |
The token is loaded lazily on the first HTTP call, so importing the package or running hermes plugins list does not require it.
Once enabled, talk to Hermes the way you'd talk to a Todoist-savvy assistant — the bundled skill tells the model which tool to pick and how to dedupe before creating.
You: Add "Buy oat milk" to Personal, due tomorrow 9am, priority 3.
Hermes calls
todoist_create_or_update_task(content="Buy oat milk", project="Personal", due_string="tomorrow 9am", priority=3). If the task already exists in Personal, the due date and priority are applied to the existing task instead of creating a duplicate.
You: What's overdue?
Hermes calls
todoist_list_tasks(filter="overdue")and summarizes the result.
You: Mark "Write report" done.
Hermes calls
todoist_list_tasks(filter="@work")(or by project) to find the ID, thentodoist_complete_task(task_id="…").
You: Delete the "Old grocery list" task.
Hermes resolves the ID, then calls
todoist_delete_task(task_id="…", confirm=true). Theconfirmflag is required — without it the tool refuses and returnscode: "confirmation_required".
import json
from hermes_todoist import tools
print(json.loads(tools.todoist_list_tasks({"filter": "today"})))
print(json.loads(tools.todoist_create_task({"content": "Ship hermes-todoist", "project": "Work"})))hermes-todoist version # 0.1.0
hermes-todoist tools # list registered tool names
hermes-todoist ping # GET /projects?limit=1 — verifies token + connectivity
hermes-todoist mcp # run the stdio MCP server (see below)hermes_todoist.mcp_server is a stdlib-only stdio MCP server that exposes the same 14 tools as native MCP tools. This is not the recommended path for Hermes — use the official hosted MCP for that. Use this stdio server from Claude Desktop, mcp-cli, or any other MCP host that does not support hosted/OAuth MCP.
Claude Desktop claude_desktop_config.json:
{
"mcpServers": {
"todoist": {
"command": "hermes-todoist",
"args": ["mcp"],
"env": { "TODOIST_API_TOKEN": "your-token-here" }
}
}
}Or via plain Python:
{
"mcpServers": {
"todoist": {
"command": "python",
"args": ["-m", "hermes_todoist.mcp_server"],
"env": { "TODOIST_API_TOKEN": "your-token-here" }
}
}
}The server speaks the 2024-11-05 revision of the protocol — initialize, tools/list, tools/call, and ping are implemented; notifications are accepted and ignored.
git clone https://github.com/mogilventures/hermes-todoist
cd hermes-todoist
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest # 55 unit tests, no token required (all HTTP is mocked)
ruff check hermes_todoist testsRecommended CI is the same command sequence on Python 3.10, 3.11, and 3.12: install .[dev], run ruff check hermes_todoist tests, then run pytest -v.
The test suite uses only unittest.mock plus the pytest framework — no other plugins. If pip install pytest is blocked in your environment, you can still smoke-test with stdlib only:
python -c "import hermes_todoist; from hermes_todoist import tools; \
ctx = type('C',(),{'register_tool':lambda self,**k:None,'register_skill':lambda self,*a:None})(); \
hermes_todoist.register(ctx); print('ok,', len(tools.TOOL_REGISTRY), 'tools')"Tests in CI do not touch the real Todoist API. If you want to verify against a real account locally:
export TODOIST_API_TOKEN=your-token
export HERMES_TODOIST_LIVE=1
hermes-todoist pingThe hermes-todoist ping command performs GET /projects?limit=1 and prints the response — minimal traffic, but enough to confirm the token works. Do not enable this in CI; the rate-limit and side-effect risks are not worth it.
- API base.
https://api.todoist.com/api/v1. The plugin setsUser-Agent: hermes-todoist/<version>. - Token loading. Lazy.
TodoistClient()succeeds without a token; the token is required only when a request is actually made. This makesimport hermes_todoistsafe inside test runners and CI. - Pagination. Todoist returns
{"results": [...], "next_cursor": "..." | null}for paginated endpoints. The plugin normalizes both shapes (resultsenvelope and bare list) and surfacesnext_cursorin every list response. - Name resolution. Projects and sections are resolved by exact case-insensitive name match against the full list (
limit=200). Numeric IDs are passed through without a lookup. If a name doesn't match, the tool returnscode: "client_error"rather than guessing. - Duplicate detection. Normalization is
lower(strip(collapse_whitespace(content))).todoist_create_or_update_taskupdatesdescription,due_*,labels,priority,duration, andduration_uniton the matched task when supplied; otherwise creates a new one.todoist_find_duplicate_tasksreports groups of size ≥ 2. - Error codes.
auth_error,rate_limited(withretry_after),api_error(with HTTP status),client_error,confirmation_required,unexpected_error.
MIT — see LICENSE.