feat: Claude Telegram Bridge MCP server

A Model Context Protocol (MCP) server that enables Claude to communicate
with users via Telegram. Provides two-way communication, notifications,
question/answer flows, and message queuing.

Features:
- MCP server implementation with 5 tools
- HTTP bridge for Telegram Bot API
- Real-time notifications with priority levels
- Question/answer blocking flow
- Message queue for async communication
- Background daemon support

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
RichardDillman
2025-11-23 00:55:30 -05:00
commit 6c8c9350a1
13 changed files with 3146 additions and 0 deletions

14
.env.example Normal file
View File

@@ -0,0 +1,14 @@
# Telegram Bot Configuration
# Get your bot token from @BotFather on Telegram
TELEGRAM_BOT_TOKEN=your_bot_token_here
# Chat ID (auto-set when you send /start to your bot)
# Leave empty on first setup
TELEGRAM_CHAT_ID=
# HTTP Server Configuration
PORT=3456
HOST=localhost
# Enable/disable notifications
ENABLED=true

27
.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# Dependencies
node_modules/
.pnpm-store/
# Build output
dist/
# Environment variables (contains secrets!)
.env
# Logs
*.log
npm-debug.log*
pnpm-debug.log*
# OS files
.DS_Store
Thumbs.db
# IDE
.vscode/
.idea/
*.swp
*.swo
# PM2
.pm2/

102
INSTALL.md Normal file
View File

@@ -0,0 +1,102 @@
# Installation Guide
Quick reference for setting up the Claude Telegram Bridge MCP server.
## Prerequisites
- Node.js 18+ installed
- pnpm installed (`npm install -g pnpm`)
- Telegram account
## Step-by-Step Setup
### 1. Get Your Telegram Bot Token
1. Open Telegram and search for `@BotFather`
2. Send: `/newbot`
3. Name your bot (e.g., "My Claude Bridge")
4. Choose username (e.g., "my_claude_bridge_bot")
5. **Save the token** BotFather gives you
### 2. Install the Bridge
```bash
# Clone/download this repo
cd claude-telegram-bridge
# Install dependencies
pnpm install
# Create environment file
cp .env.example .env
# Edit .env and add your bot token
# TELEGRAM_BOT_TOKEN=paste_your_token_here
```
### 3. Build and Start
```bash
# Build
pnpm build
# Start (choose one)
pnpm dev # Foreground
pnpm daemon # Background (requires pm2)
```
### 4. Initialize Bot
1. Open Telegram
2. Find your bot
3. Send: `/start`
4. Bot saves your chat ID automatically
### 5. Configure Claude MCP
Edit `~/.config/claude-code/settings/mcp.json`:
```json
{
"mcpServers": {
"telegram": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/claude-telegram-bridge/dist/mcp-server.js"
],
"env": {
"TELEGRAM_BRIDGE_URL": "http://localhost:3456"
}
}
}
}
```
Find your path with: `cd claude-telegram-bridge && pwd`
### 6. Test
Restart Claude Code and say:
> "Send me a test notification via Telegram"
You should receive a message in Telegram!
## Troubleshooting
**Bridge not running?**
```bash
curl http://localhost:3456/health
```
**MCP server not found?**
- Check the absolute path in your MCP config
- Make sure you ran `pnpm build`
**No Telegram messages?**
- Did you send `/start` to your bot?
- Check bridge logs: `pnpm logs` (if using daemon)
## Next Steps
See [README.md](README.md) for full documentation and usage examples.

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 rdillman@gmail.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

450
README.md Normal file
View File

@@ -0,0 +1,450 @@
# Claude Telegram Bridge
**MCP Server for Two-Way Communication with Telegram**
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
This is a proper **Model Context Protocol (MCP) server** that enables any Claude instance to communicate with you via Telegram. Just grant Claude access to this MCP server, and it can send notifications, ask questions, and receive messages from you in real-time!
> **Free to use, share, and modify!** See [LICENSE](LICENSE) for details.
## Why This Exists
After trying email, SMS, and Google Chat integrations, Telegram emerged as the best solution for:
-**Standardized MCP Integration** - Works with any Claude instance automatically
- ✅ Instant two-way communication
- ✅ Free and reliable
- ✅ Works on all devices
- ✅ Simple setup
- ✅ No carrier dependencies
## Features
- 💬 **Two-Way Communication** - Send messages to Claude, get responses back
-**Question/Answer Flow** - Claude can ask you questions and wait for answers
- 📬 **Message Queue** - Messages queue up when Claude is busy, get answered ASAP
- 🔔 **Priority Notifications** - Different icons for info, success, warning, error, question
- 🌐 **HTTP API** - Easy integration from any app/project
- 🚀 **Background Service** - Runs independently, always available
## Quick Start
### 1. Create Your Telegram Bot
1. **Open Telegram** and search for `@BotFather`
2. **Send** `/newbot`
3. **Follow the prompts:**
- Choose a name for your bot (e.g., "My Claude Bridge")
- Choose a username (e.g., "my_claude_bridge_bot")
4. **Save your bot token** - BotFather will give you a token like:
```
1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
```
5. **Find your bot** in Telegram using the username you created
### 2. Install and Configure
```bash
# Clone or download this repo
cd claude-telegram-bridge
# Install dependencies
pnpm install
# Copy environment template
cp .env.example .env
# Edit .env and add your bot token
# TELEGRAM_BOT_TOKEN=your_token_here
```
**Edit `.env`:**
```env
TELEGRAM_BOT_TOKEN=1234567890:ABCdefGHIjklMNOpqrsTUVwxyz
TELEGRAM_CHAT_ID= # Leave empty - auto-set on first use
PORT=3456
HOST=localhost
ENABLED=true
```
### 3. Build and Start
```bash
# Build the project
pnpm build
# Start the bridge service
pnpm dev
# Or run as background daemon
pnpm daemon
```
### 4. Initialize Your Bot
1. Open Telegram and find your bot
2. Send `/start` to your bot
3. The bot will reply and save your chat ID automatically
4. Test with `/status` to verify it's working
### 5. Add MCP Server to Claude
Add to your Claude Code MCP settings (`~/.config/claude-code/settings/mcp.json`):
```json
{
"mcpServers": {
"telegram": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/claude-telegram-bridge/dist/mcp-server.js"
],
"env": {
"TELEGRAM_BRIDGE_URL": "http://localhost:3456"
}
}
}
}
```
**Replace `/ABSOLUTE/PATH/TO/` with your actual path!**
To find your absolute path:
```bash
cd claude-telegram-bridge
pwd
# Copy the output and use: <output>/dist/mcp-server.js
```
### 6. Available Tools
Once configured, Claude can automatically use:
- `telegram_notify` - Send notifications
- `telegram_ask` - Ask questions and wait for answers
- `telegram_get_messages` - Check for messages from you
- `telegram_reply` - Reply to your messages
- `telegram_check_health` - Check bridge status
### 7. Test It
Restart Claude Code, then tell Claude:
> "Send me a test notification via Telegram"
Claude will automatically discover and use the `telegram_notify` tool!
## MCP Tools Reference
Once configured, Claude can automatically use these tools:
### `telegram_notify`
Send a notification to you via Telegram.
**Parameters:**
- `message` (required): The notification text (supports Markdown)
- `priority` (optional): `info` | `success` | `warning` | `error` | `question`
**Example Claude Usage:**
> "I've completed the database migration. Let me notify you."
> *Claude uses: `telegram_notify({ message: "Database migration complete!", priority: "success" })`*
### `telegram_ask`
Ask you a question and wait for your answer (blocking).
**Parameters:**
- `question` (required): The question to ask (supports Markdown)
- `timeout` (optional): Milliseconds to wait (default: 300000 = 5 min)
**Example Claude Usage:**
> "Should I deploy to production? Let me ask you."
> *Claude uses: `telegram_ask({ question: "Deploy to production now?" })`*
> *Waits for your response via Telegram*
### `telegram_get_messages`
Check for unread messages from you.
**Example Claude Usage:**
> "Let me check if you've sent any messages."
> *Claude uses: `telegram_get_messages({})`*
### `telegram_reply`
Reply to your message via Telegram.
**Parameters:**
- `message` (required): Your reply (supports Markdown)
**Example Claude Usage:**
> "I'll respond to your question via Telegram."
> *Claude uses: `telegram_reply({ message: "The build succeeded!" })`*
### `telegram_check_health`
Check if the Telegram bridge is running and healthy.
**Example Claude Usage:**
> "Let me verify the Telegram bridge is working."
> *Claude uses: `telegram_check_health({})`*
## Git Setup (For Sharing)
If you want to push this to your own Git repository:
```bash
# Initialize git (if not already done)
git init
# Add all files (gitignore protects secrets)
git add .
# Commit
git commit -m "Initial commit: Telegram MCP server"
# Add your remote
git remote add origin https://github.com/yourusername/claude-telegram-bridge.git
# Push
git push -u origin main
```
**What's Safe to Share:**
- ✅ All source code
- ✅ `.env.example` (template)
- ✅ Documentation
- ✅ Configuration templates
**What's Protected (in .gitignore):**
- 🔒 `.env` (your bot token and secrets)
- 🔒 `node_modules/`
- 🔒 `dist/`
## For Others Cloning Your Repository
When someone clones your repo, they need to:
1. **Create their own Telegram bot** with @BotFather
2. **Copy the template:** `cp .env.example .env`
3. **Add their bot token** to `.env`
4. **Install and build:** `pnpm install && pnpm build`
5. **Follow the Quick Start guide** above
## Legacy HTTP API (For Direct Integration)
If you want to use the HTTP API directly (without MCP), you can:
```typescript
// Simple notification
await fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: 'Scraping complete! Found 500 skills.',
priority: 'success'
})
});
// Question with markdown
await fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: '*Question:*\nContinue scraping sets?\n\nReply: yes/no',
priority: 'question',
parseMode: 'Markdown'
})
});
```
## Priority Levels
- `info` - General information
- `success` - ✅ Task completed
- `warning` - ⚠️ Warning message
- `error` - ❌ Error occurred
- `question` - ❓ Needs your input
## Bot Commands
Type these in Telegram to control the bridge:
- `/start` - Initialize connection and save your chat ID
- `/help` - Show all available commands and how to use the bridge
- `/status` - Check bridge status (enabled, unread messages, pending questions)
- `/test` - Send a test notification to verify it's working
## How Two-Way Communication Works
### You → Claude
1. Send any message to the bot in Telegram
2. Bot acknowledges with "💬 Message received - responding..."
3. Claude checks messages and responds when available
4. You get the response in Telegram
### Claude → You (Notifications)
Claude sends you updates via the `/notify` API endpoint with different priorities
### Claude → You (Questions)
1. Claude sends a question via `/ask` API
2. You see "❓ [question]" in Telegram
3. Your next message is automatically treated as the answer
4. Claude receives your answer and continues
## Running as Background Service
```bash
# Build production version
pnpm build
# Start as daemon (requires pm2)
npm install -g pm2
pnpm daemon
# Check logs
pnpm logs
# Stop daemon
pnpm stop
```
## API Endpoints
### POST /notify
Send a notification to user
**Request:**
```json
{
"message": "Your notification text",
"priority": "info|success|warning|error|question",
"parseMode": "Markdown|HTML"
}
```
**Response:**
```json
{
"success": true,
"chatId": "7684777367"
}
```
### GET /messages
Get unread messages from user
**Response:**
```json
{
"messages": [
{
"from": "Richard",
"message": "What's the status?",
"timestamp": "2025-11-23T04:00:52.395Z",
"read": false
}
],
"count": 1
}
```
### POST /messages/read
Mark messages as read
**Request:**
```json
{
"count": 2 // optional, marks all if not provided
}
```
**Response:**
```json
{
"markedAsRead": 2
}
```
### POST /reply
Send a reply to user's message
**Request:**
```json
{
"message": "Here's my response to your question"
}
```
**Response:**
```json
{
"success": true
}
```
### POST /ask
Ask user a question and wait for answer (blocking)
**Request:**
```json
{
"question": "Should I continue scraping?",
"timeout": 300000 // optional, 5 min default
}
```
**Response:**
```json
{
"answer": "yes"
}
```
### GET /health
Check service health
**Response:**
```json
{
"status": "running",
"enabled": true,
"chatId": "set",
"unreadMessages": 0,
"pendingQuestions": 0
}
```
## Integration with ESO-MCP
Add this helper to your ESO-MCP project:
```typescript
// src/utils/notify.ts
export async function notify(message: string, priority = 'info') {
try {
await fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, priority })
});
} catch (error) {
console.log('Telegram bridge not available');
}
}
```
Then use anywhere:
```typescript
await notify('✅ Skills scraping complete!', 'success');
await notify('❌ Failed to scrape sets page', 'error');
```
## Environment Variables
```env
TELEGRAM_BOT_TOKEN=your_token_here
TELEGRAM_CHAT_ID=auto_detected
PORT=3456
HOST=localhost
ENABLED=true
```
## Contact
rdillman@gmail.com

284
USAGE.md Normal file
View File

@@ -0,0 +1,284 @@
# Telegram MCP Server - Usage Guide
## What You've Built
You now have a **standardized MCP (Model Context Protocol) server** that any Claude instance can use to communicate with you via Telegram!
## How It Works
### Architecture
```
┌─────────────────┐ ┌──────────────────┐ ┌─────────────┐
│ Claude Code │◄────►│ MCP Server │◄────►│ HTTP API │
│ (any instance) │ │ (stdio protocol)│ │ (:3456) │
└─────────────────┘ └──────────────────┘ └──────┬──────┘
┌───────────────┐
│ Telegram Bot │
│ (Telegraf) │
└───────┬───────┘
┌───────────────┐
│ Your Phone │
│ (Telegram) │
└───────────────┘
```
### Two Components
1. **HTTP Bridge** (`pnpm dev`) - Always running service that communicates with Telegram
2. **MCP Server** (auto-started by Claude) - Translates Claude's tool calls to HTTP requests
## Installation Steps
### 1. Build the Project
```bash
cd claude-telegram-bridge
pnpm install
pnpm build
```
### 2. Start the Bridge Service
```bash
# Option 1: Development mode (foreground)
pnpm dev
# Option 2: Production mode (background daemon)
pnpm daemon
# Check daemon logs
pnpm logs
# Stop daemon
pnpm stop
```
### 3. Configure Your Claude Code MCP Settings
**Location:** `~/.config/claude-code/settings/mcp.json` (or Claude Desktop settings)
```json
{
"mcpServers": {
"telegram": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/claude-telegram-bridge/dist/mcp-server.js"
],
"env": {
"TELEGRAM_BRIDGE_URL": "http://localhost:3456"
}
}
}
}
```
**Replace `/ABSOLUTE/PATH/TO/` with your actual installation path!**
To find your path:
```bash
cd claude-telegram-bridge
pwd
# Use the output + /dist/mcp-server.js
```
### 4. Initialize Your Telegram Bot
1. Open Telegram on your phone
2. Search for your bot (get username from BotFather)
3. Send `/start` to the bot
4. The bot will save your chat ID automatically
## Using the MCP Server
Once configured, Claude will have access to these tools automatically:
### Example 1: Simple Notification
**You say:**
> "When you finish installing the packages, send me a notification via Telegram"
**Claude will:**
```typescript
// Claude automatically uses:
telegram_notify({
message: "✅ All packages installed successfully!",
priority: "success"
})
```
**You receive in Telegram:**
> ✅ All packages installed successfully!
---
### Example 2: Ask for Approval
**You say:**
> "Run the database migration, but ask me for confirmation before applying it"
**Claude will:**
```typescript
// Step 1: Prepare migration
// ...
// Step 2: Ask for approval
const answer = await telegram_ask({
question: "Database migration ready. Apply now? (yes/no)"
})
// Step 3: Proceed based on your answer
if (answer.toLowerCase() === 'yes') {
// Apply migration
}
```
**You receive in Telegram:**
> ❓ Database migration ready. Apply now? (yes/no)
**You reply:**
> yes
**Claude receives your answer and continues**
---
### Example 3: Check for Messages
**You say:**
> "Check if I've sent you any messages via Telegram"
**Claude will:**
```typescript
// Claude uses:
telegram_get_messages({})
// Then reports back to you in the chat
```
---
### Example 4: Health Check
**You say:**
> "Is the Telegram bridge working?"
**Claude will:**
```typescript
// Claude uses:
telegram_check_health({})
// Returns status, unread messages, etc.
```
## Available Tools
| Tool | Purpose | Blocks? |
|------|---------|---------|
| `telegram_notify` | Send notification | No |
| `telegram_ask` | Ask question, wait for answer | Yes (5 min timeout) |
| `telegram_get_messages` | Get unread messages | No |
| `telegram_reply` | Reply to message | No |
| `telegram_check_health` | Check bridge status | No |
## Best Practices
### When Claude Should Use These Tools
1. **Long-running operations** - Notify when complete
2. **Critical decisions** - Ask for approval via `telegram_ask`
3. **Background tasks** - Update progress periodically
4. **Errors that need attention** - Send error notifications
5. **User input needed** - Ask questions when uncertain
### Example Workflow
```
User: "Scrape the ESO website and notify me when done"
Claude thinks:
1. Start scraping
2. Every 100 items → telegram_notify({ message: "Progress: 100 items scraped" })
3. If error → telegram_notify({ message: "Error occurred", priority: "error" })
4. When complete → telegram_notify({ message: "Scraping complete!", priority: "success" })
```
## Troubleshooting
### Bridge Not Running
```bash
curl http://localhost:3456/health
```
If no response:
```bash
pnpm dev # Start the bridge
```
### MCP Server Not Found
Check the path in your MCP config:
```bash
cd claude-telegram-bridge
ls dist/mcp-server.js
```
Update the path in `mcp.json` if needed.
### Chat ID Not Set
Message your bot with `/start` in Telegram.
### Testing the Tools Manually
Test the HTTP API directly:
```bash
# Test notification
curl -X POST http://localhost:3456/notify \
-H "Content-Type: application/json" \
-d '{"message": "Test!", "priority": "info"}'
# Check health
curl http://localhost:3456/health
```
## Sharing with Other Claude Instances
Now that this is an MCP server, you can grant access to:
- Claude Code (CLI)
- Claude Desktop
- Other MCP-compatible clients
Just add the same configuration to their MCP settings!
## Environment Variables
```env
# .env file
TELEGRAM_BOT_TOKEN=your_bot_token_here
TELEGRAM_CHAT_ID=auto_set_on_start
PORT=3456
HOST=localhost
ENABLED=true
TELEGRAM_BRIDGE_URL=http://localhost:3456 # For MCP server
```
## Security Notes
- The bridge runs on `localhost:3456` by default
- Only accessible from your machine
- Your Telegram bot token is in `.env` (keep it secret!)
- Chat ID is saved after you send `/start`
## Next Steps
1. Start the bridge: `pnpm dev`
2. Add to Claude Code MCP config
3. Restart Claude Code
4. Test: "Send me a test notification via Telegram"
5. Enjoy real-time communication with Claude!

14
mcp-config.json Normal file
View File

@@ -0,0 +1,14 @@
{
"mcpServers": {
"telegram": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/claude-telegram-bridge/dist/mcp-server.js"
],
"env": {
"TELEGRAM_BRIDGE_URL": "http://localhost:3456"
}
}
}
}

40
package.json Normal file
View File

@@ -0,0 +1,40 @@
{
"name": "claude-telegram-bridge",
"version": "1.0.0",
"description": "Standalone Telegram notification bridge for Claude Code and any other apps",
"main": "dist/index.js",
"type": "module",
"bin": {
"telegram-bridge-mcp": "dist/mcp-server.js"
},
"scripts": {
"build": "tsc",
"dev": "tsx src/index.ts",
"mcp": "tsx src/mcp-server.ts",
"start": "node dist/index.js",
"daemon": "pm2 start dist/index.js --name claude-telegram",
"stop": "pm2 stop claude-telegram",
"logs": "pm2 logs claude-telegram"
},
"keywords": [
"telegram",
"notifications",
"claude",
"bridge"
],
"author": "rdillman@gmail.com",
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.22.0",
"dotenv": "^16.4.7",
"express": "^4.21.2",
"node-fetch": "^3.3.2",
"telegraf": "^4.16.3"
},
"devDependencies": {
"@types/express": "^5.0.0",
"@types/node": "^22.10.2",
"tsx": "^4.19.2",
"typescript": "^5.7.2"
}
}

1617
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

284
src/index.ts Normal file
View File

@@ -0,0 +1,284 @@
import { Telegraf } from 'telegraf';
import express from 'express';
import dotenv from 'dotenv';
import fs from 'fs/promises';
import path from 'path';
dotenv.config();
const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN!);
const app = express();
const PORT = parseInt(process.env.PORT || '3456');
const HOST = process.env.HOST || 'localhost';
const ENABLED = process.env.ENABLED !== 'false';
let chatId: string | null = process.env.TELEGRAM_CHAT_ID || null;
const envPath = path.join(process.cwd(), '.env');
// Message queue for two-way communication
interface QueuedMessage {
from: string;
message: string;
timestamp: Date;
read: boolean;
}
const messageQueue: QueuedMessage[] = [];
const pendingQuestions = new Map<string, { resolve: (answer: string) => void; timeout: NodeJS.Timeout }>();
app.use(express.json());
// Save chat ID to .env file
async function saveChatId(id: string) {
try {
const envContent = await fs.readFile(envPath, 'utf-8');
const updated = envContent.replace(
/TELEGRAM_CHAT_ID=.*/,
`TELEGRAM_CHAT_ID=${id}`
);
await fs.writeFile(envPath, updated);
console.log(`✅ Chat ID saved: ${id}`);
} catch (error) {
console.error('Failed to save chat ID:', error);
}
}
// Bot commands
bot.start(async (ctx) => {
chatId = ctx.chat.id.toString();
await saveChatId(chatId);
await ctx.reply(
'🤖 *Claude Telegram Bridge Active*\n\n' +
'I will now forward notifications from Claude Code and other apps.\n\n' +
'*Commands:*\n' +
'/status - Check bridge status\n' +
'/enable - Enable notifications\n' +
'/disable - Disable notifications\n' +
'/test - Send test notification',
{ parse_mode: 'Markdown' }
);
});
bot.command('status', async (ctx) => {
const status = ENABLED ? '✅ Enabled' : '⛔ Disabled';
await ctx.reply(
`*Bridge Status*\n\n` +
`Status: ${status}\n` +
`Chat ID: ${chatId}\n` +
`HTTP Server: http://${HOST}:${PORT}`,
{ parse_mode: 'Markdown' }
);
});
bot.command('help', async (ctx) => {
await ctx.reply(
'*Claude Telegram Bridge - Commands*\n\n' +
'*Bot Commands:*\n' +
'`/start` - Initialize and connect\n' +
'`/help` - Show this help message\n' +
'`/status` - Check bridge status\n' +
'`/test` - Send test notification\n\n' +
'*How it works:*\n' +
'• Send me any message - I forward it to Claude\n' +
'• Claude processes it and replies back\n' +
'• When Claude asks a question, your next message answers it\n\n' +
'*Features:*\n' +
'✅ Two-way communication\n' +
'✅ Question/Answer flow\n' +
'✅ Progress notifications\n' +
'✅ Error alerts\n\n' +
'More info: See README in bridge folder',
{ parse_mode: 'Markdown' }
);
});
bot.command('test', async (ctx) => {
await ctx.reply('✅ Test notification received! Bridge is working.');
});
// Listen for any text messages from user
bot.on('text', async (ctx) => {
const message = ctx.message.text;
const from = ctx.from.username || ctx.from.first_name;
console.log(`\n📨 Message from ${from}: "${message}"\n`);
// Check if this is an answer to a pending question
const questionId = Array.from(pendingQuestions.keys())[0];
if (questionId && pendingQuestions.has(questionId)) {
const { resolve, timeout } = pendingQuestions.get(questionId)!;
clearTimeout(timeout);
pendingQuestions.delete(questionId);
resolve(message);
await ctx.reply('✅ Answer received!');
return;
}
// Add to message queue for processing
messageQueue.push({
from,
message,
timestamp: new Date(),
read: false
});
// Acknowledge receipt - Claude will respond when available
await ctx.reply('💬 Message received - responding...');
console.log('📥 Queued for Claude to process');
});
// HTTP endpoint for sending notifications
app.post('/notify', async (req, res) => {
if (!ENABLED) {
return res.status(503).json({ error: 'Bridge is disabled' });
}
if (!chatId) {
return res.status(400).json({
error: 'No chat ID set. Please message the bot first with /start'
});
}
const { message, priority = 'info', parseMode = 'Markdown' } = req.body;
if (!message) {
return res.status(400).json({ error: 'Message is required' });
}
try {
const emojiMap: Record<string, string> = {
info: '',
success: '✅',
warning: '⚠️',
error: '❌',
question: '❓'
};
const emoji = emojiMap[priority] || '';
await bot.telegram.sendMessage(
chatId,
`${emoji} ${message}`,
{ parse_mode: parseMode as any }
);
res.json({ success: true, chatId });
} catch (error: any) {
console.error('Failed to send message:', error);
res.status(500).json({ error: error.message });
}
});
// Get unread messages
app.get('/messages', (req, res) => {
const unread = messageQueue.filter(m => !m.read);
res.json({ messages: unread, count: unread.length });
});
// Mark messages as read
app.post('/messages/read', (req, res) => {
const { count } = req.body;
const toMark = count || messageQueue.filter(m => !m.read).length;
let marked = 0;
for (const msg of messageQueue) {
if (!msg.read && marked < toMark) {
msg.read = true;
marked++;
}
}
res.json({ markedAsRead: marked });
});
// Send reply to user message
app.post('/reply', async (req, res) => {
if (!chatId) {
return res.status(400).json({ error: 'No chat ID set' });
}
const { message } = req.body;
if (!message) {
return res.status(400).json({ error: 'Message is required' });
}
try {
await bot.telegram.sendMessage(chatId, message, { parse_mode: 'Markdown' });
res.json({ success: true });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Ask a question and wait for answer
app.post('/ask', async (req, res) => {
if (!chatId) {
return res.status(400).json({ error: 'No chat ID set' });
}
const { question, timeout = 300000 } = req.body; // 5 min default timeout
if (!question) {
return res.status(400).json({ error: 'Question is required' });
}
try {
const questionId = Date.now().toString();
// Send question to Telegram
await bot.telegram.sendMessage(chatId, `${question}`, { parse_mode: 'Markdown' });
// Wait for answer
const answer = await new Promise<string>((resolve, reject) => {
const timer = setTimeout(() => {
pendingQuestions.delete(questionId);
reject(new Error('Timeout waiting for answer'));
}, timeout);
pendingQuestions.set(questionId, { resolve, timeout: timer });
});
res.json({ answer });
} catch (error: any) {
res.status(500).json({ error: error.message });
}
});
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'running',
enabled: ENABLED,
chatId: chatId ? 'set' : 'not set',
unreadMessages: messageQueue.filter(m => !m.read).length,
pendingQuestions: pendingQuestions.size
});
});
// Start bot
bot.launch().then(() => {
console.log('🤖 Telegram bot started');
console.log('📱 Message your bot to get started');
});
// Start HTTP server
app.listen(PORT, HOST, () => {
console.log(`🌐 HTTP server running on http://${HOST}:${PORT}`);
console.log(`\n📋 Send notifications with:\n`);
console.log(`curl -X POST http://${HOST}:${PORT}/notify \\`);
console.log(` -H "Content-Type: application/json" \\`);
console.log(` -d '{"message": "Hello from Claude!", "priority": "info"}'`);
});
// Graceful shutdown
process.once('SIGINT', () => {
console.log('\n👋 Shutting down...');
bot.stop('SIGINT');
process.exit(0);
});
process.once('SIGTERM', () => {
bot.stop('SIGTERM');
process.exit(0);
});

265
src/mcp-server.ts Normal file
View File

@@ -0,0 +1,265 @@
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
Tool,
} from '@modelcontextprotocol/sdk/types.js';
const BRIDGE_URL = process.env.TELEGRAM_BRIDGE_URL || 'http://localhost:3456';
// Define the Telegram bridge tools
const TOOLS: Tool[] = [
{
name: 'telegram_notify',
description: 'Send a notification to the user via Telegram. Use this to keep the user informed about progress, completion, warnings, or errors.',
inputSchema: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'The notification message to send. Supports Markdown formatting.',
},
priority: {
type: 'string',
enum: ['info', 'success', 'warning', 'error', 'question'],
description: 'Priority level: info ( general), success (✅ completed), warning (⚠️ alert), error (❌ failure), question (❓ needs input)',
default: 'info',
},
},
required: ['message'],
},
},
{
name: 'telegram_ask',
description: 'Ask the user a question via Telegram and wait for their answer. This blocks until the user responds. Use for decisions that require user input.',
inputSchema: {
type: 'object',
properties: {
question: {
type: 'string',
description: 'The question to ask the user. Supports Markdown formatting.',
},
timeout: {
type: 'number',
description: 'Timeout in milliseconds (default: 300000 = 5 minutes)',
default: 300000,
},
},
required: ['question'],
},
},
{
name: 'telegram_get_messages',
description: 'Retrieve unread messages from the user. Use this to check if the user has sent any messages.',
inputSchema: {
type: 'object',
properties: {},
},
},
{
name: 'telegram_reply',
description: 'Send a reply to a user message via Telegram. Use after getting messages to respond to the user.',
inputSchema: {
type: 'object',
properties: {
message: {
type: 'string',
description: 'The reply message. Supports Markdown formatting.',
},
},
required: ['message'],
},
},
{
name: 'telegram_check_health',
description: 'Check the health and status of the Telegram bridge. Returns connection status, unread message count, and pending questions.',
inputSchema: {
type: 'object',
properties: {},
},
},
];
// Create the MCP server
const server = new Server(
{
name: 'telegram-bridge',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
// Handle tool listing
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: TOOLS,
};
});
// Handle tool execution
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'telegram_notify': {
const { message, priority = 'info' } = args as { message: string; priority?: string };
const response = await fetch(`${BRIDGE_URL}/notify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, priority }),
});
if (!response.ok) {
const error: any = await response.json();
throw new Error(error.error || 'Failed to send notification');
}
return {
content: [
{
type: 'text',
text: `✅ Notification sent successfully to Telegram (priority: ${priority})`,
},
],
};
}
case 'telegram_ask': {
const { question, timeout = 300000 } = args as { question: string; timeout?: number };
const response = await fetch(`${BRIDGE_URL}/ask`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question, timeout }),
});
if (!response.ok) {
const error: any = await response.json();
throw new Error(error.error || 'Failed to ask question');
}
const result: any = await response.json();
return {
content: [
{
type: 'text',
text: `User's answer: ${result.answer}`,
},
],
};
}
case 'telegram_get_messages': {
const response = await fetch(`${BRIDGE_URL}/messages`);
if (!response.ok) {
const error: any = await response.json();
throw new Error(error.error || 'Failed to get messages');
}
const result: any = await response.json();
const messages = result.messages.map((m: any) =>
`[${m.timestamp}] ${m.from}: ${m.message}`
).join('\n');
return {
content: [
{
type: 'text',
text: result.count > 0
? `📬 ${result.count} unread message(s):\n\n${messages}`
: '📭 No unread messages',
},
],
};
}
case 'telegram_reply': {
const { message } = args as { message: string };
const response = await fetch(`${BRIDGE_URL}/reply`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message }),
});
if (!response.ok) {
const error: any = await response.json();
throw new Error(error.error || 'Failed to send reply');
}
return {
content: [
{
type: 'text',
text: '✅ Reply sent successfully to Telegram',
},
],
};
}
case 'telegram_check_health': {
const response = await fetch(`${BRIDGE_URL}/health`);
if (!response.ok) {
throw new Error('Bridge is not responding');
}
const health: any = await response.json();
const statusText = [
`🏥 Telegram Bridge Health Check`,
``,
`Status: ${health.status}`,
`Enabled: ${health.enabled ? '✅' : '❌'}`,
`Chat ID: ${health.chatId}`,
`Unread Messages: ${health.unreadMessages}`,
`Pending Questions: ${health.pendingQuestions}`,
].join('\n');
return {
content: [
{
type: 'text',
text: statusText,
},
],
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
} catch (error: unknown) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return {
content: [
{
type: 'text',
text: `❌ Error: ${errorMessage}`,
},
],
isError: true,
};
}
});
// Start the server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('🚀 Telegram MCP server running on stdio');
}
main().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

11
test.js Normal file
View File

@@ -0,0 +1,11 @@
const message = process.argv[2] || 'Test notification';
const priority = process.argv[3] || 'info';
fetch('http://localhost:3456/notify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, priority })
})
.then(res => res.json())
.then(data => console.log('✅ Sent:', data))
.catch(err => console.error('❌ Error:', err.message));

17
tsconfig.json Normal file
View File

@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"lib": ["ES2022"],
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}