Files
innervoice/src/mcp-server.ts
RichardDillman 6c8c9350a1 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>
2025-11-23 00:55:30 -05:00

266 lines
7.2 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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);
});