Custom Telegram MCP server + TUI auto-responder for running Claude Code as an autonomous Telegram agent
Project description
claude-code-telegrammer
Custom Telegram MCP server + TUI auto-responder for running Claude Code as an autonomous Telegram agent
What This Does
Turns Claude Code into a fully autonomous Telegram bot. Two subsystems work together:
-
Custom Telegram MCP Server (
ts/) -- A self-contained MCP server that replaces the broken officialplugin:telegram@claude-plugins-official. Handles all Telegram Bot API communication, message persistence, attachment handling, and access control. -
TUI Watchdog (
bin/,lib/) -- Polls a GNU Screen session, detects Claude Code's TUI state via pattern matching, and sends keystrokes to keep the agent running unattended (auto-accepts permission prompts, re-engages on idle).
Architecture
User (Telegram)
|
| Bot API (getUpdates long-polling)
v
┌──────────────────────────────────────────────────────────────┐
│ Custom Telegram MCP Server (ts/telegram-server.ts) │
│ Bun + @modelcontextprotocol/sdk │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Poller │ │ Store │ │ Tools │ │ Attachments│ │
│ │ (long │ │ (SQLite │ │ (10 MCP │ │ (download │ │
│ │ poll) │ │ WAL) │ │ tools) │ │ queue) │ │
│ └─────────┘ └─────────┘ └──────────┘ └────────────┘ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ │
│ │ Access │ │ Config │ │ Lock │ │
│ │ (allow- │ │ (env │ │ (PID │ │
│ │ list) │ │ vars) │ │ file) │ │
│ └─────────┘ └─────────┘ └──────────┘ │
└──────────────────────┬───────────────────────────────────────┘
│ MCP stdio
v
┌──────────────────────────────────────────────────────────────┐
│ Claude Code (in GNU Screen session) │
│ --channels or --mcp-config points to the MCP server │
└──────────────────────┬───────────────────────────────────────┘
│ screen buffer
v
┌──────────────────────────────────────────────────────────────┐
│ Watchdog (bin/telegrammer-watchdog) │
│ Polls screen buffer every 1.5s │
│ Detects: y/n prompt -> "1", y/y/n -> "2", idle -> cmd │
│ Throttled: burst limit, same-state delay, min interval │
└──────────────────────────────────────────────────────────────┘
Why Custom? (Official Plugin Issues)
The official plugin:telegram@claude-plugins-official has several unresolved issues that make it unusable for production:
- #851 --
STATE_DIRnot respected; access.json path hardcoded - #1075 -- 409 Conflict errors when multiple instances poll the same bot
- #1146 -- Zombie CPU consumption after session ends
This custom MCP server fixes all three: configurable state directory via TELEGRAM_STATE_DIR, PID-based single-instance lock, and clean shutdown on stdin close/SIGTERM.
Components
Custom Telegram MCP Server (ts/)
A Bun-based MCP server that communicates with the Telegram Bot API directly via fetch (no grammy dependency). Connects to Claude Code over stdio using @modelcontextprotocol/sdk.
Key files:
ts/telegram-server.ts-- Entry point, MCP server setup, shutdown handlingts/lib/poller.ts--getUpdateslong-polling loop with offset persistencets/lib/store.ts-- SQLite message store (schema v2)ts/lib/tools.ts-- All 10 MCP tool definitions and handlersts/lib/attachments.ts-- Background download queue (rate-limited, 500ms between downloads)ts/lib/access.ts-- Allowlist-based access control with mtime-cachedaccess.jsonts/lib/config.ts-- All configuration constants from environment variablests/lib/lock.ts-- PID-based single-instance enforcementts/lib/telegram-api.ts-- Raw Bot API wrapperts/lib/log.ts-- Structured JSON logging to stderr
Watchdog (bin/telegrammer-watchdog)
Polls a GNU Screen session at a configurable interval, detects Claude Code's TUI state via pattern matching, and sends keystrokes.
telegrammer-watchdog --session cld-telegram --interval 1.5
telegrammer-watchdog --dry-run # detect without responding
telegrammer-watchdog --self-test # run built-in state detection tests
Hook (bin/telegrammer-hook)
Entry point for scitex-agent-container integration. Called by agent-container's lifecycle hooks:
telegrammer-hook pre-start # Write access.json, .env, MCP config JSON
telegrammer-hook post-start # Start watchdog + send startup commands
telegrammer-hook pre-stop # Stop watchdog
The pre-start hook handles: bot token mapping (bot_token_env -> TELEGRAM_BOT_TOKEN), writing access.json from YAML allowed_users, generating MCP config for --mcp-config and .mcp.json.
Init (bin/telegrammer-init)
Sends startup commands to a running Claude Code screen session and configures access.json.
telegrammer-init --session cld-telegram --config telegram-master.yaml
Guard (bin/telegrammer-guard)
Lock/exclusivity guard. Ensures only one telegrammer instance controls a session.
telegrammer-guard acquire --lock ~/.scitex/agent-container/telegram/telegram.lock
telegrammer-guard release
telegrammer-guard status
telegrammer-guard check # exit 0 if locked, 1 if not
telegrammer-guard force # force-remove lock
Main CLI (bin/telegrammer)
Full lifecycle management of a Claude Code Telegram agent in a screen session.
telegrammer start config/telegram-master.yaml
telegrammer stop
telegrammer status
telegrammer attach
telegrammer logs
MCP Tools (10)
All tools are exposed via the MCP server and available to Claude Code during a session:
| Tool | Description |
|---|---|
reply |
Reply on Telegram. Supports threading (reply_to), auto-marks inbound as read, persists outbound to DB. |
react |
Add an emoji reaction to a message. Telegram's fixed whitelist applies. |
edit_message |
Edit a previously sent bot message. Edits don't trigger push notifications. |
get_history |
Retrieve message history (both directions) for a chat from local SQLite. |
get_unread |
List unread inbound messages, optionally filtered by chat_id. |
mark_read |
Mark messages as read by chat_id (all) or message_ids (specific rows). |
download_attachment |
Download a Telegram file by file_id, returns local path. |
send_document |
Upload a local file to a Telegram chat via sendDocument. |
search_messages |
Text search across stored messages using LIKE %query%. |
get_context |
Recent conversation formatted as compact text for LLM context. |
SQLite Schema (v2)
All messages are persisted in $TELEGRAM_STATE_DIR/messages.db using WAL mode.
messages table
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto-increment row ID |
direction |
TEXT | 'inbound' or 'outbound' |
chat_id |
TEXT | Telegram chat ID |
message_id |
TEXT | Telegram message ID |
user_id |
TEXT | Sender's Telegram user ID |
username |
TEXT | Sender's username |
text |
TEXT | Message content |
telegram_ts |
TEXT | Original Telegram timestamp (ISO 8601) |
received_at |
TEXT | When this server received the message |
read_at |
TEXT | When marked as read (NULL = unread) |
replied_at |
TEXT | When replied to (NULL = unreplied) |
reply_to_message_id |
TEXT | Telegram message ID being replied to |
reply_to_row_id |
INTEGER FK | DB row of inbound message being replied to |
host |
TEXT | Hostname of the machine running the server |
project |
TEXT | Working directory / project path |
agent_id |
TEXT | Agent identifier |
bot_token_hash |
TEXT | First 8 chars of SHA-256 of bot token |
raw_json |
TEXT | Full Telegram update JSON (inbound only) |
created_at |
TEXT | Row creation timestamp |
Key indexes: dedup on (chat_id, message_id, direction), unread index, unreplied index, agent identity index.
attachments table
| Column | Type | Description |
|---|---|---|
id |
INTEGER PK | Auto-increment |
message_row_id |
INTEGER FK | References messages(id) with CASCADE delete |
kind |
TEXT | photo, document, voice, audio, video |
file_id |
TEXT | Telegram file ID |
file_unique_id |
TEXT | Telegram file unique ID |
file_name |
TEXT | Original filename |
mime_type |
TEXT | MIME type |
file_size |
INTEGER | Size in bytes |
local_path |
TEXT | Local download path (NULL until downloaded) |
downloaded_at |
TEXT | When download completed |
meta table
| Column | Type | Description |
|---|---|---|
key |
TEXT PK | e.g., 'schema_version', 'update_offset' |
value |
TEXT | Stored value |
Configuration
Environment Variables (MCP Server)
| Variable | Required | Default | Description |
|---|---|---|---|
TELEGRAM_BOT_TOKEN |
Yes | -- | Telegram Bot API token |
TELEGRAM_STATE_DIR |
No | ~/.scitex/agent-container/telegram |
Directory for SQLite DB, access.json, lock file |
TELEGRAM_ALLOWED_USERS |
No | -- | Comma-separated Telegram user IDs for DM allowlist |
TELEGRAM_HOST_NAME |
No | os.hostname() |
Hostname stored with each message |
TELEGRAM_PROJECT |
No | process.cwd() |
Project path stored with each message |
TELEGRAM_AGENT_ID |
No | 'telegram' |
Agent identifier stored with each message |
TELEGRAM_ATTACHMENT_DIR |
No | $TELEGRAM_STATE_DIR/attachments |
Directory for downloaded attachments |
Environment Variables (Watchdog)
| Variable | Default | Description |
|---|---|---|
TELEGRAMMER_SESSION |
cld-telegram |
GNU Screen session name |
TELEGRAMMER_WATCHDOG_INTERVAL |
1.5 |
Poll interval in seconds |
TELEGRAMMER_RESP_Y_N |
1 |
Response for y/n prompts |
TELEGRAMMER_RESP_Y_Y_N |
2 |
Response for y/y/n prompts |
TELEGRAMMER_RESP_WAITING |
/speak-and-call |
Response when idle/waiting |
State Detection
The watchdog reads the screen buffer and matches against these patterns:
| State | Pattern | Response |
|---|---|---|
running |
(esc to interrupt), tokens ·, ing... |
No action |
y_n |
1. Yes + 3. No (two-choice prompt) |
Send 1 (accept) |
y_y_n |
2. Yes, and... / 2. Yes, allow... / 2. Yes, don't ask... |
Send 2 (accept all) |
waiting |
Cooking puns (Crafted for, etc.), empty > prompt, idle hints |
Send configurable command |
Response throttling: minimum interval between responses, burst limit (10 in 3s window), same-state delay.
Integration with scitex-agent-container
Add a telegram section and hooks to your agent YAML:
apiVersion: telegrammer/v1
kind: Agent
metadata:
name: telegram-master
spec:
model: opus[1m]
flags:
- --dangerously-skip-permissions
- --strict-mcp-config
- "--mcp-config /tmp/scitex-agent-container/mcp-{agent-name}.json"
- "--dangerously-load-development-channels server:telegram"
workdir: ~/proj
env:
CLAUDE_AGENT_ROLE: telegram
CLAUDE_AGENT_ID: telegram-master
telegram:
bot_token_env: SCITEX_OROCHI_TELEGRAM_BOT_TOKEN
auto_connect: true
allowed_users:
- 123456789
screen:
name: cld-telegram
watchdog:
enabled: true
interval: 1.5
responses:
y_n: "1"
y_y_n: "2"
waiting: "/speak-and-call"
lock:
path: ~/.scitex/agent-container/telegram/telegram.lock
hooks:
pre_start: telegrammer-hook pre-start
post_start: telegrammer-hook post-start
pre_stop: telegrammer-hook pre-stop
The telegrammer-hook pre-start phase generates MCP config JSON so Claude Code can discover the custom server via --mcp-config or .mcp.json.
Installation
Prerequisites
- Python >= 3.10 and GNU Screen (for watchdog/CLI)
- Bun >= 1.0 (for the MCP server)
Install
pip install claude-code-telegrammer
Or from source:
git clone https://github.com/ywatanabe1989/claude-code-telegrammer.git
cd claude-code-telegrammer
pip install -e .
# Install TypeScript dependencies for the MCP server
cd ts && bun install
Quick Start
# 1. Export your bot token
export TELEGRAM_BOT_TOKEN="123456789:AAH..."
# 2. Start the MCP server standalone (for testing)
bun run ts/telegram-server.ts
# 3. Or start a full agent with watchdog from a YAML config
telegrammer start config/telegram-master.yaml
# 4. Check status
telegrammer status
# 5. Attach to the screen session to observe
screen -r cld-telegram # Ctrl-A D to detach
Access Control
Access is managed via access.json in $TELEGRAM_STATE_DIR:
{
"dmPolicy": "allowlist",
"allowFrom": ["123456789"],
"groups": {
"-100123456": {
"requireMention": true,
"allowFrom": ["123456789"]
}
}
}
The allowlist is merged with TELEGRAM_ALLOWED_USERS env var at runtime. Mtime-based caching means edits to access.json take effect without restart.
Part of SciTeX
claude-code-telegrammer is part of SciTeX. It provides the Telegram communication layer and TUI watchdog used by scitex-agent-container for autonomous agent operation.
┌─────────────────────────────────────────────────────────┐
│ scitex-orochi — agent definitions, dashboard │
└──────────────────────────┬──────────────────────────────┘
v
┌─────────────────────────────────────────────────────────┐
│ scitex-agent-container — lifecycle, health, restart │
└──────────────────────────┬──────────────────────────────┘
v
┌─────────────────────────────────────────────────────────┐
│ claude-code-telegrammer <-- YOU ARE HERE │
│ MCP server: Telegram API, message DB, 10 tools │
│ Watchdog: TUI auto-response, screen polling │
└─────────────────────────────────────────────────────────┘
Four Freedoms for Research
- The freedom to run your research anywhere -- your machine, your terms.
- The freedom to study how every step works -- from raw data to final manuscript.
- The freedom to redistribute your workflows, not just your papers.
- The freedom to modify any module and share improvements with the community.
AGPL-3.0 -- because we believe research infrastructure deserves the same freedoms as the software it runs on.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file claude_code_telegrammer-0.3.0.tar.gz.
File metadata
- Download URL: claude_code_telegrammer-0.3.0.tar.gz
- Upload date:
- Size: 6.0 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0rc1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
85b7ec9378d98178078b5b0509f4b742dd0697a15aba1925f33b852402740bd9
|
|
| MD5 |
26708ec7d5c274655709a3ddd114d46d
|
|
| BLAKE2b-256 |
ed813f22799a9cc2ccd70edb6d546c5da72ccba22168dd6f940c4c1db97e112c
|
File details
Details for the file claude_code_telegrammer-0.3.0-py3-none-any.whl.
File metadata
- Download URL: claude_code_telegrammer-0.3.0-py3-none-any.whl
- Upload date:
- Size: 21.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.0rc1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
49bd7ed79183941aaff780d67abb7c56050f8ac84ab0b81c304630815835d535
|
|
| MD5 |
cbcd1e5a8b765edba1ea9d7fb42d6778
|
|
| BLAKE2b-256 |
686d5e01be1b8f77c5cd407467f3fd74bba3a85320e3482e6b6d38ba9cca8350
|