telegram-notify-mcp
# telegram-notify
One-shot Telegram notifications for coding agents. When a long task finishes, a deploy fails or an
agent needs a decision, it sends **one** self-contained message to your phone through your own bot.
Three ways in, one message format:
| | For | Needs |
| --- | --- | --- |
| `telegram-notify` CLI | any agent or script with a shell | `python3` (stdlib only) |
| `telegram-notify-mcp` server | any MCP client: Claude Code, Cursor, Codex, Gemini CLI… | `uv` |
| Claude Code skill | Claude Code, so it knows when and how to notify | the CLI on PATH |
[GUIDELINES.md](GUIDELINES.md) says what a good message looks like; the skill and the MCP tool
carry the same rules, so every agent writes the same kind of message:
```
✅ turtletrips 1.7.0 desplegado
📁 turtletrips · 🖥️ ginnugagap
Imagen ghcr.io/zurdi15/turtletrips:1.7.0 desplegada en el clúster.
Argo: turtletrips Synced/Healthy. Health: HTTP 200.
Novedades 1.7.0:
- Moneda secundaria por viaje (p. ej. VND), elegible junto a la base en el formulario del viaje.
- En el gasto, la moneda se elige con principal · secundaria · otra.
```
## 1. Bot and chat (once)
1. Create a bot with [@BotFather](https://t.me/BotFather) and copy its token.
2. Send it any message from the chat that should receive the notifications (your user, or a group
the bot is in).
3. Find that chat's id: `telegram-notify-setup --token <token> --find-chat` (or open
`https://api.telegram.org/bot<token>/getUpdates` and look for `"chat":{"id":…}`).
The same bot can serve as many machines and agents as you like.
## 2. Install on a machine
### With uv (recommended)
```bash
uv tool install "telegram-notify[mcp] @ git+https://github.com/zurdi15/telegram-notify"
telegram-notify-setup --token <token> --chat-id <chat_id> --test
```
`setup` writes `~/.config/telegram-notify/notify.env` (mode 600), installs the Claude Code skill
into `~/.claude/skills/telegram-notify/` and sends a test message. Drop `[mcp]` if you only want
the CLI. Upgrade later with `uv tool upgrade telegram-notify`.
### Without uv
The CLI is a single stdlib file, so it can be dropped anywhere with a `python3`:
```bash
curl -fsSL https://raw.githubusercontent.com/zurdi15/telegram-notify/main/telegram_notify/cli.py -o ~/.local/bin/telegram-notify
chmod +x ~/.local/bin/telegram-notify
mkdir -p ~/.config/telegram-notify && printf 'TELEGRAM_BOT_TOKEN=%s\nTELEGRAM_CHAT_ID=%s\n' <token> <chat_id> > ~/.config/telegram-notify/notify.env && chmod 600 ~/.config/telegram-notify/notify.env
```
`pip install git+https://github.com/zurdi15/telegram-notify` works too.
### Credentials
Resolved in this order; the first complete pair wins. They are never written anywhere else, never
logged and never printed.
1. `TELEGRAM_BOT_TOKEN` and `TELEGRAM_CHAT_ID` in the environment.
2. The file named by `TELEGRAM_NOTIFY_ENV`.
3. `~/.config/telegram-notify/notify.env` (what `setup` writes).
4. `~/.config/claude-telegram/notify.env` (legacy path, still read).
File format: `KEY=VALUE`, one per line. In CI, put the two variables in the job's secrets.
## 3. Use it
### CLI
```bash
telegram-notify -s ok -t "immich 1.140 desplegado" -H ginnugagap "Imagen desplegada. Argo Synced/Healthy. Health: HTTP 200."
pytest -q 2>&1 | tail -20 | telegram-notify -s fail -t "Tests rotos en bifrost" # body from stdin
telegram-notify -s ask -t "Necesito confirmación" "¿Borro el PVC antiguo de immich (40 GB)?"
telegram-notify --dry-run -t "Prueba" "Imprime el mensaje sin enviarlo"
```
| Flag | Meaning |
| --- | --- |
| `-s, --status` | `ok` ✅ · `fail` ❌ · `warn` ⚠️ · `info` ℹ️ · `ask` ❓ — picks the icon and the default title. Default `ok`. |
| `-t, --title` | Headline, first line of the message. |
| `-p, --project` | Project shown in the context line (default: git repo or cwd name). |
| `-H, --host` | Host shown in the context line (default: this machine). Use the deploy target when that is what matters. |
| `--no-context` | Drop the `📁 project · 🖥️ host` line. |
| `--dry-run` | Print the message instead of sending it. |
Exit code `0` means delivered; anything else prints the reason on stderr. Messages are plain text,
capped at Telegram's 4096 characters.
### MCP server
One tool, `notify(title, body, status="ok", project=None, host=None, dry_run=False)`, whose
description and the server instructions carry the message guidelines. Register it once per machine,
user-wide:
```bash
# Claude Code
claude mcp add --scope user telegram-notify -- telegram-notify-mcp
```
Any other MCP client takes the same command in its own config (Cursor `~/.cursor/mcp.json`, Codex
`~/.codex/config.toml`, Gemini CLI `~/.gemini/settings.json`…). Without the tool install, `uvx`
runs it straight from this repository:
```json
{
"mcpServers": {
"telegram-notify": {
"command": "uvx",
"args": ["--from", "telegram-notify[mcp] @ git+https://github.com/zurdi15/telegram-notify", "telegram-notify-mcp"]
}
}
}
```
The server reads the credentials from the same places as the CLI. If your client cannot see your
shell environment, add `"env": {"TELEGRAM_BOT_TOKEN": "…", "TELEGRAM_CHAT_ID": "…"}` there, or
rely on the config file. Pin a revision with `…/telegram-notify@v0.1.0` for reproducible installs.
### Claude Code skill
`telegram-notify-setup` copies [`telegram_notify/SKILL.md`](telegram_notify/SKILL.md) to
`~/.claude/skills/telegram-notify/SKILL.md`. From then on any session, in any project, knows the
tool exists, when to use it and how to write the message. Check with:
```bash
claude -p "Lista los nombres de tus skills disponibles"
```
Other agents that read skill files (or an `AGENTS.md`) can point at the same file: the rules are the
ones in `GUIDELINES.md`.
### Always notify when a turn ends (optional)
A Claude Code `Stop` hook in `~/.claude/settings.json` fires on every turn, trivial ones included;
the skill (the agent decides when it is worth it) is usually better:
```json
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "telegram-notify -s ok -t 'Claude ha terminado' 'Turno completado'" } ] } ] } }
```
## Development
```bash
python3 -m unittest discover -s tests # stdlib only; pytest works too
uv run --extra mcp telegram-notify-mcp # run the server locally
```
`telegram_notify/cli.py` must stay dependency-free and importable on its own: that is what makes the
curl install and the copy-the-file fallback possible.
TDQS
Scored across 1 tool
Only one tool exists, so there is no possibility of confusion between tools. The single tool clearly defines its purpose of sending a Telegram notification.
With only one tool, naming is trivially consistent. The verb 'notify' clearly indicates the action, fitting the expected verb_noun pattern.
A single tool for a server is extremely thin, even if the purpose is narrow. This feels more like a helper function than a full MCP server; agents would expect additional tools for managing notifications or checking status.
The tool covers sending notifications, but there is no way to list, get, or delete sent notifications, nor any configuration management. The domain is so minimal that completeness is hard to assess, but it leaves obvious gaps for any non-trivial use.