Skip to main content
Glama
mikedelcastillo

watush-mcp

watush-mcp

A robust Telegram MCP server for agents. Any agent — Claude Code, Claude Desktop, or Codex — can use it to message, send files, update, clean up after, and ask questions of you or named people (partner, family, friends) through your own Telegram bot.

So you can say:

"message me on telegram once the build is done" "send me a screenshot of the dashboard" "telegram me the report as a PDF when it's written" "keep me updated on the GPU temps in one telegram message" "ping the family: dinner at 7" "message me on watush if you need anything while I'm out"

…and it routes to the right person. You (the owner) always get an audit note for anything sent to, edited for, deleted from, or asked of someone else.

Configure several bot tokens and you get several channels — separate Telegram threads you can mute or read independently ("use the 2nd bot for the noisy stuff") — plus automatic failover when one bot is rate-limited or its token dies. See Channels.

It cannot browse your chats: the only thing it ever reads is the reply to a question it is actively waiting on.


How it works

  • Seven tools are exposed to the agent:

    • send_telegram_message({ text, to?, channel? }) — send a message. to is a person's name or a tag (case-insensitive). Omit to (or use "me") to message yourself. A tag shared by several people fans out to all of them. channel picks which bot to send through. Returns a handle like 4021@main.

    • send_telegram_file({ path, caption?, to?, channel?, as? }) — send any file: screenshots, images, video, PDFs, .md/.docx/.csv, logs, archives. path is a file on the machine running the server (or an http(s) URL for Telegram to fetch). The kind is picked from the extension — images preview inline, .mp4 plays, everything else arrives as a downloadable attachment — and as overrides that. See Sending files.

    • edit_telegram_message({ message_id, text, to? }) — rewrite a sent message in place. Perfect for live status messages: one message that keeps updating instead of a wall of notifications. No time limit. On a message sent by send_telegram_file, this rewrites its caption.

    • delete_telegram_message({ message_id, to? }) — remove a sent message to cut noise. Telegram allows this within 48 hours of sending.

    • ask_telegram_question({ question, options?, to?, timeout_seconds? }) — ask a question with tap-to-answer buttons (or free-text replies — you can always just type instead). The agent blocks until you answer; your tap or reply is returned to it directly. The question message updates itself to show what you answered.

    • list_recipients() — lets the agent discover who/what tags it can address (names + tags only; chat IDs are never exposed).

    • list_channels() — lets the agent discover which bots exist, their positions and tags, and which are currently rate-limited or unable to reach someone (tokens are never exposed).

  • Markdown formatting works in all message/question text: **bold**, *italic*, `code`, fenced blocks, [links](url), ~~strike~~, headers. Rendered as native Telegram formatting, with an automatic plain-text fallback so a message is never lost to a formatting error.

  • Timing is the agent's job. "in 30 minutes" is handled by the agent that calls the tool; the tool itself always sends now.

  • No background process. Stdio MCP servers are spawned on demand by each client, so there's nothing to "start on login" — installing simply registers the server with your agents so it's available whenever they run.


Related MCP server: Telegram Agent for Codex

Setup

1. Install (build + wire all agents)

cd ~/Code/watush-mcp
./install.sh

This installs dependencies, compiles the TypeScript, creates config.yaml (if missing), and registers the server with Claude Code, Codex, and Claude Desktop — whichever are present. Existing agent configs are backed up first. Re-run it any time (e.g. after a Node version change).

2. Configure config.yaml

config.yaml holds your secrets and is git-ignored — it is never committed. Copy the example if you don't have one yet:

cp config.example.yaml config.yaml && chmod 600 config.yaml
defaultChannel: "main"
channels:                           # one bot per channel; order = fallback order
  - name: "main"
    token: "123456789:ABC..."       # from @BotFather
    tags: ["general"]
  - name: "backup1"
    token: "987654321:XYZ..."
    tags: ["backup"]
owner:
  name: "Owner"
  chatId: "111111"                  # your numeric Telegram ID (gets audit copies)
recipients:
  - name: "Alice"
    chatId: "222222"
    tags: ["partner", "household"]
  - name: "Bob"
    chatId: "333333"
    tags: ["friend", "team"]
auditToOwner: true

3. Find chat IDs

Everyone you want to message (including you) must DM the bot once — and with several channels configured, once per bot. Chat IDs are global to Telegram, but a bot cannot open a conversation on its own, so a perfectly valid chat ID still fails on a bot that person never started. npm run doctor shows exactly which pairs are missing.

You have two ways to capture the numeric IDs — neither runs in the background:

a) On-demand /chatinfo listener (recommended)

npm run chatinfo

This starts a temporary listener on every channel at once, so one pass confirms who has started which bot. Each person DMs a bot /chatinfo and that bot replies with their own chat ID (it's also printed in your terminal, prefixed with the channel). Press Ctrl-C when you're done — it does not keep running.

npm run chatinfo -- --once             # auto-stop after the first /chatinfo
npm run chatinfo -- --timeout=60       # auto-stop after 60 seconds
npm run chatinfo -- --channel=backup1  # listen on one channel only

b) One-shot snapshot

npm run chats

Prints the chat ID + name of everyone who recently messaged any of your bots, and which bots saw each chat. (Reads only — never writes your config.)

Copy the right IDs into config.yaml, then verify the whole matrix:

npm run doctor
CHANNEL      BOT          TOKEN     Mike (you)    Meg&Mike
1. main   *  @watushbot   ok        ok            ok
2. backup1   @watush1bot  ok        ok            MISSING
3. backup2   @watush2bot  ok        ok            MISSING

MISSING means that bot cannot message that chat yet — press Start in a DM with it, or add it to that group.

4. Restart Claude Desktop

If you use Claude Desktop, restart it so it loads the new server. Claude Code and Codex pick it up on their next run.


Usage

Just ask, in any agent:

"message me on telegram: deploy finished ✅" "text my partner: leaving now" "notify the family: flight landed"

Live-updating messages — one message, continuously rewritten:

"track the download and update me in a single telegram message" "keep a running status of the migration on watush"

Cleanup — the agent can delete its own noise:

"delete those progress messages now that it's done"

Questions — the agent asks, you tap (or type), the agent continues:

"message me on watush if you need anything" → later, your phone shows "Deploy to prod now, or wait for the tests?" with [Deploy now] [Wait] buttons. Tap one and the agent carries on with your answer.

Behind the scenes the agent calls send_telegram_message, edit_telegram_message, delete_telegram_message, or ask_telegram_question. Anything involving a person other than you also sends you an audit note like:

🔔 Audit — sent to Alice: leaving now
✏️ Audit — edited message to Alice: updated ETA 18:30
❓ Audit — asked Alice: pizza or sushi?

Sending files

"send me a screenshot of the desktop" "telegram me that chart" "write the summary to a .md and send it over"

The agent takes the screenshot or writes the document first, then passes the path. The file has to live on the machine running the server — each machine runs its own copy, so "send me a screenshot" means that machine's screen.

Extension

Arrives as

Method

.jpg .jpeg .png .webp .bmp

inline photo (recompressed)

sendPhoto

.gif

looping animation

sendAnimation

.mp4 .mov .m4v .webm

inline video player

sendVideo

.mp3 .m4a .flac .aac .wav

audio track

sendAudio

.ogg .oga .opus

voice note

sendVoice

everything else.pdf .md .docx .csv .zip .log

downloadable file

sendDocument

  • as overrides detection. as: "document" keeps an image at full quality (Telegram recompresses photos); as: "photo" forces an inline preview.

  • Limits. Telegram caps bot uploads at 10 MB for photos and 50 MB for everything else. An oversized image is downgraded to a document automatically; over 50 MB is refused with a clear message.

  • Nothing gets stuck. If Telegram rejects a file as a photo/video (odd dimensions, an unsupported codec), it is retried once as a document — but only when a different kind could actually help, never on a block or a rate limit.

  • Captions take Markdown, and are trimmed to Telegram's 1024-character cap.

  • URLs work for images, video, audio, PDF and ZIP. Telegram will not fetch other file types by URL — download it locally first, then send the path.

  • Audit copies. A file sent to someone else is copyMessaged into your chat, so you get the actual media without a second upload.

How questions work (and their limits)

  • The question arrives as a normal Telegram message from your bot, with one button per option. Tapping answers instantly; typing a message answers free-form. With several questions pending in one chat, swipe-reply to a specific question message to target it; a plain message answers the newest.

  • The asking agent waits as long as its timeout_seconds (default 15 min). In Claude Code, long waits are automatically moved to a background task, so the agent keeps working until your answer arrives.

  • Several agent sessions can wait on questions at once — watush coordinates them so only one process polls Telegram (Telegram allows a single poller per bot token), and answers are routed to whichever session asked.

  • One machine at a time. The coordination state is per-machine, but the bot token's update stream is global. If agents on two different machines wait on questions at the same moment, one machine's poller can consume (and lose) the other machine's answers, and they compete for the single polling slot. Keep simultaneous questions on one machine, or give each machine its own bot token (each token gets its own state dir automatically).


Channels (several bots)

Each channel is its own Telegram bot, which means its own conversation thread. Give the noisy things their own bot and you can mute that thread without missing an alert. Configure as many as you like — @BotFather allows 20 per account.

defaultChannel: "main"
channels:
  - name: "main"
    token: "..."
    tags: ["general"]
  - name: "backup1"
    token: "..."
    tags: ["backup"]
  - name: "backup2"
    token: "..."
    tags: ["backup"]
    # fallback: false   # keep this one out of automatic failover

Order matters. The list order is the failover order. Channel 1 is the default unless defaultChannel names another — and a channel is always reachable by its own name, even one literally named main or default.

Addressing a channel

You say…

channel

Resolves to

"send it on backup1"

backup1

the channel named backup1

"use the 2nd bot"

2nd

channel at position 2

also 2, second, bot 2, #2, last

the same, by position

"use a backup"

backup

the first channel tagged backup

(omitted)

defaultChannel

A name always wins over a position reading, so backup1 means the channel named backup1 — even though it sits at position 2.

Failover

A send walks the chain until a bot gets through, and the result says so:

Sent to you — message_id 4@backup1. (watush: Unauthorized — fell back to backup1.)

It falls over on rate limits (429), dead tokens (401/404), server errors (5xx), network failures, and "this bot can't reach that chat". It does not fall over on content errors — bad markup or an over-long message fails identically on every bot, so retrying would only spend a second bot's quota to reproduce the error. At most 6 bots are tried per delivery, and if that cap is reached the result names the channels it never got to.

Messages are at-least-once. Telegram has no idempotency keys, so a send whose response times out may already have arrived; retrying it on the next bot can duplicate a notification. For messages and files that is the intended trade — a rare double ping beats a silently lost alert, which is the entire reason for having spare bots. Where a duplicate would be actively harmful the code does not retry an ambiguous failure: ask_telegram_question will not re-post a question that may already be on screen with a live keyboard, and a media audit copy that times out is not re-sent as a text note.

Edits and deletes never fail over. A message_id belongs to exactly one (bot, chat) pair, so 4021@main can only be edited by main. If that channel is down the call fails and says which channel owns the message — quietly re-posting through another bot would strand the original and split a live-progress thread across two chats mid-run.

Handles

send_telegram_message returns 4021@main, not a bare number, because with several bots the same message_id genuinely exists in several threads — three fresh bots will each mint message_id 2 in your chat. Pass the handle back to edit_telegram_message / delete_telegram_message and the right bot is used automatically. A bare number is still accepted and means the default channel.

Health memory

Failures are remembered in ~/.watush/state/channels.json so the next send routes around a known problem instead of rediscovering it:

  • Channel cooldown — the bot itself is in trouble (flood-wait, dead token, 5xx, no network). Time-based, honouring Telegram's own retry_after.

  • Unreachable pair — the bot is fine but cannot reach that one chat (nobody pressed Start; it was never added to the group). Recorded per (channel, chat), so a bot missing from one group is never treated as down for your DMs. Expires after 6 h, so adding the bot to a group heals routing without running anything.

Nothing is ever dropped on the basis of this memory — unhealthy channels sink to the back of the queue rather than being removed, so if every bot is flood-waiting, the one recovering soonest is still tried. A successful send clears both kinds of memory for that pair.


Configuration reference

Key

Meaning

channels[].name

Channel name, addressable as channel.

channels[].token

That channel's bot token from @BotFather.

channels[].tags

Aliases like backup, noisy (a tag picks the first match).

channels[].fallback

false keeps a channel out of automatic failover.

defaultChannel

Channel used when the agent names none. Defaults to the first.

botToken

Legacy single-bot form; migrated to a one-entry channel list.

owner.name

Your display name.

owner.chatId

Your numeric Telegram ID. Receives audit copies.

recipients[].name

A person's name (addressable as to).

recipients[].chatId

Their numeric Telegram ID.

recipients[].tags

Aliases like partner, family (addressable as to).

auditToOwner

true (default) to copy the owner on messages to others.

Environment overrides (handy for CI / secrets managers): TELEGRAM_BOT_TOKEN (replaces the default channel's token), TELEGRAM_CHAT_ID (owner), TELEGRAM_RECIPIENTS and WATUSH_CHANNELS (JSON arrays merged over the file, by name), and WATUSH_CONFIG (alternate config path).


Resolving to

You say…

to

Goes to

"message me"

(omitted)

owner (you)

"message Alice"

Alice

Alice

"message my partner"

partner

whoever is tagged partner

"message the family"

family

everyone tagged family

"message Dana" (unknown)

Dana

nothing — returns a helpful error listing known names/tags


Uninstall

./uninstall.sh           # unregister from all agents (keeps config.yaml)
./uninstall.sh --purge   # also remove dist/ and node_modules/

Security

  • config.yaml is git-ignored and chmod 600. The token and chat IDs never leave your machine and are never committed.

  • No chat browsing: there is no tool to read message history or list chats. Incoming messages are only read while a question is pending, and only to resolve that question.

  • Question state (pending questions, answers, poll coordination) lives in ~/.watush/state/<token-hash>/, keyed by a hash of the bot token; override the location with WATUSH_STATE_DIR.

  • If your token ever leaks, rotate it in @BotFather (/revoke) and paste the new one into config.yaml.

On send_telegram_file and file access. The tool will read any path the agent hands it, deliberately: restricting it to a workspace root would break its whole purpose (screenshots land in temp dirs, documents live anywhere), and it would buy little — an agent that can read a file could already paste the contents through send_telegram_message. The file tool is a nicer envelope for that same trust, not a new one. Two things are refused outright:

  • watush's own config.yaml, so the bot token can never be uploaded to a chat.

  • UNC paths to hosts outside the LAN (\\evil.example\share\x.png). On Windows, merely stat-ing one performs an SMB handshake that hands the remote server a crackable NTLM challenge-response. LAN shares (\\COVE\Archive, private IPs) still work.

Note that sending a file does upload its bytes to Telegram's servers. If you keep data deliberately off third-party infrastructure, that applies here.


Development

npm install
npm run build      # tsc -> dist/
npm test           # offline unit tests (resolution, config, fallback, audit logic)
npm run doctor     # check every token + who each bot can actually reach
npm start          # run the MCP server over stdio

Project layout:

src/config.ts       load YAML config + resolve recipients (pure, tested)
src/channels.ts     channel resolution (name/position/tag) + handles (pure, tested)
src/health.ts       per-channel cooldowns + per-chat reachability memory (tested)
src/format.ts       Markdown → Telegram HTML + plain-text fallback (pure, tested)
src/telegram.ts     Telegram Bot API client: send/edit/delete/callbacks/updates
src/deliver.ts      resolve + send/edit/delete + audit orchestration (pure, tested)
src/questions.ts    question engine: shared state, poll lock, answer spool
src/mcp-server.ts   the MCP server (seven tools)
src/doctor.ts       "npm run doctor" token + reachability matrix
src/list-chats.ts   read-only "npm run chats" helper
src/listen.ts       on-demand "npm run chatinfo" listener
bin/watush-mcp-server  launcher that resolves node for GUI apps / nvm
scripts/            config editors used by install/uninstall

MIT licensed.

Available Tools

2 tools
list_recipientsList Telegram recipientsA

List who can be messaged on Telegram: the owner (you) and configured recipients with their names and tags. Use this to map a phrase like 'my partner' to a valid 'to' value for send_telegram_message. Chat IDs are never returned.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses a key behavioral trait: 'Chat IDs are never returned.' This is important for privacy and security. It also implies the output contains recipient names and tags, which is sufficient for this simple tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with purpose and followed by usage guidance and a key constraint. Every sentence is informative and earns its place with no wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has no parameters, no output schema, and no annotations, the description provides all necessary context: purpose, usage guidance, and a critical behavioral detail (no chat IDs). It is fully complete for an agent to understand and select the tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters, so the description provides all meaningful semantics. It explains what the tool returns (names and tags) and explicitly states what it does not return (chat IDs). This adds complete value beyond the empty schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool lists who can be messaged (owner and configured recipients) with names and tags. It uses the verb 'List' and the resource 'Telegram recipients', and distinguishes from the sibling tool send_telegram_message by explaining its purpose for mapping phrases to valid 'to' values.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly tells when to use this tool: to map a phrase like 'my partner' to a valid 'to' value for send_telegram_message. It provides clear context for usage, though it does not include explicit when-not scenarios, which is acceptable given its straightforward purpose.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

send_telegram_messageSend Telegram messageA

Send a Telegram message to the user (yourself) or to a named/tagged person such as a partner, family member, or friend. Use whenever the user asks to be messaged, pinged, notified, texted, or alerted on Telegram, or to message someone like a partner or a named contact, or to deliver a result/reminder. Omit 'to' (or use 'me') to message the user themselves. The user (owner) always receives an audit copy of messages sent to other people.

ParametersJSON Schema
NameRequiredDescriptionDefault
textYesThe message text to send.
toNoWho to send to: a person's name (e.g. 'Alex') or a tag (e.g. 'partner', 'friend', 'family'). Omit or use 'me' to message the user (owner). Call list_recipients to see valid names and tags.

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses that the user always receives an audit copy of messages sent to others, which is a key behavioral detail. However, it does not address error conditions or prerequisites like authentication.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is concise (three sentences), front-loaded with the main purpose, and every sentence adds essential information. No wasteful repetition or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has no output schema, so the description should ideally mention the return value or success indication. It covers the main behavior and the audit copy, but omits what the agent can expect as a response (e.g., success message, error handling). For a simple send tool, it is minimally adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value by explaining that omitting 'to' or using 'me' messages the user, and by directing to list_recipients for valid names/tags, which goes beyond the schema's property descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool sends a Telegram message to the user or a named/tagged person. It specifies the verb 'send' and the resource 'Telegram message', and distinguishes itself from the sibling tool list_recipients by explaining how to specify recipients.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides explicit guidance on when to use the tool (e.g., when the user asks to be messaged, pinged, notified, etc.) and mentions using list_recipients to see valid recipients. It also explains the behavior of the 'to' parameter. While it lacks explicit 'when not to use' instructions, the context is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv1.0.0
    • First observedlist_recipients
    • First observedsend_telegram_message

TDQS

A4.6/5.0

Scored across 2 tools

Disambiguation5/5

The two tools are clearly distinct: one lists recipients, the other sends messages. No overlap or ambiguity.

Naming Consistency5/5

Both tools follow a consistent verb_noun pattern (list_recipients and send_telegram_message), making the naming predictable.

Tool Count5/5

Two tools are exactly appropriate for a focused Telegram messaging server. Each tool has a clear, indispensable role.

Completeness5/5

The toolset covers the full lifecycle of sending a Telegram message: discover recipients and send messages. No obvious gaps given the intended scope.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    B
    quality
    D
    maintenance
    A Telegram MCP server that connects agents to a real Telegram user account via MTProto, enabling reading, searching, sending, moderating, and managing Telegram chats through natural language or automated tool calls.
    100
    77
    29
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    Privacy-first Telegram MCP server enabling maintainers to triage chats, inspect context, search messages, draft replies, and send authorized messages locally without a cloud relay.
    30
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    MCP server for Telegram integration with Command Code, enabling AI agents to send messages, photos, files, and read updates via Telegram.
    2
    MIT