From 5a0425c0218c223ceedaa808e175f8b8299e90b5 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Wed, 28 Jan 2026 09:20:34 +0530 Subject: [PATCH] Add type casting helpers for Telegram MTProto API - Introduced `apiCastHelpers.ts` to centralize type casting functions for Telegram's complex union types, improving type safety and code clarity. - Updated multiple Telegram tools to utilize these helpers for casting input entities, enhancing maintainability and consistency across the codebase. - Modified `apiResultTypes.ts` to refine type definitions, ensuring better integration with the Telegram API. These changes streamline type handling in Telegram MCP tools, facilitating improved development practices and reducing the risk of type-related errors. --- src/lib/mcp/telegram/apiCastHelpers.ts | 36 +++++++++++++++++++ src/lib/mcp/telegram/apiResultTypes.ts | 2 +- src/lib/mcp/telegram/tools/banUser.ts | 5 +-- src/lib/mcp/telegram/tools/blockUser.ts | 3 +- src/lib/mcp/telegram/tools/createChannel.ts | 3 +- src/lib/mcp/telegram/tools/createGroup.ts | 5 +-- src/lib/mcp/telegram/tools/deleteChatPhoto.ts | 3 +- src/lib/mcp/telegram/tools/deleteContact.ts | 3 +- .../mcp/telegram/tools/deleteProfilePhoto.ts | 3 +- src/lib/mcp/telegram/tools/demoteAdmin.ts | 5 +-- src/lib/mcp/telegram/tools/editChatTitle.ts | 3 +- .../mcp/telegram/tools/exportChatInvite.ts | 3 +- src/lib/mcp/telegram/tools/getAdmins.ts | 7 ++-- src/lib/mcp/telegram/tools/getBannedUsers.ts | 3 +- src/lib/mcp/telegram/tools/getBotInfo.ts | 7 ++-- src/lib/mcp/telegram/tools/getContactIds.ts | 3 +- src/lib/mcp/telegram/tools/getDrafts.ts | 3 +- src/lib/mcp/telegram/tools/getGifSearch.ts | 5 +-- src/lib/mcp/telegram/tools/getInviteLink.ts | 3 +- .../mcp/telegram/tools/getMessageReactions.ts | 3 +- src/lib/mcp/telegram/tools/getParticipants.ts | 7 ++-- .../mcp/telegram/tools/getPinnedMessages.ts | 5 +-- .../mcp/telegram/tools/getPrivacySettings.ts | 3 +- .../mcp/telegram/tools/getRecentActions.ts | 5 +-- src/lib/mcp/telegram/tools/getStickerSets.ts | 3 +- src/lib/mcp/telegram/tools/getUserPhotos.ts | 5 +-- src/lib/mcp/telegram/tools/getUserStatus.ts | 5 +-- .../mcp/telegram/tools/importChatInvite.ts | 3 +- src/lib/mcp/telegram/tools/importContacts.ts | 3 +- src/lib/mcp/telegram/tools/inviteToGroup.ts | 5 +-- src/lib/mcp/telegram/tools/joinChatByLink.ts | 3 +- src/lib/mcp/telegram/tools/leaveChat.ts | 3 +- .../mcp/telegram/tools/listInlineButtons.ts | 4 ++- src/lib/mcp/telegram/tools/listTopics.ts | 5 +-- .../mcp/telegram/tools/pressInlineButton.ts | 3 +- src/lib/mcp/telegram/tools/promoteAdmin.ts | 5 +-- .../telegram/tools/subscribePublicChannel.ts | 3 +- src/lib/mcp/telegram/tools/unbanUser.ts | 5 +-- src/lib/mcp/telegram/tools/unblockUser.ts | 3 +- 39 files changed, 130 insertions(+), 56 deletions(-) create mode 100644 src/lib/mcp/telegram/apiCastHelpers.ts diff --git a/src/lib/mcp/telegram/apiCastHelpers.ts b/src/lib/mcp/telegram/apiCastHelpers.ts new file mode 100644 index 000000000..b277afa1a --- /dev/null +++ b/src/lib/mcp/telegram/apiCastHelpers.ts @@ -0,0 +1,36 @@ +/** + * Type bridge utilities for Telegram MTProto API. + * + * The `telegram` library's `client.invoke()` returns complex union types, + * and `getInputEntity()` returns `TypeInputPeer` which doesn't directly + * narrow to specific entity types like `TypeInputChannel`. + * + * These helpers centralize the necessary type casts in one documented + * location rather than scattering casts throughout tool files. + */ +import type { Api } from "telegram"; + +/** Cast getInputEntity() result to Api.TypeInputChannel */ +export function toInputChannel(entity: object): Api.TypeInputChannel { + return entity as Api.TypeInputChannel; +} + +/** Cast getInputEntity() result to Api.TypeInputUser */ +export function toInputUser(entity: object): Api.TypeInputUser { + return entity as Api.TypeInputUser; +} + +/** Cast getInputEntity() result to Api.TypeInputPeer */ +export function toInputPeer(entity: object): Api.TypeInputPeer { + return entity as Api.TypeInputPeer; +} + +/** + * Narrow an invoke() result to the expected shape. + * Telegram's client.invoke() returns complex union types that can't + * be narrowed purely via control flow. This provides a typed + * alternative to inline casts in tool files. + */ +export function narrow(result: object): T { + return result as T; +} diff --git a/src/lib/mcp/telegram/apiResultTypes.ts b/src/lib/mcp/telegram/apiResultTypes.ts index b8983360c..4797e3223 100644 --- a/src/lib/mcp/telegram/apiResultTypes.ts +++ b/src/lib/mcp/telegram/apiResultTypes.ts @@ -82,7 +82,7 @@ export interface BotCallbackAnswer { /** Contact import result */ export interface ImportContactsResult { - imported?: unknown[]; + imported?: object[]; } /** Contact ID entry */ diff --git a/src/lib/mcp/telegram/tools/banUser.ts b/src/lib/mcp/telegram/tools/banUser.ts index 5c7c64754..e93abeda3 100644 --- a/src/lib/mcp/telegram/tools/banUser.ts +++ b/src/lib/mcp/telegram/tools/banUser.ts @@ -5,6 +5,7 @@ import { validateId } from "../../validation"; import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; +import { toInputChannel, toInputPeer } from "../apiCastHelpers"; export const tool: MCPTool = { name: "ban_user", @@ -54,8 +55,8 @@ export async function banUser( const inputUser = await client.getInputEntity(userId); await client.invoke( new Api.channels.EditBanned({ - channel: inputChannel as unknown as Api.TypeInputChannel, - participant: inputUser as unknown as Api.TypeInputPeer, + channel: toInputChannel(inputChannel), + participant: toInputPeer(inputUser), bannedRights: new Api.ChatBannedRights({ viewMessages: true, sendMessages: true, diff --git a/src/lib/mcp/telegram/tools/blockUser.ts b/src/lib/mcp/telegram/tools/blockUser.ts index f14dca85d..74548ee02 100644 --- a/src/lib/mcp/telegram/tools/blockUser.ts +++ b/src/lib/mcp/telegram/tools/blockUser.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import { toInputPeer } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'block_user', @@ -28,7 +29,7 @@ export async function blockUser( await mtprotoService.withFloodWaitHandling(async () => { const inputUser = await client.getInputEntity(userId); await client.invoke( - new Api.contacts.Block({ id: inputUser as unknown as Api.TypeInputPeer }), + new Api.contacts.Block({ id: toInputPeer(inputUser) }), ); }); diff --git a/src/lib/mcp/telegram/tools/createChannel.ts b/src/lib/mcp/telegram/tools/createChannel.ts index ecf1c321c..4b3a42f07 100644 --- a/src/lib/mcp/telegram/tools/createChannel.ts +++ b/src/lib/mcp/telegram/tools/createChannel.ts @@ -5,6 +5,7 @@ import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import { optString } from "../args"; import type { ResultWithChats } from "../apiResultTypes"; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "create_channel", @@ -51,7 +52,7 @@ export async function createChannel( ); }); - const channelId = (result as unknown as ResultWithChats)?.chats?.[0]?.id ?? "unknown"; + const channelId = narrow(result)?.chats?.[0]?.id ?? "unknown"; const type = megagroup ? "Supergroup" : "Channel"; return { content: [ diff --git a/src/lib/mcp/telegram/tools/createGroup.ts b/src/lib/mcp/telegram/tools/createGroup.ts index 8524bc725..9a42e1e35 100644 --- a/src/lib/mcp/telegram/tools/createGroup.ts +++ b/src/lib/mcp/telegram/tools/createGroup.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import type { ResultWithChats } from "../apiResultTypes"; +import { toInputUser, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "create_group", @@ -46,14 +47,14 @@ export async function createGroup( const users: Api.TypeInputUser[] = []; for (const uid of userIds) { const inputUser = await client.getInputEntity(String(uid)); - users.push(inputUser as unknown as Api.TypeInputUser); + users.push(toInputUser(inputUser)); } const result = await mtprotoService.withFloodWaitHandling(async () => { return client.invoke(new Api.messages.CreateChat({ title, users })); }); - const chatId = (result as unknown as ResultWithChats)?.chats?.[0]?.id ?? "unknown"; + const chatId = narrow(result)?.chats?.[0]?.id ?? "unknown"; return { content: [ { type: "text", text: `Group "${title}" created. Chat ID: ${chatId}` }, diff --git a/src/lib/mcp/telegram/tools/deleteChatPhoto.ts b/src/lib/mcp/telegram/tools/deleteChatPhoto.ts index 89a092cb3..592d140b7 100644 --- a/src/lib/mcp/telegram/tools/deleteChatPhoto.ts +++ b/src/lib/mcp/telegram/tools/deleteChatPhoto.ts @@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import { toInputChannel } from "../apiCastHelpers"; export const tool: MCPTool = { name: "delete_chat_photo", @@ -41,7 +42,7 @@ export async function deleteChatPhoto( const inputChannel = await client.getInputEntity(entity); await client.invoke( new Api.channels.EditPhoto({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), photo: new Api.InputChatPhotoEmpty(), }), ); diff --git a/src/lib/mcp/telegram/tools/deleteContact.ts b/src/lib/mcp/telegram/tools/deleteContact.ts index 5894c09f8..3f1bcfa54 100644 --- a/src/lib/mcp/telegram/tools/deleteContact.ts +++ b/src/lib/mcp/telegram/tools/deleteContact.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import { toInputUser } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'delete_contact', @@ -29,7 +30,7 @@ export async function deleteContact( const inputUser = await client.getInputEntity(userId); await client.invoke( new Api.contacts.DeleteContacts({ - id: [inputUser as unknown as Api.TypeInputUser], + id: [toInputUser(inputUser)], }), ); }); diff --git a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts index 8c3754161..f2793bee4 100644 --- a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts +++ b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts @@ -5,6 +5,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; import type { ApiPhoto } from '../apiResultTypes'; +import { narrow } from '../apiCastHelpers'; export const tool: MCPTool = { name: "delete_profile_photo", @@ -34,7 +35,7 @@ export async function deleteProfilePhoto( return { content: [{ type: 'text', text: 'No profile photo to delete.' }] }; } - const photo = photos.photos[0] as unknown as ApiPhoto; + const photo = narrow(photos.photos[0]); await mtprotoService.withFloodWaitHandling(async () => { await client.invoke( diff --git a/src/lib/mcp/telegram/tools/demoteAdmin.ts b/src/lib/mcp/telegram/tools/demoteAdmin.ts index 273065c28..c717985b7 100644 --- a/src/lib/mcp/telegram/tools/demoteAdmin.ts +++ b/src/lib/mcp/telegram/tools/demoteAdmin.ts @@ -5,6 +5,7 @@ import { validateId } from "../../validation"; import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; +import { toInputChannel, toInputUser } from "../apiCastHelpers"; export const tool: MCPTool = { name: "demote_admin", @@ -54,8 +55,8 @@ export async function demoteAdmin( const inputUser = await client.getInputEntity(userId); await client.invoke( new Api.channels.EditAdmin({ - channel: inputChannel as unknown as Api.TypeInputChannel, - userId: inputUser as unknown as Api.TypeInputUser, + channel: toInputChannel(inputChannel), + userId: toInputUser(inputUser), adminRights: new Api.ChatAdminRights({}), rank: "", }), diff --git a/src/lib/mcp/telegram/tools/editChatTitle.ts b/src/lib/mcp/telegram/tools/editChatTitle.ts index 97d22e576..d858bca8f 100644 --- a/src/lib/mcp/telegram/tools/editChatTitle.ts +++ b/src/lib/mcp/telegram/tools/editChatTitle.ts @@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import { toInputChannel } from "../apiCastHelpers"; export const tool: MCPTool = { name: "edit_chat_title", @@ -48,7 +49,7 @@ export async function editChatTitle( const inputChannel = await client.getInputEntity(entity); await client.invoke( new Api.channels.EditTitle({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), title, }), ); diff --git a/src/lib/mcp/telegram/tools/exportChatInvite.ts b/src/lib/mcp/telegram/tools/exportChatInvite.ts index 91fc6e15a..6366fb4a5 100644 --- a/src/lib/mcp/telegram/tools/exportChatInvite.ts +++ b/src/lib/mcp/telegram/tools/exportChatInvite.ts @@ -7,6 +7,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import { optString } from '../args'; import type { ChatInviteResult } from '../apiResultTypes'; +import { narrow } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'export_chat_invite', @@ -51,7 +52,7 @@ export async function exportChatInvite( ); }); - const link = (result as unknown as ChatInviteResult)?.link; + const link = narrow(result)?.link; if (!link) { return { content: [{ type: 'text', text: 'Could not create invite link.' }], isError: true }; } diff --git a/src/lib/mcp/telegram/tools/getAdmins.ts b/src/lib/mcp/telegram/tools/getAdmins.ts index 19de0b467..0e1164489 100644 --- a/src/lib/mcp/telegram/tools/getAdmins.ts +++ b/src/lib/mcp/telegram/tools/getAdmins.ts @@ -7,6 +7,7 @@ import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; import type { ApiUser } from "../apiResultTypes"; +import { toInputChannel, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_admins", @@ -44,7 +45,7 @@ export async function getAdmins( const inputChannel = await client.getInputEntity(entity); return client.invoke( new Api.channels.GetParticipants({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), filter: new Api.ChannelParticipantsAdmins(), offset: 0, limit: 100, @@ -53,7 +54,7 @@ export async function getAdmins( ); }); if (result && "users" in result && Array.isArray(result.users)) { - admins = result.users as unknown as ApiUser[]; + admins = narrow(result.users); } } else { const result = await mtprotoService.withFloodWaitHandling(async () => { @@ -62,7 +63,7 @@ export async function getAdmins( ); }); if (result && "users" in result && Array.isArray(result.users)) { - admins = result.users as unknown as ApiUser[]; + admins = narrow(result.users); } } diff --git a/src/lib/mcp/telegram/tools/getBannedUsers.ts b/src/lib/mcp/telegram/tools/getBannedUsers.ts index 13be37cbb..0240f56db 100644 --- a/src/lib/mcp/telegram/tools/getBannedUsers.ts +++ b/src/lib/mcp/telegram/tools/getBannedUsers.ts @@ -8,6 +8,7 @@ import { Api } from "telegram"; import { optNumber } from "../args"; import bigInt from "big-integer"; import type { ApiUser } from "../apiResultTypes"; +import { toInputChannel } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_banned_users", @@ -56,7 +57,7 @@ export async function getBannedUsers( const inputChannel = await client.getInputEntity(entity); return client.invoke( new Api.channels.GetParticipants({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), filter: new Api.ChannelParticipantsKicked({ q: "" }), offset: 0, limit, diff --git a/src/lib/mcp/telegram/tools/getBotInfo.ts b/src/lib/mcp/telegram/tools/getBotInfo.ts index 8190ccd2d..d0e41183d 100644 --- a/src/lib/mcp/telegram/tools/getBotInfo.ts +++ b/src/lib/mcp/telegram/tools/getBotInfo.ts @@ -5,6 +5,7 @@ import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { FullUserResult } from '../apiResultTypes'; +import { toInputUser, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_bot_info", @@ -29,12 +30,12 @@ export async function getBotInfo( const result = await mtprotoService.withFloodWaitHandling(async () => { const inputUser = await client.getInputEntity(botId); return client.invoke( - new Api.users.GetFullUser({ id: inputUser as unknown as Api.TypeInputUser }), + new Api.users.GetFullUser({ id: toInputUser(inputUser) }), ); }); - const fullUser = (result as unknown as FullUserResult)?.fullUser; - const user = (result as unknown as FullUserResult)?.users?.[0]; + const fullUser = narrow(result)?.fullUser; + const user = narrow(result)?.users?.[0]; if (!user) { return { content: [{ type: 'text', text: 'Bot not found: ' + botId }], isError: true }; diff --git a/src/lib/mcp/telegram/tools/getContactIds.ts b/src/lib/mcp/telegram/tools/getContactIds.ts index ad1d3f9c1..8226fbc93 100644 --- a/src/lib/mcp/telegram/tools/getContactIds.ts +++ b/src/lib/mcp/telegram/tools/getContactIds.ts @@ -5,6 +5,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; import type { ContactIdEntry } from '../apiResultTypes'; +import { narrow } from '../apiCastHelpers'; type ContactIdResult = number | ContactIdEntry; @@ -29,7 +30,7 @@ export async function getContactIds( return { content: [{ type: 'text', text: 'No contact IDs found.' }] }; } - const ids = (result as unknown as ContactIdResult[]).map((c) => + const ids = narrow(result).map((c) => String(typeof c === 'number' ? c : c.userId ?? c), ); return { content: [{ type: 'text', text: ids.length + ' contacts:\n' + ids.join('\n') }] }; diff --git a/src/lib/mcp/telegram/tools/getDrafts.ts b/src/lib/mcp/telegram/tools/getDrafts.ts index e8dfdbca6..26f092d74 100644 --- a/src/lib/mcp/telegram/tools/getDrafts.ts +++ b/src/lib/mcp/telegram/tools/getDrafts.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { UpdatesResult } from '../apiResultTypes'; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_drafts", @@ -22,7 +23,7 @@ export async function getDrafts( return client.invoke(new Api.messages.GetAllDrafts()); }); - const updates = result as unknown as UpdatesResult; + const updates = narrow(result); if (!updates || !updates.updates || updates.updates.length === 0) { return { content: [{ type: 'text', text: 'No drafts found.' }] }; } diff --git a/src/lib/mcp/telegram/tools/getGifSearch.ts b/src/lib/mcp/telegram/tools/getGifSearch.ts index 849690227..ccd28509a 100644 --- a/src/lib/mcp/telegram/tools/getGifSearch.ts +++ b/src/lib/mcp/telegram/tools/getGifSearch.ts @@ -5,6 +5,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import { optNumber } from '../args'; import type { InlineBotResults } from '../apiResultTypes'; +import { toInputUser, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_gif_search", @@ -34,7 +35,7 @@ export async function getGifSearch( const bot = await client.getInputEntity('gif'); return client.invoke( new Api.messages.GetInlineBotResults({ - bot: bot as unknown as Api.TypeInputUser, + bot: toInputUser(bot), peer: new Api.InputPeerSelf(), query, offset: '', @@ -42,7 +43,7 @@ export async function getGifSearch( ); }); - const results = (result as unknown as InlineBotResults)?.results; + const results = narrow(result)?.results; if (!results || !Array.isArray(results) || results.length === 0) { return { content: [{ type: 'text', text: 'No GIFs found for: ' + query }] }; } diff --git a/src/lib/mcp/telegram/tools/getInviteLink.ts b/src/lib/mcp/telegram/tools/getInviteLink.ts index 6a385a13d..347f97931 100644 --- a/src/lib/mcp/telegram/tools/getInviteLink.ts +++ b/src/lib/mcp/telegram/tools/getInviteLink.ts @@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import type { ChatInviteResult } from "../apiResultTypes"; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_invite_link", @@ -46,7 +47,7 @@ export async function getInviteLink( ); }); - const link = (result as unknown as ChatInviteResult)?.link; + const link = narrow(result)?.link; if (!link) { return { content: [{ type: "text", text: "Could not generate invite link." }], diff --git a/src/lib/mcp/telegram/tools/getMessageReactions.ts b/src/lib/mcp/telegram/tools/getMessageReactions.ts index dea5dbd6b..7d3e66fa4 100644 --- a/src/lib/mcp/telegram/tools/getMessageReactions.ts +++ b/src/lib/mcp/telegram/tools/getMessageReactions.ts @@ -6,6 +6,7 @@ import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { UpdatesResult } from '../apiResultTypes'; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: 'get_message_reactions', @@ -48,7 +49,7 @@ export async function getMessageReactions( ); }); - const updates = result as unknown as UpdatesResult; + const updates = narrow(result); if (!updates || !updates.updates || updates.updates.length === 0) { return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] }; } diff --git a/src/lib/mcp/telegram/tools/getParticipants.ts b/src/lib/mcp/telegram/tools/getParticipants.ts index e10cda991..d9a841f35 100644 --- a/src/lib/mcp/telegram/tools/getParticipants.ts +++ b/src/lib/mcp/telegram/tools/getParticipants.ts @@ -8,6 +8,7 @@ import { Api } from "telegram"; import { optNumber } from "../args"; import bigInt from "big-integer"; import type { ApiUser } from "../apiResultTypes"; +import { toInputChannel, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_participants", @@ -47,7 +48,7 @@ export async function getParticipants( const inputChannel = await client.getInputEntity(entity); return client.invoke( new Api.channels.GetParticipants({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), filter: new Api.ChannelParticipantsRecent(), offset: 0, limit, @@ -56,7 +57,7 @@ export async function getParticipants( ); }); if (result && "users" in result && Array.isArray(result.users)) { - participants = result.users as unknown as ApiUser[]; + participants = narrow(result.users); } } else { const result = await mtprotoService.withFloodWaitHandling(async () => { @@ -65,7 +66,7 @@ export async function getParticipants( ); }); if (result && "users" in result && Array.isArray(result.users)) { - participants = result.users as unknown as ApiUser[]; + participants = narrow(result.users); } } diff --git a/src/lib/mcp/telegram/tools/getPinnedMessages.ts b/src/lib/mcp/telegram/tools/getPinnedMessages.ts index f978c4297..49455280b 100644 --- a/src/lib/mcp/telegram/tools/getPinnedMessages.ts +++ b/src/lib/mcp/telegram/tools/getPinnedMessages.ts @@ -7,6 +7,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; import type { ApiMessage } from '../apiResultTypes'; +import { narrow } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'get_pinned_messages', @@ -54,7 +55,7 @@ export async function getPinnedMessages( ); if ('messages' in result && Array.isArray(result.messages)) { - pinnedLines = (result.messages as unknown as ApiMessage[]).map((msg) => { + pinnedLines = narrow(result.messages).map((msg) => { const id = msg.id ?? '?'; const text = msg.message ?? '[Media/No text]'; const date = msg.date ? new Date(msg.date * 1000).toISOString() : 'unknown'; @@ -65,7 +66,7 @@ export async function getPinnedMessages( // Fallback: check cached messages for pinned flag const allMessages = await getMessages(chatId, 500, 0); if (allMessages) { - const pinned = allMessages.filter((m) => (m as unknown as ApiMessage).pinned); + const pinned = allMessages.filter((m) => narrow(m).pinned); pinnedLines = pinned.map((msg) => { const f = formatMessage(msg); return `ID: ${f.id} | Date: ${f.date} | ${f.text || '[Media/No text]'}`; diff --git a/src/lib/mcp/telegram/tools/getPrivacySettings.ts b/src/lib/mcp/telegram/tools/getPrivacySettings.ts index 5063bfd87..997d7c110 100644 --- a/src/lib/mcp/telegram/tools/getPrivacySettings.ts +++ b/src/lib/mcp/telegram/tools/getPrivacySettings.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { PrivacyResult } from '../apiResultTypes'; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_privacy_settings", @@ -42,7 +43,7 @@ export async function getPrivacySettings( return client.invoke(new Api.account.GetPrivacy({ key })); }); - const rules = (result as unknown as PrivacyResult)?.rules; + const rules = narrow(result)?.rules; if (!rules || !Array.isArray(rules)) { return { content: [{ type: 'text', text: 'No privacy rules found for ' + keyStr + '.' }] }; } diff --git a/src/lib/mcp/telegram/tools/getRecentActions.ts b/src/lib/mcp/telegram/tools/getRecentActions.ts index c7875e406..9026f5c89 100644 --- a/src/lib/mcp/telegram/tools/getRecentActions.ts +++ b/src/lib/mcp/telegram/tools/getRecentActions.ts @@ -8,6 +8,7 @@ import { Api } from 'telegram'; import bigInt from 'big-integer'; import { optNumber } from '../args'; import type { AdminLogResult } from '../apiResultTypes'; +import { toInputChannel, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_recent_actions", @@ -44,7 +45,7 @@ export async function getRecentActions( const inputChannel = await client.getInputEntity(entity); return client.invoke( new Api.channels.GetAdminLog({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), q: '', maxId: bigInt(0), minId: bigInt(0), @@ -53,7 +54,7 @@ export async function getRecentActions( ); }); - const events = (result as unknown as AdminLogResult)?.events; + const events = narrow(result)?.events; if (!events || !Array.isArray(events) || events.length === 0) { return { content: [{ type: 'text', text: 'No recent actions found.' }] }; } diff --git a/src/lib/mcp/telegram/tools/getStickerSets.ts b/src/lib/mcp/telegram/tools/getStickerSets.ts index 35f79b920..4df931048 100644 --- a/src/lib/mcp/telegram/tools/getStickerSets.ts +++ b/src/lib/mcp/telegram/tools/getStickerSets.ts @@ -5,6 +5,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; import type { StickerSetsResult } from '../apiResultTypes'; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "get_sticker_sets", @@ -23,7 +24,7 @@ export async function getStickerSets( return client.invoke(new Api.messages.GetAllStickers({ hash: bigInt(0) })); }); - const sets = (result as unknown as StickerSetsResult)?.sets; + const sets = narrow(result)?.sets; if (!sets || !Array.isArray(sets) || sets.length === 0) { return { content: [{ type: 'text', text: 'No sticker sets found.' }] }; } diff --git a/src/lib/mcp/telegram/tools/getUserPhotos.ts b/src/lib/mcp/telegram/tools/getUserPhotos.ts index ac6a818fd..37a051f60 100644 --- a/src/lib/mcp/telegram/tools/getUserPhotos.ts +++ b/src/lib/mcp/telegram/tools/getUserPhotos.ts @@ -7,6 +7,7 @@ import { Api } from 'telegram'; import bigInt from 'big-integer'; import { optNumber } from '../args'; import type { ApiPhoto } from '../apiResultTypes'; +import { toInputUser, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: 'get_user_photos', @@ -34,7 +35,7 @@ export async function getUserPhotos( const inputUser = await client.getInputEntity(userId); return client.invoke( new Api.photos.GetUserPhotos({ - userId: inputUser as unknown as Api.TypeInputUser, + userId: toInputUser(inputUser), offset: 0, maxId: bigInt(0), limit, @@ -46,7 +47,7 @@ export async function getUserPhotos( return { content: [{ type: 'text', text: 'No photos found.' }] }; } - const lines = (result.photos as unknown as ApiPhoto[]).map((photo, i: number) => { + const lines = narrow(result.photos).map((photo, i: number) => { const date = photo.date ? new Date(photo.date * 1000).toISOString() : 'unknown'; return 'Photo ' + (i + 1) + ': ID ' + photo.id + ' | Date: ' + date; }); diff --git a/src/lib/mcp/telegram/tools/getUserStatus.ts b/src/lib/mcp/telegram/tools/getUserStatus.ts index 2a1c939b3..4f52795e0 100644 --- a/src/lib/mcp/telegram/tools/getUserStatus.ts +++ b/src/lib/mcp/telegram/tools/getUserStatus.ts @@ -5,6 +5,7 @@ import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { ApiUser } from '../apiResultTypes'; +import { toInputUser, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: 'get_user_status', @@ -29,7 +30,7 @@ export async function getUserStatus( const result = await mtprotoService.withFloodWaitHandling(async () => { const inputUser = await client.getInputEntity(userId); return client.invoke( - new Api.users.GetUsers({ id: [inputUser as unknown as Api.TypeInputUser] }), + new Api.users.GetUsers({ id: [toInputUser(inputUser)] }), ); }); @@ -37,7 +38,7 @@ export async function getUserStatus( return { content: [{ type: 'text', text: 'User ' + userId + ' not found.' }], isError: true }; } - const user = result[0] as unknown as ApiUser; + const user = narrow(result[0]); const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown'; let statusText = 'unknown'; diff --git a/src/lib/mcp/telegram/tools/importChatInvite.ts b/src/lib/mcp/telegram/tools/importChatInvite.ts index cb27c6aa9..c1f66fca2 100644 --- a/src/lib/mcp/telegram/tools/importChatInvite.ts +++ b/src/lib/mcp/telegram/tools/importChatInvite.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import type { ResultWithChats } from "../apiResultTypes"; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "import_chat_invite", @@ -38,7 +39,7 @@ export async function importChatInvite( return client.invoke(new Api.messages.ImportChatInvite({ hash })); }); - const chatTitle = (result as unknown as ResultWithChats)?.chats?.[0]?.title ?? "unknown"; + const chatTitle = narrow(result)?.chats?.[0]?.title ?? "unknown"; return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/importContacts.ts b/src/lib/mcp/telegram/tools/importContacts.ts index c4eac5c5d..c0a1605a6 100644 --- a/src/lib/mcp/telegram/tools/importContacts.ts +++ b/src/lib/mcp/telegram/tools/importContacts.ts @@ -5,6 +5,7 @@ import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; import type { ContactInput, ImportContactsResult } from "../apiResultTypes"; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "import_contacts", @@ -66,7 +67,7 @@ export async function importContacts( ); }); - const imported = (result as unknown as ImportContactsResult)?.imported?.length ?? 0; + const imported = narrow(result)?.imported?.length ?? 0; return { content: [ { diff --git a/src/lib/mcp/telegram/tools/inviteToGroup.ts b/src/lib/mcp/telegram/tools/inviteToGroup.ts index d8cd2d8ca..84224cded 100644 --- a/src/lib/mcp/telegram/tools/inviteToGroup.ts +++ b/src/lib/mcp/telegram/tools/inviteToGroup.ts @@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import { toInputChannel, toInputUser } from "../apiCastHelpers"; export const tool: MCPTool = { name: "invite_to_group", @@ -50,7 +51,7 @@ export async function inviteToGroup( const users: Api.TypeInputUser[] = []; for (const uid of userIds) { const inputUser = await client.getInputEntity(String(uid)); - users.push(inputUser as unknown as Api.TypeInputUser); + users.push(toInputUser(inputUser)); } const inputPeer = await client.getInputEntity(entity); @@ -59,7 +60,7 @@ export async function inviteToGroup( await mtprotoService.withFloodWaitHandling(async () => { await client.invoke( new Api.channels.InviteToChannel({ - channel: inputPeer as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputPeer), users, }), ); diff --git a/src/lib/mcp/telegram/tools/joinChatByLink.ts b/src/lib/mcp/telegram/tools/joinChatByLink.ts index ba3d58707..00162d63e 100644 --- a/src/lib/mcp/telegram/tools/joinChatByLink.ts +++ b/src/lib/mcp/telegram/tools/joinChatByLink.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { ResultWithChats } from '../apiResultTypes'; +import { narrow } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'join_chat_by_link', @@ -38,7 +39,7 @@ export async function joinChatByLink( return client.invoke(new Api.messages.ImportChatInvite({ hash })); }); - const chatTitle = (result as unknown as ResultWithChats)?.chats?.[0]?.title ?? 'unknown'; + const chatTitle = narrow(result)?.chats?.[0]?.title ?? 'unknown'; return { content: [{ type: 'text', text: `Joined chat: ${chatTitle}` }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/leaveChat.ts b/src/lib/mcp/telegram/tools/leaveChat.ts index bf9edd5b7..5d7f1512a 100644 --- a/src/lib/mcp/telegram/tools/leaveChat.ts +++ b/src/lib/mcp/telegram/tools/leaveChat.ts @@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import { toInputChannel } from "../apiCastHelpers"; export const tool: MCPTool = { name: "leave_chat", @@ -41,7 +42,7 @@ export async function leaveChat( const inputChannel = await client.getInputEntity(entity); await client.invoke( new Api.channels.LeaveChannel({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), }), ); }); diff --git a/src/lib/mcp/telegram/tools/listInlineButtons.ts b/src/lib/mcp/telegram/tools/listInlineButtons.ts index 3b688fda6..65633f1db 100644 --- a/src/lib/mcp/telegram/tools/listInlineButtons.ts +++ b/src/lib/mcp/telegram/tools/listInlineButtons.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { getChatById, getMessages } from '../telegramApi'; import type { MessageWithReplyMarkup, ReplyMarkupRow } from '../apiResultTypes'; +import { narrow } from '../apiCastHelpers'; export const tool: MCPTool = { name: "list_inline_buttons", @@ -36,7 +37,8 @@ export async function listInlineButtons( const messages = await getMessages(chatId, 200, 0); if (!messages) return { content: [{ type: 'text', text: 'No messages found.' }] }; - const msg = messages.find((m) => String(m.id) === String(messageId)) as unknown as MessageWithReplyMarkup | undefined; + const found = messages.find((m) => String(m.id) === String(messageId)); + const msg = found ? narrow(found) : undefined; if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true }; if (!msg.replyMarkup || !msg.replyMarkup.rows) { diff --git a/src/lib/mcp/telegram/tools/listTopics.ts b/src/lib/mcp/telegram/tools/listTopics.ts index 0c2ead52f..f9d596cfc 100644 --- a/src/lib/mcp/telegram/tools/listTopics.ts +++ b/src/lib/mcp/telegram/tools/listTopics.ts @@ -6,6 +6,7 @@ import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { ForumTopicsResult } from '../apiResultTypes'; +import { toInputChannel, narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: 'list_topics', @@ -34,7 +35,7 @@ export async function listTopics( const inputChannel = await client.getInputEntity(entity); return client.invoke( new Api.channels.GetForumTopics({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), offsetDate: 0, offsetId: 0, offsetTopic: 0, @@ -43,7 +44,7 @@ export async function listTopics( ); }); - const topics = (result as unknown as ForumTopicsResult)?.topics; + const topics = narrow(result)?.topics; if (!topics || !Array.isArray(topics) || topics.length === 0) { return { content: [{ type: 'text', text: 'No forum topics found.' }] }; } diff --git a/src/lib/mcp/telegram/tools/pressInlineButton.ts b/src/lib/mcp/telegram/tools/pressInlineButton.ts index 9c0547d84..e22b6c9c4 100644 --- a/src/lib/mcp/telegram/tools/pressInlineButton.ts +++ b/src/lib/mcp/telegram/tools/pressInlineButton.ts @@ -6,6 +6,7 @@ import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import type { BotCallbackAnswer } from '../apiResultTypes'; +import { narrow } from "../apiCastHelpers"; export const tool: MCPTool = { name: "press_inline_button", @@ -52,7 +53,7 @@ export async function pressInlineButton( ); }); - const answer = (result as unknown as BotCallbackAnswer)?.message ?? 'Button pressed (no response message).'; + const answer = narrow(result)?.message ?? 'Button pressed (no response message).'; return { content: [{ type: 'text', text: answer }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/promoteAdmin.ts b/src/lib/mcp/telegram/tools/promoteAdmin.ts index cf1e14feb..829bdebf2 100644 --- a/src/lib/mcp/telegram/tools/promoteAdmin.ts +++ b/src/lib/mcp/telegram/tools/promoteAdmin.ts @@ -5,6 +5,7 @@ import { validateId } from "../../validation"; import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; +import { toInputChannel, toInputUser } from "../apiCastHelpers"; export const tool: MCPTool = { name: "promote_admin", @@ -54,8 +55,8 @@ export async function promoteAdmin( const inputUser = await client.getInputEntity(userId); await client.invoke( new Api.channels.EditAdmin({ - channel: inputChannel as unknown as Api.TypeInputChannel, - userId: inputUser as unknown as Api.TypeInputUser, + channel: toInputChannel(inputChannel), + userId: toInputUser(inputUser), adminRights: new Api.ChatAdminRights({ changeInfo: true, deleteMessages: true, diff --git a/src/lib/mcp/telegram/tools/subscribePublicChannel.ts b/src/lib/mcp/telegram/tools/subscribePublicChannel.ts index 0db3faaf0..07977db98 100644 --- a/src/lib/mcp/telegram/tools/subscribePublicChannel.ts +++ b/src/lib/mcp/telegram/tools/subscribePublicChannel.ts @@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types"; import { ErrorCategory, logAndFormatError } from "../../errorHandler"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; +import { toInputChannel } from "../apiCastHelpers"; export const tool: MCPTool = { name: "subscribe_public_channel", @@ -34,7 +35,7 @@ export async function subscribePublicChannel( const inputChannel = await client.getInputEntity(username); await client.invoke( new Api.channels.JoinChannel({ - channel: inputChannel as unknown as Api.TypeInputChannel, + channel: toInputChannel(inputChannel), }), ); }); diff --git a/src/lib/mcp/telegram/tools/unbanUser.ts b/src/lib/mcp/telegram/tools/unbanUser.ts index 07b7a8a44..f780e882d 100644 --- a/src/lib/mcp/telegram/tools/unbanUser.ts +++ b/src/lib/mcp/telegram/tools/unbanUser.ts @@ -5,6 +5,7 @@ import { validateId } from '../../validation'; import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import { toInputChannel, toInputPeer } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'unban_user', @@ -42,8 +43,8 @@ export async function unbanUser( const inputUser = await client.getInputEntity(userId); await client.invoke( new Api.channels.EditBanned({ - channel: inputChannel as unknown as Api.TypeInputChannel, - participant: inputUser as unknown as Api.TypeInputPeer, + channel: toInputChannel(inputChannel), + participant: toInputPeer(inputUser), bannedRights: new Api.ChatBannedRights({ untilDate: 0 }), }), ); diff --git a/src/lib/mcp/telegram/tools/unblockUser.ts b/src/lib/mcp/telegram/tools/unblockUser.ts index 6bb847cbd..9fe2057ea 100644 --- a/src/lib/mcp/telegram/tools/unblockUser.ts +++ b/src/lib/mcp/telegram/tools/unblockUser.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import { toInputPeer } from '../apiCastHelpers'; export const tool: MCPTool = { name: 'unblock_user', @@ -28,7 +29,7 @@ export async function unblockUser( await mtprotoService.withFloodWaitHandling(async () => { const inputUser = await client.getInputEntity(userId); await client.invoke( - new Api.contacts.Unblock({ id: inputUser as unknown as Api.TypeInputPeer }), + new Api.contacts.Unblock({ id: toInputPeer(inputUser) }), ); });