cursor-telegram-mcp
v0.9.3
Published
Manage Cursor from your phone over Telegram: an MCP server + auto-spawned local worker that notifies you, asks you questions, and (optionally) runs headless Cursor agents you text it. Local, bring-your-own-bot, runs entirely on your machine.
Downloads
1,527
Maintainers
Readme
cursor-telegram-mcp
Manage Cursor from your phone over Telegram. A local MCP server plus an auto-started background worker that lets a Cursor agent talk to you on Telegram, and lets you drive work from your phone:
- When the agent finishes a task, it messages you (finish notification).
- When the agent is blocked on a decision, it mirrors the question to Telegram and keeps working / waiting until your reply comes back.
- Command mode: text a task to the bot and a headless Cursor agent plans it,
sends you the plan, and runs it once you reply
YES- so you can leave the laptop on the charger and drive work entirely from your phone.
Local and bring-your-own-bot: each person installs the package, creates their own Telegram bot, and everything runs on their own machine. There is no shared server and nothing shared between users. It uses the official Telegram Bot API over HTTPS - no QR, no eSIM, no SIM. Pending questions are persisted to disk, so a worker restart restores any still-open questions instead of dropping them.
Add to Cursor (one click)
This adds the server to your global ~/.cursor/mcp.json (the "Installed MCP
Servers" list in Cursor Settings → Tools & MCP). It runs npx -y
cursor-telegram-mcp, so it requires the package to be published to npm. After
adding, run npx cursor-telegram-mcp setup once to connect your bot.
Prefer it under "Plugin MCP Servers" / the Cursor marketplace? See As a Cursor plugin below.
Quick start
The goal: your bot is online whenever your computer is on — no need to open Cursor or start anything by hand.
1. Install (one time)
npm i -g cursor-telegram-mcpA global install gives the always-on service a stable location. (You can also
run everything via npx cursor-telegram-mcp …, but the always-on installer
needs a global install so its launch agent doesn't point at a temporary cache.)
2. Configure + go always-on
cursor-telegram-mcp setupThe wizard creates a bot with @BotFather, validates
the token, captures your chat id (you message the bot once), optionally enables
command mode with a Cursor API key, and then offers to install the always-on
service (macOS launchd: starts at login, restarts itself, plus a watchdog).
Config is saved to ~/.config/cursor-telegram/config.json (or
%APPDATA%\cursor-telegram\config.json on Windows).
That's it — text your bot to test it. Run cursor-telegram-mcp doctor anytime
to check status, and cursor-telegram-mcp install / uninstall to toggle the
always-on service.
3. (optional) Let Cursor agents message you
Add this to a project's .cursor/mcp.json (or Cursor Settings -> Tools & MCP),
then reload MCP:
{
"mcpServers": {
"telegram": {
"command": "npx",
"args": ["-y", "cursor-telegram-mcp"]
}
}
}The bot itself runs from the always-on service regardless of Cursor; this step just lets in-IDE agents send you notifications and questions.
Architecture
flowchart TD
cursor["Cursor agent"] -->|"MCP stdio (npx)"| mcp["MCP server (thin) dist/index.js"]
mcp -->|"GET /health"| check{"worker up?"}
check -->|no| spawn["spawn detached worker (singleton)"]
check -->|yes| reuse["reuse running worker"]
spawn --> worker["worker dist/worker.js"]
reuse --> worker
worker --> store["persisted question store (questions.json)"]
worker -->|"command mode (optional)"| agent["headless Cursor agent (cursor-agent CLI)"]
worker -->|"Bot API: sendMessage / getUpdates"| tg["Telegram"]
tg -->|"your reply / texted task"| worker
phone["Your phone (Telegram app)"] --> tgThe worker owns the Telegram connection and survives Cursor/MCP reloads. The MCP
server is a thin client Cursor launches per project; it auto-spawns the worker if
one is not already running (the worker's port guard keeps it a singleton, so
multiple projects safely share one worker). For non-blocking questions,
ask_human_for_guidance returns a questionId immediately and the agent polls
check_human_response (or long-polls with waitMs). For blocking decisions,
ask_human_and_wait long-polls the worker and returns as soon as you reply on
Telegram (no fixed 30-second sleep after your answer).
Security
- Only your chat can drive the bot. Every inbound Telegram message and button
tap is checked against your configured
TELEGRAM_CHAT_IDand dropped otherwise. - No inbound internet exposure. The worker uses Telegram long-polling (not a
webhook) and binds its HTTP API to
127.0.0.1only. - The local HTTP API is authenticated. A random 64-char secret
(
TG_WORKER_SECRET) is generated on first run, stored0600in the config file, and required on every request (Authorization: Bearer …). Without it the worker returns401;/healthreturns only a bare{ok:true}liveness ping. The MCP server, watchdog, doctor, and mirror hook all read the same secret automatically — you never set it by hand. After upgrading, reload Cursor once so already-running MCP clients pick up the secret (they also self-heal on a 401). - DNS-rebinding hardening. Requests whose
Hostheader is not loopback get a403, so a browser pointed at a domain resolving to localhost can't reach it. - Treat the bot token like a password, and enable 2FA on your Telegram
account: in command mode, anyone who can post into your chat can run code on
your machine. The token and secret live in a
0600config file.
Timeouts (three different values)
- getUpdates 30s: worker receive path only (Bot API long poll for inbound messages).
- waitMs / ask_human_and_wait: agent wait path — long-poll the worker and wake immediately when you reply (do NOT sleep on a fixed timer between checks).
- TG_RESPONSE_TIMEOUT_MIN (default 30): minutes before a pending question reports timed_out.
Tools exposed to the agent
| Tool | Purpose |
| --- | --- |
| notify_human_task_complete(summary) | Fire-and-forget "task done" message. |
| ask_human_for_guidance(question) | Send a question, return a questionId right away. |
| ask_human_and_wait(question, timeoutMin?) | Send a question and block until you answer or it times out. |
| check_human_response(questionId, waitMs?) | Poll for your reply: answered / pending / timed_out. Long-polls by default when waitMs is omitted; pass waitMs=0 for an instant check. |
The rule .cursor/rules/telegram-hitl.mdc tells the agent to send a completion message when it finishes and to mirror any question it would ask in chat to Telegram.
Command mode (drive work from your phone)
When a Cursor API key is configured (setup step 3, or CURSOR_API_KEY), the
worker treats any message that is not answering an open question as a task:
- You text the bot, e.g.
add a healthcheck endpoint and a test. - A read-only headless Cursor agent investigates and replies with a numbered
plan (
Plan (C-1) ...). No files are changed yet. - You reply
YESto execute (orNOto cancel). OnYES, a second agent carries out the plan and texts back a summary.
You can also use /ask <question> (read-only Q&A) and /plan <task> explicitly,
send /status to see what's running, /reset (or /new) to start a fresh
conversation, and /help for the full command list. To drive multiple repos and
agent threads from one chat (#project, $agent, tappable choosers), see
Multiple projects & agents. Command
mode needs the Cursor CLI (cursor-agent) installed:
- macOS / Linux:
curl https://cursor.com/install -fsS | bash - Windows (PowerShell):
irm 'https://cursor.com/install?win32=true' | iex
The headless agent runs locally against TG_AGENT_CWD (default: the directory
the worker started in). Every task is gated: nothing changes code until you
approve the plan.
Rolling chat (knowledge carries across messages)
By default (TG_ROLLING=true) command mode keeps ONE long-lived Cursor agent
and sends every turn — plan, execute, and /ask — to it. That means:
- The executor remembers the plan it just made, and the next prompt remembers the whole conversation, so you can keep building from your phone without re-explaining context.
- The agent id is persisted to
TG_SESSION_PATH(default<configDir>/rolling-session.json), so the thread survives worker restarts (including the self-update restart) and resumes where you left off. - Send
/reset(or/new) to drop the memory and start a fresh thread.
Set TG_ROLLING=false to go back to a stateless agent per message.
See the same chat on your computer
Every turn is also appended to a Markdown transcript at TG_TRANSCRIPT_PATH
(default <TG_AGENT_CWD>/remote-chat.md). Open it in Cursor (or any editor) to
read the same conversation you are driving from your phone. Because it lives in
the repo, git tracks its history — commit it whenever you want a snapshot.
Keeping the worker alive
The worker runs only while your laptop is awake. It auto-starts with the MCP server and stays up across Cursor reloads, so for "laptop on the charger, manage from my phone" you usually need nothing else.
If you want it to survive reboots without opening Cursor, install it as an always-on background service. On macOS this is one command:
cursor-telegram-mcp install # start now + at every login (launchd)
cursor-telegram-mcp uninstall # stop and remove itinstall writes a per-user launch agent
(~/Library/LaunchAgents/com.cursor-telegram.worker.plist) that points at your
current Node and the installed CLI — no hardcoded paths — runs the worker under
caffeinate so idle sleep never blocks delivery, and restarts it if it crashes
(KeepAlive). Logs go to ~/Library/Logs/cursor-telegram-worker.log. Keep the
Mac on AC power so a closed lid does not fully sleep.
install also sets up a watchdog (com.cursor-telegram.watchdog, every
120s) that checks the worker's /health and restarts it if it is unreachable
or its poll loop has wedged. Combined with the worker's own retry/backoff (every
Bot API call has a hard abort timeout, so a stale socket after sleep/wake can't
silently hang it), the bot stays online as long as the Mac is on.
To stay online the Mac must not fully sleep. The worker runs under caffeinate
so idle sleep is prevented while it's up, but a closed lid on battery still
sleeps. For a machine you want always-on:
- Keep it on AC power.
- Optionally allow it to run with the lid closed on AC:
sudo pmset -c sleep 0 disablesleep 1(revert withsudo pmset -c disablesleep 0).
On Linux/Windows, run cursor-telegram-mcp worker under your own service
manager (systemd / Task Scheduler). (Note: on macOS, launchd agents cannot read
files under ~/Desktop/~/Documents/~/Downloads; install the package or
project outside those folders.)
Configuration
Values resolve in this order: real environment variables (e.g. set in
mcp.json) > the config file written by setup > a local .env (dev) >
defaults.
| Variable | Default | Description |
| --- | --- | --- |
| TELEGRAM_BOT_TOKEN | (required) | Bot token from @BotFather. |
| TELEGRAM_CHAT_ID | (required) | Chat to message; replies are matched to it. |
| TG_PROJECT | default | Project label prefixed to this client's messages. |
| TG_WORKER_URL | http://127.0.0.1:8787 | Worker base URL the MCP calls. |
| TG_WORKER_HOST | 127.0.0.1 | Host the worker binds to. |
| TG_WORKER_PORT | 8787 | Port the worker listens on. |
| TG_WORKER_SECRET | (auto-generated) | Shared secret authenticating local HTTP calls. Generated 0600 on first run; set to override. |
| TG_MIN_SEND_GAP_MS | 3000 | Minimum gap between outgoing messages. |
| TG_RESPONSE_TIMEOUT_MIN | 30 | Minutes before a pending question reports timed_out. |
| CURSOR_API_KEY | (unset) | Enables command mode. Cursor -> Integrations. |
| TG_AGENT_CWD | worker cwd | Working dir for the default sandbox command-mode project. |
| TG_PROJECTS_ROOT | (unset) | Folder auto-scanned for git repos to register as projects. |
| TG_AGENT_MODEL | composer-2.5 | Model id for the headless agent. |
| TG_AGENT_LOAD_SETTINGS | false | true to load TG_AGENT_CWD's .cursor rules/MCP during runs. |
| TG_ROLLING | true | Keep one rolling agent thread so knowledge carries across messages. |
| TG_TRANSCRIPT_PATH | <cwd>/remote-chat.md | Markdown transcript of the rolling chat. |
| TG_SESSION_PATH | <configDir>/rolling-session.json | Where the rolling agent id is persisted. |
CLI
cursor-telegram-mcp [mcp] Start the MCP server (default; Cursor runs this)
cursor-telegram-mcp setup First-time setup: create/link your bot
cursor-telegram-mcp login Print and save your Telegram chat id
cursor-telegram-mcp worker Run the background worker in the foreground
cursor-telegram-mcp install Install the always-on worker + watchdog (macOS launchd)
cursor-telegram-mcp uninstall Remove the always-on worker + watchdog
cursor-telegram-mcp watchdog One-shot health check; restarts the worker if down
cursor-telegram-mcp doctor Diagnose configuration and connectivityAs a Cursor plugin
This repo is also packaged as a Cursor plugin (manifest in .cursor-plugin/plugin.json, server config in mcp.json, behavior rule in .cursor/rules/telegram-hitl.mdc). Installing it as a plugin makes the server show up under "Plugin MCP Servers" and bundles the human-in-the-loop rule automatically.
Try it locally (no publishing required):
# make the `npx -y cursor-telegram-mcp` command resolve locally
npm run build && npm pack && npm i -g ./cursor-telegram-mcp-*.tgz
# load the plugin from this checkout, then reload Cursor
ln -s "$(pwd)" ~/.cursor/plugins/local/cursor-telegram-mcpThen "Developer: Reload Window" in Cursor — the telegram server appears under
Settings → Tools & MCP → "Plugin MCP Servers". To list it in the in-app
marketplace for one-click discovery, publish the repo (public, open source) at
cursor.com/marketplace/publish.
Multiple projects & agents (command mode)
One worker can drive command-mode work across many repos, each with multiple agent threads, all addressable from your phone. Switch the active target with a single character prefix and the rest of the line becomes the task:
#altus fix the linear issues # switch to the "altus" project and run a task
$research summarize the docs # switch/create the "research" agent thread
/ask is it live? # ask the active agent (read-only)
/plan add a healthcheck endpoint # plan on the active agent (approve to run)Listing & management (the /# and /$ lists reply with tappable buttons):
| Command | Does |
| --- | --- |
| /help | List every command with a short description. |
| /# | List projects as tappable buttons (tap to switch). |
| /# add <name> <path> | Register a project at a path. |
| /# remove <name> | Unregister a project. |
| /# scan <root> | Auto-register every git repo directly under <root>. |
| /$ | List the project's real Cursor chats (newest first) as tappable buttons, plus New chat. |
| /$ new <full name> | Create a named thread (names may contain spaces). |
| /$ remove <name> | Remove a named thread. |
| /status | Show the active target and what each runner is doing. |
| /reset | Start a fresh conversation for the active thread. |
Continuing your real Cursor chats from your phone
Command mode runs through the cursor-agent CLI, so every thread the bot drives
is a real Cursor chat: it's written to the same on-disk store the desktop IDE
reads, and it's resumable with full context across messages and worker restarts.
/$lists the project's actual chats (the same ones you see in the IDE), newest first, by title. Tap one to continue it from your phone.- The first message after you tap a pre-existing IDE chat is seeded with that chat's transcript (a one-time handoff — the CLI can't resume an IDE chat's private history directly, so the bot starts a fresh chat primed with the prior conversation). After that, the thread rolls on its own with full context.
- Tap New chat (or send
$<name>) to start a brand-new thread. $<id-prefix>(e.g.$0f1a8e4f) re-addresses a specific chat by id.
Real-time caveat: a thread you drive from the phone shows up in Cursor's chat history when you reopen/refresh that chat — it does not stream live into a tab you already have open. Cursor exposes no way for an external process to push turns into an open desktop tab.
Notes:
- Inline
#name/$nametake a single token (letters, digits,.,-,_). Multi-word thread names are created with/$ new <full name>. - Switching a project automatically shows that project's chats, so you can immediately pick which one to continue.
- Each thread is an independent rolling Cursor chat with its own working directory, session, and queue. Different targets run concurrently; a single target runs its tasks one at a time.
- Set
TG_AGENT_CWDfor the defaultsandboxproject, andTG_PROJECTS_ROOTto auto-scan a folder of repos on startup. The worker refuses to register its own repo (a command-mode agent editing it would self-restart). - Question routing for IDE agents is unchanged: each project's
mcp.jsonsets a differentTG_PROJECT, and questions arrive prefixed and numbered ([billing-api] Q-7). The#/$scheme governs only where command-mode tasks run.
Replying
Reply with a normal Telegram message. With several questions open, reply to the specific question message to answer it precisely; otherwise the oldest open question is answered first.
Answering from your phone
When the agent needs a decision while you are away from the laptop:
- Reply on Telegram. Prefer Telegram Reply on the specific Q-n message so the worker matches your answer to the right question.
- Ignore the IDE Questions panel (A/B/C, Skip, Continue) when the agent says the question was also sent to Telegram as Q-n. That panel is separate; Skip there does not read your Telegram reply.
- The agent should resume as soon as it sees your answer (
ask_human_and_waitwakes immediately on reply;check_human_responselong-polls by default when you omitwaitMs, or passwaitMs=0for an instant status check). If it picks a default instead, send a follow-up message in the IDE chat with your choice.
Use Telegram for remote decisions; use the IDE panel only when you are at the desk and not relying on Telegram for that question.
Develop from source
npm install
npm run setup # or: npm run login (after a token is set)
npm run worker # foreground worker (tsx)
npm run start # MCP server (tsx)
npm run typecheck
npm run build # emit dist/ for publishingNotes & limitations
- Local-only: if the laptop sleeps or the worker stops, messaging pauses until it is back. Open questions are persisted to disk and restored on the next worker start, so a restart no longer drops unanswered questions.
- Per user: each person creates their own bot and runs their own worker (one bot token can only be polled by one process).
- Command mode requires the
cursor-agentCLI and a Cursor API key; without them, notifications and questions still work.
Project layout
src/
cli.ts # CLI dispatcher (bin) -> mcp / setup / login / worker / doctor
index.ts # thin MCP stdio server; auto-spawns the worker
worker.ts # long-running Telegram worker + localhost HTTP API
config.ts # config resolution (env > config file > .env > defaults)
setup.ts # interactive first-time setup wizard
login.ts # chat-id discovery helper
doctor.ts # diagnostics
telegram.ts # Bot API client: long-poll getUpdates, sendText, media
agentRunner.ts# command mode: rolling/resumable headless Cursor agent (lazy @cursor/sdk)
projects.ts # project + agent registry (projects.json) and active-target state
runners.ts # per-(project,agent) runner registry: own cwd, session, queue, transcript
session.ts # persist the rolling agent id across worker restarts
transcript.ts # append-only remote-chat.md transcript of the rolling chat
store.ts # pending-question store (persisted to disk) + reply matching
install.ts # `install`/`uninstall`: generate per-user launchd worker + watchdog (macOS)
watchdog.ts # `watchdog`: one-shot /health check that restarts a wedged worker
parseInbound.ts / splitMessage.ts / taskQueue.ts / formatTelegram.ts
answerWaiters.ts # wake-on-answer for long-polling GET /response/:id
.cursor/
rules/telegram-hitl.mdc # agent behavior rule
mcp.client.template.json # per-project MCP client block to copy