Skip to content

dzarlax/todoist_bot

Repository files navigation

Todoist Telegram Bot + MCP Server

Table of Contents


Description

Go service that does two things in one process, one container, one binary:

  1. Telegram → Todoist bot. Forward messages or write text to the bot — it creates tasks in Todoist in the right project automatically. Messages arriving within the timer window merge into a single task.
  2. Todoist MCP server. Exposes full CRUD over HTTP at /todoist so other AI agents (e.g. a personal assistant) can read and write your Todoist through standard MCP tools.

Originally a Node.js bot; rewritten in Go (2026-04) with the MCP server merged in from the personal_memory project — so everything Todoist-related now lives in one place.

Key Features

  • Automatic task creation: Text and forwarded messages become Todoist tasks. Messages within the timer window are merged into one task (first line = title, rest = description).
  • Priority via !p1!p4: Add !p2 to your message — the task gets priority 2. Marker is stripped from the final title.
  • Labels via #tag: Write #work or #urgent — the task gets those labels. Multiple tags supported. Does not match # inside URLs.
  • Media support: Photos, videos, documents, audio, voice, animations, stickers, locations, contacts, polls, venues — all converted to Markdown links embedded in the task.
  • Album / media-group support: Telegram albums are aggregated and attached as multi-line task content.
  • URL & hyperlink detection: Plain URLs, www. prefixes, and Telegram text_link entities become clickable Markdown links in Todoist.
  • Project mapping by username: Each Telegram user routes to a Todoist project via PROJECT_USERS_* env vars. Unknown senders fall back to Inbox with a notification.
  • Auto-whitelist: Users listed in PROJECT_USERS_* are automatically whitelisted — no duplicate config.
  • Bot-admin role: One user is admin via BOT_ADMIN. Only they can run /list.
  • Optional due date: AUTO_ADD_DUE_DATE=true → every new task gets today's date.
  • Embedded MCP server: /todoist endpoint with 7 tools, X-API-Key protected. Call it from personal-assistant, Claude Desktop, or any MCP client.
  • Docker-ready: Single static binary (~11 MB), alpine image, GitHub Actions builds & pushes to GHCR.

Why Go?

This repo once held a Node.js bot; the MCP server came from personal_memory, which itself was Python before being rewritten. When it came time to unify them, Go was the obvious target:

  • Stack consistency. Every other Go service in my Personal stack (personal_memory, personal_assistant, book, calendar-mcp) already uses the same libraries (chi, mcp-go, slog). Staying on Go means one toolchain, one set of idioms, and code can be copied between projects almost verbatim — which is literally what happened with internal/todoist.
  • mcp-go was already battle-tested. The Go port of the MCP SDK was running in personal_memory for months. Rewriting the Python MCP server into Go gave mcp-go another real-world consumer without rewriting the wire protocol layer.
  • One static binary, no runtime. CGO_ENABLED=0 go build → an ~11 MB binary that runs on FROM alpine with nothing but ca-certificates. The old Node image dragged in 200 MB of npm deps; a Python equivalent would pull in a similar amount of wheels.
  • Concurrency is free. Telegram long-polling + HTTP server + debounce timers run as three goroutines with a single sync.Mutex around the message buffer. In Python this would mean asyncio + aiohttp + careful locking; in Node it was callback soup.
  • Strong typing catches bot-API mismatches at compile time. tgbotapi.Message.Photo[] vs .Video vs .Document — the compiler forces you to handle every field rather than hoping msg.photo?.[0]?.file_id doesn't panic at runtime.

Configuration

Parsed in internal/config/config.go. All values come from environment variables (.env is auto-loaded via godotenv).

Variable Required Default Description
TELEGRAM_TOKEN yes Bot token from @BotFather
TODOIST_TOKEN yes Todoist API token (Settings → Integrations → Developer)
API_KEY recommended Required header value (X-API-Key) for MCP /todoist calls. Empty disables auth — dev only.
PORT 3000 HTTP port for /health and /todoist
BOT_ADMIN Admin Telegram username (with or without @). Gates /list.
TIMER 5 Seconds to buffer messages before creating a task
AUTO_ADD_DUE_DATE false true → every task gets today's date as due
PROJECT_USERS_<NAME> Map project <NAME> to users, e.g. PROJECT_USERS_Work="@alice,@bob"
ALLOWED_USERNAMES Extra whitelisted usernames, comma-separated, @ optional
MAX_RETRIES 3 Retries for transient Todoist API failures (429, 5xx)
RETRY_DELAY 1000 Initial retry backoff in ms (doubles each attempt)

Access control notes:

  • @username entries in PROJECT_USERS_* auto-add to the whitelist.
  • Full-name entries (without @) are used for routing but not whitelisting.
  • Empty whitelist = allow all users (legacy behaviour).

Commands

Command Who Description
/start Everyone Welcome message
/help Everyone List commands (admin sees /list)
/status Everyone Bot status + your role + current timer/auto-date settings
/list [project] Admin only Show up to 20 active tasks, optionally filtered by project name

Message Syntax

Buy groceries !p2 #personal #home
  • !p2 → priority 2 (1 = normal, 4 = urgent). Defaults to 1 if absent.
  • #personal #home → labels. Multiple supported.

Both markers are stripped from the final task title.

Examples of what the bot does with common inputs:

You send Task title in Todoist
Call Mom !p3 #family @you: Call Mom (priority 3, label family)
Photo with caption Kitchen remodel !p2 @you: Kitchen remodel [фото](https://…)
Forwarded message from @alice @alice: <original text>
Read https://example.com @you: Read [https://example.com](https://example.com)

Media Support

All 10+ Telegram attachment types are rendered as clickable Markdown:

Type Rendered as
Photo [фото](https://t.me/file/…)
Video [видео (file.mp4)](https://t.me/file/…)
Document [документ (report.pdf)](https://…)
Audio [аудио (Title - Artist)](https://…)
Voice message [голосовое сообщение](https://…)
Animation / GIF [анимация](https://…)
Sticker [стикер (😀)](https://…)
Location [локация](https://www.google.com/maps?q=…)
Contact [контакт](tel:+123…) — Name: phone
Poll Inline question + options
Venue [место](https://…) — Name, address

MCP Server

The /todoist endpoint uses mcp-go's Streamable HTTP transport and is protected by X-API-Key.

7 tools exposed:

Tool Purpose
get_projects List all Todoist projects
get_labels List all labels
get_tasks List tasks (optional project_id, filter, limit)
create_task Create a task (content required; optional description, project_id, due_string/due_date, priority, labels)
update_task Update an existing task (task_id required)
delete_task Delete a task
complete_task Mark a task complete

Example mcp.json entry for Claude Desktop or personal-assistant:

{
  "mcpServers": {
    "todoist": {
      "url": "http://todoist-bot:3000/todoist",
      "headers": { "X-API-Key": "your-api-key" }
    }
  }
}

GET /health returns {"status":"ok"} and is unauthenticated — useful for Docker healthchecks and Traefik probes.

Technologies

Go 1.24 · go-telegram-bot-api/v5 · mark3labs/mcp-go · go-chi/chi/v5 · joho/godotenv · Todoist REST API v1 · Docker · GitHub Actions → GHCR.

Getting Started

Prerequisites

  • Telegram account and bot token from @BotFather
  • Todoist account and API token (Settings → Integrations → Developer)
  • Docker installed

Installation

git clone https://github.com/dzarlax/todoist_bot.git
cd todoist_bot
cp docker-compose_example.yml docker-compose.yml

Edit docker-compose.yml and fill in your values:

environment:
  TELEGRAM_TOKEN: your_botfather_token
  TODOIST_TOKEN: your_todoist_api_token
  API_KEY: pick-a-strong-key
  BOT_ADMIN: "@yourusername"
  PROJECT_USERS_Work: "@alice,@bob"
  PROJECT_USERS_Family: "@wife"
  TIMER: 5
  AUTO_ADD_DUE_DATE: "true"

Launch

docker compose up -d
docker compose logs -f todoist-bot

Stop

docker compose down

Development

go run ./cmd/todoist-bot   # requires .env with TELEGRAM_TOKEN, TODOIST_TOKEN
go test ./...              # unit tests
go vet ./...
go build ./...

CI runs go vet + go test on every push/PR and builds/pushes ghcr.io/dzarlax/todoist-bot:latest + :${sha} on pushes to main (.github/workflows/docker.yml).

Migrating from the Node.js version

If you're currently running the old Node bot (dzarlax/todoist_bot on Docker Hub), the upgrade is drop-in. Todoist data is untouched — the bot is stateless, it only talks to Todoist's cloud API.

Changes you need to know:

  • Image: dzarlax/todoist_bot (Docker Hub) → ghcr.io/dzarlax/todoist-bot (GHCR). Note the dash in the new name.
  • Container name: todoisttodoist-bot (by convention — you can still set whatever you want).
  • Healthcheck URL: unchanged — http://localhost:3000/health.
  • Tokens: TELEGRAM_TOKEN / TODOIST_TOKEN — unchanged, reuse them as-is.
  • Project/user config: PROJECT_USERS_*, BOT_ADMIN, ALLOWED_USERNAMES, TIMER, AUTO_ADD_DUE_DATEall unchanged.
  • New: API_KEY — only needed if you plan to call the MCP /todoist endpoint from another service. Leave empty/unset for pure bot-mode.
  • Removed (no longer needed): LOG_LEVEL, NODE_ENV, MAX_RETRIES/RETRY_DELAY (now hardcoded sensible defaults — still overridable).

Step-by-step:

  1. Stash your old token values. If they're in docker-compose.yml, pull them out now — moving them into .env is recommended for the new setup.

    grep -E 'TELEGRAM_TOKEN|TODOIST_TOKEN|BOT_ADMIN|PROJECT_USERS|ALLOWED_USERNAMES' docker-compose.yml
  2. Stop and remove the old container. Don't worry — no volumes to lose.

    docker compose down
  3. Pull the latest docker-compose_example.yml from this repo (or update your existing docker-compose.yml by hand):

    wget https://raw.githubusercontent.com/dzarlax/todoist_bot/main/docker-compose_example.yml -O docker-compose.yml
  4. Create .env alongside docker-compose.yml with your tokens:

    TELEGRAM_TOKEN=<existing value>
    TODOIST_TOKEN=<existing value>
    API_KEY=<new, pick a strong random string>
  5. Edit docker-compose.yml to set BOT_ADMIN, PROJECT_USERS_*, etc. — values carry over 1:1.

  6. Start up:

    docker compose pull
    docker compose up -d
    docker compose logs -f todoist-bot

    You should see INFO starting todoist-bot followed by INFO telegram bot started user=<your_bot_username> within a second. If the logs show telegram init failed error="Not Found", double-check the Telegram token was copied intact (including the : and the secret part).

  7. Optional — wire up the MCP endpoint. Point any MCP client at http://<host>:3000/todoist with X-API-Key: <your API_KEY>. If you were previously hitting a Todoist MCP hosted in another service, switch its URL and key here.

Rollback, if needed: docker compose down and put back the old image: dzarlax/todoist_bot line + restart. No data migration to undo.


Todoist Telegram Bot + MCP-сервер

Описание

Go-сервис, который делает две вещи в одном процессе, одном контейнере, одном бинарнике:

  1. Telegram → Todoist бот. Пересылаете сообщение или пишете текст — бот создаёт задачу в нужном проекте Todoist. Сообщения в пределах таймера склеиваются в одну задачу.
  2. Todoist MCP-сервер. Полный CRUD по HTTP на /todoist, чтобы другие AI-агенты (например, personal-assistant) могли читать и писать ваш Todoist через стандартные MCP-инструменты.

Изначально был Node.js-бот; переписан на Go (2026-04) с интеграцией MCP-сервера из проекта personal_memory — теперь всё, что касается Todoist, в одном месте.

Основные функции

  • Автоматическое создание задач: Текстовые и пересланные сообщения становятся задачами Todoist. Сообщения в пределах таймера объединяются в одну задачу (первая строка — заголовок, остальные — описание).
  • Приоритет через !p1!p4: Добавьте !p2 в сообщение — задача получит приоритет 2. Маркер удаляется из итогового заголовка.
  • Ярлыки через #tag: Пишете #work или #срочно — задача получит эти метки. Несколько поддерживается. Не ловит # внутри URL.
  • Поддержка медиа: Фото, видео, документы, аудио, голосовые, анимации, стикеры, локации, контакты, опросы, места — всё конвертируется в Markdown-ссылки в задаче.
  • Альбомы / медиа-группы: Альбомы Telegram агрегируются и вкладываются многострочным описанием.
  • Распознавание URL и гиперссылок: Обычные URL, www. префиксы и Telegram text_link становятся кликабельными Markdown-ссылками в Todoist.
  • Маппинг проектов по пользователю: Каждый Telegram-пользователь роутится в Todoist-проект через PROJECT_USERS_*. Неизвестные отправители — в Inbox с уведомлением.
  • Автоматический вайтлист: Пользователи из PROJECT_USERS_* автоматически добавляются в whitelist — без дубля конфига.
  • Роль админа: Один пользователь — админ через BOT_ADMIN. Только он может вызывать /list.
  • Опциональная дата выполнения: AUTO_ADD_DUE_DATE=true → каждая новая задача получает срок на сегодня.
  • Встроенный MCP-сервер: Эндпоинт /todoist с 7 инструментами, защищён X-API-Key. Подключается из personal-assistant, Claude Desktop или любого MCP-клиента.
  • Docker-ready: Один статический бинарник (~11 МБ), alpine-образ, GitHub Actions пушат в GHCR.

Почему Go?

Раньше тут жил Node.js-бот; MCP-сервер пришёл из personal_memory, который сам был на Python до переписывания. Когда встала задача объединить всё это в одном месте, Go был очевидным выбором:

  • Консистентность стека. Все остальные Go-сервисы в моём Personal-стеке (personal_memory, personal_assistant, book, calendar-mcp) уже используют одни и те же библиотеки (chi, mcp-go, slog). Остаться на Go — значит иметь один тулчейн, одни идиомы и возможность буквально копировать код между проектами — что и случилось с internal/todoist.
  • mcp-go уже обкатан. Go-порт MCP SDK работал в personal_memory несколько месяцев. Переписывая Python-версию MCP-сервера на Go, я получил ещё одного боевого потребителя mcp-go без переписывания уровня протокола.
  • Один статический бинарник, без runtime. CGO_ENABLED=0 go build → ~11 МБ бинарник на FROM alpine и ca-certificates. Старый Node-образ тянул 200 МБ npm-зависимостей; Python-эквивалент притащил бы похожий объём wheels.
  • Конкурентность бесплатная. Telegram long-polling + HTTP-сервер + debounce-таймеры — три горутины с одним sync.Mutex вокруг буфера сообщений. На Python — asyncio + aiohttp + аккуратная блокировка; на Node — callback-суп.
  • Строгая типизация ловит несовпадения с bot API на компиляции. tgbotapi.Message.Photo[] против .Video против .Document — компилятор заставляет обработать каждое поле, а не надеяться, что msg.photo?.[0]?.file_id не упадёт в рантайме.

Конфигурация

Парсится в internal/config/config.go. Все значения — из переменных окружения (.env автозагружается через godotenv).

Переменная Обяз. По умолчанию Описание
TELEGRAM_TOKEN да Токен бота от @BotFather
TODOIST_TOKEN да API-токен Todoist (Настройки → Интеграции → Разработчик)
API_KEY рекоменд. Значение заголовка X-API-Key для MCP-вызовов /todoist. Пустой отключает auth — только для dev.
PORT 3000 HTTP-порт для /health и /todoist
BOT_ADMIN Админ Telegram username (с @ или без). Открывает /list.
TIMER 5 Секунды буферизации сообщений перед созданием задачи
AUTO_ADD_DUE_DATE false true → каждая задача получает срок на сегодня
PROJECT_USERS_<NAME> Маппинг проекта <NAME> на пользователей, например PROJECT_USERS_Work="@alice,@bob"
ALLOWED_USERNAMES Дополнительные разрешённые пользователи, через запятую, @ опционально
MAX_RETRIES 3 Повторы при временных ошибках Todoist API (429, 5xx)
RETRY_DELAY 1000 Начальный retry backoff в мс (удваивается с каждой попыткой)

Про контроль доступа:

  • Записи @username в PROJECT_USERS_* автоматически добавляются в whitelist.
  • Записи с полным именем (без @) используются для роутинга, но не для whitelist.
  • Пустой whitelist = разрешить всем (legacy поведение).

Команды

Команда Кому Описание
/start Всем Приветственное сообщение
/help Всем Список команд (админ видит /list)
/status Всем Статус бота + ваша роль + текущий таймер и авто-дата
/list [проект] Только админ До 20 активных задач, опционально фильтр по проекту

Синтаксис сообщений

Купить продукты !p2 #личное #дом
  • !p2 → приоритет 2 (1 = обычный, 4 = срочный). По умолчанию 1 если нет маркера.
  • #личное #дом → ярлыки. Несколько поддерживается.

Оба маркера удаляются из итогового заголовка задачи.

Примеры того, что бот делает с типичными вводами:

Вы шлёте Заголовок задачи в Todoist
Позвонить маме !p3 #семья @вы: Позвонить маме (приоритет 3, ярлык семья)
Фото с подписью Ремонт кухни !p2 @вы: Ремонт кухни [фото](https://…)
Пересланное от @alice @alice: <оригинальный текст>
Почитать https://example.com @вы: Почитать [https://example.com](https://example.com)

Поддержка медиа

Все 10+ типов вложений Telegram рендерятся как кликабельный Markdown:

Тип Рендерится как
Фото [фото](https://t.me/file/…)
Видео [видео (file.mp4)](https://t.me/file/…)
Документ [документ (report.pdf)](https://…)
Аудио [аудио (Название - Исполнитель)](https://…)
Голосовое [голосовое сообщение](https://…)
Анимация / GIF [анимация](https://…)
Стикер [стикер (😀)](https://…)
Локация [локация](https://www.google.com/maps?q=…)
Контакт [контакт](tel:+123…) — Имя: телефон
Опрос Вопрос + варианты встроенно
Место [место](https://…) — Название, адрес

MCP-сервер

Эндпоинт /todoist использует Streamable HTTP транспорт из mcp-go и защищён X-API-Key.

Экспонируются 7 инструментов:

Инструмент Назначение
get_projects Список всех проектов Todoist
get_labels Список всех меток
get_tasks Список задач (опционально project_id, filter, limit)
create_task Создать задачу (content обязателен; опционально description, project_id, due_string/due_date, priority, labels)
update_task Обновить существующую задачу (task_id обязателен)
delete_task Удалить задачу
complete_task Завершить задачу

Пример записи в mcp.json для Claude Desktop или personal-assistant:

{
  "mcpServers": {
    "todoist": {
      "url": "http://todoist-bot:3000/todoist",
      "headers": { "X-API-Key": "ваш-api-ключ" }
    }
  }
}

GET /health возвращает {"status":"ok"} и не требует аутентификации — удобно для Docker healthcheck'ов и Traefik-проб.

Технологии

Go 1.24 · go-telegram-bot-api/v5 · mark3labs/mcp-go · go-chi/chi/v5 · joho/godotenv · Todoist REST API v1 · Docker · GitHub Actions → GHCR.

Начало работы

Предварительные требования

  • Аккаунт Telegram и токен бота от @BotFather
  • Аккаунт Todoist и API-токен (Настройки → Интеграции → Разработчик)
  • Установленный Docker

Установка

git clone https://github.com/dzarlax/todoist_bot.git
cd todoist_bot
cp docker-compose_example.yml docker-compose.yml

Откройте docker-compose.yml и заполните переменные:

environment:
  TELEGRAM_TOKEN: ваш_токен_от_botfather
  TODOIST_TOKEN: ваш_todoist_api_токен
  API_KEY: выберите-сильный-ключ
  BOT_ADMIN: "@ваш_username"
  PROJECT_USERS_Work: "@alice,@bob"
  PROJECT_USERS_Family: "@wife"
  TIMER: 5
  AUTO_ADD_DUE_DATE: "true"

Запуск

docker compose up -d
docker compose logs -f todoist-bot

Остановка

docker compose down

Разработка

go run ./cmd/todoist-bot   # нужен .env с TELEGRAM_TOKEN, TODOIST_TOKEN
go test ./...              # юнит-тесты
go vet ./...
go build ./...

CI запускает go vet + go test на каждый push/PR и собирает/пушит ghcr.io/dzarlax/todoist-bot:latest + :${sha} при пушах в main (.github/workflows/docker.yml).

Миграция с Node.js-версии

Если сейчас у вас запущен старый Node-бот (dzarlax/todoist_bot на Docker Hub), обновление — drop-in. Данные Todoist не трогаются — бот stateless, он просто ходит в облачный API Todoist.

Что изменилось:

  • Образ: dzarlax/todoist_bot (Docker Hub) → ghcr.io/dzarlax/todoist-bot (GHCR). Обратите внимание на дефис в новом имени.
  • Имя контейнера: todoisttodoist-bot (по соглашению — можно ставить любое).
  • Healthcheck URL: не изменился — http://localhost:3000/health.
  • Токены: TELEGRAM_TOKEN / TODOIST_TOKEN — без изменений, переиспользуйте as-is.
  • Конфиг проектов/пользователей: PROJECT_USERS_*, BOT_ADMIN, ALLOWED_USERNAMES, TIMER, AUTO_ADD_DUE_DATEбез изменений.
  • Новое: API_KEY — нужен только если будете вызывать MCP /todoist из другого сервиса. Для чисто бот-режима можно не задавать.
  • Убрано (больше не нужно): LOG_LEVEL, NODE_ENV, MAX_RETRIES/RETRY_DELAY (теперь sensible defaults зашиты в код, но всё ещё переопределяемые).

Пошагово:

  1. Запишите куда-нибудь значения старых токенов. Если они прямо в docker-compose.yml — вытащите их сейчас, в новом setup'е их рекомендуется держать в .env.

    grep -E 'TELEGRAM_TOKEN|TODOIST_TOKEN|BOT_ADMIN|PROJECT_USERS|ALLOWED_USERNAMES' docker-compose.yml
  2. Остановите и удалите старый контейнер. Волноваться не о чем — volumes для потери нет.

    docker compose down
  3. Подтяните свежий docker-compose_example.yml из этого репо (или обновите свой вручную):

    wget https://raw.githubusercontent.com/dzarlax/todoist_bot/main/docker-compose_example.yml -O docker-compose.yml
  4. Создайте .env рядом с docker-compose.yml с токенами:

    TELEGRAM_TOKEN=<старое значение>
    TODOIST_TOKEN=<старое значение>
    API_KEY=<новое, сгенерируйте сильную случайную строку>
  5. Отредактируйте docker-compose.yml: проставьте BOT_ADMIN, PROJECT_USERS_* и т.д. — значения переносятся 1:1.

  6. Запускайте:

    docker compose pull
    docker compose up -d
    docker compose logs -f todoist-bot

    В логах в течение секунды должны появиться INFO starting todoist-bot и INFO telegram bot started user=<имя_вашего_бота>. Если видите telegram init failed error="Not Found" — проверьте, что Telegram-токен скопирован целиком (включая : и секретную часть).

  7. Опционально — подключите MCP-эндпоинт. Направьте любой MCP-клиент на http://<host>:3000/todoist с X-API-Key: <ваш API_KEY>. Если раньше ходили в Todoist MCP, хостящийся в другом сервисе, — смените URL и ключ здесь.

Откатиться, если понадобится: docker compose down и вернуть старую строку image: dzarlax/todoist_bot + перезапуск. Никакой миграции данных откатывать не нужно.

About

This Telegram Bot integrates with Todoist to add tasks directly from Telegram. It concatenates messages within a timer interval into single tasks and optionally sets today as the due date. Built with Node.js and Docker, it's designed for quick setup and easy use.

Topics

Resources

License

Stars

Watchers

Forks

Contributors