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>
266 lines
7.2 KiB
JavaScript
266 lines
7.2 KiB
JavaScript
#!/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);
|
||
});
|