Skip to main content
Glama
mia-oc

grokbot-telegram-bridge

by mia-oc

grokbot-telegram-bridge

Open-source Telegram ↔ Grok Bot bridge: a local webhook listener, on-disk spool, and stdio MCP server so Grok Bot can drain Telegram messages and reply immediately when woken.

What this is / what it is not

Is

  • A small Node.js service that receives Telegram Bot API webhooks on 127.0.0.1:8787

  • An atomic on-disk spool of inbound updates (spool/<update_id>.json)

  • A stdio MCP server exposing Telegram helpers (tg_send_message, tg_edit_message, tg_list_spool, …) that Grok Bot can call as a custom MCP

  • Designed to wake Grok Bot via a webhook routine on each new Telegram message (NOT cron)

  • OpenClaw-style progress drafts: one editable status message while the agent works, then delete + final answer

Is not

  • Native Telegram support inside Grok Bot (there is none)

  • A hosted SaaS or always-on cloud bot framework

  • A cron-first design — polling/cron drain was the wrong architecture; use a webhook routine

  • A replacement for BotFather, Telegram clients, or Grok Bot itself

  • Full OpenClaw tool-stream UI (this bridge approximates streaming.mode=progress with edit/delete, not native Telegram streaming)

Architecture in one line: Telegram → HTTPS relay → local listener → spool + progress draft + keepalive ticker → wake POST → Grok Bot webhook routine → MCP (tg_progress hold / tg_delete_message / tg_send_message).

Related MCP server: io.github.daedalus/mcp-telegram-bot

Progress drafts (OpenClaw streaming.mode=progress)

OpenClaw’s Telegram channel can show a live progress message that is edited as the agent works, then cleared when the final answer lands. This bridge mirrors that UX.

Telegram twin of Grok in-app commentary

The progress draft is the Telegram twin of Grok Bot’s in-app working commentary (the short status lines you see in the app while tools run — e.g. Running a few commands).

  • At each beat, the agent must call tg_progress (or tg_edit_message) with the same wording as that in-app commentary — not a paraphrase, not a custom “status protocol”.

  • Example: when the app shows Running a few commands, Telegram should show Running a few commands (optionally with a leading ● if you keep the draft’s bullet style consistent).

  • Before the final answer: delete the draft (tg_delete_message), then tg_send_message with the real reply.

  • The listener’s first line is ● waking Grok Bot, then the listener keepalive cycles Grok-style lines until the agent’s first tg_progress / tg_edit_message sets hold: true (or the draft is deleted / spool acked / ~90s). After hold, agent edits should be exact commentary mirrors.

Phase

What happens

Inbound

Listener sends one status draft (● waking Grok Bot). Persists { chat_id, progress_message_id } to spool/<update_id>.meta.json (mode 0600). Idempotent redelivery does not send another draft or start another ticker.

Listener keepalive

After a successful draft, the listener starts a non-blocking ticker (~every 2.5s, up to ~90s) that editMessageTexts the draft through Grok-style lines (e.g. ● Running a few commands → ● Reading files → ● Searching the web → ● Writing a reply). This keeps the user seeing motion even when the wake agent is slow or skips tg_progress.

Agent hold

When tg_progress / tg_edit_message succeeds for a chat whose spool *.meta.json matches that progress_message_id, the MCP server sets hold: true on that meta (best-effort). The listener ticker stops overwriting agent commentary.

Working

Agent edits that same message via tg_progress / tg_edit_message with the exact in-app Grok commentary at each beat (e.g. Running a few commands). First successful edit holds the ticker.

Done

Agent deletes the progress draft (tg_delete_message), then sends the final answer as a new normal message (tg_send_message). Delete also stops the ticker (Telegram message-not-found).

Typing

Optional/secondary keepalive only — progress text is the signal of life, not “typing…”.

Keepalive stop conditions: spool file for that update_id is gone (acked); Telegram returns message-not-found (agent deleted draft); meta.json has hold: true (agent took over); ~90s timeout. The ticker never blocks the webhook 200 or the wake POST. Never logs the bot token.

A static “Queued…” receipt is not the design. Progress text that updates — mirroring in-app commentary — is.

When woken, the webhook routine should:

  1. Call tg_list_spool (preview includes progress_message_id when present; wake body may also carry it)

  2. If empty → stay silent

  3. While working: at each commentary beat, tg_progress(chat_id, progress_message_id, "<exact in-app wording>") (e.g. Running a few commands)

  4. When done: tg_delete_message(chat_id, progress_message_id) → then tg_send_message with the final answer

  5. tg_ack_spool for each handled update_id

Suggested prompt sketch:

On wake: call tg_list_spool. If count is 0, do nothing. Otherwise, for each pending item, use progress_message_id from the preview (or wake payload). The progress draft is the Telegram twin of Grok in-app commentary: whenever the app would show a working line (e.g. Running a few commands), call tg_progress / tg_edit_message with that same wording. When finished: delete the progress draft with tg_delete_message, then send the final answer as a new tg_send_message. Ack with tg_ack_spool. Never print tokens or secrets. Typing alone is not progress — mirror commentary into the draft.

Prerequisites

  • Node.js 18+ (20+ recommended)

  • A Telegram bot from @BotFather (API token)

  • Your numeric Telegram chat id (allowlisted)

  • Grok Bot desktop (to register the stdio MCP and create a webhook routine)

  • A public HTTPS front-door for Telegram webhooks (see Public HTTPS)

Install

git clone <this-repo-url> grokbot-telegram-bridge
cd grokbot-telegram-bridge
npm install

Secrets (never commit)

Create secrets outside the repo or only in gitignored paths. Modes should be 0600.

Helper (creates empty/minted files in the package directory — they are gitignored):

npm run setup-secrets
# or: bash scripts/setup-secrets.sh

Then edit:

File

Purpose

token

BotFather token (or set TELEGRAM_BOT_TOKEN)

webhook-secret

Random secret_token for setWebhook / X-Telegram-Bot-Api-Secret-Token

ALLOWED_CHAT_ID

Your numeric chat id (progress draft, typing, and wake respect this)

grokbot-wake-url

Full HTTPS URL of the Grok Bot webhook routine

grokbot-wake-secret

Sender key / secret for that routine (Authorization Bearer + X-Webhook-Secret)

example.env.example shows placeholder env keys only:

TELEGRAM_BOT_TOKEN=
ALLOWED_CHAT_ID=
# Optional override: GROKBOT_WAKE_HEADER="Header-Name: value"

Do not put real tokens in the repo, in README snippets, or in chat. If a token is ever pasted into chat, revoke/rotate it in BotFather.

Run the listener

Bind address is hard-coded to loopback:

  • GET http://127.0.0.1:8787/healthz → 200 ok

  • POST http://127.0.0.1:8787/telegram-webhook → Telegram updates (requires secret header)

Foreground:

npm run listener
# or: node listener.mjs

Optional supervisor (restart-friendly, writes listener.pid / listener.log):

npm run supervise
# or: bash supervisor.sh

Confirm health:

curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8787/healthz
# expect 200

Public HTTPS — prefer smee.io

Telegram requires a public HTTPS webhook URL. Your listener stays on 127.0.0.1:8787.

Cloudflare Tunnel often fails Telegram’s DNS/resolve checks for some setups. Prefer a relay such as smee.io:

  1. Create a smee channel; note the public HTTPS URL.

  2. Run the smee client locally, forwarding to http://127.0.0.1:8787/telegram-webhook.

  3. Point Telegram’s webhook at the smee (or other relay) public URL that ultimately POSTs to /telegram-webhook.

Any stable HTTPS reverse proxy/tunnel that Telegram can resolve is fine; smee is the documented default recommendation here because Cloudflare tunnels frequently fail Telegram resolve.

Do not paste your public smee/relay URL into shared issue trackers or chat logs.

setWebhook (and never mix with getUpdates)

With the listener running and the relay forwarding:

  1. Ensure webhook-secret exists (npm run setup-secrets).

  2. Call Telegram setWebhook with:

    • url = your public HTTPS webhook URL

    • secret_token = contents of webhook-secret (same value the listener checks on X-Telegram-Bot-Api-Secret-Token)

Via MCP (once registered):

  • Tool: tg_set_webhook

  • Arg: public_url = your public HTTPS URL

Or via curl (do not echo the token/secret into shell history logs you share):

# Illustrative only — load token/secret from files; do not paste into chat
curl -sS -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/setWebhook" \
  -H 'Content-Type: application/json' \
  -d "{\"url\":\"https://YOUR_PUBLIC_HOST/telegram-webhook\",\"secret_token\":\"${WEBHOOK_SECRET}\"}"

Important: While a webhook is active, do not call getUpdates. Telegram rejects long-polling when a webhook is set. Use tg_get_updates only for pre-webhook debugging. Inspect webhook state with tg_webhook_info.

Register as a Grok Bot custom MCP (stdio)

In Grok Bot → custom MCP / Add MCP server (stdio):

Field

Suggested value

Name

grokbot-telegram-bridge (or telegram)

Command

node

Args

/ABS/PATH/TO/grokbot-telegram-bridge/mcp-server.mjs

CWD (if offered)

/ABS/PATH/TO/grokbot-telegram-bridge

Dogfood / box path example:

  • Command: node

  • Args: /home/box/.local/telegram-mcp/mcp-server.mjs

The MCP process reads token / webhook-secret / spool/ relative to the script directory (or TELEGRAM_BOT_TOKEN from the environment). Never put the token in the MCP args.

MCP tools

Tool

Purpose

tg_get_me

Bot identity

tg_send_message

chat_id, text — final answers

tg_edit_message

chat_id, message_id, text — edit progress draft

tg_delete_message

chat_id, message_id — clear progress draft before final reply

tg_progress

Alias of tg_edit_message for status updates

tg_send_chat_action

chat_id, action (default typing) — optional keepalive

tg_list_spool

Pending inbound updates (preview includes progress_message_id when present)

tg_ack_spool

Archive update_id → spool/done/ (also moves .meta.json)

tg_webhook_info

Telegram getWebhookInfo

tg_get_updates

Pre-webhook long-poll only

tg_set_webhook

Set webhook; reads webhook-secret

Wake Grok Bot with a webhook routine (primary path)

Cron/polling drain was the wrong design. Wire immediacy like this:

  1. In Grok Bot, create a webhook routine (not a scheduled/cron routine).

  2. Copy the routine’s HTTPS URL into grokbot-wake-url (mode 0600).

  3. Copy the routine’s sender key / secret into grokbot-wake-secret (mode 0600).

  4. Restart the listener (or supervisor) so it can read the new files.

On each new spool write (created === true), after Telegram already got 200 ok, the listener sends the progress draft (when allowlisted), then asynchronously POSTs:

{
  "source": "telegram-bridge",
  "update_id": "...",
  "chat_id": 123,
  "progress_message_id": 456
}

progress_message_id is included when the draft send succeeded. Auth headers (default): both Authorization: Bearer <secret> and X-Webhook-Secret: <secret>.
Optional override: set env GROKBOT_WAKE_HEADER to Name: value to send that single header instead.

If either wake file is missing/empty: spool + progress draft + typing still run; wake is skipped with log line wake skipped: missing grokbot-wake-url/secret. Wake failures never fail the Telegram webhook response. Only allowlisted chats (ALLOWED_CHAT_ID) trigger a wake (same gate as the progress draft).

Optional smoke test (does not print secrets):

node scripts/send-wake-test.mjs

Security

  • Never commit token, webhook-secret, grokbot-wake-url, grokbot-wake-secret, ALLOWED_CHAT_ID, allowed-chat-id, .env, spool/, or logs

  • File mode 0600 for secrets and .meta.json; spool dirs preferably 0700

  • Allowlist your chat id; do not run an open relay for the world

  • Rotate the BotFather token if it was pasted into chat, committed, or leaked

  • Listener binds 127.0.0.1 only; expose it only through a deliberate HTTPS relay

  • Validate Telegram’s X-Telegram-Bot-Api-Secret-Token (built-in) — keep webhook-secret long and random

  • Do not log tokens, secrets, wake URLs, or full public relay URLs in shared issue trackers

Dogfood checklist

  • npm install succeeds on Node 18+

  • npm run setup-secrets then fill token + ALLOWED_CHAT_ID (0600)

  • Create Grok Bot webhook routine; paste URL + sender key into grokbot-wake-url / grokbot-wake-secret (0600)

  • npm run supervise (or npm run listener) — GET /healthz returns 200

  • HTTPS relay (prefer smee.io) forwards to http://127.0.0.1:8787/telegram-webhook

  • tg_set_webhook / setWebhook with secret_token; tg_webhook_info shows the URL

  • Send yourself a Telegram message → file under spool/ + progress draft (● waking Grok Bot) cycling keepalive lines + .meta.json + wake POST; agent tg_progress sets hold: true

  • MCP registered in Grok Bot (node + absolute mcp-server.mjs)

  • Webhook routine: edit progress → delete draft → tg_send_message final → tg_ack_spool

  • Empty spool runs stay silent

  • Confirm .gitignore excludes secrets; no secrets under version control

Layout

grokbot-telegram-bridge/
  listener.mjs          # HTTP webhook + spool + progress draft + keepalive + wake POST
  mcp-server.mjs        # stdio MCP tools (incl. edit/delete/progress)
  supervisor.sh         # simple process keeper
  package.json
  scripts/setup-secrets.sh
  scripts/send-wake-test.mjs
  example.env.example
  LICENSE               # MIT
  README.md

Runtime data (gitignored): token, webhook-secret, grokbot-wake-url, grokbot-wake-secret, ALLOWED_CHAT_ID, spool/ (incl. *.meta.json), listener.pid, *.log.

License

MIT — see LICENSE.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that enables interaction with Telegram to send, read, and search messages across chats and dialogs. It supports waiting for incoming messages and retrieving conversation history through natural language commands.
    15 npm
    3
    MIT
  • A
    license
    A
    quality
    C
    maintenance
    MCP server that exposes a Telegram bot, enabling sending messages and retrieving updates through natural language.
    3
    1
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server that enables Telegram bot interaction via Telegraf, providing tools for sending, replying, reacting, editing, deleting, forwarding messages, and receiving Telegram events over an optional notification channel.
    35 npm
    4
    MIT
  • F
    license
    Not graded
    quality
    B
    maintenance
    An MCP server that connects to a Telegram group chat, persists messages to a local SQLite database, and exposes tools to search, retrieve, and send messages via SSE.
    -