Telegram Per-Project
Connect a Telegram bot to Claude Code via an MCP channel plugin. Per-project mode gives each project its own bot, access policy, and message inbox. Includes a claude-gram watchdog that monitors the session, sends Telegram notifications on exit, and auto-retries.
Prerequisites
- Bun — the MCP server runs on Bun. The installer will set it up if missing.
curl — used by claude-gram for Telegram API calls (pre-installed on most systems).
Installation
Clone the repo and run the installer:
git clone https://github.com/trezero/telegram-per-project.git
cd telegram-per-project
./install.sh
This does two things:
- Copies
claude-gram to ~/.local/bin (or ~/bin) so it's available globally
- Installs Bun if not already present (the plugin runtime)
The installed copy is independent of the repo — re-run ./install.sh to deploy updates.
You can specify a custom install directory: ./install.sh /usr/local/bin
Loading the plugin
The plugin is loaded by Claude Code at session start. Choose one:
# Development — loads from the cloned repo (this session only)
claude --plugin-dir /path/to/telegram-per-project
# Marketplace — permanent install (when published)
claude plugin install telegram-per-project@<marketplace>
When loaded via --plugin-dir, the local copy takes precedence over any installed marketplace version with the same name.
Quick Setup
Default pairing flow for a single-user DM bot. See ACCESS.md for groups and multi-user setups.
1. Create a bot with BotFather.
Open a chat with @BotFather on Telegram and send /newbot. BotFather asks for two things:
- Name — the display name shown in chat headers (anything, can contain spaces)
- Username — a unique handle ending in
bot (e.g. my_assistant_bot). This becomes your bot's link: t.me/my_assistant_bot.
BotFather replies with a token that looks like 123456789:AAHfiqksKZ8... — that's the whole token, copy it including the leading number and colon.
2. Load the plugin and provide the token.
When you enable the plugin, Claude Code prompts for the bot token (stored securely in the system keychain). Alternatively, configure manually:
/telegram-per-project:configure 123456789:AAHfiqksKZ8...
3. Launch.
cd ~/projects/myproject
claude-gram
If no Telegram config exists for the project, claude-gram guides you through interactive setup (project ID, token validation, config file creation).
Or launch manually without the watchdog:
claude --channels plugin:telegram@claude-plugins-official --plugin-dir /path/to/telegram-per-project
4. Pair.
DM your bot on Telegram — it replies with a 6-character pairing code. In your Claude Code session:
/telegram-per-project:access pair <code>
Your next DM reaches the assistant.
Unlike Discord, there's no server invite step — Telegram bots accept DMs immediately. Pairing handles the user-ID lookup so you never touch numeric IDs.
5. Lock it down.
Pairing is for capturing IDs. Once you're in, switch to allowlist so strangers don't get pairing-code replies:
/telegram-per-project:access policy allowlist
Plugin Structure
This plugin follows the Claude Code plugin conventions:
telegram-per-project/
├── .claude-plugin/
│ └── plugin.json # Manifest: channels, userConfig, mcpServers
├── skills/
│ ├── access/SKILL.md # /telegram-per-project:access — pairing, allowlists, policies
│ └── configure/SKILL.md # /telegram-per-project:configure — token setup, status
├── hooks/
│ └── hooks.json # SessionStart: install deps via ${CLAUDE_PLUGIN_DATA}
├── .mcp.json # MCP server config (telegram channel server)
├── server.ts # Telegram bot + MCP server (bun)
├── package.json # Dependencies: grammy, @modelcontextprotocol/sdk
├── claude-gram # Session launcher & watchdog script
├── install.sh # Installer (bun + claude-gram copy)
└── uninstall.sh # Cleanup script
Key conventions used:
channels in plugin.json — declares this as a channel plugin bound to the telegram MCP server
userConfig with sensitive: true — bot token prompted at enable time, stored in system keychain
${CLAUDE_PLUGIN_DATA} — persistent directory for node_modules that survives plugin updates
- SessionStart hook — installs dependencies once, re-installs only when
package.json changes
Bot token resolution
The server reads the bot token from the first available source:
CLAUDE_PLUGIN_OPTION_bot_token — set by plugin userConfig (keychain-backed)
TELEGRAM_BOT_TOKEN — set via settings.local.json env block or shell environment
.env file in the state directory — legacy fallback for existing installations