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

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);
});