From f5e54efa72cf01ad1d447b88b2b78df2f2842ec2 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Wed, 28 Jan 2026 06:24:33 +0530 Subject: [PATCH] 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. --- src/lib/mcp/telegram/index.ts | 29 ++ src/lib/mcp/telegram/server.ts | 205 +++++++++++ src/lib/mcp/telegram/telegramApi.ts | 187 ++++++++++ src/lib/mcp/telegram/toolActionParser.ts | 333 ++++++++++++++++++ src/lib/mcp/telegram/tools/addContact.ts | 24 ++ src/lib/mcp/telegram/tools/archiveChat.ts | 20 ++ src/lib/mcp/telegram/tools/banUser.ts | 23 ++ src/lib/mcp/telegram/tools/blockUser.ts | 20 ++ src/lib/mcp/telegram/tools/clearDraft.ts | 20 ++ src/lib/mcp/telegram/tools/createChannel.ts | 23 ++ src/lib/mcp/telegram/tools/createGroup.ts | 23 ++ src/lib/mcp/telegram/tools/createPoll.ts | 24 ++ src/lib/mcp/telegram/tools/deleteChatPhoto.ts | 20 ++ src/lib/mcp/telegram/tools/deleteContact.ts | 20 ++ src/lib/mcp/telegram/tools/deleteMessage.ts | 23 ++ .../mcp/telegram/tools/deleteProfilePhoto.ts | 16 + src/lib/mcp/telegram/tools/demoteAdmin.ts | 23 ++ src/lib/mcp/telegram/tools/editChatPhoto.ts | 20 ++ src/lib/mcp/telegram/tools/editChatTitle.ts | 23 ++ src/lib/mcp/telegram/tools/editMessage.ts | 24 ++ .../mcp/telegram/tools/exportChatInvite.ts | 20 ++ src/lib/mcp/telegram/tools/exportContacts.ts | 16 + src/lib/mcp/telegram/tools/forwardMessage.ts | 24 ++ src/lib/mcp/telegram/tools/getAdmins.ts | 20 ++ src/lib/mcp/telegram/tools/getBannedUsers.ts | 20 ++ src/lib/mcp/telegram/tools/getBlockedUsers.ts | 16 + src/lib/mcp/telegram/tools/getBotInfo.ts | 20 ++ src/lib/mcp/telegram/tools/getChat.ts | 74 ++++ src/lib/mcp/telegram/tools/getChats.ts | 52 +++ src/lib/mcp/telegram/tools/getContactChats.ts | 20 ++ src/lib/mcp/telegram/tools/getContactIds.ts | 16 + .../telegram/tools/getDirectChatByContact.ts | 20 ++ src/lib/mcp/telegram/tools/getDrafts.ts | 16 + src/lib/mcp/telegram/tools/getGifSearch.ts | 20 ++ src/lib/mcp/telegram/tools/getHistory.ts | 23 ++ src/lib/mcp/telegram/tools/getInviteLink.ts | 20 ++ .../mcp/telegram/tools/getLastInteraction.ts | 20 ++ src/lib/mcp/telegram/tools/getMe.ts | 50 +++ src/lib/mcp/telegram/tools/getMediaInfo.ts | 23 ++ .../mcp/telegram/tools/getMessageContext.ts | 24 ++ .../mcp/telegram/tools/getMessageReactions.ts | 23 ++ src/lib/mcp/telegram/tools/getMessages.ts | 69 ++++ src/lib/mcp/telegram/tools/getParticipants.ts | 23 ++ .../mcp/telegram/tools/getPinnedMessages.ts | 20 ++ .../mcp/telegram/tools/getPrivacySettings.ts | 16 + .../mcp/telegram/tools/getRecentActions.ts | 23 ++ src/lib/mcp/telegram/tools/getStickerSets.ts | 16 + src/lib/mcp/telegram/tools/getUserPhotos.ts | 23 ++ src/lib/mcp/telegram/tools/getUserStatus.ts | 20 ++ .../mcp/telegram/tools/importChatInvite.ts | 20 ++ src/lib/mcp/telegram/tools/importContacts.ts | 20 ++ src/lib/mcp/telegram/tools/index.ts | 93 +++++ src/lib/mcp/telegram/tools/inviteToGroup.ts | 23 ++ src/lib/mcp/telegram/tools/joinChatByLink.ts | 20 ++ src/lib/mcp/telegram/tools/leaveChat.ts | 20 ++ src/lib/mcp/telegram/tools/listChats.ts | 66 ++++ src/lib/mcp/telegram/tools/listContacts.ts | 16 + .../mcp/telegram/tools/listInlineButtons.ts | 23 ++ src/lib/mcp/telegram/tools/listMessages.ts | 90 +++++ src/lib/mcp/telegram/tools/listTopics.ts | 16 + src/lib/mcp/telegram/tools/markAsRead.ts | 20 ++ src/lib/mcp/telegram/tools/muteChat.ts | 23 ++ src/lib/mcp/telegram/tools/notImplemented.ts | 8 + src/lib/mcp/telegram/tools/pinMessage.ts | 23 ++ .../mcp/telegram/tools/pressInlineButton.ts | 24 ++ src/lib/mcp/telegram/tools/promoteAdmin.ts | 23 ++ src/lib/mcp/telegram/tools/removeReaction.ts | 24 ++ src/lib/mcp/telegram/tools/replyToMessage.ts | 76 ++++ src/lib/mcp/telegram/tools/resolveUsername.ts | 53 +++ src/lib/mcp/telegram/tools/saveDraft.ts | 23 ++ src/lib/mcp/telegram/tools/searchContacts.ts | 20 ++ src/lib/mcp/telegram/tools/searchMessages.ts | 63 ++++ .../mcp/telegram/tools/searchPublicChats.ts | 40 +++ src/lib/mcp/telegram/tools/sendMessage.ts | 55 +++ src/lib/mcp/telegram/tools/sendReaction.ts | 24 ++ src/lib/mcp/telegram/tools/setBotCommands.ts | 23 ++ .../mcp/telegram/tools/setPrivacySettings.ts | 23 ++ src/lib/mcp/telegram/tools/setProfilePhoto.ts | 16 + .../telegram/tools/subscribePublicChannel.ts | 20 ++ src/lib/mcp/telegram/tools/unarchiveChat.ts | 20 ++ src/lib/mcp/telegram/tools/unbanUser.ts | 23 ++ src/lib/mcp/telegram/tools/unblockUser.ts | 20 ++ src/lib/mcp/telegram/tools/unmuteChat.ts | 20 ++ src/lib/mcp/telegram/tools/unpinMessage.ts | 23 ++ src/lib/mcp/telegram/tools/updateProfile.ts | 23 ++ src/lib/mcp/telegram/types.ts | 17 + 86 files changed, 2981 insertions(+) create mode 100644 src/lib/mcp/telegram/index.ts create mode 100644 src/lib/mcp/telegram/server.ts create mode 100644 src/lib/mcp/telegram/telegramApi.ts create mode 100644 src/lib/mcp/telegram/toolActionParser.ts create mode 100644 src/lib/mcp/telegram/tools/addContact.ts create mode 100644 src/lib/mcp/telegram/tools/archiveChat.ts create mode 100644 src/lib/mcp/telegram/tools/banUser.ts create mode 100644 src/lib/mcp/telegram/tools/blockUser.ts create mode 100644 src/lib/mcp/telegram/tools/clearDraft.ts create mode 100644 src/lib/mcp/telegram/tools/createChannel.ts create mode 100644 src/lib/mcp/telegram/tools/createGroup.ts create mode 100644 src/lib/mcp/telegram/tools/createPoll.ts create mode 100644 src/lib/mcp/telegram/tools/deleteChatPhoto.ts create mode 100644 src/lib/mcp/telegram/tools/deleteContact.ts create mode 100644 src/lib/mcp/telegram/tools/deleteMessage.ts create mode 100644 src/lib/mcp/telegram/tools/deleteProfilePhoto.ts create mode 100644 src/lib/mcp/telegram/tools/demoteAdmin.ts create mode 100644 src/lib/mcp/telegram/tools/editChatPhoto.ts create mode 100644 src/lib/mcp/telegram/tools/editChatTitle.ts create mode 100644 src/lib/mcp/telegram/tools/editMessage.ts create mode 100644 src/lib/mcp/telegram/tools/exportChatInvite.ts create mode 100644 src/lib/mcp/telegram/tools/exportContacts.ts create mode 100644 src/lib/mcp/telegram/tools/forwardMessage.ts create mode 100644 src/lib/mcp/telegram/tools/getAdmins.ts create mode 100644 src/lib/mcp/telegram/tools/getBannedUsers.ts create mode 100644 src/lib/mcp/telegram/tools/getBlockedUsers.ts create mode 100644 src/lib/mcp/telegram/tools/getBotInfo.ts create mode 100644 src/lib/mcp/telegram/tools/getChat.ts create mode 100644 src/lib/mcp/telegram/tools/getChats.ts create mode 100644 src/lib/mcp/telegram/tools/getContactChats.ts create mode 100644 src/lib/mcp/telegram/tools/getContactIds.ts create mode 100644 src/lib/mcp/telegram/tools/getDirectChatByContact.ts create mode 100644 src/lib/mcp/telegram/tools/getDrafts.ts create mode 100644 src/lib/mcp/telegram/tools/getGifSearch.ts create mode 100644 src/lib/mcp/telegram/tools/getHistory.ts create mode 100644 src/lib/mcp/telegram/tools/getInviteLink.ts create mode 100644 src/lib/mcp/telegram/tools/getLastInteraction.ts create mode 100644 src/lib/mcp/telegram/tools/getMe.ts create mode 100644 src/lib/mcp/telegram/tools/getMediaInfo.ts create mode 100644 src/lib/mcp/telegram/tools/getMessageContext.ts create mode 100644 src/lib/mcp/telegram/tools/getMessageReactions.ts create mode 100644 src/lib/mcp/telegram/tools/getMessages.ts create mode 100644 src/lib/mcp/telegram/tools/getParticipants.ts create mode 100644 src/lib/mcp/telegram/tools/getPinnedMessages.ts create mode 100644 src/lib/mcp/telegram/tools/getPrivacySettings.ts create mode 100644 src/lib/mcp/telegram/tools/getRecentActions.ts create mode 100644 src/lib/mcp/telegram/tools/getStickerSets.ts create mode 100644 src/lib/mcp/telegram/tools/getUserPhotos.ts create mode 100644 src/lib/mcp/telegram/tools/getUserStatus.ts create mode 100644 src/lib/mcp/telegram/tools/importChatInvite.ts create mode 100644 src/lib/mcp/telegram/tools/importContacts.ts create mode 100644 src/lib/mcp/telegram/tools/index.ts create mode 100644 src/lib/mcp/telegram/tools/inviteToGroup.ts create mode 100644 src/lib/mcp/telegram/tools/joinChatByLink.ts create mode 100644 src/lib/mcp/telegram/tools/leaveChat.ts create mode 100644 src/lib/mcp/telegram/tools/listChats.ts create mode 100644 src/lib/mcp/telegram/tools/listContacts.ts create mode 100644 src/lib/mcp/telegram/tools/listInlineButtons.ts create mode 100644 src/lib/mcp/telegram/tools/listMessages.ts create mode 100644 src/lib/mcp/telegram/tools/listTopics.ts create mode 100644 src/lib/mcp/telegram/tools/markAsRead.ts create mode 100644 src/lib/mcp/telegram/tools/muteChat.ts create mode 100644 src/lib/mcp/telegram/tools/notImplemented.ts create mode 100644 src/lib/mcp/telegram/tools/pinMessage.ts create mode 100644 src/lib/mcp/telegram/tools/pressInlineButton.ts create mode 100644 src/lib/mcp/telegram/tools/promoteAdmin.ts create mode 100644 src/lib/mcp/telegram/tools/removeReaction.ts create mode 100644 src/lib/mcp/telegram/tools/replyToMessage.ts create mode 100644 src/lib/mcp/telegram/tools/resolveUsername.ts create mode 100644 src/lib/mcp/telegram/tools/saveDraft.ts create mode 100644 src/lib/mcp/telegram/tools/searchContacts.ts create mode 100644 src/lib/mcp/telegram/tools/searchMessages.ts create mode 100644 src/lib/mcp/telegram/tools/searchPublicChats.ts create mode 100644 src/lib/mcp/telegram/tools/sendMessage.ts create mode 100644 src/lib/mcp/telegram/tools/sendReaction.ts create mode 100644 src/lib/mcp/telegram/tools/setBotCommands.ts create mode 100644 src/lib/mcp/telegram/tools/setPrivacySettings.ts create mode 100644 src/lib/mcp/telegram/tools/setProfilePhoto.ts create mode 100644 src/lib/mcp/telegram/tools/subscribePublicChannel.ts create mode 100644 src/lib/mcp/telegram/tools/unarchiveChat.ts create mode 100644 src/lib/mcp/telegram/tools/unbanUser.ts create mode 100644 src/lib/mcp/telegram/tools/unblockUser.ts create mode 100644 src/lib/mcp/telegram/tools/unmuteChat.ts create mode 100644 src/lib/mcp/telegram/tools/unpinMessage.ts create mode 100644 src/lib/mcp/telegram/tools/updateProfile.ts create mode 100644 src/lib/mcp/telegram/types.ts diff --git a/src/lib/mcp/telegram/index.ts b/src/lib/mcp/telegram/index.ts new file mode 100644 index 000000000..a1d2f14f6 --- /dev/null +++ b/src/lib/mcp/telegram/index.ts @@ -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'; diff --git a/src/lib/mcp/telegram/server.ts b/src/lib/mcp/telegram/server.ts new file mode 100644 index 000000000..d0e9619e7 --- /dev/null +++ b/src/lib/mcp/telegram/server.ts @@ -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 { + 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 { + 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 = { + 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]; + } +} diff --git a/src/lib/mcp/telegram/telegramApi.ts b/src/lib/mcp/telegram/telegramApi.ts new file mode 100644 index 000000000..c4074a92c --- /dev/null +++ b/src/lib/mcp/telegram/telegramApi.ts @@ -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 { + 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 { + 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 { + 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; +} diff --git a/src/lib/mcp/telegram/toolActionParser.ts b/src/lib/mcp/telegram/toolActionParser.ts new file mode 100644 index 000000000..5cf09c89a --- /dev/null +++ b/src/lib/mcp/telegram/toolActionParser.ts @@ -0,0 +1,333 @@ +/** + * Tool Action Parser - Converts tool inputs to human-readable descriptions + */ + +type ToolArguments = Record; + +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> = { + 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}`; + }, +}; diff --git a/src/lib/mcp/telegram/tools/addContact.ts b/src/lib/mcp/telegram/tools/addContact.ts new file mode 100644 index 000000000..c8fd7ce3b --- /dev/null +++ b/src/lib/mcp/telegram/tools/addContact.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('add_contact'); +} diff --git a/src/lib/mcp/telegram/tools/archiveChat.ts b/src/lib/mcp/telegram/tools/archiveChat.ts new file mode 100644 index 000000000..399d091aa --- /dev/null +++ b/src/lib/mcp/telegram/tools/archiveChat.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('archive_chat'); +} diff --git a/src/lib/mcp/telegram/tools/banUser.ts b/src/lib/mcp/telegram/tools/banUser.ts new file mode 100644 index 000000000..c4ae2a497 --- /dev/null +++ b/src/lib/mcp/telegram/tools/banUser.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('ban_user'); +} diff --git a/src/lib/mcp/telegram/tools/blockUser.ts b/src/lib/mcp/telegram/tools/blockUser.ts new file mode 100644 index 000000000..45dffb908 --- /dev/null +++ b/src/lib/mcp/telegram/tools/blockUser.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('block_user'); +} diff --git a/src/lib/mcp/telegram/tools/clearDraft.ts b/src/lib/mcp/telegram/tools/clearDraft.ts new file mode 100644 index 000000000..f4c6b0473 --- /dev/null +++ b/src/lib/mcp/telegram/tools/clearDraft.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('clear_draft'); +} diff --git a/src/lib/mcp/telegram/tools/createChannel.ts b/src/lib/mcp/telegram/tools/createChannel.ts new file mode 100644 index 000000000..5ff529688 --- /dev/null +++ b/src/lib/mcp/telegram/tools/createChannel.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('create_channel'); +} diff --git a/src/lib/mcp/telegram/tools/createGroup.ts b/src/lib/mcp/telegram/tools/createGroup.ts new file mode 100644 index 000000000..9e647b60e --- /dev/null +++ b/src/lib/mcp/telegram/tools/createGroup.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('create_group'); +} diff --git a/src/lib/mcp/telegram/tools/createPoll.ts b/src/lib/mcp/telegram/tools/createPoll.ts new file mode 100644 index 000000000..9336fea6c --- /dev/null +++ b/src/lib/mcp/telegram/tools/createPoll.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('create_poll'); +} diff --git a/src/lib/mcp/telegram/tools/deleteChatPhoto.ts b/src/lib/mcp/telegram/tools/deleteChatPhoto.ts new file mode 100644 index 000000000..696df7ca6 --- /dev/null +++ b/src/lib/mcp/telegram/tools/deleteChatPhoto.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('delete_chat_photo'); +} diff --git a/src/lib/mcp/telegram/tools/deleteContact.ts b/src/lib/mcp/telegram/tools/deleteContact.ts new file mode 100644 index 000000000..b66191ac7 --- /dev/null +++ b/src/lib/mcp/telegram/tools/deleteContact.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('delete_contact'); +} diff --git a/src/lib/mcp/telegram/tools/deleteMessage.ts b/src/lib/mcp/telegram/tools/deleteMessage.ts new file mode 100644 index 000000000..916d988b3 --- /dev/null +++ b/src/lib/mcp/telegram/tools/deleteMessage.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('delete_message'); +} diff --git a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts new file mode 100644 index 000000000..aa864885c --- /dev/null +++ b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('delete_profile_photo'); +} diff --git a/src/lib/mcp/telegram/tools/demoteAdmin.ts b/src/lib/mcp/telegram/tools/demoteAdmin.ts new file mode 100644 index 000000000..42ef2b011 --- /dev/null +++ b/src/lib/mcp/telegram/tools/demoteAdmin.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('demote_admin'); +} diff --git a/src/lib/mcp/telegram/tools/editChatPhoto.ts b/src/lib/mcp/telegram/tools/editChatPhoto.ts new file mode 100644 index 000000000..b96432a76 --- /dev/null +++ b/src/lib/mcp/telegram/tools/editChatPhoto.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('edit_chat_photo'); +} diff --git a/src/lib/mcp/telegram/tools/editChatTitle.ts b/src/lib/mcp/telegram/tools/editChatTitle.ts new file mode 100644 index 000000000..070598302 --- /dev/null +++ b/src/lib/mcp/telegram/tools/editChatTitle.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('edit_chat_title'); +} diff --git a/src/lib/mcp/telegram/tools/editMessage.ts b/src/lib/mcp/telegram/tools/editMessage.ts new file mode 100644 index 000000000..16f84c35f --- /dev/null +++ b/src/lib/mcp/telegram/tools/editMessage.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('edit_message'); +} diff --git a/src/lib/mcp/telegram/tools/exportChatInvite.ts b/src/lib/mcp/telegram/tools/exportChatInvite.ts new file mode 100644 index 000000000..aa27022e5 --- /dev/null +++ b/src/lib/mcp/telegram/tools/exportChatInvite.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('export_chat_invite'); +} diff --git a/src/lib/mcp/telegram/tools/exportContacts.ts b/src/lib/mcp/telegram/tools/exportContacts.ts new file mode 100644 index 000000000..7f68316d8 --- /dev/null +++ b/src/lib/mcp/telegram/tools/exportContacts.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('export_contacts'); +} diff --git a/src/lib/mcp/telegram/tools/forwardMessage.ts b/src/lib/mcp/telegram/tools/forwardMessage.ts new file mode 100644 index 000000000..e7828ba0b --- /dev/null +++ b/src/lib/mcp/telegram/tools/forwardMessage.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('forward_message'); +} diff --git a/src/lib/mcp/telegram/tools/getAdmins.ts b/src/lib/mcp/telegram/tools/getAdmins.ts new file mode 100644 index 000000000..df61aba60 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getAdmins.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_admins'); +} diff --git a/src/lib/mcp/telegram/tools/getBannedUsers.ts b/src/lib/mcp/telegram/tools/getBannedUsers.ts new file mode 100644 index 000000000..2b92993fb --- /dev/null +++ b/src/lib/mcp/telegram/tools/getBannedUsers.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_banned_users'); +} diff --git a/src/lib/mcp/telegram/tools/getBlockedUsers.ts b/src/lib/mcp/telegram/tools/getBlockedUsers.ts new file mode 100644 index 000000000..75a5ba20b --- /dev/null +++ b/src/lib/mcp/telegram/tools/getBlockedUsers.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_blocked_users'); +} diff --git a/src/lib/mcp/telegram/tools/getBotInfo.ts b/src/lib/mcp/telegram/tools/getBotInfo.ts new file mode 100644 index 000000000..333d53ca0 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getBotInfo.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_bot_info'); +} diff --git a/src/lib/mcp/telegram/tools/getChat.ts b/src/lib/mcp/telegram/tools/getChat.ts new file mode 100644 index 000000000..a68e91c76 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getChat.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/getChats.ts b/src/lib/mcp/telegram/tools/getChats.ts new file mode 100644 index 000000000..93780cb21 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getChats.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/getContactChats.ts b/src/lib/mcp/telegram/tools/getContactChats.ts new file mode 100644 index 000000000..d62557f41 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getContactChats.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_contact_chats'); +} diff --git a/src/lib/mcp/telegram/tools/getContactIds.ts b/src/lib/mcp/telegram/tools/getContactIds.ts new file mode 100644 index 000000000..4837c5df9 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getContactIds.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_contact_ids'); +} diff --git a/src/lib/mcp/telegram/tools/getDirectChatByContact.ts b/src/lib/mcp/telegram/tools/getDirectChatByContact.ts new file mode 100644 index 000000000..a2e32cd50 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getDirectChatByContact.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_direct_chat_by_contact'); +} diff --git a/src/lib/mcp/telegram/tools/getDrafts.ts b/src/lib/mcp/telegram/tools/getDrafts.ts new file mode 100644 index 000000000..de9164898 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getDrafts.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_drafts'); +} diff --git a/src/lib/mcp/telegram/tools/getGifSearch.ts b/src/lib/mcp/telegram/tools/getGifSearch.ts new file mode 100644 index 000000000..ce3ffa374 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getGifSearch.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_gif_search'); +} diff --git a/src/lib/mcp/telegram/tools/getHistory.ts b/src/lib/mcp/telegram/tools/getHistory.ts new file mode 100644 index 000000000..324346719 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getHistory.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_history'); +} diff --git a/src/lib/mcp/telegram/tools/getInviteLink.ts b/src/lib/mcp/telegram/tools/getInviteLink.ts new file mode 100644 index 000000000..08a43c2c2 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getInviteLink.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_invite_link'); +} diff --git a/src/lib/mcp/telegram/tools/getLastInteraction.ts b/src/lib/mcp/telegram/tools/getLastInteraction.ts new file mode 100644 index 000000000..c1679a14b --- /dev/null +++ b/src/lib/mcp/telegram/tools/getLastInteraction.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_last_interaction'); +} diff --git a/src/lib/mcp/telegram/tools/getMe.ts b/src/lib/mcp/telegram/tools/getMe.ts new file mode 100644 index 000000000..d8e449b91 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getMe.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/getMediaInfo.ts b/src/lib/mcp/telegram/tools/getMediaInfo.ts new file mode 100644 index 000000000..bc78c1f53 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getMediaInfo.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_media_info'); +} diff --git a/src/lib/mcp/telegram/tools/getMessageContext.ts b/src/lib/mcp/telegram/tools/getMessageContext.ts new file mode 100644 index 000000000..c00b6eeac --- /dev/null +++ b/src/lib/mcp/telegram/tools/getMessageContext.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_message_context'); +} diff --git a/src/lib/mcp/telegram/tools/getMessageReactions.ts b/src/lib/mcp/telegram/tools/getMessageReactions.ts new file mode 100644 index 000000000..35cce4923 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getMessageReactions.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_message_reactions'); +} diff --git a/src/lib/mcp/telegram/tools/getMessages.ts b/src/lib/mcp/telegram/tools/getMessages.ts new file mode 100644 index 000000000..9e01b0808 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getMessages.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/getParticipants.ts b/src/lib/mcp/telegram/tools/getParticipants.ts new file mode 100644 index 000000000..b709aa374 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getParticipants.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_participants'); +} diff --git a/src/lib/mcp/telegram/tools/getPinnedMessages.ts b/src/lib/mcp/telegram/tools/getPinnedMessages.ts new file mode 100644 index 000000000..b2f82a9c3 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getPinnedMessages.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_pinned_messages'); +} diff --git a/src/lib/mcp/telegram/tools/getPrivacySettings.ts b/src/lib/mcp/telegram/tools/getPrivacySettings.ts new file mode 100644 index 000000000..f293218ee --- /dev/null +++ b/src/lib/mcp/telegram/tools/getPrivacySettings.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_privacy_settings'); +} diff --git a/src/lib/mcp/telegram/tools/getRecentActions.ts b/src/lib/mcp/telegram/tools/getRecentActions.ts new file mode 100644 index 000000000..a3d5d4351 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getRecentActions.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_recent_actions'); +} diff --git a/src/lib/mcp/telegram/tools/getStickerSets.ts b/src/lib/mcp/telegram/tools/getStickerSets.ts new file mode 100644 index 000000000..710cbe627 --- /dev/null +++ b/src/lib/mcp/telegram/tools/getStickerSets.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_sticker_sets'); +} diff --git a/src/lib/mcp/telegram/tools/getUserPhotos.ts b/src/lib/mcp/telegram/tools/getUserPhotos.ts new file mode 100644 index 000000000..c9ce0b76e --- /dev/null +++ b/src/lib/mcp/telegram/tools/getUserPhotos.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_user_photos'); +} diff --git a/src/lib/mcp/telegram/tools/getUserStatus.ts b/src/lib/mcp/telegram/tools/getUserStatus.ts new file mode 100644 index 000000000..ecf49a6da --- /dev/null +++ b/src/lib/mcp/telegram/tools/getUserStatus.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('get_user_status'); +} diff --git a/src/lib/mcp/telegram/tools/importChatInvite.ts b/src/lib/mcp/telegram/tools/importChatInvite.ts new file mode 100644 index 000000000..c178cb321 --- /dev/null +++ b/src/lib/mcp/telegram/tools/importChatInvite.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('import_chat_invite'); +} diff --git a/src/lib/mcp/telegram/tools/importContacts.ts b/src/lib/mcp/telegram/tools/importContacts.ts new file mode 100644 index 000000000..3d6523455 --- /dev/null +++ b/src/lib/mcp/telegram/tools/importContacts.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('import_contacts'); +} diff --git a/src/lib/mcp/telegram/tools/index.ts b/src/lib/mcp/telegram/tools/index.ts new file mode 100644 index 000000000..ad2b338a4 --- /dev/null +++ b/src/lib/mcp/telegram/tools/index.ts @@ -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'; diff --git a/src/lib/mcp/telegram/tools/inviteToGroup.ts b/src/lib/mcp/telegram/tools/inviteToGroup.ts new file mode 100644 index 000000000..9cf18f60f --- /dev/null +++ b/src/lib/mcp/telegram/tools/inviteToGroup.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('invite_to_group'); +} diff --git a/src/lib/mcp/telegram/tools/joinChatByLink.ts b/src/lib/mcp/telegram/tools/joinChatByLink.ts new file mode 100644 index 000000000..d1844edc0 --- /dev/null +++ b/src/lib/mcp/telegram/tools/joinChatByLink.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('join_chat_by_link'); +} diff --git a/src/lib/mcp/telegram/tools/leaveChat.ts b/src/lib/mcp/telegram/tools/leaveChat.ts new file mode 100644 index 000000000..fcdd2e291 --- /dev/null +++ b/src/lib/mcp/telegram/tools/leaveChat.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('leave_chat'); +} diff --git a/src/lib/mcp/telegram/tools/listChats.ts b/src/lib/mcp/telegram/tools/listChats.ts new file mode 100644 index 000000000..f3b729383 --- /dev/null +++ b/src/lib/mcp/telegram/tools/listChats.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/listContacts.ts b/src/lib/mcp/telegram/tools/listContacts.ts new file mode 100644 index 000000000..8cc19d6f1 --- /dev/null +++ b/src/lib/mcp/telegram/tools/listContacts.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('list_contacts'); +} diff --git a/src/lib/mcp/telegram/tools/listInlineButtons.ts b/src/lib/mcp/telegram/tools/listInlineButtons.ts new file mode 100644 index 000000000..7850f91f5 --- /dev/null +++ b/src/lib/mcp/telegram/tools/listInlineButtons.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('list_inline_buttons'); +} diff --git a/src/lib/mcp/telegram/tools/listMessages.ts b/src/lib/mcp/telegram/tools/listMessages.ts new file mode 100644 index 000000000..bb0838500 --- /dev/null +++ b/src/lib/mcp/telegram/tools/listMessages.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/listTopics.ts b/src/lib/mcp/telegram/tools/listTopics.ts new file mode 100644 index 000000000..f64484a05 --- /dev/null +++ b/src/lib/mcp/telegram/tools/listTopics.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('list_topics'); +} diff --git a/src/lib/mcp/telegram/tools/markAsRead.ts b/src/lib/mcp/telegram/tools/markAsRead.ts new file mode 100644 index 000000000..f6bfe465a --- /dev/null +++ b/src/lib/mcp/telegram/tools/markAsRead.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('mark_as_read'); +} diff --git a/src/lib/mcp/telegram/tools/muteChat.ts b/src/lib/mcp/telegram/tools/muteChat.ts new file mode 100644 index 000000000..192f09a8a --- /dev/null +++ b/src/lib/mcp/telegram/tools/muteChat.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('mute_chat'); +} diff --git a/src/lib/mcp/telegram/tools/notImplemented.ts b/src/lib/mcp/telegram/tools/notImplemented.ts new file mode 100644 index 000000000..92532a8c4 --- /dev/null +++ b/src/lib/mcp/telegram/tools/notImplemented.ts @@ -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, + }; +} diff --git a/src/lib/mcp/telegram/tools/pinMessage.ts b/src/lib/mcp/telegram/tools/pinMessage.ts new file mode 100644 index 000000000..f17615012 --- /dev/null +++ b/src/lib/mcp/telegram/tools/pinMessage.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('pin_message'); +} diff --git a/src/lib/mcp/telegram/tools/pressInlineButton.ts b/src/lib/mcp/telegram/tools/pressInlineButton.ts new file mode 100644 index 000000000..69c710265 --- /dev/null +++ b/src/lib/mcp/telegram/tools/pressInlineButton.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('press_inline_button'); +} diff --git a/src/lib/mcp/telegram/tools/promoteAdmin.ts b/src/lib/mcp/telegram/tools/promoteAdmin.ts new file mode 100644 index 000000000..3036e2efa --- /dev/null +++ b/src/lib/mcp/telegram/tools/promoteAdmin.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('promote_admin'); +} diff --git a/src/lib/mcp/telegram/tools/removeReaction.ts b/src/lib/mcp/telegram/tools/removeReaction.ts new file mode 100644 index 000000000..c615516c0 --- /dev/null +++ b/src/lib/mcp/telegram/tools/removeReaction.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('remove_reaction'); +} diff --git a/src/lib/mcp/telegram/tools/replyToMessage.ts b/src/lib/mcp/telegram/tools/replyToMessage.ts new file mode 100644 index 000000000..835089716 --- /dev/null +++ b/src/lib/mcp/telegram/tools/replyToMessage.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/resolveUsername.ts b/src/lib/mcp/telegram/tools/resolveUsername.ts new file mode 100644 index 000000000..48491fcf9 --- /dev/null +++ b/src/lib/mcp/telegram/tools/resolveUsername.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/saveDraft.ts b/src/lib/mcp/telegram/tools/saveDraft.ts new file mode 100644 index 000000000..69e68c402 --- /dev/null +++ b/src/lib/mcp/telegram/tools/saveDraft.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('save_draft'); +} diff --git a/src/lib/mcp/telegram/tools/searchContacts.ts b/src/lib/mcp/telegram/tools/searchContacts.ts new file mode 100644 index 000000000..d0340d47c --- /dev/null +++ b/src/lib/mcp/telegram/tools/searchContacts.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('search_contacts'); +} diff --git a/src/lib/mcp/telegram/tools/searchMessages.ts b/src/lib/mcp/telegram/tools/searchMessages.ts new file mode 100644 index 000000000..d79a56e51 --- /dev/null +++ b/src/lib/mcp/telegram/tools/searchMessages.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/searchPublicChats.ts b/src/lib/mcp/telegram/tools/searchPublicChats.ts new file mode 100644 index 000000000..c9e393419 --- /dev/null +++ b/src/lib/mcp/telegram/tools/searchPublicChats.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/sendMessage.ts b/src/lib/mcp/telegram/tools/sendMessage.ts new file mode 100644 index 000000000..8068808f7 --- /dev/null +++ b/src/lib/mcp/telegram/tools/sendMessage.ts @@ -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 { + 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, + ); + } +} diff --git a/src/lib/mcp/telegram/tools/sendReaction.ts b/src/lib/mcp/telegram/tools/sendReaction.ts new file mode 100644 index 000000000..a8d634fc0 --- /dev/null +++ b/src/lib/mcp/telegram/tools/sendReaction.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('send_reaction'); +} diff --git a/src/lib/mcp/telegram/tools/setBotCommands.ts b/src/lib/mcp/telegram/tools/setBotCommands.ts new file mode 100644 index 000000000..94cf194f0 --- /dev/null +++ b/src/lib/mcp/telegram/tools/setBotCommands.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('set_bot_commands'); +} diff --git a/src/lib/mcp/telegram/tools/setPrivacySettings.ts b/src/lib/mcp/telegram/tools/setPrivacySettings.ts new file mode 100644 index 000000000..7578dab81 --- /dev/null +++ b/src/lib/mcp/telegram/tools/setPrivacySettings.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('set_privacy_settings'); +} diff --git a/src/lib/mcp/telegram/tools/setProfilePhoto.ts b/src/lib/mcp/telegram/tools/setProfilePhoto.ts new file mode 100644 index 000000000..e5a3f2cca --- /dev/null +++ b/src/lib/mcp/telegram/tools/setProfilePhoto.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('set_profile_photo'); +} diff --git a/src/lib/mcp/telegram/tools/subscribePublicChannel.ts b/src/lib/mcp/telegram/tools/subscribePublicChannel.ts new file mode 100644 index 000000000..84d067bed --- /dev/null +++ b/src/lib/mcp/telegram/tools/subscribePublicChannel.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('subscribe_public_channel'); +} diff --git a/src/lib/mcp/telegram/tools/unarchiveChat.ts b/src/lib/mcp/telegram/tools/unarchiveChat.ts new file mode 100644 index 000000000..00f641d77 --- /dev/null +++ b/src/lib/mcp/telegram/tools/unarchiveChat.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('unarchive_chat'); +} diff --git a/src/lib/mcp/telegram/tools/unbanUser.ts b/src/lib/mcp/telegram/tools/unbanUser.ts new file mode 100644 index 000000000..4f20ed58e --- /dev/null +++ b/src/lib/mcp/telegram/tools/unbanUser.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('unban_user'); +} diff --git a/src/lib/mcp/telegram/tools/unblockUser.ts b/src/lib/mcp/telegram/tools/unblockUser.ts new file mode 100644 index 000000000..82642c273 --- /dev/null +++ b/src/lib/mcp/telegram/tools/unblockUser.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('unblock_user'); +} diff --git a/src/lib/mcp/telegram/tools/unmuteChat.ts b/src/lib/mcp/telegram/tools/unmuteChat.ts new file mode 100644 index 000000000..c8048145d --- /dev/null +++ b/src/lib/mcp/telegram/tools/unmuteChat.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('unmute_chat'); +} diff --git a/src/lib/mcp/telegram/tools/unpinMessage.ts b/src/lib/mcp/telegram/tools/unpinMessage.ts new file mode 100644 index 000000000..688076543 --- /dev/null +++ b/src/lib/mcp/telegram/tools/unpinMessage.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('unpin_message'); +} diff --git a/src/lib/mcp/telegram/tools/updateProfile.ts b/src/lib/mcp/telegram/tools/updateProfile.ts new file mode 100644 index 000000000..34e04ca81 --- /dev/null +++ b/src/lib/mcp/telegram/tools/updateProfile.ts @@ -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, + _context: TelegramMCPContext, +): Promise { + return notImplemented('update_profile'); +} diff --git a/src/lib/mcp/telegram/types.ts b/src/lib/mcp/telegram/types.ts new file mode 100644 index 000000000..9af309450 --- /dev/null +++ b/src/lib/mcp/telegram/types.ts @@ -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, + context: TelegramMCPContext, +) => Promise;