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.
This commit is contained in:
Steven Enamakel
2026-01-28 09:20:34 +05:30
parent bd1d240738
commit 5a0425c021
39 changed files with 130 additions and 56 deletions
+36
View File
@@ -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<T extends object>(result: object): T {
return result as T;
}
+1 -1
View File
@@ -82,7 +82,7 @@ export interface BotCallbackAnswer {
/** Contact import result */
export interface ImportContactsResult {
imported?: unknown[];
imported?: object[];
}
/** Contact ID entry */
+3 -2
View File
@@ -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,
+2 -1
View File
@@ -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) }),
);
});
+2 -1
View File
@@ -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<ResultWithChats>(result)?.chats?.[0]?.id ?? "unknown";
const type = megagroup ? "Supergroup" : "Channel";
return {
content: [
+3 -2
View File
@@ -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<ResultWithChats>(result)?.chats?.[0]?.id ?? "unknown";
return {
content: [
{ type: "text", text: `Group "${title}" created. Chat ID: ${chatId}` },
@@ -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(),
}),
);
+2 -1
View File
@@ -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)],
}),
);
});
@@ -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<ApiPhoto>(photos.photos[0]);
await mtprotoService.withFloodWaitHandling(async () => {
await client.invoke(
+3 -2
View File
@@ -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: "",
}),
+2 -1
View File
@@ -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,
}),
);
@@ -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<ChatInviteResult>(result)?.link;
if (!link) {
return { content: [{ type: 'text', text: 'Could not create invite link.' }], isError: true };
}
+4 -3
View File
@@ -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<ApiUser[]>(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<ApiUser[]>(result.users);
}
}
+2 -1
View File
@@ -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,
+4 -3
View File
@@ -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<FullUserResult>(result)?.fullUser;
const user = narrow<FullUserResult>(result)?.users?.[0];
if (!user) {
return { content: [{ type: 'text', text: 'Bot not found: ' + botId }], isError: true };
+2 -1
View File
@@ -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<ContactIdResult[]>(result).map((c) =>
String(typeof c === 'number' ? c : c.userId ?? c),
);
return { content: [{ type: 'text', text: ids.length + ' contacts:\n' + ids.join('\n') }] };
+2 -1
View File
@@ -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<UpdatesResult>(result);
if (!updates || !updates.updates || updates.updates.length === 0) {
return { content: [{ type: 'text', text: 'No drafts found.' }] };
}
+3 -2
View File
@@ -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<InlineBotResults>(result)?.results;
if (!results || !Array.isArray(results) || results.length === 0) {
return { content: [{ type: 'text', text: 'No GIFs found for: ' + query }] };
}
+2 -1
View File
@@ -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<ChatInviteResult>(result)?.link;
if (!link) {
return {
content: [{ type: "text", text: "Could not generate invite link." }],
@@ -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<UpdatesResult>(result);
if (!updates || !updates.updates || updates.updates.length === 0) {
return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] };
}
@@ -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<ApiUser[]>(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<ApiUser[]>(result.users);
}
}
@@ -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<ApiMessage[]>(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<ApiMessage>(m).pinned);
pinnedLines = pinned.map((msg) => {
const f = formatMessage(msg);
return `ID: ${f.id} | Date: ${f.date} | ${f.text || '[Media/No text]'}`;
@@ -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<PrivacyResult>(result)?.rules;
if (!rules || !Array.isArray(rules)) {
return { content: [{ type: 'text', text: 'No privacy rules found for ' + keyStr + '.' }] };
}
@@ -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<AdminLogResult>(result)?.events;
if (!events || !Array.isArray(events) || events.length === 0) {
return { content: [{ type: 'text', text: 'No recent actions found.' }] };
}
+2 -1
View File
@@ -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<StickerSetsResult>(result)?.sets;
if (!sets || !Array.isArray(sets) || sets.length === 0) {
return { content: [{ type: 'text', text: 'No sticker sets found.' }] };
}
+3 -2
View File
@@ -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<ApiPhoto[]>(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;
});
+3 -2
View File
@@ -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<ApiUser>(result[0]);
const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
let statusText = 'unknown';
@@ -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<ResultWithChats>(result)?.chats?.[0]?.title ?? "unknown";
return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] };
} catch (error) {
return logAndFormatError(
+2 -1
View File
@@ -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<ImportContactsResult>(result)?.imported?.length ?? 0;
return {
content: [
{
+3 -2
View File
@@ -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,
}),
);
+2 -1
View File
@@ -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<ResultWithChats>(result)?.chats?.[0]?.title ?? 'unknown';
return { content: [{ type: 'text', text: `Joined chat: ${chatTitle}` }] };
} catch (error) {
return logAndFormatError(
+2 -1
View File
@@ -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),
}),
);
});
@@ -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<MessageWithReplyMarkup>(found) : undefined;
if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true };
if (!msg.replyMarkup || !msg.replyMarkup.rows) {
+3 -2
View File
@@ -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<ForumTopicsResult>(result)?.topics;
if (!topics || !Array.isArray(topics) || topics.length === 0) {
return { content: [{ type: 'text', text: 'No forum topics found.' }] };
}
@@ -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<BotCallbackAnswer>(result)?.message ?? 'Button pressed (no response message).';
return { content: [{ type: 'text', text: answer }] };
} catch (error) {
return logAndFormatError(
+3 -2
View File
@@ -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,
@@ -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),
}),
);
});
+3 -2
View File
@@ -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 }),
}),
);
+2 -1
View File
@@ -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) }),
);
});