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.
This commit is contained in:
Steven Enamakel
2026-01-28 07:31:35 +05:30
parent d7d848df36
commit 86cc53aa76
32 changed files with 333 additions and 46 deletions
+127
View File
@@ -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=<loginToken>`
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.
+130
View File
@@ -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;
}
+2 -1
View File
@@ -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: [
+2 -1
View File
@@ -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}` },
@@ -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(
@@ -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 };
}
+2 -1
View File
@@ -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 ?? "",
+5 -4
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 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}` : "";
+2 -1
View File
@@ -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}` : "";
@@ -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();
+3 -2
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 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 };
+2 -1
View File
@@ -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(
+2 -1
View File
@@ -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.' }] };
}
+3 -2
View File
@@ -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;
});
+2 -1
View File
@@ -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." }],
@@ -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 + '.' }] };
}
@@ -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}` : "";
@@ -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]'}`;
@@ -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(
@@ -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;
+3 -2
View File
@@ -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)';
});
+2 -1
View File
@@ -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;
});
+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 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';
@@ -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(
+3 -2
View File
@@ -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: [
{
+2 -1
View File
@@ -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(
+2 -1
View File
@@ -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}` : '';
@@ -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 ?? '?') + '"');
});
}
+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 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');
});
@@ -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(
+2 -1
View File
@@ -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();
+2 -1
View File
@@ -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 ?? '') }),
);