Add Telegram MCP tools and server components for enhanced functionality

- Introduced core components for Telegram MCP integration, including server management, API helpers, and tool action parsers.
- Implemented various tools for user and chat management, such as adding contacts, archiving chats, and sending messages.
- Created utility functions for handling Telegram-specific actions and responses, improving interaction with the Telegram API.
- Defined types and context for better type safety and clarity in tool implementations.

These changes establish a comprehensive framework for Telegram MCP interactions, enhancing the application's capabilities in managing Telegram functionalities.
This commit is contained in:
Steven Enamakel
2026-01-28 06:24:33 +05:30
parent 1d687a07f8
commit f5e54efa72
86 changed files with 2981 additions and 0 deletions
+29
View File
@@ -0,0 +1,29 @@
/**
* Telegram MCP Server
* Main entry point for Telegram MCP integration
*/
import type { Socket } from 'socket.io-client';
import { TelegramMCPServer } from './server';
let telegramMCPInstance: TelegramMCPServer | undefined;
export function initTelegramMCPServer(socket: Socket | null | undefined): TelegramMCPServer {
telegramMCPInstance = new TelegramMCPServer(socket);
console.log('[MCP] Telegram MCP server initialized');
return telegramMCPInstance;
}
export function getTelegramMCPServer(): TelegramMCPServer | undefined {
return telegramMCPInstance;
}
export function updateTelegramMCPServerSocket(socket: Socket | null | undefined): void {
if (telegramMCPInstance) {
telegramMCPInstance.updateSocket(socket);
console.log('[MCP] Telegram MCP server socket updated');
}
}
export { toHumanReadableAction } from './toolActionParser';
export type { TelegramMCPServer } from './server';
+205
View File
@@ -0,0 +1,205 @@
/**
* Telegram MCP Server
* Provides tools for interacting with Telegram via MCP protocol
*/
import type { Socket } from "socket.io-client";
import type { TelegramState } from "../../../store/telegram/types";
import type {
MCPServerConfig,
MCPTool,
MCPToolCall,
MCPToolResult,
} from "../types";
import { store } from "../../../store";
import { ErrorCategory, logAndFormatError } from "../errorHandler";
import { ValidationError } from "../validation";
import { SocketIOMCPTransportImpl } from "../transport";
import { mcpLog } from "../logger";
import type { TelegramMCPToolHandler } from "./types";
import * as tools from "./tools";
export class TelegramMCPServer {
private transport: SocketIOMCPTransportImpl;
private config: MCPServerConfig;
constructor(socket: Socket | null | undefined) {
this.transport = new SocketIOMCPTransportImpl(socket ?? undefined);
this.config = { name: "telegram-mcp", version: "1.0.0" };
this.setupHandlers();
}
updateSocket(socket: Socket | null | undefined): void {
this.transport.updateSocket(socket ?? undefined);
}
private setupHandlers(): void {
this.transport.on(
"toolCall",
(data: { requestId: string; toolCall: MCPToolCall }) => {
void this.handleToolCallRequest(data);
},
);
this.transport.on("listTools", (data: { requestId: string }) => {
const { requestId } = data;
try {
const toolsList = this.listTools();
this.transport.emit("listToolsResponse", {
requestId,
tools: toolsList,
});
} catch (error) {
console.error("[MCP] Failed to list tools", error);
this.transport.emit("listToolsResponse", { requestId, tools: [] });
}
});
}
private async handleToolCallRequest(data: {
requestId: string;
toolCall: MCPToolCall;
}): Promise<void> {
const { requestId, toolCall } = data;
try {
const result = await this.handleToolCall(toolCall);
this.transport.emit("toolResult", { requestId, result });
} catch (error) {
const errorResult = logAndFormatError(
"handleToolCall",
error instanceof Error ? error : new Error(String(error)),
);
this.transport.emit("toolResult", { requestId, result: errorResult });
}
}
private listTools(): MCPTool[] {
return Object.values(tools).filter((t): t is MCPTool => {
return (
t !== undefined &&
typeof t === "object" &&
"name" in t &&
"description" in t &&
"inputSchema" in t &&
typeof (t as MCPTool).name === "string" &&
typeof (t as MCPTool).description === "string"
);
});
}
private async handleToolCall(toolCall: MCPToolCall): Promise<MCPToolResult> {
const { name, arguments: args } = toolCall;
mcpLog(`Executing tool: ${name}`, args);
const toolHandler = this.findToolHandler(name);
if (!toolHandler) {
return {
content: [{ type: "text", text: `Tool '${name}' not found` }],
isError: true,
};
}
const telegramState: TelegramState = store.getState().telegram;
try {
return await toolHandler(args, {
telegramState,
transport: this.transport,
});
} catch (error) {
if (error instanceof ValidationError) {
return logAndFormatError(name, error, ErrorCategory.VALIDATION);
}
return logAndFormatError(
name,
error instanceof Error ? error : new Error(String(error)),
);
}
}
private findToolHandler(name: string): TelegramMCPToolHandler | undefined {
const toolMap: Record<string, TelegramMCPToolHandler> = {
get_chats: tools.getChats,
list_chats: tools.listChats,
get_chat: tools.getChat,
create_group: tools.createGroup,
invite_to_group: tools.inviteToGroup,
create_channel: tools.createChannel,
edit_chat_title: tools.editChatTitle,
delete_chat_photo: tools.deleteChatPhoto,
leave_chat: tools.leaveChat,
get_participants: tools.getParticipants,
get_admins: tools.getAdmins,
get_banned_users: tools.getBannedUsers,
promote_admin: tools.promoteAdmin,
demote_admin: tools.demoteAdmin,
ban_user: tools.banUser,
unban_user: tools.unbanUser,
get_invite_link: tools.getInviteLink,
export_chat_invite: tools.exportChatInvite,
import_chat_invite: tools.importChatInvite,
join_chat_by_link: tools.joinChatByLink,
subscribe_public_channel: tools.subscribePublicChannel,
get_messages: tools.getMessages,
list_messages: tools.listMessages,
list_topics: tools.listTopics,
send_message: tools.sendMessage,
reply_to_message: tools.replyToMessage,
edit_message: tools.editMessage,
delete_message: tools.deleteMessage,
forward_message: tools.forwardMessage,
pin_message: tools.pinMessage,
unpin_message: tools.unpinMessage,
mark_as_read: tools.markAsRead,
get_message_context: tools.getMessageContext,
get_history: tools.getHistory,
get_pinned_messages: tools.getPinnedMessages,
send_reaction: tools.sendReaction,
remove_reaction: tools.removeReaction,
get_message_reactions: tools.getMessageReactions,
list_contacts: tools.listContacts,
search_contacts: tools.searchContacts,
add_contact: tools.addContact,
delete_contact: tools.deleteContact,
block_user: tools.blockUser,
unblock_user: tools.unblockUser,
get_blocked_users: tools.getBlockedUsers,
get_me: tools.getMe,
update_profile: tools.updateProfile,
get_user_photos: tools.getUserPhotos,
get_user_status: tools.getUserStatus,
mute_chat: tools.muteChat,
unmute_chat: tools.unmuteChat,
archive_chat: tools.archiveChat,
unarchive_chat: tools.unarchiveChat,
get_privacy_settings: tools.getPrivacySettings,
set_privacy_settings: tools.setPrivacySettings,
list_inline_buttons: tools.listInlineButtons,
press_inline_button: tools.pressInlineButton,
save_draft: tools.saveDraft,
get_drafts: tools.getDrafts,
clear_draft: tools.clearDraft,
search_public_chats: tools.searchPublicChats,
search_messages: tools.searchMessages,
resolve_username: tools.resolveUsername,
get_media_info: tools.getMediaInfo,
get_recent_actions: tools.getRecentActions,
create_poll: tools.createPoll,
get_bot_info: tools.getBotInfo,
set_bot_commands: tools.setBotCommands,
set_profile_photo: tools.setProfilePhoto,
delete_profile_photo: tools.deleteProfilePhoto,
edit_chat_photo: tools.editChatPhoto,
get_sticker_sets: tools.getStickerSets,
get_gif_search: tools.getGifSearch,
get_contact_ids: tools.getContactIds,
import_contacts: tools.importContacts,
export_contacts: tools.exportContacts,
get_direct_chat_by_contact: tools.getDirectChatByContact,
get_contact_chats: tools.getContactChats,
get_last_interaction: tools.getLastInteraction,
};
return toolMap[name];
}
}
+187
View File
@@ -0,0 +1,187 @@
/**
* Telegram API helpers for MCP tools
* Uses mtprotoService + Redux telegram state (alphahuman)
*/
import { store } from '../../../store';
import { mtprotoService } from '../../../services/mtprotoService';
import {
selectOrderedChats,
selectChatMessages,
selectCurrentUser,
} from '../../../store/telegramSelectors';
import type { TelegramChat, TelegramUser, TelegramMessage } from '../../../store/telegram/types';
export interface FormattedEntity {
id: string;
name: string;
type: string;
username?: string;
phone?: string;
}
export interface FormattedMessage {
id: number | string;
date: string;
text: string;
from_id?: string;
has_media?: boolean;
media_type?: string;
}
function getTelegramState() {
return store.getState().telegram;
}
/**
* Get chat by ID or username
*/
export function getChatById(chatId: string | number): TelegramChat | undefined {
const state = getTelegramState();
const idStr = String(chatId);
const chat = state.chats[idStr];
if (chat) return chat;
if (typeof chatId === 'string' && (chatId.startsWith('@') || /^[a-zA-Z0-9_]+$/.test(chatId))) {
const username = chatId.startsWith('@') ? chatId : `@${chatId}`;
return Object.values(state.chats).find(
(c) => c.username && (c.username === username || c.username === username.slice(1)),
);
}
return undefined;
}
/**
* Get user by ID (current user only for now; no full user cache)
*/
export function getUserById(userId: string | number): TelegramUser | undefined {
const state = getTelegramState();
const current = state.currentUser;
if (!current) return undefined;
if (String(current.id) === String(userId)) return current;
return undefined;
}
/**
* Get messages from a chat (from store cache)
* @param offset - numeric offset for pagination (default 0)
*/
export async function getMessages(
chatId: string | number,
limit = 20,
offset = 0,
): Promise<TelegramMessage[] | undefined> {
const chat = getChatById(chatId);
if (!chat) return undefined;
const state = getTelegramState();
const order = state.messagesOrder[chat.id] ?? [];
const byId = state.messages[chat.id] ?? {};
const all = order.map((id) => byId[id]).filter(Boolean);
const list = all.slice(offset, offset + limit);
return list.length ? list : undefined;
}
/**
* Send a message to a chat
*/
export async function sendMessage(
chatId: string | number,
message: string,
replyToMessageId?: number,
): Promise<{ id: string } | undefined> {
const chat = getChatById(chatId);
if (!chat) return undefined;
const entity = chat.username ? `@${chat.username.replace('@', '')}` : chat.id;
if (replyToMessageId !== undefined) {
const client = mtprotoService.getClient();
await mtprotoService.withFloodWaitHandling(async () => {
await client.sendMessage(entity, {
message,
replyTo: replyToMessageId,
});
});
} else {
await mtprotoService.sendMessage(entity, message);
}
return { id: String(Date.now()) };
}
/**
* Get list of chats (from store)
*/
export async function getChats(limit = 20): Promise<TelegramChat[]> {
const state = store.getState();
const ordered = selectOrderedChats(state);
return ordered.slice(0, limit);
}
/**
* Search chats by query (filter by title/username from store)
*/
export async function searchChats(query: string): Promise<TelegramChat[]> {
const state = store.getState();
const ordered = selectOrderedChats(state);
const q = query.toLowerCase();
return ordered.filter((c) => {
const title = (c.title ?? '').toLowerCase();
const un = (c.username ?? '').toLowerCase();
return title.includes(q) || un.includes(q);
});
}
/**
* Get current user info
*/
export function getCurrentUser(): TelegramUser | undefined {
const state = store.getState();
return selectCurrentUser(state) ?? undefined;
}
/**
* Format entity (chat or user) for display
*/
export function formatEntity(entity: TelegramChat | TelegramUser): FormattedEntity {
if ('title' in entity) {
const chat = entity as TelegramChat;
const type = chat.type === 'channel' ? 'channel' : chat.type === 'supergroup' ? 'group' : chat.type;
return {
id: chat.id,
name: chat.title ?? 'Unknown',
type,
username: chat.username,
};
}
const user = entity as TelegramUser;
const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
return {
id: user.id,
name,
type: 'user',
username: user.username,
phone: user.phoneNumber,
};
}
/**
* Format message for display
*/
export function formatMessage(message: TelegramMessage): FormattedMessage {
const result: FormattedMessage = {
id: message.id,
date: new Date(message.date * 1000).toISOString(),
text: message.message ?? '',
};
if (message.fromId) result.from_id = message.fromId;
if (message.media?.type) {
result.has_media = true;
result.media_type = message.media.type;
}
return result;
}
+333
View File
@@ -0,0 +1,333 @@
/**
* Tool Action Parser - Converts tool inputs to human-readable descriptions
*/
type ToolArguments = Record<string, unknown>;
function formatId(id: string | number | undefined, prefix = ''): string {
if (!id) return '';
const str = String(id);
return prefix ? `${prefix} ${str}` : str;
}
function truncateText(text: string, maxLength = 50): string {
if (text.length <= maxLength) return text;
return `${text.substring(0, maxLength)}...`;
}
export function toHumanReadableAction(toolName: string, args: ToolArguments): string {
const parser = toolParsers[toolName];
if (parser) return parser(args);
return `Executing ${toolName} with provided parameters`;
}
const toolParsers: Record<string, (args: ToolArguments) => string> = {
send_message: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const message = truncateText((args.message as string) || '', 100);
return `Send message to ${chatId}: "${message}"`;
},
reply_to_message: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
const text = truncateText((args.text as string) || '', 100);
return `Reply to message ${messageId} in ${chatId}: "${text}"`;
},
edit_message: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
const newText = truncateText((args.new_text as string) || '', 100);
return `Edit message ${messageId} in ${chatId} to: "${newText}"`;
},
delete_message: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
return `Delete message ${messageId} from ${chatId}`;
},
forward_message: (args) => {
const fromChat = formatId(args.from_chat_id as string | number, 'chat');
const toChat = formatId(args.to_chat_id as string | number, 'chat');
const messageId = args.message_id;
return `Forward message ${messageId} from ${fromChat} to ${toChat}`;
},
pin_message: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
return `Pin message ${messageId} in ${chatId}`;
},
unpin_message: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
return `Unpin message ${messageId} in ${chatId}`;
},
mark_as_read: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Mark messages as read in ${chatId}`;
},
send_reaction: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
const reaction = (args.reaction as string) || '👍';
return `Add reaction ${reaction} to message ${messageId} in ${chatId}`;
},
remove_reaction: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
const reaction = (args.reaction as string) || '';
return `Remove reaction ${reaction} from message ${messageId} in ${chatId}`;
},
save_draft: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const text = truncateText((args.text as string) || '', 50);
return `Save draft in ${chatId}: "${text}"`;
},
clear_draft: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Clear draft in ${chatId}`;
},
list_chats: (args) => {
const chatType = (args.chat_type as string) || 'all';
const limit = (args.limit as number) || 20;
return `List ${limit} ${chatType} chats`;
},
get_chat: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Get information for ${chatId}`;
},
create_group: (args) => {
const title = (args.title as string) || 'Untitled Group';
const userCount = Array.isArray(args.user_ids) ? args.user_ids.length : 0;
return `Create group "${title}" with ${userCount} member${userCount !== 1 ? 's' : ''}`;
},
create_channel: (args) => {
const title = (args.title as string) || 'Untitled Channel';
const description = args.description
? `: ${truncateText((args.description as string) || '', 50)}`
: '';
return `Create channel "${title}"${description}`;
},
edit_chat_title: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const newTitle = (args.new_title as string) || '';
return `Change title of ${chatId} to "${newTitle}"`;
},
delete_chat_photo: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Remove photo from ${chatId}`;
},
edit_chat_photo: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Update photo for ${chatId}`;
},
leave_chat: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Leave ${chatId}`;
},
mute_chat: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const muteFor = args.mute_for ? ` for ${args.mute_for} seconds` : '';
return `Mute ${chatId}${muteFor}`;
},
unmute_chat: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Unmute ${chatId}`;
},
archive_chat: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Archive ${chatId}`;
},
unarchive_chat: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Unarchive ${chatId}`;
},
invite_to_group: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const userCount = Array.isArray(args.user_ids) ? args.user_ids.length : 0;
return `Invite ${userCount} user${userCount !== 1 ? 's' : ''} to ${chatId}`;
},
ban_user: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const userId = formatId(args.user_id as string | number, 'user');
return `Ban ${userId} from ${chatId}`;
},
unban_user: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const userId = formatId(args.user_id as string | number, 'user');
return `Unban ${userId} from ${chatId}`;
},
promote_admin: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const userId = formatId(args.user_id as string | number, 'user');
return `Promote ${userId} to admin in ${chatId}`;
},
demote_admin: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const userId = formatId(args.user_id as string | number, 'user');
return `Demote ${userId} from admin in ${chatId}`;
},
block_user: (args) => {
const userId = formatId(args.user_id as string | number, 'user');
return `Block ${userId}`;
},
unblock_user: (args) => {
const userId = formatId(args.user_id as string | number, 'user');
return `Unblock ${userId}`;
},
add_contact: (args) => {
const firstName = (args.first_name as string) || '';
const lastName = (args.last_name as string) || '';
const phone = (args.phone_number as string) || '';
const name = [firstName, lastName].filter(Boolean).join(' ') || phone;
return `Add contact: ${name}`;
},
delete_contact: (args) => {
const userId = formatId(args.user_id as string | number, 'user');
return `Delete contact ${userId}`;
},
list_contacts: (args) => {
const limit = (args.limit as number) || 20;
return `List ${limit} contacts`;
},
search_contacts: (args) => {
const query = truncateText((args.query as string) || '', 30);
return `Search contacts for "${query}"`;
},
search_messages: (args) => {
const chatId = args.chat_id ? formatId(args.chat_id as string | number, 'chat') : 'all chats';
const query = truncateText((args.query as string) || '', 30);
const limit = (args.limit as number) || 20;
return `Search for "${query}" in ${chatId} (limit: ${limit})`;
},
search_public_chats: (args) => {
const query = truncateText((args.query as string) || '', 30);
return `Search public chats for "${query}"`;
},
resolve_username: (args) => {
const username = (args.username as string) || '';
return `Resolve username @${username.replace('@', '')}`;
},
get_messages: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const limit = (args.limit as number) || 20;
return `Get ${limit} messages from ${chatId}`;
},
list_messages: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const limit = (args.limit as number) || 20;
return `List ${limit} messages from ${chatId}`;
},
get_history: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const limit = (args.limit as number) || 20;
return `Get message history from ${chatId} (${limit} messages)`;
},
get_pinned_messages: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Get pinned messages from ${chatId}`;
},
get_message_context: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
const limit = (args.limit as number) || 5;
return `Get context around message ${messageId} in ${chatId}${limit} messages)`;
},
list_topics: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `List topics in ${chatId}`;
},
get_participants: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const limit = (args.limit as number) || 100;
return `Get ${limit} participants from ${chatId}`;
},
get_admins: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Get administrators of ${chatId}`;
},
get_banned_users: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Get banned users from ${chatId}`;
},
get_blocked_users: () => 'Get list of blocked users',
get_invite_link: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Get invite link for ${chatId}`;
},
export_chat_invite: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Create invite link for ${chatId}`;
},
import_chat_invite: (args) => {
const inviteHash = args.invite_hash ? truncateText((args.invite_hash as string) || '', 20) : '';
return `Join chat via invite link: ${inviteHash}`;
},
join_chat_by_link: (args) => {
const inviteLink = args.invite_link ? truncateText((args.invite_link as string) || '', 30) : '';
return `Join chat via link: ${inviteLink}`;
},
subscribe_public_channel: (args) => {
const username = (args.username as string) || '';
return `Subscribe to public channel @${username.replace('@', '')}`;
},
update_profile: (args) => {
const updates: string[] = [];
if (args.first_name) updates.push(`first name: "${args.first_name as string}"`);
if (args.last_name) updates.push(`last name: "${args.last_name as string}"`);
if (args.bio) updates.push(`bio: "${truncateText((args.bio as string) || '', 30)}"`);
return `Update profile (${updates.join(', ')})`;
},
set_profile_photo: () => 'Set profile photo',
delete_profile_photo: () => 'Delete profile photo',
get_user_photos: (args) => {
const userId = formatId(args.user_id as string | number, 'user');
const limit = (args.limit as number) || 20;
return `Get ${limit} photos from ${userId}`;
},
get_user_status: (args) => {
const userId = formatId(args.user_id as string | number, 'user');
return `Get status of ${userId}`;
},
get_me: () => 'Get current user information',
list_inline_buttons: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
return `Get inline buttons from message ${messageId} in ${chatId}`;
},
press_inline_button: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
const buttonText = args.button_text ? ` "${truncateText((args.button_text as string) || '', 30)}"` : '';
return `Press inline button${buttonText} on message ${messageId} in ${chatId}`;
},
create_poll: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const question = truncateText((args.question as string) || '', 50);
const optionCount = Array.isArray(args.options) ? args.options.length : 0;
return `Create poll in ${chatId}: "${question}" with ${optionCount} options`;
},
get_bot_info: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
return `Get bot information from ${chatId}`;
},
set_bot_commands: (args) => {
const chatId = args.chat_id ? formatId(args.chat_id as string | number, 'chat') : 'all chats';
const commandCount = Array.isArray(args.commands) ? args.commands.length : 0;
return `Set ${commandCount} bot commands for ${chatId}`;
},
get_privacy_settings: () => 'Get privacy settings',
set_privacy_settings: (args) => {
const setting = (args.setting as string) || 'unknown';
const value = (args.value as string) || '';
return `Set privacy setting "${setting}" to ${value}`;
},
get_media_info: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const messageId = args.message_id;
return `Get media information from message ${messageId} in ${chatId}`;
},
get_recent_actions: (args) => {
const chatId = formatId(args.chat_id as string | number, 'chat');
const limit = (args.limit as number) || 20;
return `Get ${limit} recent admin actions from ${chatId}`;
},
};
+24
View File
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'add_contact',
description: 'Add a contact',
inputSchema: {
type: 'object',
properties: {
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
phone_number: { type: 'string', description: 'Phone number' },
},
required: ['phone_number'],
},
};
export async function addContact(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('add_contact');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'archive_chat',
description: 'Archive a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function archiveChat(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('archive_chat');
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'ban_user',
description: 'Ban user from chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
user_id: { type: 'string', description: 'User ID' },
},
required: ['chat_id', 'user_id'],
},
};
export async function banUser(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('ban_user');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'block_user',
description: 'Block a user',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function blockUser(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('block_user');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'clear_draft',
description: 'Clear draft in a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function clearDraft(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('clear_draft');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'create_channel',
description: 'Create a channel',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Channel title' },
description: { type: 'string', description: 'Channel description' },
},
required: ['title'],
},
};
export async function createChannel(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('create_channel');
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'create_group',
description: 'Create a group',
inputSchema: {
type: 'object',
properties: {
title: { type: 'string', description: 'Group title' },
user_ids: { type: 'array', description: 'User IDs to add' },
},
required: ['title'],
},
};
export async function createGroup(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('create_group');
}
+24
View File
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'create_poll',
description: 'Create a poll in a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
question: { type: 'string', description: 'Poll question' },
options: { type: 'array', description: 'Poll options' },
},
required: ['chat_id', 'question', 'options'],
},
};
export async function createPoll(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('create_poll');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'delete_chat_photo',
description: 'Delete chat photo',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function deleteChatPhoto(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('delete_chat_photo');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'delete_contact',
description: 'Delete a contact',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function deleteContact(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('delete_contact');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'delete_message',
description: 'Delete a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['chat_id', 'message_id'],
},
};
export async function deleteMessage(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('delete_message');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'delete_profile_photo',
description: 'Delete profile photo',
inputSchema: { type: 'object', properties: {} },
};
export async function deleteProfilePhoto(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('delete_profile_photo');
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'demote_admin',
description: 'Demote admin',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
user_id: { type: 'string', description: 'User ID' },
},
required: ['chat_id', 'user_id'],
},
};
export async function demoteAdmin(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('demote_admin');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'edit_chat_photo',
description: 'Edit chat photo',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function editChatPhoto(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('edit_chat_photo');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'edit_chat_title',
description: 'Edit chat title',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
new_title: { type: 'string', description: 'New title' },
},
required: ['chat_id', 'new_title'],
},
};
export async function editChatTitle(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('edit_chat_title');
}
+24
View File
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'edit_message',
description: 'Edit an existing message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
new_text: { type: 'string', description: 'New message text' },
},
required: ['chat_id', 'message_id', 'new_text'],
},
};
export async function editMessage(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('edit_message');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'export_chat_invite',
description: 'Create invite link for a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function exportChatInvite(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('export_chat_invite');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'export_contacts',
description: 'Export contacts',
inputSchema: { type: 'object', properties: {} },
};
export async function exportContacts(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('export_contacts');
}
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'forward_message',
description: 'Forward a message to another chat',
inputSchema: {
type: 'object',
properties: {
from_chat_id: { type: 'string', description: 'Source chat ID' },
to_chat_id: { type: 'string', description: 'Target chat ID' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['from_chat_id', 'to_chat_id', 'message_id'],
},
};
export async function forwardMessage(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('forward_message');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_admins',
description: 'Get admins of a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function getAdmins(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_admins');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_banned_users',
description: 'Get banned users in a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function getBannedUsers(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_banned_users');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_blocked_users',
description: 'Get list of blocked users',
inputSchema: { type: 'object', properties: {} },
};
export async function getBlockedUsers(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_blocked_users');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_bot_info',
description: 'Get bot information in a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function getBotInfo(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_bot_info');
}
+74
View File
@@ -0,0 +1,74 @@
/**
* Get Chat tool - Get detailed information about a specific chat
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatEntity, getChatById } from '../telegramApi';
import { validateId } from '../../validation';
export const tool: MCPTool = {
name: 'get_chat',
description: 'Get detailed information about a specific chat',
inputSchema: {
type: 'object',
properties: {
chat_id: {
type: 'string',
description: 'The ID or username of the chat',
},
},
required: ['chat_id'],
},
};
export async function getChat(
args: { chat_id: string | number },
context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chatId = validateId(args.chat_id, 'chat_id');
const chat = getChatById(chatId);
if (!chat) {
return {
content: [{ type: 'text', text: `Chat not found: ${chatId}` }],
isError: true,
};
}
const entity = formatEntity(chat);
const result: string[] = [];
result.push(`ID: ${entity.id}`);
result.push(`Title: ${entity.name}`);
result.push(`Type: ${entity.type}`);
if (entity.username) result.push(`Username: @${entity.username}`);
if ('participantsCount' in chat && chat.participantsCount) {
result.push(`Participants: ${chat.participantsCount}`);
}
if ('unreadCount' in chat) {
result.push(`Unread Messages: ${chat.unreadCount ?? 0}`);
}
const lastMsg = chat.lastMessage;
if (lastMsg) {
const from = lastMsg.fromName ?? lastMsg.fromId ?? 'Unknown';
const date = new Date(lastMsg.date * 1000).toISOString();
result.push(`Last Message: From ${from} at ${date}`);
result.push(`Message: ${lastMsg.message || '[Media/No text]'}`);
}
return {
content: [{ type: 'text', text: result.join('\n') }],
};
} catch (error) {
return logAndFormatError(
'get_chat',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CHAT,
);
}
}
+52
View File
@@ -0,0 +1,52 @@
/**
* Get Chats tool - Get a paginated list of chats
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatEntity, getChats as getChatsApi } from '../telegramApi';
export const tool: MCPTool = {
name: 'get_chats',
description: 'Get a paginated list of chats',
inputSchema: {
type: 'object',
properties: {
page: { type: 'number', description: 'Page number (1-indexed)', default: 1 },
page_size: { type: 'number', description: 'Number of chats per page', default: 20 },
},
},
};
export async function getChats(
args: { page?: number; page_size?: number },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const page = args.page ?? 1;
const pageSize = args.page_size ?? 20;
const start = (page - 1) * pageSize;
const chats = await getChatsApi(pageSize + start);
const paginatedChats = chats.slice(start, start + pageSize);
if (paginatedChats.length === 0) {
return { content: [{ type: 'text', text: 'Page out of range.' }] };
}
const lines = paginatedChats.map((chat) => {
const entity = formatEntity(chat);
return `Chat ID: ${entity.id}, Title: ${entity.name}`;
});
return { content: [{ type: 'text', text: lines.join('\n') }] };
} catch (error) {
return logAndFormatError(
'get_chats',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CHAT,
);
}
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_contact_chats',
description: 'Get chats with a contact',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function getContactChats(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_contact_chats');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_contact_ids',
description: 'Get contact IDs',
inputSchema: { type: 'object', properties: {} },
};
export async function getContactIds(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_contact_ids');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_direct_chat_by_contact',
description: 'Get direct chat by contact',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function getDirectChatByContact(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_direct_chat_by_contact');
}
+16
View File
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_drafts',
description: 'Get all drafts',
inputSchema: { type: 'object', properties: {} },
};
export async function getDrafts(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_drafts');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_gif_search',
description: 'Search GIFs',
inputSchema: {
type: 'object',
properties: { query: { type: 'string', description: 'Search query' } },
required: ['query'],
},
};
export async function getGifSearch(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_gif_search');
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_history',
description: 'Get message history from a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
limit: { type: 'number', description: 'Number of messages', default: 20 },
},
required: ['chat_id'],
},
};
export async function getHistory(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_history');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_invite_link',
description: 'Get invite link for a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function getInviteLink(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_invite_link');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_last_interaction',
description: 'Get last interaction with a user',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function getLastInteraction(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_last_interaction');
}
+50
View File
@@ -0,0 +1,50 @@
/**
* Get Me tool - Get your own user information
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatEntity, getCurrentUser } from '../telegramApi';
export const tool: MCPTool = {
name: 'get_me',
description: 'Get your own user information',
inputSchema: {
type: 'object',
properties: {},
},
};
export async function getMe(
_args: Record<string, never>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const user = getCurrentUser();
if (!user) {
return {
content: [{ type: 'text', text: 'User information not available' }],
isError: true,
};
}
const entity = formatEntity(user);
const result = {
id: entity.id,
name: entity.name,
type: entity.type,
...(entity.username && { username: entity.username }),
...(entity.phone && { phone: entity.phone }),
};
return {
content: [{ type: 'text', text: JSON.stringify(result, undefined, 2) }],
};
} catch (error) {
return logAndFormatError(
'get_me',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
}
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_media_info',
description: 'Get media info from a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['chat_id', 'message_id'],
},
};
export async function getMediaInfo(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_media_info');
}
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_message_context',
description: 'Get context around a specific message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
limit: { type: 'number', description: 'Number of messages before/after', default: 5 },
},
required: ['chat_id', 'message_id'],
},
};
export async function getMessageContext(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_message_context');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_message_reactions',
description: 'Get reactions on a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['chat_id', 'message_id'],
},
};
export async function getMessageReactions(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_message_reactions');
}
+69
View File
@@ -0,0 +1,69 @@
/**
* Get Messages tool - Get paginated messages from a specific chat
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatMessage, getChatById, getMessages as getMessagesApi } from '../telegramApi';
import { validateId } from '../../validation';
import type { TelegramMessage } from '../../../store/telegram/types';
export const tool: MCPTool = {
name: 'get_messages',
description: 'Get paginated messages from a specific chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'The ID or username of the chat' },
page: { type: 'number', description: 'Page number (1-indexed)', default: 1 },
page_size: { type: 'number', description: 'Number of messages per page', default: 20 },
},
required: ['chat_id'],
},
};
function senderDisplay(msg: TelegramMessage): string {
return msg.fromName ?? msg.fromId ?? 'Unknown';
}
export async function getMessages(
args: { chat_id: string | number; page?: number; page_size?: number },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chatId = validateId(args.chat_id, 'chat_id');
const page = args.page ?? 1;
const pageSize = args.page_size ?? 20;
const offset = (page - 1) * pageSize;
const chat = getChatById(chatId);
if (!chat) {
return {
content: [{ type: 'text', text: `Chat not found: ${chatId}` }],
isError: true,
};
}
const messages = await getMessagesApi(chatId, pageSize, offset);
if (!messages || messages.length === 0) {
return { content: [{ type: 'text', text: 'No messages found for this page.' }] };
}
const lines = messages.map((msg) => {
const formatted = formatMessage(msg);
const replyStr = msg.replyToMessageId ? ` | reply to ${msg.replyToMessageId}` : '';
const msgText = formatted.text || '[Media/No text]';
return `ID: ${formatted.id} | ${senderDisplay(msg)} | Date: ${formatted.date}${replyStr} | Message: ${msgText}`;
});
return { content: [{ type: 'text', text: lines.join('\n') }] };
} catch (error) {
return logAndFormatError(
'get_messages',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
}
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_participants',
description: 'Get participants in a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
limit: { type: 'number', description: 'Max participants', default: 100 },
},
required: ['chat_id'],
},
};
export async function getParticipants(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_participants');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_pinned_messages',
description: 'Get pinned messages from a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function getPinnedMessages(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_pinned_messages');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_privacy_settings',
description: 'Get privacy settings',
inputSchema: { type: 'object', properties: {} },
};
export async function getPrivacySettings(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_privacy_settings');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_recent_actions',
description: 'Get recent admin actions in a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
limit: { type: 'number', description: 'Max actions', default: 20 },
},
required: ['chat_id'],
},
};
export async function getRecentActions(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_recent_actions');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_sticker_sets',
description: 'Get sticker sets',
inputSchema: { type: 'object', properties: {} },
};
export async function getStickerSets(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_sticker_sets');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_user_photos',
description: 'Get photos of a user',
inputSchema: {
type: 'object',
properties: {
user_id: { type: 'string', description: 'User ID' },
limit: { type: 'number', description: 'Max photos', default: 20 },
},
required: ['user_id'],
},
};
export async function getUserPhotos(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_user_photos');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'get_user_status',
description: 'Get online status of a user',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function getUserStatus(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('get_user_status');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'import_chat_invite',
description: 'Join chat via invite hash',
inputSchema: {
type: 'object',
properties: { invite_hash: { type: 'string', description: 'Invite hash' } },
required: ['invite_hash'],
},
};
export async function importChatInvite(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('import_chat_invite');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'import_contacts',
description: 'Import contacts',
inputSchema: {
type: 'object',
properties: { contacts: { type: 'array', description: 'Contacts to import' } },
required: ['contacts'],
},
};
export async function importContacts(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('import_contacts');
}
+93
View File
@@ -0,0 +1,93 @@
/**
* MCP Tools for Telegram
* Each tool exports a 'tool' definition and a handler function
*/
export { getChats, tool as getChatsTool } from './getChats';
export { listChats, tool as listChatsTool } from './listChats';
export { getChat, tool as getChatTool } from './getChat';
export { createGroup, tool as createGroupTool } from './createGroup';
export { inviteToGroup, tool as inviteToGroupTool } from './inviteToGroup';
export { createChannel, tool as createChannelTool } from './createChannel';
export { editChatTitle, tool as editChatTitleTool } from './editChatTitle';
export { deleteChatPhoto, tool as deleteChatPhotoTool } from './deleteChatPhoto';
export { leaveChat, tool as leaveChatTool } from './leaveChat';
export { getParticipants, tool as getParticipantsTool } from './getParticipants';
export { getAdmins, tool as getAdminsTool } from './getAdmins';
export { getBannedUsers, tool as getBannedUsersTool } from './getBannedUsers';
export { promoteAdmin, tool as promoteAdminTool } from './promoteAdmin';
export { demoteAdmin, tool as demoteAdminTool } from './demoteAdmin';
export { banUser, tool as banUserTool } from './banUser';
export { unbanUser, tool as unbanUserTool } from './unbanUser';
export { getInviteLink, tool as getInviteLinkTool } from './getInviteLink';
export { exportChatInvite, tool as exportChatInviteTool } from './exportChatInvite';
export { importChatInvite, tool as importChatInviteTool } from './importChatInvite';
export { joinChatByLink, tool as joinChatByLinkTool } from './joinChatByLink';
export { subscribePublicChannel, tool as subscribePublicChannelTool } from './subscribePublicChannel';
export { muteChat, tool as muteChatTool } from './muteChat';
export { unmuteChat, tool as unmuteChatTool } from './unmuteChat';
export { archiveChat, tool as archiveChatTool } from './archiveChat';
export { unarchiveChat, tool as unarchiveChatTool } from './unarchiveChat';
export { getMessages, tool as getMessagesTool } from './getMessages';
export { listMessages, tool as listMessagesTool } from './listMessages';
export { listTopics, tool as listTopicsTool } from './listTopics';
export { sendMessage, tool as sendMessageTool } from './sendMessage';
export { replyToMessage, tool as replyToMessageTool } from './replyToMessage';
export { editMessage, tool as editMessageTool } from './editMessage';
export { deleteMessage, tool as deleteMessageTool } from './deleteMessage';
export { forwardMessage, tool as forwardMessageTool } from './forwardMessage';
export { pinMessage, tool as pinMessageTool } from './pinMessage';
export { unpinMessage, tool as unpinMessageTool } from './unpinMessage';
export { markAsRead, tool as markAsReadTool } from './markAsRead';
export { getMessageContext, tool as getMessageContextTool } from './getMessageContext';
export { getHistory, tool as getHistoryTool } from './getHistory';
export { getPinnedMessages, tool as getPinnedMessagesTool } from './getPinnedMessages';
export { sendReaction, tool as sendReactionTool } from './sendReaction';
export { removeReaction, tool as removeReactionTool } from './removeReaction';
export { getMessageReactions, tool as getMessageReactionsTool } from './getMessageReactions';
export { listInlineButtons, tool as listInlineButtonsTool } from './listInlineButtons';
export { pressInlineButton, tool as pressInlineButtonTool } from './pressInlineButton';
export { saveDraft, tool as saveDraftTool } from './saveDraft';
export { getDrafts, tool as getDraftsTool } from './getDrafts';
export { clearDraft, tool as clearDraftTool } from './clearDraft';
export { listContacts, tool as listContactsTool } from './listContacts';
export { searchContacts, tool as searchContactsTool } from './searchContacts';
export { addContact, tool as addContactTool } from './addContact';
export { deleteContact, tool as deleteContactTool } from './deleteContact';
export { blockUser, tool as blockUserTool } from './blockUser';
export { unblockUser, tool as unblockUserTool } from './unblockUser';
export { getBlockedUsers, tool as getBlockedUsersTool } from './getBlockedUsers';
export { getMe, tool as getMeTool } from './getMe';
export { updateProfile, tool as updateProfileTool } from './updateProfile';
export { getUserPhotos, tool as getUserPhotosTool } from './getUserPhotos';
export { getUserStatus, tool as getUserStatusTool } from './getUserStatus';
export { searchPublicChats, tool as searchPublicChatsTool } from './searchPublicChats';
export { searchMessages, tool as searchMessagesTool } from './searchMessages';
export { resolveUsername, tool as resolveUsernameTool } from './resolveUsername';
export { getMediaInfo, tool as getMediaInfoTool } from './getMediaInfo';
export { getRecentActions, tool as getRecentActionsTool } from './getRecentActions';
export { createPoll, tool as createPollTool } from './createPoll';
export { getBotInfo, tool as getBotInfoTool } from './getBotInfo';
export { setBotCommands, tool as setBotCommandsTool } from './setBotCommands';
export { getPrivacySettings, tool as getPrivacySettingsTool } from './getPrivacySettings';
export { setPrivacySettings, tool as setPrivacySettingsTool } from './setPrivacySettings';
export { setProfilePhoto, tool as setProfilePhotoTool } from './setProfilePhoto';
export { deleteProfilePhoto, tool as deleteProfilePhotoTool } from './deleteProfilePhoto';
export { editChatPhoto, tool as editChatPhotoTool } from './editChatPhoto';
export { getStickerSets, tool as getStickerSetsTool } from './getStickerSets';
export { getGifSearch, tool as getGifSearchTool } from './getGifSearch';
export { getContactIds, tool as getContactIdsTool } from './getContactIds';
export { importContacts, tool as importContactsTool } from './importContacts';
export { exportContacts, tool as exportContactsTool } from './exportContacts';
export { getDirectChatByContact, tool as getDirectChatByContactTool } from './getDirectChatByContact';
export { getContactChats, tool as getContactChatsTool } from './getContactChats';
export { getLastInteraction, tool as getLastInteractionTool } from './getLastInteraction';
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'invite_to_group',
description: 'Invite users to a group',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
user_ids: { type: 'array', description: 'User IDs to invite' },
},
required: ['chat_id', 'user_ids'],
},
};
export async function inviteToGroup(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('invite_to_group');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'join_chat_by_link',
description: 'Join chat via invite link',
inputSchema: {
type: 'object',
properties: { invite_link: { type: 'string', description: 'Invite link URL' } },
required: ['invite_link'],
},
};
export async function joinChatByLink(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('join_chat_by_link');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'leave_chat',
description: 'Leave a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function leaveChat(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('leave_chat');
}
+66
View File
@@ -0,0 +1,66 @@
/**
* List Chats tool - List available chats with metadata
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatEntity, getChats as getChatsApi } from '../telegramApi';
import { toHumanReadableAction } from '../toolActionParser';
export const tool: MCPTool = {
name: 'list_chats',
description: 'List available chats with metadata',
inputSchema: {
type: 'object',
properties: {
chat_type: {
type: 'string',
enum: ['user', 'group', 'channel'],
description: "Filter by chat type ('user', 'group', 'channel', or omit for all)",
},
limit: { type: 'number', description: 'Maximum number of chats to retrieve', default: 20 },
},
},
toHumanReadableAction: (args) => toHumanReadableAction('list_chats', args),
};
export async function listChats(
args: { chat_type?: string; limit?: number },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const limit = args.limit ?? 20;
const chatType = args.chat_type?.toLowerCase();
const chats = await getChatsApi(limit);
const contentItems: Array<{ type: 'text'; text: string }> = [];
for (const chat of chats) {
const entity = formatEntity(chat);
if (chatType && entity.type !== chatType) continue;
let chatInfo = `Chat ID: ${entity.id}, Title: ${entity.name}, Type: ${entity.type}`;
if (entity.username) chatInfo += `, Username: @${entity.username}`;
chatInfo +=
'unreadCount' in chat && chat.unreadCount
? `, Unread: ${chat.unreadCount}`
: ', No unread messages';
contentItems.push({ type: 'text', text: chatInfo });
}
if (contentItems.length === 0) {
return { content: [{ type: 'text', text: 'No chats found matching the criteria.' }] };
}
return { content: contentItems };
} catch (error) {
return logAndFormatError(
'list_chats',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CHAT,
);
}
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'list_contacts',
description: 'List all contacts in your Telegram account',
inputSchema: { type: 'object', properties: {} },
};
export async function listContacts(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('list_contacts');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'list_inline_buttons',
description: 'List inline buttons on a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['chat_id', 'message_id'],
},
};
export async function listInlineButtons(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('list_inline_buttons');
}
@@ -0,0 +1,90 @@
/**
* List Messages tool - Retrieve messages with optional filters
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatMessage, getChatById, getMessages as getMessagesApi } from '../telegramApi';
import { validateId } from '../../validation';
export const tool: MCPTool = {
name: 'list_messages',
description: 'Retrieve messages with optional filters',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'The ID or username of the chat to get messages from' },
limit: { type: 'number', description: 'Maximum number of messages to retrieve', default: 20 },
search_query: { type: 'string', description: 'Filter messages containing this text' },
from_date: { type: 'string', description: 'Filter messages from this date (YYYY-MM-DD)' },
to_date: { type: 'string', description: 'Filter messages until this date (YYYY-MM-DD)' },
},
required: ['chat_id'],
},
};
export async function listMessages(
args: {
chat_id: string | number;
limit?: number;
search_query?: string;
from_date?: string;
to_date?: string;
},
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chatId = validateId(args.chat_id, 'chat_id');
const limit = args.limit ?? 20;
const chat = getChatById(chatId);
if (!chat) {
return {
content: [{ type: 'text', text: `Chat not found: ${chatId}` }],
isError: true,
};
}
let messages = await getMessagesApi(chatId, limit * 2, 0);
if (!messages || messages.length === 0) {
return { content: [{ type: 'text', text: 'No messages found matching the criteria.' }] };
}
if (args.search_query) {
const q = args.search_query.toLowerCase();
messages = messages.filter((m) => (m.message ?? '').toLowerCase().includes(q));
}
if (args.from_date || args.to_date) {
messages = messages.filter((m) => {
const d = new Date(m.date * 1000);
if (args.from_date && d < new Date(args.from_date)) return false;
if (args.to_date) {
const to = new Date(args.to_date);
to.setHours(23, 59, 59, 999);
if (d > to) return false;
}
return true;
});
}
const sliced = messages.slice(0, limit);
const contentItems = sliced.map((msg) => {
const formatted = formatMessage(msg);
const from = msg.fromName ?? msg.fromId ?? 'Unknown';
const replyStr = msg.replyToMessageId ? ` | reply to ${msg.replyToMessageId}` : '';
const text = `ID: ${formatted.id} | ${from} | Date: ${formatted.date}${replyStr} | Message: ${formatted.text || '[Media/No text]'}`;
return { type: 'text' as const, text };
});
return { content: contentItems };
} catch (error) {
return logAndFormatError(
'list_messages',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
}
}
+16
View File
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'list_topics',
description: 'List topics in a forum chat',
inputSchema: { type: 'object', properties: { chat_id: { type: 'string', description: 'Chat ID or username' } }, required: ['chat_id'] },
};
export async function listTopics(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('list_topics');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'mark_as_read',
description: 'Mark messages as read in a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function markAsRead(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('mark_as_read');
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'mute_chat',
description: 'Mute a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
mute_for: { type: 'number', description: 'Mute duration in seconds' },
},
required: ['chat_id'],
},
};
export async function muteChat(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('mute_chat');
}
@@ -0,0 +1,8 @@
import type { MCPToolResult } from '../../types';
export function notImplemented(name: string): MCPToolResult {
return {
content: [{ type: 'text', text: `${name} is not implemented yet.` }],
isError: true,
};
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'pin_message',
description: 'Pin a message in a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['chat_id', 'message_id'],
},
};
export async function pinMessage(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('pin_message');
}
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'press_inline_button',
description: 'Press an inline button on a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
button_text: { type: 'string', description: 'Button text or data' },
},
required: ['chat_id', 'message_id'],
},
};
export async function pressInlineButton(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('press_inline_button');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'promote_admin',
description: 'Promote user to admin',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
user_id: { type: 'string', description: 'User ID' },
},
required: ['chat_id', 'user_id'],
},
};
export async function promoteAdmin(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('promote_admin');
}
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'remove_reaction',
description: 'Remove a reaction from a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
reaction: { type: 'string', description: 'Reaction to remove' },
},
required: ['chat_id', 'message_id'],
},
};
export async function removeReaction(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('remove_reaction');
}
@@ -0,0 +1,76 @@
/**
* Reply To Message tool - Reply to a specific message in a chat
*/
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
import { ErrorCategory, logAndFormatError } from "../../errorHandler";
import { getChatById, sendMessage } from "../telegramApi";
import { toHumanReadableAction } from "../toolActionParser";
import { validateId } from "../../validation";
export const tool: MCPTool = {
name: "reply_to_message",
description: "Reply to a specific message in a chat",
inputSchema: {
type: "object",
properties: {
chat_id: {
type: "string",
description: "The ID or username of the chat",
},
message_id: { type: "number", description: "The message ID to reply to" },
text: { type: "string", description: "The reply message text" },
},
required: ["chat_id", "message_id", "text"],
},
toHumanReadableAction: (args) =>
toHumanReadableAction("reply_to_message", args),
};
export async function replyToMessage(
args: { chat_id: string | number; message_id: number; text: string },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chatId = validateId(args.chat_id, "chat_id");
const { message_id, text } = args;
const chat = getChatById(chatId);
if (!chat) {
return {
content: [{ type: "text", text: `Chat not found: ${chatId}` }],
isError: true,
};
}
const result = await sendMessage(chatId, text, message_id);
if (!result) {
return {
content: [
{
type: "text",
text: `Failed to reply to message ${message_id} in chat ${chatId}.`,
},
],
isError: true,
};
}
return {
content: [
{
type: "text",
text: `Replied to message ${message_id} in chat ${chatId}.`,
},
],
};
} catch (error) {
return logAndFormatError(
"reply_to_message",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
}
}
@@ -0,0 +1,53 @@
/**
* Resolve Username tool - Resolve a username to a user or chat ID
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatEntity, getChatById } from '../telegramApi';
export const tool: MCPTool = {
name: 'resolve_username',
description: 'Resolve a username to a user or chat ID',
inputSchema: {
type: 'object',
properties: {
username: { type: 'string', description: 'Username to resolve (without @)' },
},
required: ['username'],
},
};
export async function resolveUsername(
args: { username: string },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const raw = args.username;
const username = raw.startsWith('@') ? raw : `@${raw}`;
const chat = getChatById(username);
if (!chat) {
return {
content: [{ type: 'text', text: `Username ${username} not found` }],
isError: true,
};
}
const entity = formatEntity(chat);
return {
content: [
{
type: 'text',
text: JSON.stringify({ id: entity.id, name: entity.name, type: entity.type, username: entity.username }, undefined, 2),
},
],
};
} catch (error) {
return logAndFormatError(
'resolve_username',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.SEARCH,
);
}
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'save_draft',
description: 'Save a draft in a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
text: { type: 'string', description: 'Draft text' },
},
required: ['chat_id', 'text'],
},
};
export async function saveDraft(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('save_draft');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'search_contacts',
description: 'Search contacts by name or phone',
inputSchema: {
type: 'object',
properties: { query: { type: 'string', description: 'Search query' } },
required: ['query'],
},
};
export async function searchContacts(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('search_contacts');
}
@@ -0,0 +1,63 @@
/**
* Search Messages tool - Search for messages in a chat by text
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatMessage, getChatById, getMessages } from '../telegramApi';
import { validateId } from '../../validation';
export const tool: MCPTool = {
name: 'search_messages',
description: 'Search for messages in a chat by text',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'The chat ID or username' },
query: { type: 'string', description: 'Search query' },
limit: { type: 'number', description: 'Maximum number of messages to return', default: 20 },
},
required: ['chat_id', 'query'],
},
};
export async function searchMessages(
args: { chat_id: string | number; query: string; limit?: number },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chatId = validateId(args.chat_id, 'chat_id');
const { query, limit = 20 } = args;
const chat = getChatById(chatId);
if (!chat) {
return {
content: [{ type: 'text', text: `Chat ${chatId} not found` }],
isError: true,
};
}
const messages = await getMessages(chatId, Math.min(limit * 3, 100), 0);
if (!messages || messages.length === 0) {
return { content: [{ type: 'text', text: 'No messages found.' }] };
}
const q = query.toLowerCase();
const filtered = messages.filter((m) => (m.message ?? '').toLowerCase().includes(q)).slice(0, limit);
const lines = filtered.map((m) => {
const f = formatMessage(m);
return `ID: ${f.id} | ${m.fromName ?? m.fromId ?? 'Unknown'} | ${f.date} | ${f.text || '[Media]'}`;
});
return { content: [{ type: 'text', text: lines.join('\n') }] };
} catch (error) {
return logAndFormatError(
'search_messages',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.SEARCH,
);
}
}
@@ -0,0 +1,40 @@
/**
* Search Public Chats tool - Search for public chats, channels, or bots
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { formatEntity, searchChats } from '../telegramApi';
export const tool: MCPTool = {
name: 'search_public_chats',
description: 'Search for public chats, channels, or bots by username or title',
inputSchema: {
type: 'object',
properties: {
query: { type: 'string', description: 'Search query' },
},
required: ['query'],
},
};
export async function searchPublicChats(
args: { query: string },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chats = await searchChats(args.query);
const results = chats.map(formatEntity);
return {
content: [{ type: 'text', text: JSON.stringify(results, undefined, 2) }],
};
} catch (error) {
return logAndFormatError(
'search_public_chats',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.SEARCH,
);
}
}
+55
View File
@@ -0,0 +1,55 @@
/**
* Send Message tool - Send a message to a specific chat
*/
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { ErrorCategory, logAndFormatError } from '../../errorHandler';
import { sendMessage as sendMessageApi } from '../telegramApi';
import { toHumanReadableAction } from '../toolActionParser';
import { validateId } from '../../validation';
export const tool: MCPTool = {
name: 'send_message',
description: 'Send a message to a specific chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'The ID or username of the chat' },
message: { type: 'string', description: 'The message content to send' },
},
required: ['chat_id', 'message'],
},
toHumanReadableAction: (args) => toHumanReadableAction('send_message', args),
};
export async function sendMessage(
args: { chat_id: string | number; message: string },
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
try {
const chatId = validateId(args.chat_id, 'chat_id');
const { message } = args;
if (!message || typeof message !== 'string') {
return {
content: [{ type: 'text', text: 'Message content is required' }],
isError: true,
};
}
const result = await sendMessageApi(chatId, message);
if (!result) {
return {
content: [{ type: 'text', text: `Failed to send message to chat ${chatId}` }],
isError: true,
};
}
return { content: [{ type: 'text', text: 'Message sent successfully.' }] };
} catch (error) {
return logAndFormatError(
'send_message',
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
}
}
@@ -0,0 +1,24 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'send_reaction',
description: 'Send a reaction to a message',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
reaction: { type: 'string', description: 'Reaction emoji' },
},
required: ['chat_id', 'message_id'],
},
};
export async function sendReaction(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('send_reaction');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'set_bot_commands',
description: 'Set bot commands',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
commands: { type: 'array', description: 'List of commands' },
},
required: ['commands'],
},
};
export async function setBotCommands(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('set_bot_commands');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'set_privacy_settings',
description: 'Set privacy settings',
inputSchema: {
type: 'object',
properties: {
setting: { type: 'string', description: 'Setting name' },
value: { type: 'string', description: 'Value' },
},
required: ['setting', 'value'],
},
};
export async function setPrivacySettings(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('set_privacy_settings');
}
@@ -0,0 +1,16 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'set_profile_photo',
description: 'Set profile photo',
inputSchema: { type: 'object', properties: {} },
};
export async function setProfilePhoto(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('set_profile_photo');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'subscribe_public_channel',
description: 'Subscribe to public channel by username',
inputSchema: {
type: 'object',
properties: { username: { type: 'string', description: 'Channel username' } },
required: ['username'],
},
};
export async function subscribePublicChannel(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('subscribe_public_channel');
}
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'unarchive_chat',
description: 'Unarchive a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function unarchiveChat(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('unarchive_chat');
}
+23
View File
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'unban_user',
description: 'Unban user from chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
user_id: { type: 'string', description: 'User ID' },
},
required: ['chat_id', 'user_id'],
},
};
export async function unbanUser(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('unban_user');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'unblock_user',
description: 'Unblock a user',
inputSchema: {
type: 'object',
properties: { user_id: { type: 'string', description: 'User ID' } },
required: ['user_id'],
},
};
export async function unblockUser(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('unblock_user');
}
+20
View File
@@ -0,0 +1,20 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'unmute_chat',
description: 'Unmute a chat',
inputSchema: {
type: 'object',
properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
required: ['chat_id'],
},
};
export async function unmuteChat(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('unmute_chat');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'unpin_message',
description: 'Unpin a message in a chat',
inputSchema: {
type: 'object',
properties: {
chat_id: { type: 'string', description: 'Chat ID or username' },
message_id: { type: 'number', description: 'Message ID' },
},
required: ['chat_id', 'message_id'],
},
};
export async function unpinMessage(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('unpin_message');
}
@@ -0,0 +1,23 @@
import type { MCPTool, MCPToolResult } from '../../types';
import type { TelegramMCPContext } from '../types';
import { notImplemented } from './notImplemented';
export const tool: MCPTool = {
name: 'update_profile',
description: 'Update your profile',
inputSchema: {
type: 'object',
properties: {
first_name: { type: 'string', description: 'First name' },
last_name: { type: 'string', description: 'Last name' },
bio: { type: 'string', description: 'Bio' },
},
},
};
export async function updateProfile(
_args: Record<string, unknown>,
_context: TelegramMCPContext,
): Promise<MCPToolResult> {
return notImplemented('update_profile');
}
+17
View File
@@ -0,0 +1,17 @@
/**
* Telegram MCP server types
*/
import type { SocketIOMCPTransportImpl } from '../transport';
import type { MCPToolResult } from '../../mcp/types';
import type { TelegramState } from '../../../store/telegram/types';
export interface TelegramMCPContext {
telegramState: TelegramState;
transport: SocketIOMCPTransportImpl;
}
export type TelegramMCPToolHandler = (
args: Record<string, unknown>,
context: TelegramMCPContext,
) => Promise<MCPToolResult>;