grokbot-telegram
by jigjoy-ai
README.md
# Telegram for Cursor
[](https://www.npmjs.com/package/grokbot-telegram)
[](https://github.com/jigjoy-ai/grokbot-telegram/actions/workflows/ci.yml)
[](LICENSE)
Installable [Cursor plugin](https://cursor.com/docs/plugins) with **two** first-party MCP servers:
| Mode | MCP server | Identity | Protocol |
| --- | --- | --- | --- |
| **Bot** | `telegram-bot` | Your [@BotFather](https://t.me/BotFather) bot | Official [HTTP Bot API](https://core.telegram.org/bots/api) |
| **User account** | `telegram-user` | **Your personal Telegram account** | [MTProto](https://core.telegram.org/mtproto) via [teleproto](https://github.com/sanyok12345/teleproto) (maintained [GramJS](https://github.com/gram-js/gramjs) fork) |
Use one or both. v0.2.0 kept the v0.1.0 Bot API path and added user-account login; v0.3.0 ships both servers as the single npm package `grokbot-telegram` and appends a disclaimer footer to every outgoing message.
This is **not** Bot API–only, and it is **not** user-account–only. Marketplace copy: a Cursor plugin that can send/read Telegram as a bot **and/or** as the logged-in user.
## Which mode should I use?
| Need | Mode |
| --- | --- |
| Notifications, CI alerts, channel posts as `@mybot` | **Bot API** — `TELEGRAM_BOT_TOKEN` |
| “Send this in **my** name”, read my real chats / dialog list | **User account** — `TELEGRAM_API_ID` + `TELEGRAM_API_HASH` + session |
| Both (bot for alerts, account for DMs) | Configure **both** variable sets |
Bot API **cannot** list your personal inbox. User-account mode **can** (`list_dialogs`), because it is logged in as you.
## What you must provide
### Bot API (`telegram-bot`)
1. Create a bot with [@BotFather](https://t.me/BotFather) → `/newbot`.
2. Set **`TELEGRAM_BOT_TOKEN`** in Cursor **Plugins → Configure**.
3. Open a DM with the bot and tap **Start** (`/start`). Bots cannot message you first.
No `api_id` is required for this mode.
### User account (`telegram-user`) — first run
1. Open [https://my.telegram.org/apps](https://my.telegram.org/apps), log in with the **same phone** as the Telegram account you want to use, and create an app.
2. Set **`TELEGRAM_API_ID`** and **`TELEGRAM_API_HASH`** in **Plugins → Configure**.
3. Complete **one-time login** (phone + code, QR / Link Desktop Device, or an existing session string).
4. Persist the session as **`TELEGRAM_SESSION`** and/or the file `~/.grokbot-telegram/user.session` (mode `0600`).
You will be asked for api_id / api_hash / login on first run. A bot token is **not** used for this server.
**The session is full account access.** Treat it like a password. Never commit it.
## Message disclaimer
Every message sent through either server carries a footer, so the person on the other end knows an agent — not a human typing — produced it:
```
your message text
— sent by grokbot-telegram on my behalf
```
The Bot API server uses `— sent by grokbot-telegram` (it is already visibly a bot). `TELEGRAM_DISCLAIMER` controls it:
| Value | Result |
| --- | --- |
| unset | default wording above |
| any text | that text is used verbatim |
| `off`, `false`, `0`, `no`, `none`, empty | no footer |
The wording names the package rather than linking to it, on purpose. A URL in the footer would make Telegram attach a link-preview card to every message you send, and a link in someone's personal chat reads as advertising — the name is still searchable on npm and GitHub. Set `TELEGRAM_DISCLAIMER` to a URL yourself if you disagree.
Two details worth knowing:
- The footer counts against Telegram's 4096-character limit. If text + footer would exceed it, the send is **refused** with an error naming the overflow rather than quietly truncating your words or dropping the footer.
- With `parse_mode`, the footer is escaped for that mode. This matters for `MarkdownV2`, where an unescaped `.` or `-` makes Telegram reject the whole message.
## Why you need your own api_id
Telegram issues `api_id`/`api_hash` per developer and rejects credentials that have been published: an app that ships a shared pair earns its users an [`API_ID_PUBLISHED_FLOOD`](https://core.telegram.org/api/obtaining_api_id) error at login. So this package deliberately does not ship one. It is a one-time, two-minute detour at [my.telegram.org/apps](https://my.telegram.org/apps), and it keeps the blast radius at one user instead of everyone at once.
You do not have to put the values in a config file. Hand them to the agent and it calls `save_api_credentials`, which writes them next to the session at mode `0600`. `TELEGRAM_API_ID` / `TELEGRAM_API_HASH` still work as environment variables and take precedence, which is the path for developers bringing their own app.
**Only user-account mode needs this.** Bot API mode needs nothing but a [@BotFather](https://t.me/BotFather) token, which takes about thirty seconds inside Telegram. If you only want notifications or to post to a channel, use that.
## Forum topics
Groups with topics turned on keep their messages in separate threads. `list_dialogs` marks those with `isForum`, `list_forum_topics` lists the threads, and `send_message` / `get_messages` take an optional `topic_id` to work inside one. Without `topic_id` nothing changes — the message goes to the chat as before.
A wrong `topic_id` is worth understanding: Telegram does not reject it, it quietly files the message under General, in front of everyone. So `send_message` checks the id against the topic list before sending, and refuses a closed topic outright. That costs one extra call, only when a topic is named.
## Naming a chat
`send_message` and `get_messages` take an id, `me`, an `@username`, or a chat title. Ids and `@usernames` go straight out. A title is resolved against your dialog list first, and an ambiguous one is refused with the candidates named rather than guessed at.
This exists because handing a title to the underlying client makes it look for a username that was never there, which in a short-lived process means a minute of hanging instead of an error. Prefer an id from `list_dialogs` when you have one: it is the only form with no lookup at all.
## Security
### Bot token
Anyone with `TELEGRAM_BOT_TOKEN` can send as that bot, read updates, and change bot settings. Set it only in Plugins → Configure. The bot MCP redacts token-shaped strings from errors.
### User session (stronger warning)
- A GramJS **session string** or session file is equivalent to being logged in on a new device.
- Anyone who has it can **read your chats and send as you**.
- Never commit `TELEGRAM_SESSION`, `api_hash`, `.env`, or `*.session` files.
- After first login, save the session in Plugins → Configure or keep the `0600` session file. Do not leave the string in chat history if you can avoid it.
- Revoke: Telegram → **Settings → Devices** → terminate the unknown session, then delete the session file / clear the variable.
- Automating a user account is a Telegram ToS gray area. Keep sends human-paced. No spam or bulk broadcast.
The repo never contains real tokens, api hashes, or sessions. `mcp.json` only has `${VAR}` placeholders that match the manifest.
## MCP tools
### `telegram-bot` (unchanged Bot API v1)
| Tool | Telegram method | Purpose |
| --- | --- | --- |
| `send_message` | `sendMessage` | Send text as the **bot** (`chat_id` or public `@username`) |
| `get_me` | `getMe` | Confirm the token; bot username |
| `get_updates` | `getUpdates` | Debug recent updates |
| `list_recent_chats` | derived from `getUpdates` | Best-effort recent chats (not a full inbox) |
### `telegram-user` (MTProto)
| Tool | Purpose |
| --- | --- |
| `save_api_credentials` | Store api_id / api_hash at 0600 so no config file is needed |
| `auth_status` | Credentials + whether the session is authorized |
| `start_login` / `complete_login` | Phone + login code (+ optional 2FA password) |
| `start_qr_login` / `complete_qr_login` | QR / Link Desktop Device (+ optional 2FA) |
| `get_me` | Logged-in **user** (not a bot) |
| `list_dialogs` | Real dialog / inbox list, `isForum` marks groups with topics |
| `list_forum_topics` | Topics of a forum group, with the ids to send into |
| `search_dialogs` | Filter dialogs by title, username, or id |
| `get_messages` | Recent history for a chat |
| `send_message` | Send text **as the user** (`me`, `@username`, dialog id, or a chat title) |
Both servers expose `send_message` and `get_me`. Prefer the MCP server name (`telegram-bot` vs `telegram-user`). Tool descriptions are prefixed `[Bot API]` or `[User account]`.
## Skills
- **telegram-mode-guide** — when to use bot vs user
- **telegram-bot-setup** — BotFather, `TELEGRAM_BOT_TOKEN`, smoke-test
- **send-telegram-message** — send as the bot; confirm first
- **telegram-user-setup** — my.telegram.org, first login, session persistence
- **send-telegram-user-message** — resolve from the dialog list; confirm first
## Install
### Any MCP client (`npx`)
Both servers ship as one npm package, [`grokbot-telegram`](https://www.npmjs.com/package/grokbot-telegram), behind a single command:
```bash
npx grokbot-telegram bot # Bot API server over stdio
npx grokbot-telegram user # user-account (MTProto) server over stdio
npx grokbot-telegram login # interactive login, prints a TELEGRAM_SESSION
```
With no command the mode is read from the environment: `TELEGRAM_BOT_TOKEN` selects `bot`, `TELEGRAM_API_ID` + `TELEGRAM_API_HASH` select `user`. If both are set it refuses to guess and asks for an explicit mode.
Drop this into any MCP client config (Claude Code, Claude Desktop, Cursor, …):
```json
{
"mcpServers": {
"telegram-bot": {
"command": "npx",
"args": ["-y", "grokbot-telegram@0.4.0", "bot"],
"env": { "TELEGRAM_BOT_TOKEN": "…" }
},
"telegram-user": {
"command": "npx",
"args": ["-y", "grokbot-telegram@0.4.0", "user"],
"env": {
"TELEGRAM_API_ID": "…",
"TELEGRAM_API_HASH": "…",
"TELEGRAM_SESSION": "…"
}
}
}
}
```
### Cursor Marketplace (when listed)
1. Open **Customize** → search **grokbot-telegram**, or visit [cursor.com/marketplace](https://cursor.com/marketplace).
2. Install the plugin.
3. **Plugins → Configure** → set the variables for the mode(s) you want. For user-account mode you can skip this and let the agent store `api_id`/`api_hash` for you.
### From this repository
```bash
mkdir -p ~/.cursor/plugins/local
ln -s /path/to/grokbot-telegram ~/.cursor/plugins/local/telegram-bot
```
Reload the window (**Developer: Reload Window**). Team / Enterprise admins may need to allow local plugin imports.
Note that `mcp.json` launches the servers through `npx grokbot-telegram`, so a symlinked clone still runs the **published** build, not your working tree. To exercise local changes, either `npm run build && npm link` in the repo root, or point `mcp.json` at `${PLUGIN_ROOT}/dist/bot.js` and `${PLUGIN_ROOT}/dist/user.js` with `"command": "node"` while you work (`npm run validate` will flag that, which is the reminder to change it back).
Submit the public repo at [cursor.com/marketplace/publish](https://cursor.com/marketplace/publish) and/or list it on [cursor.directory](https://cursor.directory).
## Configure
The plugin declares variables in `.cursor-plugin/plugin.json` and substitutes `${VAR}` placeholders in `mcp.json`. None are required in the schema so you can enable only one mode.
| Variable | Mode | Where to get it |
| --- | --- | --- |
| `TELEGRAM_BOT_TOKEN` | Bot | [@BotFather](https://t.me/BotFather) |
| `TELEGRAM_API_ID` | User | [my.telegram.org/apps](https://my.telegram.org/apps) — or let the agent store it, see below |
| `TELEGRAM_API_HASH` | User | [my.telegram.org/apps](https://my.telegram.org/apps) — or let the agent store it, see below |
| `TELEGRAM_SESSION` | User | Returned after first login (optional if the session file exists) |
| `TELEGRAM_DISCLAIMER` | Both | Optional. Footer wording, or `off` to disable (see below) |
Optional env (not a marketplace variable): `TELEGRAM_SESSION_PATH` overrides the default session file `~/.grokbot-telegram/user.session`.
Login is two calls — `start_login` sends the code, `complete_login` redeems it — and some MCP hosts start a fresh process per call. So the in-flight state is written next to the session file as `user.session.pending.json` (mode 0600) and picked back up by the next process. It holds a pre-authorization session: it cannot read or send anything, but it is the auth key the code is bound to, so it is deleted the moment login succeeds and discarded after 15 minutes.
**QR login does not survive a restart, and cannot.** Telegram announces the scan on the connection that exported the code, and the authorization only exists once that same connection redeems it. The process that called `start_qr_login` has to stay alive until the scan lands. On a host that starts a process per tool call, use phone login. `complete_qr_login` still adopts an already-authorized session if there is one, and otherwise hands back a fresh `login_url`, because exporting again yields a *different* code and re-showing the old one can never work.
The project was called `cursor-telegram-plugin` before it became `grokbot-telegram`. A session left in the old `~/.cursor-telegram-plugin/user.session` is still read, so an existing login keeps working; new sessions are written to the new path.
## Example prompts
**Bot**
- “Verify my Telegram bot with `telegram-bot` `get_me`. Do not print the token.”
- “I just /start’ed the bot. List recent chats, then send ‘hello from Cursor’ to my DM after I confirm.”
**User**
- “I want messages sent as me, not as a bot. Walk me through my.telegram.org and first login.”
- “Search my Telegram dialogs for ‘Ada’, then send this text after I confirm.”
- “Show recent messages in Saved Messages (`me`) using the user account.”
## How to test
### Bot API `send_message`
1. `/start` the bot (or add it to a group/channel).
2. Set `TELEGRAM_BOT_TOKEN`.
3. In Agent chat: `get_me` → `list_recent_chats` → confirm → `send_message`.
4. Optional:
```bash
export TELEGRAM_BOT_TOKEN="…" # local shell only
curl -sS "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/getMe"
```
### User-account send (as you)
1. Set `TELEGRAM_API_ID` and `TELEGRAM_API_HASH`.
2. Login once:
```bash
export TELEGRAM_API_ID="…"
export TELEGRAM_API_HASH="…"
npx grokbot-telegram login
```
From a clone, the equivalent is `node dist/login.js` after `npm run build`.
Or use `start_login` / `start_qr_login` from the agent.
For login through a chat, prefer `start_login`. A QR token lives about 30 seconds and that clock starts when the tool returns, not when the code reaches the screen — which is usually not enough time in a conversation. The token cannot be extended; it is Telegram's limit. What the tools do instead is remove the round trips. `start_qr_login` and every waiting reply from `complete_qr_login` return the code **as a scannable PNG in the tool result**, ahead of the JSON, so the agent forwards an image rather than rendering one itself — and `login_url` stays in the payload as a fallback for clients that show no images. Each waiting reply also carries a fresh code and `expires_in_seconds`, so a retry gives the user a full window instead of a dead code.
The QR is drawn locally. A login token is a credential, so it never goes to an external image service.
Not every MCP host paints tool-result images on its own; where it does not, the agent forwards the base64 it was handed. Either way the code is generated once, here, rather than round-tripped through the agent.
The session shows up in Telegram → Settings → Devices as **grokbot-telegram**, so it is easy to find and revoke.
3. Confirm `get_me` is your user (`isBot: false`).
4. `list_dialogs` / `search_dialogs`, confirm destination + text, then `send_message`.
Automated tests mock Telegram (no live token or account). Node.js 20+ is required.
```bash
npm install --prefix packages/mcp-server
npm install --prefix packages/mcp-user-server
npm test # both packages
npm run typecheck
npm run build # per-package bundles, then collected into the root dist/
npm run validate # manifest + wiring invariants
```
Working on this with a coding agent? Start from [AGENTS.md](AGENTS.md).
## Chat list limitation (Bot API only)
The Bot API **cannot** list every chat a bot belongs to. `list_recent_chats` only inspects recent `getUpdates` events. If nothing shows up: the user has not `/start`ed the bot, the group is quiet, or a **webhook** is set.
User-account mode uses the real dialog list and does not have this limitation.
## Why first-party servers (not a random npm MCP)
Public packages exist (Bot API wrappers; user MCPs such as [`@overpod/mcp-telegram`](https://www.npmjs.com/package/@overpod/mcp-telegram) / [mcp-telegram/mcp-telegram](https://github.com/mcp-telegram/mcp-telegram)). They are real, but they are either very large or a different product.
This plugin ships two small stdio servers:
- **Bot:** official HTTP Bot API only (`packages/mcp-server`)
- **User:** teleproto MTProto user client (`packages/mcp-user-server`), tools limited to login, dialogs, history, and send
Both are bundled with esbuild — every dependency (including teleproto) is inlined, so the published package installs with zero runtime dependencies. `npm run build` produces the per-package bundles and collects them into the publishable root `dist/`:
- `dist/bot.js`, `dist/user.js`, `dist/login.js` — the bundles
- `dist/cli.js` — the `grokbot-telegram` dispatcher that picks between them
```bash
npm run build
```
`mcp.json` launches both:
```json
{
"telegram-bot": {
"command": "npx",
"args": ["-y", "grokbot-telegram@0.4.0", "bot"],
"env": { "TELEGRAM_BOT_TOKEN": "${TELEGRAM_BOT_TOKEN}" }
},
"telegram-user": {
"command": "npx",
"args": ["-y", "grokbot-telegram@0.4.0", "user"],
"env": {
"TELEGRAM_API_ID": "${TELEGRAM_API_ID}",
"TELEGRAM_API_HASH": "${TELEGRAM_API_HASH}",
"TELEGRAM_SESSION": "${TELEGRAM_SESSION}"
}
}
}
```
## Marketplace notes
- Single Cursor Plugin (`.cursor-plugin/plugin.json`), not a multi-plugin `marketplace.json` repo.
- **MTProto user client + Bot API**, not Bot API alone.
- Plugin `name` is `grokbot-telegram`, matching the npm package and the repository.
- MIT. Version lives in `package.json`; `.cursor-plugin/plugin.json` and the pin in `mcp.json` are checked against it by `npm run validate`.
- Logo at `assets/logo.svg` — a plain outline mark, deliberately not Telegram's own logo or brand colour, since this is a third-party plugin.
- `mcp.json` pins the version it launches, so what a reviewer reads is what runs.
- Variables: `TELEGRAM_BOT_TOKEN`, `TELEGRAM_API_ID`, `TELEGRAM_API_HASH`, `TELEGRAM_SESSION`, `TELEGRAM_DISCLAIMER` (all optional in the schema).
- Submit: [cursor.com/marketplace/publish](https://cursor.com/marketplace/publish).
## Layout
```text
.
├── .cursor-plugin/plugin.json
├── mcp.json
├── skills/
│ ├── telegram-mode-guide/SKILL.md
│ ├── telegram-bot-setup/SKILL.md
│ ├── send-telegram-message/SKILL.md
│ ├── telegram-user-setup/SKILL.md
│ └── send-telegram-user-message/SKILL.md
├── assets/logo.svg
├── AGENTS.md # guidance for coding agents (CLAUDE.md points here)
├── src/cli.js # grokbot-telegram dispatcher (bot | user | login)
├── dist/ # published bundles, built (gitignored)
├── packages/mcp-server/ # Bot API MCP + dist bundle
├── packages/mcp-user-server/ # GramJS user MCP + dist bundle
├── LICENSE
└── README.md
```
## License
[MIT](LICENSE) © Lotus / Lotus015 ([GitHub](https://github.com/Lotus015))