From 86cc53aa767411ece39886a90cf443ae025eb883 Mon Sep 17 00:00:00 2001 From: Steven Enamakel Date: Wed, 28 Jan 2026 07:31:35 +0530 Subject: [PATCH] Add CLAUDE.md and apiResultTypes.ts for Telegram MCP documentation and type definitions - Created CLAUDE.md to provide comprehensive guidance on the project, including architecture, commands, and environment variables. - Introduced apiResultTypes.ts to define TypeScript interfaces for various API responses from the Telegram MTProto API, enhancing type safety and clarity in tool implementations. - Updated multiple Telegram MCP tools to utilize the new type definitions, improving consistency and maintainability across the codebase. These changes establish a solid foundation for documentation and type safety, facilitating better development practices and integration with the Telegram API. --- CLAUDE.md | 127 +++++++++++++++++ src/lib/mcp/telegram/apiResultTypes.ts | 130 ++++++++++++++++++ src/lib/mcp/telegram/tools/createChannel.ts | 3 +- src/lib/mcp/telegram/tools/createGroup.ts | 3 +- .../mcp/telegram/tools/deleteProfilePhoto.ts | 3 +- .../mcp/telegram/tools/exportChatInvite.ts | 3 +- src/lib/mcp/telegram/tools/exportContacts.ts | 3 +- src/lib/mcp/telegram/tools/getAdmins.ts | 9 +- src/lib/mcp/telegram/tools/getBannedUsers.ts | 3 +- src/lib/mcp/telegram/tools/getBlockedUsers.ts | 3 +- src/lib/mcp/telegram/tools/getBotInfo.ts | 5 +- 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 | 9 +- .../mcp/telegram/tools/getPinnedMessages.ts | 5 +- .../mcp/telegram/tools/getPrivacySettings.ts | 5 +- .../mcp/telegram/tools/getRecentActions.ts | 5 +- src/lib/mcp/telegram/tools/getStickerSets.ts | 5 +- src/lib/mcp/telegram/tools/getUserPhotos.ts | 3 +- src/lib/mcp/telegram/tools/getUserStatus.ts | 3 +- .../mcp/telegram/tools/importChatInvite.ts | 3 +- src/lib/mcp/telegram/tools/importContacts.ts | 5 +- src/lib/mcp/telegram/tools/joinChatByLink.ts | 3 +- src/lib/mcp/telegram/tools/listContacts.ts | 3 +- .../mcp/telegram/tools/listInlineButtons.ts | 7 +- src/lib/mcp/telegram/tools/listTopics.ts | 5 +- .../mcp/telegram/tools/pressInlineButton.ts | 3 +- src/lib/mcp/telegram/tools/searchContacts.ts | 3 +- src/lib/mcp/telegram/tools/setBotCommands.ts | 3 +- 32 files changed, 333 insertions(+), 46 deletions(-) create mode 100644 CLAUDE.md create mode 100644 src/lib/mcp/telegram/apiResultTypes.ts diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..662fa39dc --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,127 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Summary + +Cross-platform crypto community communication platform built with **Tauri v2** (React 19 + Rust). Targets desktop (Windows, macOS) and mobile (Android, iOS). Features deep Telegram integration via MTProto, real-time Socket.io communication, and an MCP (Model Context Protocol) tool system for AI-driven Telegram interactions. + +## Commands + +```bash +# Frontend dev server only (port 1420) +npm run dev + +# Desktop dev with hot-reload (starts Vite + Tauri) +npm run tauri dev + +# Production build (TypeScript compile + Vite build + Tauri bundle) +npm run tauri build + +# Debug build with .app bundle (required for deep link testing on macOS) +npm run tauri build -- --debug --bundles app + +# Android +npm run tauri android dev +npm run tauri android build + +# iOS +npm run tauri ios dev +npm run tauri ios build + +# Rust checks +cargo check --manifest-path src-tauri/Cargo.toml +cargo clippy --manifest-path src-tauri/Cargo.toml +``` + +No test framework is currently configured. No ESLint or Prettier configuration exists in the repo. + +## Architecture + +### Provider Chain (App.tsx) + +The app wraps in this order: `Redux Provider` → `PersistGate` → `SocketProvider` → `TelegramProvider` → `BrowserRouter` → `AppRoutes`. This ordering matters because Socket.io and Telegram providers depend on Redux auth state. + +### State Management (Redux Toolkit + Persist) + +State lives in `src/store/` using Redux Toolkit slices: +- **authSlice** — JWT token, onboarding completion flag (persisted) +- **userSlice** — user profile +- **socketSlice** — connection status, socket ID +- **telegramSlice** — connection/auth status, chats, messages, threads (selectively persisted; loading/error states excluded) + +Redux Persist stores auth and telegram state to localStorage. The telegram slice has a complex nested structure in `src/store/telegram/` with separate files for types, reducers, extraReducers, and thunks. + +### Service Layer (Singletons) + +- **mtprotoService** (`src/services/mtprotoService.ts`) — Telegram MTProto client via `telegram` npm package. Session stored in localStorage as `telegram_session`. Auto-retries FLOOD_WAIT up to 60s. +- **socketService** (`src/services/socketService.ts`) — Socket.io client. Auth token passed in socket `auth` object (not query string). Transports: polling first, then WebSocket. +- **apiClient** (`src/services/apiClient.ts`) — HTTP client for REST backend. + +### MCP System (`src/lib/mcp/`) + +Model Context Protocol implementation for AI tool execution over Socket.io: +- `transport.ts` — Socket.io JSON-RPC 2.0 transport with 30s timeout +- `telegram/server.ts` — TelegramMCPServer manages 99 tool definitions +- `telegram/tools/` — Individual tool files (one per Telegram API operation) +- Tools use `big-integer` library for Telegram's large integer IDs + +### Routing (`src/AppRoutes.tsx`) + +``` +/ → Welcome (public) +/login → Login (public) +/onboarding → Onboarding (protected, requires auth, not yet onboarded) +/home → Home (protected, requires auth + onboarded) +* → DefaultRedirect (routes based on auth state) +``` + +`PublicRoute` redirects authenticated users away. `ProtectedRoute` enforces auth and optionally onboarding status. + +### Deep Link Auth Flow + +Web-to-desktop handoff using `outsourced://` URL scheme: +1. User authenticates in browser +2. Browser redirects to `outsourced://auth?token=` +3. Tauri catches the deep link, Rust `exchange_token` command calls backend via `reqwest` (bypasses CORS) +4. Backend returns `sessionToken` + user object +5. App stores session in Redux, navigates to onboarding/home + +Key file: `src/utils/desktopDeepLinkListener.ts` (lazy-loaded in `main.tsx`). Uses `localStorage.deepLinkHandled` flag to prevent infinite reload loops. Deep links do NOT work in `tauri dev` on macOS — must use built `.app` bundle. + +### Rust Backend (`src-tauri/src/lib.rs`) + +Minimal — two Tauri commands: +- `greet` — demo command +- `exchange_token` — CORS-free HTTP POST to backend for token exchange + +Deep link plugin registered at setup. `register_all()` called only on Windows/Linux (panics on macOS). + +## Environment Variables + +Set in `.env` (Vite exposes `VITE_*` prefixed vars): + +| Variable | Purpose | +|----------|---------| +| `VITE_BACKEND_URL` | Backend API URL (default: `http://localhost:5005`) | +| `VITE_TELEGRAM_API_ID` | Telegram MTProto API ID | +| `VITE_TELEGRAM_API_HASH` | Telegram MTProto API hash | +| `VITE_TELEGRAM_BOT_USERNAME` | Telegram bot username | +| `VITE_TELEGRAM_BOT_ID` | Telegram bot numeric ID | +| `VITE_DEBUG` | Debug mode flag | + +Production defaults are in `src/utils/config.ts`. + +## Key Patterns + +- **Node polyfills**: Vite config (`vite.config.ts`) polyfills `buffer`, `process`, `util`, `os`, `crypto`, `stream` for the `telegram` npm package which requires Node APIs. +- **Telegram IDs**: Use `big-integer` library, not native JS numbers (Telegram IDs exceed `Number.MAX_SAFE_INTEGER`). +- **MCP tool files**: Each tool in `src/lib/mcp/telegram/tools/` exports a handler conforming to `TelegramMCPToolHandler` interface. Tool names are typed in `src/lib/mcp/telegram/types.ts`. +- **Tauri IPC**: Frontend calls Rust via `invoke()` from `@tauri-apps/api/core`. Rust commands are registered in `generate_handler![]` macro. +- **CORS workaround**: External HTTP requests from the WebView hit CORS. Use Rust `reqwest` via Tauri commands instead of browser `fetch()`. + +## Platform Gotchas + +- **macOS deep links**: Require `.app` bundle (not `tauri dev`). Clear WebKit caches when debugging stale content: `rm -rf ~/Library/WebKit/com.megamind.tauri-app ~/Library/Caches/com.megamind.tauri-app` +- **Cargo caching**: May serve stale frontend assets on incremental builds. Run `cargo clean --manifest-path src-tauri/Cargo.toml` if the app shows outdated UI. +- **`window.__TAURI__`**: Not available at module load time. Use dynamic `import()` and try/catch for Tauri plugin calls. diff --git a/src/lib/mcp/telegram/apiResultTypes.ts b/src/lib/mcp/telegram/apiResultTypes.ts new file mode 100644 index 000000000..5b04363d1 --- /dev/null +++ b/src/lib/mcp/telegram/apiResultTypes.ts @@ -0,0 +1,130 @@ +import type bigInt from "big-integer"; + +/** User object as returned by Telegram MTProto API */ +export interface ApiUser { + id: bigInt.BigInteger; + firstName?: string; + lastName?: string; + username?: string; + phone?: string; + bot?: boolean; + status?: { + className?: string; + wasOnline?: number; + }; +} + +/** Result containing chats array (CreateChat, CreateChannel, ImportChatInvite) */ +export interface ResultWithChats { + chats?: Array<{ id: bigInt.BigInteger; title?: string }>; +} + +/** Full user info from users.GetFullUser */ +export interface FullUserResult { + fullUser?: { + about?: string; + botInfo?: { + description?: string; + commands?: Array<{ command: string; description: string }>; + }; + }; + users?: ApiUser[]; +} + +/** Sticker sets from messages.GetAllStickers */ +export interface StickerSetsResult { + sets?: Array<{ id: bigInt.BigInteger; title?: string; count?: number }>; +} + +/** Admin log from channels.GetAdminLog */ +export interface AdminLogResult { + events?: Array<{ date?: number; action?: { className?: string } }>; +} + +/** Forum topics from channels.GetForumTopics */ +export interface ForumTopicsResult { + topics?: Array<{ id: number; title?: string }>; +} + +/** Privacy rules from account.GetPrivacy */ +export interface PrivacyResult { + rules?: Array<{ className?: string }>; +} + +/** Inline bot results from messages.GetInlineBotResults */ +export interface InlineBotResults { + results?: Array<{ title?: string; description?: string }>; +} + +/** Chat invite from messages.ExportChatInvite */ +export interface ChatInviteResult { + link?: string; +} + +/** Updates result (drafts, reactions) */ +export interface UpdatesResult { + updates?: Array<{ + draft?: { message?: string }; + peer?: { userId?: number; chatId?: number; channelId?: number }; + reactions?: { + results?: Array<{ + reaction?: { emoticon?: string; className?: string }; + count?: number; + }>; + }; + }>; +} + +/** Bot callback answer */ +export interface BotCallbackAnswer { + message?: string; +} + +/** Contact import result */ +export interface ImportContactsResult { + imported?: unknown[]; +} + +/** Contact ID entry */ +export interface ContactIdEntry { + userId?: number; +} + +/** Photo object from the API */ +export interface ApiPhoto { + id: bigInt.BigInteger; + date?: number; + accessHash?: bigInt.BigInteger; + fileReference?: Buffer; +} + +/** API message shape (from search results) */ +export interface ApiMessage { + id: number; + message?: string; + date?: number; + pinned?: boolean; +} + +/** Reply markup types for inline buttons */ +export interface ReplyMarkupRow { + buttons?: Array<{ text?: string }>; +} + +export interface MessageWithReplyMarkup { + id: string | number; + replyMarkup?: { rows?: ReplyMarkupRow[] }; +} + +/** Bot command input shape */ +export interface BotCommandInput { + command?: string; + description?: string; +} + +/** Contact input shape (for importContacts) */ +export interface ContactInput { + phone?: string; + first_name?: string; + last_name?: string; +} diff --git a/src/lib/mcp/telegram/tools/createChannel.ts b/src/lib/mcp/telegram/tools/createChannel.ts index 6ffc515a0..ecf1c321c 100644 --- a/src/lib/mcp/telegram/tools/createChannel.ts +++ b/src/lib/mcp/telegram/tools/createChannel.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import { optString } from "../args"; +import type { ResultWithChats } from "../apiResultTypes"; export const tool: MCPTool = { name: "create_channel", @@ -50,7 +51,7 @@ export async function createChannel( ); }); - const channelId = (result as any)?.chats?.[0]?.id ?? "unknown"; + const channelId = (result as unknown as ResultWithChats)?.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 8ed382651..8524bc725 100644 --- a/src/lib/mcp/telegram/tools/createGroup.ts +++ b/src/lib/mcp/telegram/tools/createGroup.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 type { ResultWithChats } from "../apiResultTypes"; export const tool: MCPTool = { name: "create_group", @@ -52,7 +53,7 @@ export async function createGroup( return client.invoke(new Api.messages.CreateChat({ title, users })); }); - const chatId = (result as any)?.chats?.[0]?.id ?? "unknown"; + const chatId = (result as unknown as ResultWithChats)?.chats?.[0]?.id ?? "unknown"; return { content: [ { type: "text", text: `Group "${title}" created. Chat ID: ${chatId}` }, diff --git a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts index a6b141341..8c3754161 100644 --- a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts +++ b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; +import type { ApiPhoto } from '../apiResultTypes'; export const tool: MCPTool = { name: "delete_profile_photo", @@ -33,7 +34,7 @@ export async function deleteProfilePhoto( return { content: [{ type: 'text', text: 'No profile photo to delete.' }] }; } - const photo = photos.photos[0] as any; + const photo = photos.photos[0] as unknown as ApiPhoto; await mtprotoService.withFloodWaitHandling(async () => { await client.invoke( diff --git a/src/lib/mcp/telegram/tools/exportChatInvite.ts b/src/lib/mcp/telegram/tools/exportChatInvite.ts index 21184ff78..91fc6e15a 100644 --- a/src/lib/mcp/telegram/tools/exportChatInvite.ts +++ b/src/lib/mcp/telegram/tools/exportChatInvite.ts @@ -6,6 +6,7 @@ import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import { optString } from '../args'; +import type { ChatInviteResult } from '../apiResultTypes'; export const tool: MCPTool = { name: 'export_chat_invite', @@ -50,7 +51,7 @@ export async function exportChatInvite( ); }); - const link = (result as any)?.link; + const link = (result as unknown as ChatInviteResult)?.link; if (!link) { return { content: [{ type: 'text', text: 'Could not create invite link.' }], isError: true }; } diff --git a/src/lib/mcp/telegram/tools/exportContacts.ts b/src/lib/mcp/telegram/tools/exportContacts.ts index 025d6d9a7..f124d2698 100644 --- a/src/lib/mcp/telegram/tools/exportContacts.ts +++ b/src/lib/mcp/telegram/tools/exportContacts.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import type { ApiUser } from "../apiResultTypes"; export const tool: MCPTool = { name: "export_contacts", @@ -31,7 +32,7 @@ export async function exportContacts( return { content: [{ type: "text", text: "No contacts to export." }] }; } - const contacts = result.users.map((u: any) => ({ + const contacts = result.users.map((u: ApiUser) => ({ id: String(u.id), firstName: u.firstName ?? "", lastName: u.lastName ?? "", diff --git a/src/lib/mcp/telegram/tools/getAdmins.ts b/src/lib/mcp/telegram/tools/getAdmins.ts index 13230c0bc..19de0b467 100644 --- a/src/lib/mcp/telegram/tools/getAdmins.ts +++ b/src/lib/mcp/telegram/tools/getAdmins.ts @@ -6,6 +6,7 @@ import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import type { ApiUser } from "../apiResultTypes"; export const tool: MCPTool = { name: "get_admins", @@ -36,7 +37,7 @@ export async function getAdmins( const client = mtprotoService.getClient(); const entity = chat.username ? chat.username : chat.id; - let admins: any[] = []; + let admins: ApiUser[] = []; if (chat.type === "channel" || chat.type === "supergroup") { const result = await mtprotoService.withFloodWaitHandling(async () => { @@ -52,7 +53,7 @@ export async function getAdmins( ); }); if (result && "users" in result && Array.isArray(result.users)) { - admins = result.users; + admins = result.users as unknown as ApiUser[]; } } else { const result = await mtprotoService.withFloodWaitHandling(async () => { @@ -61,7 +62,7 @@ export async function getAdmins( ); }); if (result && "users" in result && Array.isArray(result.users)) { - admins = result.users; + admins = result.users as unknown as ApiUser[]; } } @@ -69,7 +70,7 @@ export async function getAdmins( return { content: [{ type: "text", text: "No admins found." }] }; } - const lines = admins.map((u: any) => { + const lines = admins.map((u: ApiUser) => { const name = [u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown"; const username = u.username ? `@${u.username}` : ""; diff --git a/src/lib/mcp/telegram/tools/getBannedUsers.ts b/src/lib/mcp/telegram/tools/getBannedUsers.ts index 9b77c720b..13be37cbb 100644 --- a/src/lib/mcp/telegram/tools/getBannedUsers.ts +++ b/src/lib/mcp/telegram/tools/getBannedUsers.ts @@ -7,6 +7,7 @@ import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import { optNumber } from "../args"; import bigInt from "big-integer"; +import type { ApiUser } from "../apiResultTypes"; export const tool: MCPTool = { name: "get_banned_users", @@ -73,7 +74,7 @@ export async function getBannedUsers( return { content: [{ type: "text", text: "No banned users found." }] }; } - const lines = result.users.map((u: any) => { + const lines = result.users.map((u: ApiUser) => { const name = [u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown"; const username = u.username ? `@${u.username}` : ""; diff --git a/src/lib/mcp/telegram/tools/getBlockedUsers.ts b/src/lib/mcp/telegram/tools/getBlockedUsers.ts index d23dcd1de..bdb09121a 100644 --- a/src/lib/mcp/telegram/tools/getBlockedUsers.ts +++ b/src/lib/mcp/telegram/tools/getBlockedUsers.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import { optNumber } from '../args'; +import type { ApiUser } from '../apiResultTypes'; export const tool: MCPTool = { name: 'get_blocked_users', @@ -34,7 +35,7 @@ export async function getBlockedUsers( return { content: [{ type: 'text', text: 'No blocked users.' }] }; } - const lines = result.users.map((u: any) => { + const lines = result.users.map((u: ApiUser) => { const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown'; const username = u.username ? `@${u.username}` : ''; return `ID: ${u.id} | ${name} ${username}`.trim(); diff --git a/src/lib/mcp/telegram/tools/getBotInfo.ts b/src/lib/mcp/telegram/tools/getBotInfo.ts index 2db9ee949..8190ccd2d 100644 --- a/src/lib/mcp/telegram/tools/getBotInfo.ts +++ b/src/lib/mcp/telegram/tools/getBotInfo.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import type { FullUserResult } from '../apiResultTypes'; export const tool: MCPTool = { name: "get_bot_info", @@ -32,8 +33,8 @@ export async function getBotInfo( ); }); - const fullUser = (result as any)?.fullUser; - const user = (result as any)?.users?.[0]; + const fullUser = (result as unknown as FullUserResult)?.fullUser; + const user = (result as unknown as FullUserResult)?.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 277ef699a..1d8bb1929 100644 --- a/src/lib/mcp/telegram/tools/getContactIds.ts +++ b/src/lib/mcp/telegram/tools/getContactIds.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; +import type { ContactIdEntry } from '../apiResultTypes'; export const tool: MCPTool = { name: 'get_contact_ids', @@ -26,7 +27,7 @@ export async function getContactIds( return { content: [{ type: 'text', text: 'No contact IDs found.' }] }; } - const ids = result.map((c: any) => String(c.userId ?? c)); + const ids = result.map((c: ContactIdEntry) => String(c.userId ?? c)); return { content: [{ type: 'text', text: ids.length + ' contacts:\n' + ids.join('\n') }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/getDrafts.ts b/src/lib/mcp/telegram/tools/getDrafts.ts index 4b28d0b3e..e8dfdbca6 100644 --- a/src/lib/mcp/telegram/tools/getDrafts.ts +++ b/src/lib/mcp/telegram/tools/getDrafts.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 type { UpdatesResult } from '../apiResultTypes'; export const tool: MCPTool = { name: "get_drafts", @@ -21,7 +22,7 @@ export async function getDrafts( return client.invoke(new Api.messages.GetAllDrafts()); }); - const updates = result as any; + const updates = result as unknown as UpdatesResult; 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 5b910989a..849690227 100644 --- a/src/lib/mcp/telegram/tools/getGifSearch.ts +++ b/src/lib/mcp/telegram/tools/getGifSearch.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import { optNumber } from '../args'; +import type { InlineBotResults } from '../apiResultTypes'; export const tool: MCPTool = { name: "get_gif_search", @@ -41,12 +42,12 @@ export async function getGifSearch( ); }); - const results = (result as any)?.results; + const results = (result as unknown as InlineBotResults)?.results; if (!results || !Array.isArray(results) || results.length === 0) { return { content: [{ type: 'text', text: 'No GIFs found for: ' + query }] }; } - const lines = results.slice(0, limit).map((r: any, i: number) => { + const lines = results.slice(0, limit).map((r, i: number) => { const title = r.title ?? r.description ?? 'GIF ' + (i + 1); return (i + 1) + '. ' + title; }); diff --git a/src/lib/mcp/telegram/tools/getInviteLink.ts b/src/lib/mcp/telegram/tools/getInviteLink.ts index 06e2af14e..6a385a13d 100644 --- a/src/lib/mcp/telegram/tools/getInviteLink.ts +++ b/src/lib/mcp/telegram/tools/getInviteLink.ts @@ -5,6 +5,7 @@ import { validateId } from "../../validation"; import { getChatById } from "../telegramApi"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; +import type { ChatInviteResult } from "../apiResultTypes"; export const tool: MCPTool = { name: "get_invite_link", @@ -45,7 +46,7 @@ export async function getInviteLink( ); }); - const link = (result as any)?.link; + const link = (result as unknown as ChatInviteResult)?.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 0b296cde5..dea5dbd6b 100644 --- a/src/lib/mcp/telegram/tools/getMessageReactions.ts +++ b/src/lib/mcp/telegram/tools/getMessageReactions.ts @@ -5,6 +5,7 @@ import { validateId } from '../../validation'; import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import type { UpdatesResult } from '../apiResultTypes'; export const tool: MCPTool = { name: 'get_message_reactions', @@ -47,7 +48,7 @@ export async function getMessageReactions( ); }); - const updates = result as any; + const updates = result as unknown as UpdatesResult; 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 f46fdf070..e10cda991 100644 --- a/src/lib/mcp/telegram/tools/getParticipants.ts +++ b/src/lib/mcp/telegram/tools/getParticipants.ts @@ -7,6 +7,7 @@ import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import { optNumber } from "../args"; import bigInt from "big-integer"; +import type { ApiUser } from "../apiResultTypes"; export const tool: MCPTool = { name: "get_participants", @@ -39,7 +40,7 @@ export async function getParticipants( const client = mtprotoService.getClient(); const entity = chat.username ? chat.username : chat.id; - let participants: any[] = []; + let participants: ApiUser[] = []; if (chat.type === "channel" || chat.type === "supergroup") { const result = await mtprotoService.withFloodWaitHandling(async () => { @@ -55,7 +56,7 @@ export async function getParticipants( ); }); if (result && "users" in result && Array.isArray(result.users)) { - participants = result.users; + participants = result.users as unknown as ApiUser[]; } } else { const result = await mtprotoService.withFloodWaitHandling(async () => { @@ -64,7 +65,7 @@ export async function getParticipants( ); }); if (result && "users" in result && Array.isArray(result.users)) { - participants = result.users; + participants = result.users as unknown as ApiUser[]; } } @@ -72,7 +73,7 @@ export async function getParticipants( return { content: [{ type: "text", text: "No participants found." }] }; } - const lines = participants.map((u: any) => { + const lines = participants.map((u: ApiUser) => { const name = [u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown"; const username = u.username ? `@${u.username}` : ""; diff --git a/src/lib/mcp/telegram/tools/getPinnedMessages.ts b/src/lib/mcp/telegram/tools/getPinnedMessages.ts index a78008625..f978c4297 100644 --- a/src/lib/mcp/telegram/tools/getPinnedMessages.ts +++ b/src/lib/mcp/telegram/tools/getPinnedMessages.ts @@ -6,6 +6,7 @@ import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; +import type { ApiMessage } from '../apiResultTypes'; export const tool: MCPTool = { name: 'get_pinned_messages', @@ -53,7 +54,7 @@ export async function getPinnedMessages( ); if ('messages' in result && Array.isArray(result.messages)) { - pinnedLines = result.messages.map((msg: any) => { + pinnedLines = (result.messages as unknown as ApiMessage[]).map((msg) => { const id = msg.id ?? '?'; const text = msg.message ?? '[Media/No text]'; const date = msg.date ? new Date(msg.date * 1000).toISOString() : 'unknown'; @@ -64,7 +65,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: any) => m.pinned); + const pinned = allMessages.filter((m) => (m as unknown as ApiMessage).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 496a7f7c4..5063bfd87 100644 --- a/src/lib/mcp/telegram/tools/getPrivacySettings.ts +++ b/src/lib/mcp/telegram/tools/getPrivacySettings.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 type { PrivacyResult } from '../apiResultTypes'; export const tool: MCPTool = { name: "get_privacy_settings", @@ -41,12 +42,12 @@ export async function getPrivacySettings( return client.invoke(new Api.account.GetPrivacy({ key })); }); - const rules = (result as any)?.rules; + const rules = (result as unknown as PrivacyResult)?.rules; if (!rules || !Array.isArray(rules)) { return { content: [{ type: 'text', text: 'No privacy rules found for ' + keyStr + '.' }] }; } - const lines = rules.map((r: any) => r.className ?? 'Unknown rule'); + const lines = rules.map((r) => r.className ?? 'Unknown rule'); return { content: [{ type: 'text', text: 'Privacy settings for ' + keyStr + ':\n' + lines.join('\n') }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/getRecentActions.ts b/src/lib/mcp/telegram/tools/getRecentActions.ts index 5ccd36df8..2fc7e89be 100644 --- a/src/lib/mcp/telegram/tools/getRecentActions.ts +++ b/src/lib/mcp/telegram/tools/getRecentActions.ts @@ -7,6 +7,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; import { optNumber } from '../args'; +import type { AdminLogResult } from '../apiResultTypes'; export const tool: MCPTool = { name: "get_recent_actions", @@ -52,12 +53,12 @@ export async function getRecentActions( ); }); - const events = (result as any)?.events; + const events = (result as unknown as AdminLogResult)?.events; if (!events || !Array.isArray(events) || events.length === 0) { return { content: [{ type: 'text', text: 'No recent actions found.' }] }; } - const lines = events.map((e: any) => { + const lines = events.map((e) => { const date = e.date ? new Date(e.date * 1000).toISOString() : 'unknown'; const action = e.action?.className ?? 'unknown'; return date + ' | ' + action; diff --git a/src/lib/mcp/telegram/tools/getStickerSets.ts b/src/lib/mcp/telegram/tools/getStickerSets.ts index ed34cb4ee..35f79b920 100644 --- a/src/lib/mcp/telegram/tools/getStickerSets.ts +++ b/src/lib/mcp/telegram/tools/getStickerSets.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; +import type { StickerSetsResult } from '../apiResultTypes'; export const tool: MCPTool = { name: "get_sticker_sets", @@ -22,12 +23,12 @@ export async function getStickerSets( return client.invoke(new Api.messages.GetAllStickers({ hash: bigInt(0) })); }); - const sets = (result as any)?.sets; + const sets = (result as unknown as StickerSetsResult)?.sets; if (!sets || !Array.isArray(sets) || sets.length === 0) { return { content: [{ type: 'text', text: 'No sticker sets found.' }] }; } - const lines = sets.map((s: any) => { + const lines = sets.map((s) => { return 'ID: ' + s.id + ' | ' + (s.title ?? 'Untitled') + ' (' + (s.count ?? 0) + ' stickers)'; }); diff --git a/src/lib/mcp/telegram/tools/getUserPhotos.ts b/src/lib/mcp/telegram/tools/getUserPhotos.ts index 98e823ba4..18279ed9e 100644 --- a/src/lib/mcp/telegram/tools/getUserPhotos.ts +++ b/src/lib/mcp/telegram/tools/getUserPhotos.ts @@ -6,6 +6,7 @@ import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; import { optNumber } from '../args'; +import type { ApiPhoto } from '../apiResultTypes'; export const tool: MCPTool = { name: 'get_user_photos', @@ -45,7 +46,7 @@ export async function getUserPhotos( return { content: [{ type: 'text', text: 'No photos found.' }] }; } - const lines = result.photos.map((photo: any, i: number) => { + const lines = result.photos.map((photo: ApiPhoto, 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 d361d827a..2a1c939b3 100644 --- a/src/lib/mcp/telegram/tools/getUserStatus.ts +++ b/src/lib/mcp/telegram/tools/getUserStatus.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import type { ApiUser } from '../apiResultTypes'; export const tool: MCPTool = { name: 'get_user_status', @@ -36,7 +37,7 @@ export async function getUserStatus( return { content: [{ type: 'text', text: 'User ' + userId + ' not found.' }], isError: true }; } - const user = result[0] as any; + const user = result[0] as unknown as ApiUser; 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 49a2452f8..cb27c6aa9 100644 --- a/src/lib/mcp/telegram/tools/importChatInvite.ts +++ b/src/lib/mcp/telegram/tools/importChatInvite.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 type { ResultWithChats } from "../apiResultTypes"; export const tool: MCPTool = { name: "import_chat_invite", @@ -37,7 +38,7 @@ export async function importChatInvite( return client.invoke(new Api.messages.ImportChatInvite({ hash })); }); - const chatTitle = (result as any)?.chats?.[0]?.title ?? "unknown"; + const chatTitle = (result as unknown as ResultWithChats)?.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 6d2debc67..c4eac5c5d 100644 --- a/src/lib/mcp/telegram/tools/importContacts.ts +++ b/src/lib/mcp/telegram/tools/importContacts.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from "../../errorHandler"; import { mtprotoService } from "../../../../services/mtprotoService"; import { Api } from "telegram"; import bigInt from "big-integer"; +import type { ContactInput, ImportContactsResult } from "../apiResultTypes"; export const tool: MCPTool = { name: "import_contacts", @@ -48,7 +49,7 @@ export async function importContacts( } const inputContacts = contactsArg.map( - (c: any, i: number) => + (c: ContactInput, i: number) => new Api.InputPhoneContact({ clientId: bigInt(i), phone: String(c.phone ?? ""), @@ -65,7 +66,7 @@ export async function importContacts( ); }); - const imported = (result as any)?.imported?.length ?? 0; + const imported = (result as unknown as ImportContactsResult)?.imported?.length ?? 0; return { content: [ { diff --git a/src/lib/mcp/telegram/tools/joinChatByLink.ts b/src/lib/mcp/telegram/tools/joinChatByLink.ts index b617228fd..ba3d58707 100644 --- a/src/lib/mcp/telegram/tools/joinChatByLink.ts +++ b/src/lib/mcp/telegram/tools/joinChatByLink.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 type { ResultWithChats } from '../apiResultTypes'; export const tool: MCPTool = { name: 'join_chat_by_link', @@ -37,7 +38,7 @@ export async function joinChatByLink( return client.invoke(new Api.messages.ImportChatInvite({ hash })); }); - const chatTitle = (result as any)?.chats?.[0]?.title ?? 'unknown'; + const chatTitle = (result as unknown as ResultWithChats)?.chats?.[0]?.title ?? 'unknown'; return { content: [{ type: 'text', text: `Joined chat: ${chatTitle}` }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/listContacts.ts b/src/lib/mcp/telegram/tools/listContacts.ts index b855e25da..27fb8c36d 100644 --- a/src/lib/mcp/telegram/tools/listContacts.ts +++ b/src/lib/mcp/telegram/tools/listContacts.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import bigInt from 'big-integer'; +import type { ApiUser } from '../apiResultTypes'; export const tool: MCPTool = { name: 'list_contacts', @@ -26,7 +27,7 @@ export async function listContacts( return { content: [{ type: 'text', text: 'No contacts found.' }] }; } - const lines = result.users.map((u: any) => { + const lines = result.users.map((u: ApiUser) => { const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown'; const username = u.username ? `@${u.username}` : ''; const phone = u.phone ? `+${u.phone}` : ''; diff --git a/src/lib/mcp/telegram/tools/listInlineButtons.ts b/src/lib/mcp/telegram/tools/listInlineButtons.ts index e302f1fa0..3b688fda6 100644 --- a/src/lib/mcp/telegram/tools/listInlineButtons.ts +++ b/src/lib/mcp/telegram/tools/listInlineButtons.ts @@ -3,6 +3,7 @@ import type { TelegramMCPContext } from "../types"; import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { validateId } from '../../validation'; import { getChatById, getMessages } from '../telegramApi'; +import type { MessageWithReplyMarkup, ReplyMarkupRow } from '../apiResultTypes'; export const tool: MCPTool = { name: "list_inline_buttons", @@ -35,7 +36,7 @@ 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 any; + const msg = messages.find((m) => String(m.id) === String(messageId)) as unknown as MessageWithReplyMarkup | undefined; if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true }; if (!msg.replyMarkup || !msg.replyMarkup.rows) { @@ -43,9 +44,9 @@ export async function listInlineButtons( } const lines: string[] = []; - msg.replyMarkup.rows.forEach((row: any, ri: number) => { + msg.replyMarkup.rows.forEach((row: ReplyMarkupRow, ri: number) => { if (row.buttons) { - row.buttons.forEach((btn: any, bi: number) => { + row.buttons.forEach((btn, bi: number) => { lines.push('Row ' + ri + ', Button ' + bi + ': "' + (btn.text ?? '?') + '"'); }); } diff --git a/src/lib/mcp/telegram/tools/listTopics.ts b/src/lib/mcp/telegram/tools/listTopics.ts index 0cd38a4c5..96f33af7b 100644 --- a/src/lib/mcp/telegram/tools/listTopics.ts +++ b/src/lib/mcp/telegram/tools/listTopics.ts @@ -5,6 +5,7 @@ import { validateId } from '../../validation'; import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import type { ForumTopicsResult } from '../apiResultTypes'; export const tool: MCPTool = { name: 'list_topics', @@ -42,12 +43,12 @@ export async function listTopics( ); }); - const topics = (result as any)?.topics; + const topics = (result as unknown as ForumTopicsResult)?.topics; if (!topics || !Array.isArray(topics) || topics.length === 0) { return { content: [{ type: 'text', text: 'No forum topics found.' }] }; } - const lines = topics.map((t: any) => { + const lines = topics.map((t) => { return 'ID: ' + t.id + ' | ' + (t.title ?? 'Untitled'); }); diff --git a/src/lib/mcp/telegram/tools/pressInlineButton.ts b/src/lib/mcp/telegram/tools/pressInlineButton.ts index 4ecf1bed6..9c0547d84 100644 --- a/src/lib/mcp/telegram/tools/pressInlineButton.ts +++ b/src/lib/mcp/telegram/tools/pressInlineButton.ts @@ -5,6 +5,7 @@ import { validateId } from '../../validation'; import { getChatById } from '../telegramApi'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; +import type { BotCallbackAnswer } from '../apiResultTypes'; export const tool: MCPTool = { name: "press_inline_button", @@ -51,7 +52,7 @@ export async function pressInlineButton( ); }); - const answer = (result as any)?.message ?? 'Button pressed (no response message).'; + const answer = (result as unknown as BotCallbackAnswer)?.message ?? 'Button pressed (no response message).'; return { content: [{ type: 'text', text: answer }] }; } catch (error) { return logAndFormatError( diff --git a/src/lib/mcp/telegram/tools/searchContacts.ts b/src/lib/mcp/telegram/tools/searchContacts.ts index b05f9d924..595314200 100644 --- a/src/lib/mcp/telegram/tools/searchContacts.ts +++ b/src/lib/mcp/telegram/tools/searchContacts.ts @@ -4,6 +4,7 @@ import { ErrorCategory, logAndFormatError } from '../../errorHandler'; import { mtprotoService } from '../../../../services/mtprotoService'; import { Api } from 'telegram'; import { optNumber } from '../args'; +import type { ApiUser } from '../apiResultTypes'; export const tool: MCPTool = { name: 'search_contacts', @@ -38,7 +39,7 @@ export async function searchContacts( return { content: [{ type: 'text', text: `No contacts found for "${query}".` }] }; } - const lines = result.users.map((u: any) => { + const lines = result.users.map((u: ApiUser) => { const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown'; const username = u.username ? `@${u.username}` : ''; return `ID: ${u.id} | ${name} ${username}`.trim(); diff --git a/src/lib/mcp/telegram/tools/setBotCommands.ts b/src/lib/mcp/telegram/tools/setBotCommands.ts index 3218da940..7d15d0ac1 100644 --- a/src/lib/mcp/telegram/tools/setBotCommands.ts +++ b/src/lib/mcp/telegram/tools/setBotCommands.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 type { BotCommandInput } from '../apiResultTypes'; export const tool: MCPTool = { name: "set_bot_commands", @@ -27,7 +28,7 @@ export async function setBotCommands( const client = mtprotoService.getClient(); - const botCommands = cmds.map((c: any) => + const botCommands = cmds.map((c: BotCommandInput) => new Api.BotCommand({ command: String(c.command ?? ''), description: String(c.description ?? '') }), );