diff --git a/CLAUDE.md b/CLAUDE.md
index b95fd4301..f53f2e2e7 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -79,11 +79,18 @@ State lives in `src/store/` using Redux Toolkit slices:
- **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.
+Redux Persist stores auth and telegram state (storage backend is configurable; default uses localStorage). The telegram slice has a complex nested structure in `src/store/telegram/` with separate files for types, reducers, extraReducers, and thunks.
+
+### LocalStorage
+
+- **Do not use `localStorage` (or `sessionStorage`) for app state or feature logic.** Use Redux (and Redux Persist where needed) instead.
+- **Remove any existing `localStorage` usage** when touching related code. User-scoped data (auth, onboarding, Telegram session, socket state) lives in Redux, keyed by user id where applicable. Telegram session is in `telegram.byUser[userId].sessionString`, not localStorage.
+- **Exceptions**: Redux-persist may use a localStorage-backed storage adapter by default; that is the persistence layer, not app logic. Any other remaining usage (e.g. deep-link `deepLinkHandled` flag) should be migrated to Redux or similar when that code is modified.
+- **General rule**: Avoid adding new `localStorage` or `sessionStorage` usage; prefer Redux and remove existing usage when you work on affected areas.
### 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.
+- **mtprotoService** (`src/services/mtprotoService.ts`) — Telegram MTProto client via `telegram` npm package. Session stored in Redux (`telegram.byUser[userId].sessionString`), not localStorage. 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.
@@ -116,7 +123,7 @@ Web-to-desktop handoff using `outsourced://` URL scheme:
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.
+Key file: `src/utils/desktopDeepLinkListener.ts` (lazy-loaded in `main.tsx`). Uses a `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`)
@@ -180,6 +187,7 @@ Key updates from recent commits:
## Key Patterns
+- **No localStorage**: Avoid `localStorage` and `sessionStorage`; use Redux (and persist) for app state. Remove any direct usage when working on affected code.
- **Modal System**: Settings modal uses `createPortal` pattern with URL-based routing. Clean white design (not glass morphism) for system settings. Navigate with `/settings` and `/settings/connections` paths.
- **Component Reuse**: Connection management reuses `connectOptions` array and components from onboarding flow. Maintains consistent UX patterns across features.
- **Redux Integration**: Settings modal integrates with existing slices - auth for logout, user for profile display, telegram for connection status. No new state management needed.
diff --git a/src/App.tsx b/src/App.tsx
index 25c82aaca..eb670b21d 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,22 +1,25 @@
-import { HashRouter as Router } from 'react-router-dom';
-import { Provider } from 'react-redux';
-import { PersistGate } from 'redux-persist/integration/react';
-import { store, persistor } from './store';
-import SocketProvider from './providers/SocketProvider';
-import TelegramProvider from './providers/TelegramProvider';
-import AppRoutes from './AppRoutes';
+import { HashRouter as Router } from "react-router-dom";
+import { Provider } from "react-redux";
+import { PersistGate } from "redux-persist/integration/react";
+import { store, persistor } from "./store";
+import UserProvider from "./providers/UserProvider";
+import SocketProvider from "./providers/SocketProvider";
+import TelegramProvider from "./providers/TelegramProvider";
+import AppRoutes from "./AppRoutes";
function App() {
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
diff --git a/src/AppRoutes.tsx b/src/AppRoutes.tsx
index 4176f1cc1..9beba0f2b 100644
--- a/src/AppRoutes.tsx
+++ b/src/AppRoutes.tsx
@@ -1,12 +1,12 @@
-import { Routes, Route } from 'react-router-dom';
-import Welcome from './pages/Welcome';
-import Login from './pages/Login';
-import Onboarding from './pages/onboarding/Onboarding';
-import Home from './pages/Home';
-import PublicRoute from './components/PublicRoute';
-import ProtectedRoute from './components/ProtectedRoute';
-import DefaultRedirect from './components/DefaultRedirect';
-import SettingsModal from './components/settings/SettingsModal';
+import { Routes, Route } from "react-router-dom";
+import Welcome from "./pages/Welcome";
+import Login from "./pages/Login";
+import Onboarding from "./pages/onboarding/Onboarding";
+import Home from "./pages/Home";
+import PublicRoute from "./components/PublicRoute";
+import ProtectedRoute from "./components/ProtectedRoute";
+import DefaultRedirect from "./components/DefaultRedirect";
+import SettingsModal from "./components/settings/SettingsModal";
const AppRoutes = () => {
return (
@@ -42,7 +42,11 @@ const AppRoutes = () => {
+
}
@@ -52,7 +56,11 @@ const AppRoutes = () => {
+
}
diff --git a/src/assets/icons/GmailIcon.tsx b/src/assets/icons/GmailIcon.tsx
index 2a3f644e5..535514f5e 100644
--- a/src/assets/icons/GmailIcon.tsx
+++ b/src/assets/icons/GmailIcon.tsx
@@ -1,8 +1,8 @@
const GmailIcon = ({ className = "w-4 h-4" }: { className?: string }) => {
return (
);
};
diff --git a/src/assets/icons/GoogleIcon.tsx b/src/assets/icons/GoogleIcon.tsx
index 26f621f40..3d649f048 100644
--- a/src/assets/icons/GoogleIcon.tsx
+++ b/src/assets/icons/GoogleIcon.tsx
@@ -1,10 +1,22 @@
const GoogleIcon = ({ className = "w-5 h-5" }: { className?: string }) => {
return (
);
};
diff --git a/src/components/ConnectionIndicator.tsx b/src/components/ConnectionIndicator.tsx
index 3dafb6071..be9ba35de 100644
--- a/src/components/ConnectionIndicator.tsx
+++ b/src/components/ConnectionIndicator.tsx
@@ -1,34 +1,35 @@
-import { useAppSelector } from '../store/hooks';
+import { useAppSelector } from "../store/hooks";
+import { selectSocketStatus } from "../store/socketSelectors";
interface ConnectionIndicatorProps {
- status?: 'connected' | 'disconnected' | 'connecting';
+ status?: "connected" | "disconnected" | "connecting";
description?: string;
className?: string;
}
const ConnectionIndicator = ({
status: overrideStatus,
- description = 'Your browser is now connected to the AlphaHuman AI. Keep this tab open to keep the connection alive. You can message your assistant with the button below.',
- className = '',
+ description = "Your browser is now connected to the AlphaHuman AI. Keep this tab open to keep the connection alive. You can message your assistant with the button below.",
+ className = "",
}: ConnectionIndicatorProps) => {
// Use socket store status, but allow override via props
- const storeStatus = useAppSelector((state) => state.socket.status);
+ const storeStatus = useAppSelector(selectSocketStatus);
const status = overrideStatus || storeStatus;
const statusConfig = {
connected: {
- color: 'bg-sage-500',
- textColor: 'text-sage-500',
- text: 'Connected to AlphaHuman AI 🚀',
+ color: "bg-sage-500",
+ textColor: "text-sage-500",
+ text: "Connected to AlphaHuman AI 🚀",
},
disconnected: {
- color: 'bg-coral-500',
- textColor: 'text-coral-500',
- text: 'Disconnected',
+ color: "bg-coral-500",
+ textColor: "text-coral-500",
+ text: "Disconnected",
},
connecting: {
- color: 'bg-amber-500',
- textColor: 'text-amber-500',
- text: 'Connecting',
+ color: "bg-amber-500",
+ textColor: "text-amber-500",
+ text: "Connecting",
},
};
@@ -37,7 +38,9 @@ const ConnectionIndicator = ({
return (
-
+
{config.text}
{description && (
diff --git a/src/components/DefaultRedirect.tsx b/src/components/DefaultRedirect.tsx
index cc6278260..f65fc2ebc 100644
--- a/src/components/DefaultRedirect.tsx
+++ b/src/components/DefaultRedirect.tsx
@@ -1,5 +1,6 @@
-import { Navigate } from 'react-router-dom';
-import { useAppSelector } from '../store/hooks';
+import { Navigate } from "react-router-dom";
+import { useAppSelector } from "../store/hooks";
+import { selectIsOnboarded } from "../store/authSelectors";
/**
* Default redirect component that routes users based on their auth and onboarding status
@@ -9,7 +10,7 @@ import { useAppSelector } from '../store/hooks';
*/
const DefaultRedirect = () => {
const token = useAppSelector((state) => state.auth.token);
- const isOnboarded = useAppSelector((state) => state.auth.isOnboarded);
+ const isOnboarded = useAppSelector(selectIsOnboarded);
if (token && isOnboarded) {
return ;
diff --git a/src/components/DesignSystemShowcase.tsx b/src/components/DesignSystemShowcase.tsx
index e55c47ae3..cbdd9814f 100644
--- a/src/components/DesignSystemShowcase.tsx
+++ b/src/components/DesignSystemShowcase.tsx
@@ -1,4 +1,4 @@
-import React from 'react';
+import React from "react";
/**
* Design System Showcase Component
@@ -46,17 +46,23 @@ const DesignSystemShowcase: React.FC = () => {
{/* Quick Actions */}
- The typography system is designed to create clear visual hierarchy while maintaining
- excellent readability. Each text size has been carefully calibrated with appropriate
- line heights and letter spacing to ensure optimal legibility across all device sizes.
+ The typography system is designed to create clear visual hierarchy
+ while maintaining excellent readability. Each text size has been
+ carefully calibrated with appropriate line heights and letter
+ spacing to ensure optimal legibility across all device sizes.
{passwordHint
? `Your account is protected with two-step verification. Hint: ${passwordHint}`
- : 'Your account is protected with two-step verification. Please enter your password to continue.'}
+ : "Your account is protected with two-step verification. Please enter your password to continue."}
- All data and credentials are stored locally with zero-data retention policy.
- Your information is encrypted and never shared with third parties.
+ All data and credentials are stored locally with zero-data
+ retention policy. Your information is encrypted and never
+ shared with third parties.
- Configure your messaging preferences, notifications, and communication settings.
+ Configure your messaging preferences, notifications, and
+ communication settings.
@@ -36,4 +49,4 @@ const MessagingPanel = () => {
);
};
-export default MessagingPanel;
\ No newline at end of file
+export default MessagingPanel;
diff --git a/src/components/settings/panels/PrivacyPanel.tsx b/src/components/settings/panels/PrivacyPanel.tsx
index f3094608c..914eb9f0a 100644
--- a/src/components/settings/panels/PrivacyPanel.tsx
+++ b/src/components/settings/panels/PrivacyPanel.tsx
@@ -1,5 +1,5 @@
-import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
-import SettingsHeader from '../components/SettingsHeader';
+import { useSettingsNavigation } from "../hooks/useSettingsNavigation";
+import SettingsHeader from "../components/SettingsHeader";
const PrivacyPanel = () => {
const { navigateBack } = useSettingsNavigation();
@@ -16,13 +16,26 @@ const PrivacyPanel = () => {
-
-
+
+
-
Privacy & Security
+
+ Privacy & Security
+
- Manage your privacy settings, data retention policies, and security preferences.
+ Manage your privacy settings, data retention policies, and
+ security preferences.
@@ -36,4 +49,4 @@ const PrivacyPanel = () => {
);
};
-export default PrivacyPanel;
\ No newline at end of file
+export default PrivacyPanel;
diff --git a/src/components/settings/panels/ProfilePanel.tsx b/src/components/settings/panels/ProfilePanel.tsx
index 8c68c85f6..3334f5783 100644
--- a/src/components/settings/panels/ProfilePanel.tsx
+++ b/src/components/settings/panels/ProfilePanel.tsx
@@ -1,5 +1,5 @@
-import { useSettingsNavigation } from '../hooks/useSettingsNavigation';
-import SettingsHeader from '../components/SettingsHeader';
+import { useSettingsNavigation } from "../hooks/useSettingsNavigation";
+import SettingsHeader from "../components/SettingsHeader";
const ProfilePanel = () => {
const { navigateBack } = useSettingsNavigation();
@@ -16,11 +16,23 @@ const ProfilePanel = () => {
-
-
+
+
-
Profile Settings
+
+ Profile Settings
+
Update your profile information, avatar, and personal preferences.
@@ -36,4 +48,4 @@ const ProfilePanel = () => {
);
};
-export default ProfilePanel;
\ No newline at end of file
+export default ProfilePanel;
diff --git a/src/data/countries.ts b/src/data/countries.ts
index 974d836a7..2bff46f8d 100644
--- a/src/data/countries.ts
+++ b/src/data/countries.ts
@@ -1,24 +1,24 @@
-import { Country } from '../types/onboarding';
+import { Country } from "../types/onboarding";
export const countries: Country[] = [
- { code: 'US', name: 'United States', flag: '🇺🇸', dialCode: '+1' },
- { code: 'CA', name: 'Canada', flag: '🇨🇦', dialCode: '+1' },
- { code: 'GB', name: 'United Kingdom', flag: '🇬🇧', dialCode: '+44' },
- { code: 'DE', name: 'Germany', flag: '🇩🇪', dialCode: '+49' },
- { code: 'FR', name: 'France', flag: '🇫🇷', dialCode: '+33' },
- { code: 'JP', name: 'Japan', flag: '🇯🇵', dialCode: '+81' },
- { code: 'KR', name: 'South Korea', flag: '🇰🇷', dialCode: '+82' },
- { code: 'CN', name: 'China', flag: '🇨🇳', dialCode: '+86' },
- { code: 'IN', name: 'India', flag: '🇮🇳', dialCode: '+91' },
- { code: 'AU', name: 'Australia', flag: '🇦🇺', dialCode: '+61' },
- { code: 'BR', name: 'Brazil', flag: '🇧🇷', dialCode: '+55' },
- { code: 'MX', name: 'Mexico', flag: '🇲🇽', dialCode: '+52' },
- { code: 'AR', name: 'Argentina', flag: '🇦🇷', dialCode: '+54' },
- { code: 'CL', name: 'Chile', flag: '🇨🇱', dialCode: '+56' },
- { code: 'SG', name: 'Singapore', flag: '🇸🇬', dialCode: '+65' },
- { code: 'HK', name: 'Hong Kong', flag: '🇭🇰', dialCode: '+852' },
- { code: 'CH', name: 'Switzerland', flag: '🇨🇭', dialCode: '+41' },
- { code: 'NL', name: 'Netherlands', flag: '🇳🇱', dialCode: '+31' },
- { code: 'SE', name: 'Sweden', flag: '🇸🇪', dialCode: '+46' },
- { code: 'NO', name: 'Norway', flag: '🇳🇴', dialCode: '+47' },
-];
\ No newline at end of file
+ { code: "US", name: "United States", flag: "🇺🇸", dialCode: "+1" },
+ { code: "CA", name: "Canada", flag: "🇨🇦", dialCode: "+1" },
+ { code: "GB", name: "United Kingdom", flag: "🇬🇧", dialCode: "+44" },
+ { code: "DE", name: "Germany", flag: "🇩🇪", dialCode: "+49" },
+ { code: "FR", name: "France", flag: "🇫🇷", dialCode: "+33" },
+ { code: "JP", name: "Japan", flag: "🇯🇵", dialCode: "+81" },
+ { code: "KR", name: "South Korea", flag: "🇰🇷", dialCode: "+82" },
+ { code: "CN", name: "China", flag: "🇨🇳", dialCode: "+86" },
+ { code: "IN", name: "India", flag: "🇮🇳", dialCode: "+91" },
+ { code: "AU", name: "Australia", flag: "🇦🇺", dialCode: "+61" },
+ { code: "BR", name: "Brazil", flag: "🇧🇷", dialCode: "+55" },
+ { code: "MX", name: "Mexico", flag: "🇲🇽", dialCode: "+52" },
+ { code: "AR", name: "Argentina", flag: "🇦🇷", dialCode: "+54" },
+ { code: "CL", name: "Chile", flag: "🇨🇱", dialCode: "+56" },
+ { code: "SG", name: "Singapore", flag: "🇸🇬", dialCode: "+65" },
+ { code: "HK", name: "Hong Kong", flag: "🇭🇰", dialCode: "+852" },
+ { code: "CH", name: "Switzerland", flag: "🇨🇭", dialCode: "+41" },
+ { code: "NL", name: "Netherlands", flag: "🇳🇱", dialCode: "+31" },
+ { code: "SE", name: "Sweden", flag: "🇸🇪", dialCode: "+46" },
+ { code: "NO", name: "Norway", flag: "🇳🇴", dialCode: "+47" },
+];
diff --git a/src/hooks/useSocket.ts b/src/hooks/useSocket.ts
index cafbc333f..8bec21f8d 100644
--- a/src/hooks/useSocket.ts
+++ b/src/hooks/useSocket.ts
@@ -1,22 +1,23 @@
-import { useEffect, useRef } from 'react';
-import { socketService } from '../services/socketService';
-import { useAppSelector } from '../store/hooks';
-import type { Socket } from 'socket.io-client';
+import { useEffect, useRef } from "react";
+import { socketService } from "../services/socketService";
+import { useAppSelector } from "../store/hooks";
+import { selectSocketStatus } from "../store/socketSelectors";
+import type { Socket } from "socket.io-client";
/**
* React hook for using the Socket.IO connection
* Note: The socket connection is managed by SocketProvider based on JWT token.
* This hook provides access to the socket instance and methods.
- *
+ *
* @example
* ```tsx
* const { socket, isConnected, emit, on, off } = useSocket();
- *
+ *
* useEffect(() => {
* on('ready', () => {
* console.log('Socket ready!');
* });
- *
+ *
* return () => {
* off('ready');
* };
@@ -24,8 +25,10 @@ import type { Socket } from 'socket.io-client';
* ```
*/
export const useSocket = () => {
- const listenersRef = useRef void }>>([]);
- const socketStatus = useAppSelector((state) => state.socket.status);
+ const listenersRef = useRef<
+ Array<{ event: string; callback: (...args: unknown[]) => void }>
+ >([]);
+ const socketStatus = useAppSelector(selectSocketStatus);
useEffect(() => {
return () => {
@@ -50,10 +53,13 @@ export const useSocket = () => {
socketService.off(event, callback);
if (callback) {
listenersRef.current = listenersRef.current.filter(
- (listener) => listener.event !== event || listener.callback !== callback
+ (listener) =>
+ listener.event !== event || listener.callback !== callback,
);
} else {
- listenersRef.current = listenersRef.current.filter((listener) => listener.event !== event);
+ listenersRef.current = listenersRef.current.filter(
+ (listener) => listener.event !== event,
+ );
}
};
@@ -63,7 +69,7 @@ export const useSocket = () => {
return {
socket: socketService.getSocket() as Socket | null,
- isConnected: socketStatus === 'connected',
+ isConnected: socketStatus === "connected",
status: socketStatus,
emit,
on,
diff --git a/src/hooks/useUser.ts b/src/hooks/useUser.ts
index 48bf54640..7c3ec7a46 100644
--- a/src/hooks/useUser.ts
+++ b/src/hooks/useUser.ts
@@ -1,6 +1,6 @@
-import { useEffect } from 'react';
-import { useAppSelector, useAppDispatch } from '../store/hooks';
-import { fetchCurrentUser } from '../store/userSlice';
+import { useEffect } from "react";
+import { useAppSelector, useAppDispatch } from "../store/hooks";
+import { fetchCurrentUser } from "../store/userSlice";
/**
* Hook to access user data and automatically fetch it when token is available
diff --git a/src/lib/mcp/errorHandler.ts b/src/lib/mcp/errorHandler.ts
index 3212cb855..9f0a2c406 100644
--- a/src/lib/mcp/errorHandler.ts
+++ b/src/lib/mcp/errorHandler.ts
@@ -2,42 +2,43 @@
* Error handling utilities for MCP server
*/
-import type { MCPToolResult } from './types';
-import { ValidationError } from './validation';
+import type { MCPToolResult } from "./types";
+import { ValidationError } from "./validation";
export enum ErrorCategory {
- CHAT = 'CHAT',
- MSG = 'MSG',
- CONTACT = 'CONTACT',
- GROUP = 'GROUP',
- MEDIA = 'MEDIA',
- PROFILE = 'PROFILE',
- AUTH = 'AUTH',
- ADMIN = 'ADMIN',
- VALIDATION = 'VALIDATION',
- SEARCH = 'SEARCH',
- DRAFT = 'DRAFT',
+ CHAT = "CHAT",
+ MSG = "MSG",
+ CONTACT = "CONTACT",
+ GROUP = "GROUP",
+ MEDIA = "MEDIA",
+ PROFILE = "PROFILE",
+ AUTH = "AUTH",
+ ADMIN = "ADMIN",
+ VALIDATION = "VALIDATION",
+ SEARCH = "SEARCH",
+ DRAFT = "DRAFT",
}
function generateErrorCode(
functionName: string,
category?: ErrorCategory | string,
): string {
- if (category === 'VALIDATION-001' || category === ErrorCategory.VALIDATION) {
- return 'VALIDATION-001';
+ if (category === "VALIDATION-001" || category === ErrorCategory.VALIDATION) {
+ return "VALIDATION-001";
}
const prefix = category
- ? typeof category === 'string' && category.startsWith('VALIDATION')
+ ? typeof category === "string" && category.startsWith("VALIDATION")
? category
: category
- : 'GEN';
+ : "GEN";
- const hash = Math.abs(
- functionName.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0),
- ) % 1000;
+ const hash =
+ Math.abs(
+ functionName.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0),
+ ) % 1000;
- return `${prefix}-ERR-${hash.toString().padStart(3, '0')}`;
+ return `${prefix}-ERR-${hash.toString().padStart(3, "0")}`;
}
export function logAndFormatError(
@@ -50,8 +51,8 @@ export function logAndFormatError(
const contextStr = context
? Object.entries(context)
.map(([k, v]) => `${k}=${String(v)}`)
- .join(', ')
- : '';
+ .join(", ")
+ : "";
console.error(
`[MCP] Error in ${functionName} (${contextStr}) - Code: ${errorCode}`,
@@ -64,20 +65,19 @@ export function logAndFormatError(
: `An error occurred (code: ${errorCode}). Check logs for details.`;
return {
- content: [{ type: 'text', text: userMessage }],
+ content: [{ type: "text", text: userMessage }],
isError: true,
};
}
-export function withErrorHandling Promise>(
- fn: T,
- category?: ErrorCategory,
-): T {
+export function withErrorHandling<
+ T extends (...args: unknown[]) => Promise,
+>(fn: T, category?: ErrorCategory): T {
return (async (...args: Parameters): Promise => {
try {
return await fn(...args);
} catch (error) {
- const functionName = fn.name || 'unknown';
+ const functionName = fn.name || "unknown";
return logAndFormatError(
functionName,
error instanceof Error ? error : new Error(String(error)),
diff --git a/src/lib/mcp/index.ts b/src/lib/mcp/index.ts
index 74c97db59..750b7f66c 100644
--- a/src/lib/mcp/index.ts
+++ b/src/lib/mcp/index.ts
@@ -3,8 +3,8 @@
* Used by MCP servers (e.g. telegram, gmail, etc.)
*/
-export * from './types';
-export * from './validation';
-export * from './errorHandler';
-export * from './logger';
-export { SocketIOMCPTransportImpl } from './transport';
+export * from "./types";
+export * from "./validation";
+export * from "./errorHandler";
+export * from "./logger";
+export { SocketIOMCPTransportImpl } from "./transport";
diff --git a/src/lib/mcp/logger.ts b/src/lib/mcp/logger.ts
index 41a95ca61..09492934a 100644
--- a/src/lib/mcp/logger.ts
+++ b/src/lib/mcp/logger.ts
@@ -2,23 +2,28 @@
* MCP logger - simple console logger with [MCP] prefix
*/
-type LogLevel = 'log' | 'warn' | 'error';
+type LogLevel = "log" | "warn" | "error";
-const PREFIX = '[MCP]';
+const PREFIX = "[MCP]";
function log(level: LogLevel, message: string, ...data: unknown[]): void {
- const fn = level === 'error' ? console.error : level === 'warn' ? console.warn : console.log;
+ const fn =
+ level === "error"
+ ? console.error
+ : level === "warn"
+ ? console.warn
+ : console.log;
fn(PREFIX, message, ...data);
}
export function mcpLog(message: string, ...data: unknown[]): void {
- log('log', message, ...data);
+ log("log", message, ...data);
}
export function mcpWarn(message: string, ...data: unknown[]): void {
- log('warn', message, ...data);
+ log("warn", message, ...data);
}
export function mcpError(message: string, ...data: unknown[]): void {
- log('error', message, ...data);
+ log("error", message, ...data);
}
diff --git a/src/lib/mcp/telegram/index.ts b/src/lib/mcp/telegram/index.ts
index 0058f26be..1e3574ec8 100644
--- a/src/lib/mcp/telegram/index.ts
+++ b/src/lib/mcp/telegram/index.ts
@@ -3,14 +3,16 @@
* Main entry point for Telegram MCP integration
*/
-import type { Socket } from 'socket.io-client';
-import { TelegramMCPServer } from './server';
+import type { Socket } from "socket.io-client";
+import { TelegramMCPServer } from "./server";
let telegramMCPInstance: TelegramMCPServer | undefined;
-export function initTelegramMCPServer(socket: Socket | null | undefined): TelegramMCPServer {
+export function initTelegramMCPServer(
+ socket: Socket | null | undefined,
+): TelegramMCPServer {
telegramMCPInstance = new TelegramMCPServer(socket);
- console.log('[MCP] Telegram MCP server initialized');
+ console.log("[MCP] Telegram MCP server initialized");
return telegramMCPInstance;
}
@@ -18,19 +20,21 @@ export function getTelegramMCPServer(): TelegramMCPServer | undefined {
return telegramMCPInstance;
}
-export function updateTelegramMCPServerSocket(socket: Socket | null | undefined): void {
+export function updateTelegramMCPServerSocket(
+ socket: Socket | null | undefined,
+): void {
if (telegramMCPInstance) {
telegramMCPInstance.updateSocket(socket);
- console.log('[MCP] Telegram MCP server socket updated');
+ console.log("[MCP] Telegram MCP server socket updated");
}
}
export function cleanupTelegramMCPServer(): void {
if (telegramMCPInstance) {
telegramMCPInstance = undefined;
- console.log('[MCP] Telegram MCP server cleaned up');
+ console.log("[MCP] Telegram MCP server cleaned up");
}
}
-export { toHumanReadableAction } from './toolActionParser';
-export type { TelegramMCPServer } from './server';
+export { toHumanReadableAction } from "./toolActionParser";
+export type { TelegramMCPServer } from "./server";
diff --git a/src/lib/mcp/telegram/server.ts b/src/lib/mcp/telegram/server.ts
index 8b06af3e8..df14916f1 100644
--- a/src/lib/mcp/telegram/server.ts
+++ b/src/lib/mcp/telegram/server.ts
@@ -13,6 +13,7 @@ import type {
} from "../types";
import { store } from "../../../store";
+import { selectTelegramUserState } from "../../../store/telegramSelectors";
import { ErrorCategory, logAndFormatError } from "../errorHandler";
import { ValidationError } from "../validation";
import { SocketIOMCPTransportImpl } from "../transport";
@@ -104,7 +105,8 @@ export class TelegramMCPServer {
};
}
- const telegramState: TelegramState = store.getState().telegram;
+ const telegramState: TelegramState =
+ selectTelegramUserState(store.getState());
try {
return await toolHandler(args, {
diff --git a/src/lib/mcp/telegram/telegramApi.ts b/src/lib/mcp/telegram/telegramApi.ts
index 044859101..807457ed0 100644
--- a/src/lib/mcp/telegram/telegramApi.ts
+++ b/src/lib/mcp/telegram/telegramApi.ts
@@ -3,13 +3,22 @@
* Uses mtprotoService + Redux telegram state (alphahuman)
*/
-import { store } from '../../../store';
-import { mtprotoService } from '../../../services/mtprotoService';
+import { store } from "../../../store";
+import { mtprotoService } from "../../../services/mtprotoService";
import {
selectOrderedChats,
selectCurrentUser,
-} from '../../../store/telegramSelectors';
-import type { TelegramChat, TelegramUser, TelegramMessage } from '../../../store/telegram/types';
+ selectTelegramUserState,
+} from "../../../store/telegramSelectors";
+import type {
+ TelegramChat,
+ TelegramUser,
+ TelegramMessage,
+} from "../../../store/telegram/types";
+
+function getTelegramState() {
+ return selectTelegramUserState(store.getState());
+}
export interface FormattedEntity {
id: string;
@@ -28,10 +37,6 @@ export interface FormattedMessage {
media_type?: string;
}
-function getTelegramState() {
- return store.getState().telegram;
-}
-
/**
* Get chat by ID or username
*/
@@ -42,10 +47,15 @@ export function getChatById(chatId: string | number): TelegramChat | undefined {
const chat = state.chats[idStr];
if (chat) return chat;
- if (typeof chatId === 'string' && (chatId.startsWith('@') || /^[a-zA-Z0-9_]+$/.test(chatId))) {
- const username = chatId.startsWith('@') ? chatId : `@${chatId}`;
+ if (
+ typeof chatId === "string" &&
+ (chatId.startsWith("@") || /^[a-zA-Z0-9_]+$/.test(chatId))
+ ) {
+ const username = chatId.startsWith("@") ? chatId : `@${chatId}`;
return Object.values(state.chats).find(
- (c) => c.username && (c.username === username || c.username === username.slice(1)),
+ (c) =>
+ c.username &&
+ (c.username === username || c.username === username.slice(1)),
);
}
@@ -95,7 +105,7 @@ export async function sendMessage(
const chat = getChatById(chatId);
if (!chat) return undefined;
- const entity = chat.username ? `@${chat.username.replace('@', '')}` : chat.id;
+ const entity = chat.username ? `@${chat.username.replace("@", "")}` : chat.id;
if (replyToMessageId !== undefined) {
const client = mtprotoService.getClient();
@@ -129,8 +139,8 @@ export async function searchChats(query: string): Promise {
const ordered = selectOrderedChats(state);
const q = query.toLowerCase();
return ordered.filter((c) => {
- const title = (c.title ?? '').toLowerCase();
- const un = (c.username ?? '').toLowerCase();
+ const title = (c.title ?? "").toLowerCase();
+ const un = (c.username ?? "").toLowerCase();
return title.includes(q) || un.includes(q);
});
}
@@ -146,23 +156,31 @@ export function getCurrentUser(): TelegramUser | undefined {
/**
* Format entity (chat or user) for display
*/
-export function formatEntity(entity: TelegramChat | TelegramUser): FormattedEntity {
- if ('title' in entity) {
+export function formatEntity(
+ entity: TelegramChat | TelegramUser,
+): FormattedEntity {
+ if ("title" in entity) {
const chat = entity as TelegramChat;
- const type = chat.type === 'channel' ? 'channel' : chat.type === 'supergroup' ? 'group' : chat.type;
+ const type =
+ chat.type === "channel"
+ ? "channel"
+ : chat.type === "supergroup"
+ ? "group"
+ : chat.type;
return {
id: chat.id,
- name: chat.title ?? 'Unknown',
+ name: chat.title ?? "Unknown",
type,
username: chat.username,
};
}
const user = entity as TelegramUser;
- const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
+ const name =
+ [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown";
return {
id: user.id,
name,
- type: 'user',
+ type: "user",
username: user.username,
phone: user.phoneNumber,
};
@@ -175,7 +193,7 @@ export function formatMessage(message: TelegramMessage): FormattedMessage {
const result: FormattedMessage = {
id: message.id,
date: new Date(message.date * 1000).toISOString(),
- text: message.message ?? '',
+ text: message.message ?? "",
};
if (message.fromId) result.from_id = message.fromId;
if (message.media?.type) {
diff --git a/src/lib/mcp/telegram/toolActionParser.ts b/src/lib/mcp/telegram/toolActionParser.ts
index 5cf09c89a..e275d246e 100644
--- a/src/lib/mcp/telegram/toolActionParser.ts
+++ b/src/lib/mcp/telegram/toolActionParser.ts
@@ -4,8 +4,8 @@
type ToolArguments = Record;
-function formatId(id: string | number | undefined, prefix = ''): string {
- if (!id) return '';
+function formatId(id: string | number | undefined, prefix = ""): string {
+ if (!id) return "";
const str = String(id);
return prefix ? `${prefix} ${str}` : str;
}
@@ -15,7 +15,10 @@ function truncateText(text: string, maxLength = 50): string {
return `${text.substring(0, maxLength)}...`;
}
-export function toHumanReadableAction(toolName: string, args: ToolArguments): string {
+export function toHumanReadableAction(
+ toolName: string,
+ args: ToolArguments,
+): string {
const parser = toolParsers[toolName];
if (parser) return parser(args);
return `Executing ${toolName} with provided parameters`;
@@ -23,165 +26,165 @@ export function toHumanReadableAction(toolName: string, args: ToolArguments): st
const toolParsers: Record string> = {
send_message: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const message = truncateText((args.message as string) || '', 100);
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const message = truncateText((args.message as string) || "", 100);
return `Send message to ${chatId}: "${message}"`;
},
reply_to_message: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
- const text = truncateText((args.text as string) || '', 100);
+ const text = truncateText((args.text as string) || "", 100);
return `Reply to message ${messageId} in ${chatId}: "${text}"`;
},
edit_message: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
- const newText = truncateText((args.new_text as string) || '', 100);
+ const newText = truncateText((args.new_text as string) || "", 100);
return `Edit message ${messageId} in ${chatId} to: "${newText}"`;
},
delete_message: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
return `Delete message ${messageId} from ${chatId}`;
},
forward_message: (args) => {
- const fromChat = formatId(args.from_chat_id as string | number, 'chat');
- const toChat = formatId(args.to_chat_id as string | number, 'chat');
+ const fromChat = formatId(args.from_chat_id as string | number, "chat");
+ const toChat = formatId(args.to_chat_id as string | number, "chat");
const messageId = args.message_id;
return `Forward message ${messageId} from ${fromChat} to ${toChat}`;
},
pin_message: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
return `Pin message ${messageId} in ${chatId}`;
},
unpin_message: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
return `Unpin message ${messageId} in ${chatId}`;
},
mark_as_read: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Mark messages as read in ${chatId}`;
},
send_reaction: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
- const reaction = (args.reaction as string) || '👍';
+ const reaction = (args.reaction as string) || "👍";
return `Add reaction ${reaction} to message ${messageId} in ${chatId}`;
},
remove_reaction: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
- const reaction = (args.reaction as string) || '';
+ const reaction = (args.reaction as string) || "";
return `Remove reaction ${reaction} from message ${messageId} in ${chatId}`;
},
save_draft: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const text = truncateText((args.text as string) || '', 50);
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const text = truncateText((args.text as string) || "", 50);
return `Save draft in ${chatId}: "${text}"`;
},
clear_draft: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Clear draft in ${chatId}`;
},
list_chats: (args) => {
- const chatType = (args.chat_type as string) || 'all';
+ const chatType = (args.chat_type as string) || "all";
const limit = (args.limit as number) || 20;
return `List ${limit} ${chatType} chats`;
},
get_chat: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Get information for ${chatId}`;
},
create_group: (args) => {
- const title = (args.title as string) || 'Untitled Group';
+ const title = (args.title as string) || "Untitled Group";
const userCount = Array.isArray(args.user_ids) ? args.user_ids.length : 0;
- return `Create group "${title}" with ${userCount} member${userCount !== 1 ? 's' : ''}`;
+ return `Create group "${title}" with ${userCount} member${userCount !== 1 ? "s" : ""}`;
},
create_channel: (args) => {
- const title = (args.title as string) || 'Untitled Channel';
+ const title = (args.title as string) || "Untitled Channel";
const description = args.description
- ? `: ${truncateText((args.description as string) || '', 50)}`
- : '';
+ ? `: ${truncateText((args.description as string) || "", 50)}`
+ : "";
return `Create channel "${title}"${description}`;
},
edit_chat_title: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const newTitle = (args.new_title as string) || '';
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const newTitle = (args.new_title as string) || "";
return `Change title of ${chatId} to "${newTitle}"`;
},
delete_chat_photo: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Remove photo from ${chatId}`;
},
edit_chat_photo: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Update photo for ${chatId}`;
},
leave_chat: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Leave ${chatId}`;
},
mute_chat: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const muteFor = args.mute_for ? ` for ${args.mute_for} seconds` : '';
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const muteFor = args.mute_for ? ` for ${args.mute_for} seconds` : "";
return `Mute ${chatId}${muteFor}`;
},
unmute_chat: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Unmute ${chatId}`;
},
archive_chat: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Archive ${chatId}`;
},
unarchive_chat: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Unarchive ${chatId}`;
},
invite_to_group: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const userCount = Array.isArray(args.user_ids) ? args.user_ids.length : 0;
- return `Invite ${userCount} user${userCount !== 1 ? 's' : ''} to ${chatId}`;
+ return `Invite ${userCount} user${userCount !== 1 ? "s" : ""} to ${chatId}`;
},
ban_user: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const userId = formatId(args.user_id as string | number, 'user');
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const userId = formatId(args.user_id as string | number, "user");
return `Ban ${userId} from ${chatId}`;
},
unban_user: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const userId = formatId(args.user_id as string | number, 'user');
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const userId = formatId(args.user_id as string | number, "user");
return `Unban ${userId} from ${chatId}`;
},
promote_admin: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const userId = formatId(args.user_id as string | number, 'user');
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const userId = formatId(args.user_id as string | number, "user");
return `Promote ${userId} to admin in ${chatId}`;
},
demote_admin: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const userId = formatId(args.user_id as string | number, 'user');
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const userId = formatId(args.user_id as string | number, "user");
return `Demote ${userId} from admin in ${chatId}`;
},
block_user: (args) => {
- const userId = formatId(args.user_id as string | number, 'user');
+ const userId = formatId(args.user_id as string | number, "user");
return `Block ${userId}`;
},
unblock_user: (args) => {
- const userId = formatId(args.user_id as string | number, 'user');
+ const userId = formatId(args.user_id as string | number, "user");
return `Unblock ${userId}`;
},
add_contact: (args) => {
- const firstName = (args.first_name as string) || '';
- const lastName = (args.last_name as string) || '';
- const phone = (args.phone_number as string) || '';
- const name = [firstName, lastName].filter(Boolean).join(' ') || phone;
+ const firstName = (args.first_name as string) || "";
+ const lastName = (args.last_name as string) || "";
+ const phone = (args.phone_number as string) || "";
+ const name = [firstName, lastName].filter(Boolean).join(" ") || phone;
return `Add contact: ${name}`;
},
delete_contact: (args) => {
- const userId = formatId(args.user_id as string | number, 'user');
+ const userId = formatId(args.user_id as string | number, "user");
return `Delete contact ${userId}`;
},
list_contacts: (args) => {
@@ -189,144 +192,159 @@ const toolParsers: Record string> = {
return `List ${limit} contacts`;
},
search_contacts: (args) => {
- const query = truncateText((args.query as string) || '', 30);
+ const query = truncateText((args.query as string) || "", 30);
return `Search contacts for "${query}"`;
},
search_messages: (args) => {
- const chatId = args.chat_id ? formatId(args.chat_id as string | number, 'chat') : 'all chats';
- const query = truncateText((args.query as string) || '', 30);
+ const chatId = args.chat_id
+ ? formatId(args.chat_id as string | number, "chat")
+ : "all chats";
+ const query = truncateText((args.query as string) || "", 30);
const limit = (args.limit as number) || 20;
return `Search for "${query}" in ${chatId} (limit: ${limit})`;
},
search_public_chats: (args) => {
- const query = truncateText((args.query as string) || '', 30);
+ const query = truncateText((args.query as string) || "", 30);
return `Search public chats for "${query}"`;
},
resolve_username: (args) => {
- const username = (args.username as string) || '';
- return `Resolve username @${username.replace('@', '')}`;
+ const username = (args.username as string) || "";
+ return `Resolve username @${username.replace("@", "")}`;
},
get_messages: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const limit = (args.limit as number) || 20;
return `Get ${limit} messages from ${chatId}`;
},
list_messages: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const limit = (args.limit as number) || 20;
return `List ${limit} messages from ${chatId}`;
},
get_history: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const limit = (args.limit as number) || 20;
return `Get message history from ${chatId} (${limit} messages)`;
},
get_pinned_messages: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Get pinned messages from ${chatId}`;
},
get_message_context: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
const limit = (args.limit as number) || 5;
return `Get context around message ${messageId} in ${chatId} (±${limit} messages)`;
},
list_topics: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `List topics in ${chatId}`;
},
get_participants: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const limit = (args.limit as number) || 100;
return `Get ${limit} participants from ${chatId}`;
},
get_admins: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Get administrators of ${chatId}`;
},
get_banned_users: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Get banned users from ${chatId}`;
},
- get_blocked_users: () => 'Get list of blocked users',
+ get_blocked_users: () => "Get list of blocked users",
get_invite_link: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Get invite link for ${chatId}`;
},
export_chat_invite: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Create invite link for ${chatId}`;
},
import_chat_invite: (args) => {
- const inviteHash = args.invite_hash ? truncateText((args.invite_hash as string) || '', 20) : '';
+ const inviteHash = args.invite_hash
+ ? truncateText((args.invite_hash as string) || "", 20)
+ : "";
return `Join chat via invite link: ${inviteHash}`;
},
join_chat_by_link: (args) => {
- const inviteLink = args.invite_link ? truncateText((args.invite_link as string) || '', 30) : '';
+ const inviteLink = args.invite_link
+ ? truncateText((args.invite_link as string) || "", 30)
+ : "";
return `Join chat via link: ${inviteLink}`;
},
subscribe_public_channel: (args) => {
- const username = (args.username as string) || '';
- return `Subscribe to public channel @${username.replace('@', '')}`;
+ const username = (args.username as string) || "";
+ return `Subscribe to public channel @${username.replace("@", "")}`;
},
update_profile: (args) => {
const updates: string[] = [];
- if (args.first_name) updates.push(`first name: "${args.first_name as string}"`);
- if (args.last_name) updates.push(`last name: "${args.last_name as string}"`);
- if (args.bio) updates.push(`bio: "${truncateText((args.bio as string) || '', 30)}"`);
- return `Update profile (${updates.join(', ')})`;
+ if (args.first_name)
+ updates.push(`first name: "${args.first_name as string}"`);
+ if (args.last_name)
+ updates.push(`last name: "${args.last_name as string}"`);
+ if (args.bio)
+ updates.push(`bio: "${truncateText((args.bio as string) || "", 30)}"`);
+ return `Update profile (${updates.join(", ")})`;
},
- set_profile_photo: () => 'Set profile photo',
- delete_profile_photo: () => 'Delete profile photo',
+ set_profile_photo: () => "Set profile photo",
+ delete_profile_photo: () => "Delete profile photo",
get_user_photos: (args) => {
- const userId = formatId(args.user_id as string | number, 'user');
+ const userId = formatId(args.user_id as string | number, "user");
const limit = (args.limit as number) || 20;
return `Get ${limit} photos from ${userId}`;
},
get_user_status: (args) => {
- const userId = formatId(args.user_id as string | number, 'user');
+ const userId = formatId(args.user_id as string | number, "user");
return `Get status of ${userId}`;
},
- get_me: () => 'Get current user information',
+ get_me: () => "Get current user information",
list_inline_buttons: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
return `Get inline buttons from message ${messageId} in ${chatId}`;
},
press_inline_button: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
- const buttonText = args.button_text ? ` "${truncateText((args.button_text as string) || '', 30)}"` : '';
+ const buttonText = args.button_text
+ ? ` "${truncateText((args.button_text as string) || "", 30)}"`
+ : "";
return `Press inline button${buttonText} on message ${messageId} in ${chatId}`;
},
create_poll: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
- const question = truncateText((args.question as string) || '', 50);
+ const chatId = formatId(args.chat_id as string | number, "chat");
+ const question = truncateText((args.question as string) || "", 50);
const optionCount = Array.isArray(args.options) ? args.options.length : 0;
return `Create poll in ${chatId}: "${question}" with ${optionCount} options`;
},
get_bot_info: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
return `Get bot information from ${chatId}`;
},
set_bot_commands: (args) => {
- const chatId = args.chat_id ? formatId(args.chat_id as string | number, 'chat') : 'all chats';
- const commandCount = Array.isArray(args.commands) ? args.commands.length : 0;
+ const chatId = args.chat_id
+ ? formatId(args.chat_id as string | number, "chat")
+ : "all chats";
+ const commandCount = Array.isArray(args.commands)
+ ? args.commands.length
+ : 0;
return `Set ${commandCount} bot commands for ${chatId}`;
},
- get_privacy_settings: () => 'Get privacy settings',
+ get_privacy_settings: () => "Get privacy settings",
set_privacy_settings: (args) => {
- const setting = (args.setting as string) || 'unknown';
- const value = (args.value as string) || '';
+ const setting = (args.setting as string) || "unknown";
+ const value = (args.value as string) || "";
return `Set privacy setting "${setting}" to ${value}`;
},
get_media_info: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const messageId = args.message_id;
return `Get media information from message ${messageId} in ${chatId}`;
},
get_recent_actions: (args) => {
- const chatId = formatId(args.chat_id as string | number, 'chat');
+ const chatId = formatId(args.chat_id as string | number, "chat");
const limit = (args.limit as number) || 20;
return `Get ${limit} recent admin actions from ${chatId}`;
},
diff --git a/src/lib/mcp/telegram/tools/addContact.ts b/src/lib/mcp/telegram/tools/addContact.ts
index 529ac9b62..458defd36 100644
--- a/src/lib/mcp/telegram/tools/addContact.ts
+++ b/src/lib/mcp/telegram/tools/addContact.ts
@@ -1,22 +1,22 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import { optString } from '../args';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
+import bigInt from "big-integer";
+import { optString } from "../args";
export const tool: MCPTool = {
- name: 'add_contact',
- description: 'Add a contact to your Telegram account',
+ name: "add_contact",
+ description: "Add a contact to your Telegram account",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- phone: { type: 'string', description: 'Phone number' },
- first_name: { type: 'string', description: 'First name' },
- last_name: { type: 'string', description: 'Last name' },
+ phone: { type: "string", description: "Phone number" },
+ first_name: { type: "string", description: "First name" },
+ last_name: { type: "string", description: "Last name" },
},
- required: ['phone', 'first_name'],
+ required: ["phone", "first_name"],
},
};
@@ -25,12 +25,21 @@ export async function addContact(
_context: TelegramMCPContext,
): Promise {
try {
- const phone = typeof args.phone === 'string' ? args.phone : '';
- const firstName = typeof args.first_name === 'string' ? args.first_name : '';
- const lastName = optString(args, 'last_name') ?? '';
+ const phone = typeof args.phone === "string" ? args.phone : "";
+ const firstName =
+ typeof args.first_name === "string" ? args.first_name : "";
+ const lastName = optString(args, "last_name") ?? "";
- if (!phone) return { content: [{ type: 'text', text: 'phone is required' }], isError: true };
- if (!firstName) return { content: [{ type: 'text', text: 'first_name is required' }], isError: true };
+ if (!phone)
+ return {
+ content: [{ type: "text", text: "phone is required" }],
+ isError: true,
+ };
+ if (!firstName)
+ return {
+ content: [{ type: "text", text: "first_name is required" }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
@@ -49,10 +58,18 @@ export async function addContact(
);
});
- return { content: [{ type: 'text', text: 'Contact ' + firstName + ' ' + lastName + ' (' + phone + ') added.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text:
+ "Contact " + firstName + " " + lastName + " (" + phone + ") added.",
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'add_contact',
+ "add_contact",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/blockUser.ts b/src/lib/mcp/telegram/tools/blockUser.ts
index 74548ee02..b0870b3a9 100644
--- a/src/lib/mcp/telegram/tools/blockUser.ts
+++ b/src/lib/mcp/telegram/tools/blockUser.ts
@@ -1,20 +1,20 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { toInputPeer } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'Block a user',
+ name: "block_user",
+ description: "Block a user",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- user_id: { type: 'string', description: 'User ID to block' },
+ user_id: { type: "string", description: "User ID to block" },
},
- required: ['user_id'],
+ required: ["user_id"],
},
};
@@ -23,7 +23,7 @@ export async function blockUser(
_context: TelegramMCPContext,
): Promise {
try {
- const userId = validateId(args.user_id, 'user_id');
+ const userId = validateId(args.user_id, "user_id");
const client = mtprotoService.getClient();
await mtprotoService.withFloodWaitHandling(async () => {
@@ -33,10 +33,12 @@ export async function blockUser(
);
});
- return { content: [{ type: 'text', text: 'User ' + userId + ' blocked.' }] };
+ return {
+ content: [{ type: "text", text: "User " + userId + " blocked." }],
+ };
} catch (error) {
return logAndFormatError(
- 'block_user',
+ "block_user",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/clearDraft.ts b/src/lib/mcp/telegram/tools/clearDraft.ts
index 3ec573ba1..f973f49dd 100644
--- a/src/lib/mcp/telegram/tools/clearDraft.ts
+++ b/src/lib/mcp/telegram/tools/clearDraft.ts
@@ -1,10 +1,10 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
name: "clear_draft",
@@ -23,10 +23,14 @@ export async function clearDraft(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
+ const chatId = validateId(args.chat_id, "chat_id");
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -36,15 +40,19 @@ export async function clearDraft(
await client.invoke(
new Api.messages.SaveDraft({
peer: inputPeer,
- message: '',
+ message: "",
}),
);
});
- return { content: [{ type: 'text', text: 'Draft cleared in chat ' + chatId + '.' }] };
+ return {
+ content: [
+ { type: "text", text: "Draft cleared in chat " + chatId + "." },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'clear_draft',
+ "clear_draft",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.DRAFT,
);
diff --git a/src/lib/mcp/telegram/tools/createChannel.ts b/src/lib/mcp/telegram/tools/createChannel.ts
index 4b3a42f07..35382892b 100644
--- a/src/lib/mcp/telegram/tools/createChannel.ts
+++ b/src/lib/mcp/telegram/tools/createChannel.ts
@@ -52,7 +52,8 @@ export async function createChannel(
);
});
- const channelId = narrow(result)?.chats?.[0]?.id ?? "unknown";
+ const channelId =
+ narrow(result)?.chats?.[0]?.id ?? "unknown";
const type = megagroup ? "Supergroup" : "Channel";
return {
content: [
diff --git a/src/lib/mcp/telegram/tools/createPoll.ts b/src/lib/mcp/telegram/tools/createPoll.ts
index 0077922f5..9a95596cc 100644
--- a/src/lib/mcp/telegram/tools/createPoll.ts
+++ b/src/lib/mcp/telegram/tools/createPoll.ts
@@ -1,11 +1,11 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
+import bigInt from "big-integer";
export const tool: MCPTool = {
name: "create_poll",
@@ -26,15 +26,27 @@ export async function createPoll(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const question = typeof args.question === 'string' ? args.question : '';
+ const chatId = validateId(args.chat_id, "chat_id");
+ const question = typeof args.question === "string" ? args.question : "";
const options = Array.isArray(args.options) ? args.options.map(String) : [];
- if (!question) return { content: [{ type: 'text', text: 'question is required' }], isError: true };
- if (options.length < 2) return { content: [{ type: 'text', text: 'At least 2 options are required' }], isError: true };
+ if (!question)
+ return {
+ content: [{ type: "text", text: "question is required" }],
+ isError: true,
+ };
+ if (options.length < 2)
+ return {
+ content: [{ type: "text", text: "At least 2 options are required" }],
+ isError: true,
+ };
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -47,25 +59,29 @@ export async function createPoll(
media: new Api.InputMediaPoll({
poll: new Api.Poll({
id: bigInt(0),
- question: new Api.TextWithEntities({ text: question, entities: [] }),
- answers: options.map((opt, i) =>
- new Api.PollAnswer({
- text: new Api.TextWithEntities({ text: opt, entities: [] }),
- option: Buffer.from([i]),
- }),
+ question: new Api.TextWithEntities({
+ text: question,
+ entities: [],
+ }),
+ answers: options.map(
+ (opt, i) =>
+ new Api.PollAnswer({
+ text: new Api.TextWithEntities({ text: opt, entities: [] }),
+ option: Buffer.from([i]),
+ }),
),
}),
}),
- message: '',
+ message: "",
randomId: bigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)),
}),
);
});
- return { content: [{ type: 'text', text: 'Poll created: ' + question }] };
+ return { content: [{ type: "text", text: "Poll created: " + question }] };
} catch (error) {
return logAndFormatError(
- 'create_poll',
+ "create_poll",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/deleteContact.ts b/src/lib/mcp/telegram/tools/deleteContact.ts
index 3f1bcfa54..48eb92bba 100644
--- a/src/lib/mcp/telegram/tools/deleteContact.ts
+++ b/src/lib/mcp/telegram/tools/deleteContact.ts
@@ -1,20 +1,23 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { toInputUser } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'Delete a contact from your Telegram account',
+ name: "delete_contact",
+ description: "Delete a contact from your Telegram account",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- user_id: { type: 'string', description: 'User ID to remove from contacts' },
+ user_id: {
+ type: "string",
+ description: "User ID to remove from contacts",
+ },
},
- required: ['user_id'],
+ required: ["user_id"],
},
};
@@ -23,7 +26,7 @@ export async function deleteContact(
_context: TelegramMCPContext,
): Promise {
try {
- const userId = validateId(args.user_id, 'user_id');
+ const userId = validateId(args.user_id, "user_id");
const client = mtprotoService.getClient();
await mtprotoService.withFloodWaitHandling(async () => {
@@ -35,10 +38,12 @@ export async function deleteContact(
);
});
- return { content: [{ type: 'text', text: 'Contact ' + userId + ' deleted.' }] };
+ return {
+ content: [{ type: "text", text: "Contact " + userId + " deleted." }],
+ };
} catch (error) {
return logAndFormatError(
- 'delete_contact',
+ "delete_contact",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/deleteMessage.ts b/src/lib/mcp/telegram/tools/deleteMessage.ts
index 743388586..073fc9695 100644
--- a/src/lib/mcp/telegram/tools/deleteMessage.ts
+++ b/src/lib/mcp/telegram/tools/deleteMessage.ts
@@ -1,20 +1,20 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
export const tool: MCPTool = {
- name: 'delete_message',
- description: 'Delete a message',
+ name: "delete_message",
+ description: "Delete a message",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -23,21 +23,27 @@ export async function deleteMessage(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be a positive integer' }],
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
isError: true,
};
}
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const entity = chat.username ? chat.username : chat.id;
@@ -48,11 +54,13 @@ export async function deleteMessage(
});
return {
- content: [{ type: 'text', text: `Message ${messageId} deleted successfully.` }],
+ content: [
+ { type: "text", text: `Message ${messageId} deleted successfully.` },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'delete_message',
+ "delete_message",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts
index f2793bee4..1f9f5e684 100644
--- a/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts
+++ b/src/lib/mcp/telegram/tools/deleteProfilePhoto.ts
@@ -1,11 +1,11 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import type { ApiPhoto } from '../apiResultTypes';
-import { narrow } from '../apiCastHelpers';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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",
@@ -31,8 +31,15 @@ export async function deleteProfilePhoto(
);
});
- if (!photos || !('photos' in photos) || !Array.isArray(photos.photos) || photos.photos.length === 0) {
- return { content: [{ type: 'text', text: 'No profile photo to delete.' }] };
+ if (
+ !photos ||
+ !("photos" in photos) ||
+ !Array.isArray(photos.photos) ||
+ photos.photos.length === 0
+ ) {
+ return {
+ content: [{ type: "text", text: "No profile photo to delete." }],
+ };
}
const photo = narrow(photos.photos[0]);
@@ -40,15 +47,21 @@ export async function deleteProfilePhoto(
await mtprotoService.withFloodWaitHandling(async () => {
await client.invoke(
new Api.photos.DeletePhotos({
- id: [new Api.InputPhoto({ id: photo.id, accessHash: photo.accessHash, fileReference: photo.fileReference })],
+ id: [
+ new Api.InputPhoto({
+ id: photo.id,
+ accessHash: photo.accessHash,
+ fileReference: photo.fileReference,
+ }),
+ ],
}),
);
});
- return { content: [{ type: 'text', text: 'Profile photo deleted.' }] };
+ return { content: [{ type: "text", text: "Profile photo deleted." }] };
} catch (error) {
return logAndFormatError(
- 'delete_profile_photo',
+ "delete_profile_photo",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
diff --git a/src/lib/mcp/telegram/tools/editChatPhoto.ts b/src/lib/mcp/telegram/tools/editChatPhoto.ts
index acce6aaa5..241f26082 100644
--- a/src/lib/mcp/telegram/tools/editChatPhoto.ts
+++ b/src/lib/mcp/telegram/tools/editChatPhoto.ts
@@ -8,9 +8,9 @@ export const tool: MCPTool = {
type: "object",
properties: {
chat_id: { type: "string", description: "Chat ID or username" },
- file_path: { type: 'string', description: 'Path to the photo file' },
+ file_path: { type: "string", description: "Path to the photo file" },
},
- required: ["chat_id", 'file_path'],
+ required: ["chat_id", "file_path"],
},
};
@@ -19,7 +19,12 @@ export async function editChatPhoto(
_context: TelegramMCPContext,
): Promise {
return {
- content: [{ type: 'text', text: 'edit_chat_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.' }],
+ content: [
+ {
+ type: "text",
+ text: "edit_chat_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.",
+ },
+ ],
isError: true,
};
}
diff --git a/src/lib/mcp/telegram/tools/editMessage.ts b/src/lib/mcp/telegram/tools/editMessage.ts
index 9fde09b8a..d37096566 100644
--- a/src/lib/mcp/telegram/tools/editMessage.ts
+++ b/src/lib/mcp/telegram/tools/editMessage.ts
@@ -1,21 +1,21 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
export const tool: MCPTool = {
- name: 'edit_message',
- description: 'Edit an existing message',
+ name: "edit_message",
+ description: "Edit an existing message",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
- new_text: { type: 'string', description: 'New message text' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
+ new_text: { type: "string", description: "New message text" },
},
- required: ['chat_id', 'message_id', 'new_text'],
+ required: ["chat_id", "message_id", "new_text"],
},
};
@@ -24,28 +24,34 @@ export async function editMessage(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
- const newText = typeof args.new_text === 'string' ? args.new_text : '';
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
+ const newText = typeof args.new_text === "string" ? args.new_text : "";
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be a positive integer' }],
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
isError: true,
};
}
if (!newText) {
return {
- content: [{ type: 'text', text: 'new_text is required' }],
+ content: [{ type: "text", text: "new_text is required" }],
isError: true,
};
}
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const entity = chat.username ? chat.username : chat.id;
@@ -56,11 +62,13 @@ export async function editMessage(
});
return {
- content: [{ type: 'text', text: `Message ${messageId} edited successfully.` }],
+ content: [
+ { type: "text", text: `Message ${messageId} edited successfully.` },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'edit_message',
+ "edit_message",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/exportChatInvite.ts b/src/lib/mcp/telegram/tools/exportChatInvite.ts
index 6366fb4a5..6164f862c 100644
--- a/src/lib/mcp/telegram/tools/exportChatInvite.ts
+++ b/src/lib/mcp/telegram/tools/exportChatInvite.ts
@@ -1,26 +1,26 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { optString } from '../args';
-import type { ChatInviteResult } from '../apiResultTypes';
-import { narrow } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+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',
- description: 'Export a new chat invite link',
+ name: "export_chat_invite",
+ description: "Export a new chat invite link",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- title: { type: 'string', description: 'Link title' },
- expire_date: { type: 'number', description: 'Expiration timestamp' },
- usage_limit: { type: 'number', description: 'Max number of uses' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ title: { type: "string", description: "Link title" },
+ expire_date: { type: "number", description: "Expiration timestamp" },
+ usage_limit: { type: "number", description: "Max number of uses" },
},
- required: ['chat_id'],
+ required: ["chat_id"],
},
};
@@ -29,13 +29,19 @@ export async function exportChatInvite(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const title = optString(args, 'title');
- const expireDate = typeof args.expire_date === 'number' ? args.expire_date : undefined;
- const usageLimit = typeof args.usage_limit === 'number' ? args.usage_limit : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const title = optString(args, "title");
+ const expireDate =
+ typeof args.expire_date === "number" ? args.expire_date : undefined;
+ const usageLimit =
+ typeof args.usage_limit === "number" ? args.usage_limit : undefined;
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -54,13 +60,18 @@ export async function exportChatInvite(
const link = narrow(result)?.link;
if (!link) {
- return { content: [{ type: 'text', text: 'Could not create invite link.' }], isError: true };
+ return {
+ content: [{ type: "text", text: "Could not create invite link." }],
+ isError: true,
+ };
}
- return { content: [{ type: 'text', text: `Invite link created: ${link}` }] };
+ return {
+ content: [{ type: "text", text: `Invite link created: ${link}` }],
+ };
} catch (error) {
return logAndFormatError(
- 'export_chat_invite',
+ "export_chat_invite",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.GROUP,
);
diff --git a/src/lib/mcp/telegram/tools/forwardMessage.ts b/src/lib/mcp/telegram/tools/forwardMessage.ts
index af8149e2f..7c8e43f88 100644
--- a/src/lib/mcp/telegram/tools/forwardMessage.ts
+++ b/src/lib/mcp/telegram/tools/forwardMessage.ts
@@ -1,21 +1,21 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
export const tool: MCPTool = {
- name: 'forward_message',
- description: 'Forward a message to another chat',
+ name: "forward_message",
+ description: "Forward a message to another chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- from_chat_id: { type: 'string', description: 'Source chat ID' },
- to_chat_id: { type: 'string', description: 'Target chat ID' },
- message_id: { type: 'number', description: 'Message ID' },
+ from_chat_id: { type: "string", description: "Source chat ID" },
+ to_chat_id: { type: "string", description: "Target chat ID" },
+ message_id: { type: "number", description: "Message ID" },
},
- required: ['from_chat_id', 'to_chat_id', 'message_id'],
+ required: ["from_chat_id", "to_chat_id", "message_id"],
},
};
@@ -24,27 +24,38 @@ export async function forwardMessage(
_context: TelegramMCPContext,
): Promise {
try {
- const fromChatId = validateId(args.from_chat_id, 'from_chat_id');
- const toChatId = validateId(args.to_chat_id, 'to_chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
+ const fromChatId = validateId(args.from_chat_id, "from_chat_id");
+ const toChatId = validateId(args.to_chat_id, "to_chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be a positive integer' }],
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
isError: true,
};
}
const fromChat = getChatById(fromChatId);
if (!fromChat) {
- return { content: [{ type: 'text', text: `Source chat not found: ${fromChatId}` }], isError: true };
+ return {
+ content: [
+ { type: "text", text: `Source chat not found: ${fromChatId}` },
+ ],
+ isError: true,
+ };
}
const toChat = getChatById(toChatId);
if (!toChat) {
- return { content: [{ type: 'text', text: `Target chat not found: ${toChatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Target chat not found: ${toChatId}` }],
+ isError: true,
+ };
}
const fromEntity = fromChat.username ? fromChat.username : fromChat.id;
@@ -59,11 +70,16 @@ export async function forwardMessage(
});
return {
- content: [{ type: 'text', text: `Message ${messageId} forwarded from ${fromChatId} to ${toChatId}.` }],
+ content: [
+ {
+ type: "text",
+ text: `Message ${messageId} forwarded from ${fromChatId} to ${toChatId}.`,
+ },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'forward_message',
+ "forward_message",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/getBlockedUsers.ts b/src/lib/mcp/telegram/tools/getBlockedUsers.ts
index bdb09121a..16ba3f9a4 100644
--- a/src/lib/mcp/telegram/tools/getBlockedUsers.ts
+++ b/src/lib/mcp/telegram/tools/getBlockedUsers.ts
@@ -1,18 +1,18 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { optNumber } from '../args';
-import type { ApiUser } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'Get list of blocked users',
+ name: "get_blocked_users",
+ description: "Get list of blocked users",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- limit: { type: 'number', description: 'Max results', default: 50 },
+ limit: { type: "number", description: "Max results", default: 50 },
},
},
};
@@ -22,29 +22,33 @@ export async function getBlockedUsers(
_context: TelegramMCPContext,
): Promise {
try {
- const limit = optNumber(args, 'limit', 50);
+ const limit = optNumber(args, "limit", 50);
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
- return client.invoke(
- new Api.contacts.GetBlocked({ offset: 0, limit }),
- );
+ return client.invoke(new Api.contacts.GetBlocked({ offset: 0, limit }));
});
- if (!result || !('users' in result) || !Array.isArray(result.users) || result.users.length === 0) {
- return { content: [{ type: 'text', text: 'No blocked users.' }] };
+ if (
+ !result ||
+ !("users" in result) ||
+ !Array.isArray(result.users) ||
+ result.users.length === 0
+ ) {
+ return { content: [{ type: "text", text: "No blocked users." }] };
}
const lines = result.users.map((u: ApiUser) => {
- const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
- const username = u.username ? `@${u.username}` : '';
+ const name =
+ [u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
+ const username = u.username ? `@${u.username}` : "";
return `ID: ${u.id} | ${name} ${username}`.trim();
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_blocked_users',
+ "get_blocked_users",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/getBotInfo.ts b/src/lib/mcp/telegram/tools/getBotInfo.ts
index d0e41183d..56f96b8e5 100644
--- a/src/lib/mcp/telegram/tools/getBotInfo.ts
+++ b/src/lib/mcp/telegram/tools/getBotInfo.ts
@@ -1,10 +1,10 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { FullUserResult } from '../apiResultTypes';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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 = {
@@ -24,7 +24,7 @@ export async function getBotInfo(
_context: TelegramMCPContext,
): Promise {
try {
- const botId = validateId(args.chat_id, 'chat_id');
+ const botId = validateId(args.chat_id, "chat_id");
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
@@ -38,30 +38,34 @@ export async function getBotInfo(
const user = narrow(result)?.users?.[0];
if (!user) {
- return { content: [{ type: 'text', text: 'Bot not found: ' + botId }], isError: true };
+ return {
+ content: [{ type: "text", text: "Bot not found: " + botId }],
+ isError: true,
+ };
}
- const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
+ const name =
+ [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown";
const lines = [
- 'Name: ' + name,
- 'Username: @' + (user.username ?? 'N/A'),
- 'ID: ' + user.id,
- 'Bot: ' + (user.bot ? 'Yes' : 'No'),
- 'About: ' + (fullUser?.about ?? 'N/A'),
- 'Bot Info Description: ' + (fullUser?.botInfo?.description ?? 'N/A'),
+ "Name: " + name,
+ "Username: @" + (user.username ?? "N/A"),
+ "ID: " + user.id,
+ "Bot: " + (user.bot ? "Yes" : "No"),
+ "About: " + (fullUser?.about ?? "N/A"),
+ "Bot Info Description: " + (fullUser?.botInfo?.description ?? "N/A"),
];
if (fullUser?.botInfo?.commands) {
- lines.push('Commands:');
+ lines.push("Commands:");
for (const cmd of fullUser.botInfo.commands) {
- lines.push(' /' + cmd.command + ' - ' + cmd.description);
+ lines.push(" /" + cmd.command + " - " + cmd.description);
}
}
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_bot_info',
+ "get_bot_info",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/getChat.ts b/src/lib/mcp/telegram/tools/getChat.ts
index 02f217e00..f48d63aa6 100644
--- a/src/lib/mcp/telegram/tools/getChat.ts
+++ b/src/lib/mcp/telegram/tools/getChat.ts
@@ -2,25 +2,25 @@
* Get Chat tool - Get detailed information about a specific chat
*/
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { formatEntity, getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { formatEntity, getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
export const tool: MCPTool = {
- name: 'get_chat',
- description: 'Get detailed information about a specific chat',
+ name: "get_chat",
+ description: "Get detailed information about a specific chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
chat_id: {
- type: 'string',
- description: 'The ID or username of the chat',
+ type: "string",
+ description: "The ID or username of the chat",
},
},
- required: ['chat_id'],
+ required: ["chat_id"],
},
};
@@ -29,12 +29,12 @@ export async function getChat(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
+ const chatId = validateId(args.chat_id, "chat_id");
const chat = getChatById(chatId);
if (!chat) {
return {
- content: [{ type: 'text', text: `Chat not found: ${chatId}` }],
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
isError: true,
};
}
@@ -46,27 +46,27 @@ export async function getChat(
result.push(`Title: ${entity.name}`);
result.push(`Type: ${entity.type}`);
if (entity.username) result.push(`Username: @${entity.username}`);
- if ('participantsCount' in chat && chat.participantsCount) {
+ if ("participantsCount" in chat && chat.participantsCount) {
result.push(`Participants: ${chat.participantsCount}`);
}
- if ('unreadCount' in chat) {
+ if ("unreadCount" in chat) {
result.push(`Unread Messages: ${chat.unreadCount ?? 0}`);
}
const lastMsg = chat.lastMessage;
if (lastMsg) {
- const from = lastMsg.fromName ?? lastMsg.fromId ?? 'Unknown';
+ const from = lastMsg.fromName ?? lastMsg.fromId ?? "Unknown";
const date = new Date(lastMsg.date * 1000).toISOString();
result.push(`Last Message: From ${from} at ${date}`);
- result.push(`Message: ${lastMsg.message || '[Media/No text]'}`);
+ result.push(`Message: ${lastMsg.message || "[Media/No text]"}`);
}
return {
- content: [{ type: 'text', text: result.join('\n') }],
+ content: [{ type: "text", text: result.join("\n") }],
};
} catch (error) {
return logAndFormatError(
- 'get_chat',
+ "get_chat",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CHAT,
);
diff --git a/src/lib/mcp/telegram/tools/getChats.ts b/src/lib/mcp/telegram/tools/getChats.ts
index 450998353..c0059c372 100644
--- a/src/lib/mcp/telegram/tools/getChats.ts
+++ b/src/lib/mcp/telegram/tools/getChats.ts
@@ -2,21 +2,29 @@
* Get Chats tool - Get a paginated list of chats
*/
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { optNumber } from '../args';
-import { formatEntity, getChats as getChatsApi } from '../telegramApi';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { optNumber } from "../args";
+import { formatEntity, getChats as getChatsApi } from "../telegramApi";
export const tool: MCPTool = {
- name: 'get_chats',
- description: 'Get a paginated list of chats',
+ name: "get_chats",
+ description: "Get a paginated list of chats",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- page: { type: 'number', description: 'Page number (1-indexed)', default: 1 },
- page_size: { type: 'number', description: 'Number of chats per page', default: 20 },
+ page: {
+ type: "number",
+ description: "Page number (1-indexed)",
+ default: 1,
+ },
+ page_size: {
+ type: "number",
+ description: "Number of chats per page",
+ default: 20,
+ },
},
},
};
@@ -26,15 +34,15 @@ export async function getChats(
_context: TelegramMCPContext,
): Promise {
try {
- const page = optNumber(args, 'page', 1);
- const pageSize = optNumber(args, 'page_size', 20);
+ const page = optNumber(args, "page", 1);
+ const pageSize = optNumber(args, "page_size", 20);
const start = (page - 1) * pageSize;
const chats = await getChatsApi(pageSize + start);
const paginatedChats = chats.slice(start, start + pageSize);
if (paginatedChats.length === 0) {
- return { content: [{ type: 'text', text: 'Page out of range.' }] };
+ return { content: [{ type: "text", text: "Page out of range." }] };
}
const lines = paginatedChats.map((chat) => {
@@ -42,10 +50,10 @@ export async function getChats(
return `Chat ID: ${entity.id}, Title: ${entity.name}`;
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_chats',
+ "get_chats",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CHAT,
);
diff --git a/src/lib/mcp/telegram/tools/getContactChats.ts b/src/lib/mcp/telegram/tools/getContactChats.ts
index 13cbc8238..82794c848 100644
--- a/src/lib/mcp/telegram/tools/getContactChats.ts
+++ b/src/lib/mcp/telegram/tools/getContactChats.ts
@@ -1,13 +1,13 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { store } from '../../../../store';
-import { selectOrderedChats } from '../../../../store/telegramSelectors';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { store } from "../../../../store";
+import { selectOrderedChats } from "../../../../store/telegramSelectors";
export const tool: MCPTool = {
- name: 'get_contact_chats',
- description: 'Get all chats that are direct messages with contacts',
- inputSchema: { type: 'object', properties: {} },
+ name: "get_contact_chats",
+ description: "Get all chats that are direct messages with contacts",
+ inputSchema: { type: "object", properties: {} },
};
export async function getContactChats(
@@ -17,21 +17,28 @@ export async function getContactChats(
try {
const state = store.getState();
const chats = selectOrderedChats(state);
- const dmChats = chats.filter((c) => c.type === 'private');
+ const dmChats = chats.filter((c) => c.type === "private");
if (dmChats.length === 0) {
- return { content: [{ type: 'text', text: 'No contact chats found.' }] };
+ return { content: [{ type: "text", text: "No contact chats found." }] };
}
const lines = dmChats.map((c) => {
- const username = c.username ? '@' + c.username : '';
- return ('ID: ' + c.id + ' | ' + (c.title ?? 'DM') + ' ' + username).trim();
+ const username = c.username ? "@" + c.username : "";
+ return (
+ "ID: " +
+ c.id +
+ " | " +
+ (c.title ?? "DM") +
+ " " +
+ username
+ ).trim();
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_contact_chats',
+ "get_contact_chats",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/getContactIds.ts b/src/lib/mcp/telegram/tools/getContactIds.ts
index 8226fbc93..8fcb7159d 100644
--- a/src/lib/mcp/telegram/tools/getContactIds.ts
+++ b/src/lib/mcp/telegram/tools/getContactIds.ts
@@ -1,18 +1,18 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import type { ContactIdEntry } from '../apiResultTypes';
-import { narrow } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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;
export const tool: MCPTool = {
- name: 'get_contact_ids',
- description: 'Get IDs of all contacts',
- inputSchema: { type: 'object', properties: {} },
+ name: "get_contact_ids",
+ description: "Get IDs of all contacts",
+ inputSchema: { type: "object", properties: {} },
};
export async function getContactIds(
@@ -27,16 +27,20 @@ export async function getContactIds(
});
if (!result || !Array.isArray(result) || result.length === 0) {
- return { content: [{ type: 'text', text: 'No contact IDs found.' }] };
+ return { content: [{ type: "text", text: "No contact IDs found." }] };
}
const ids = narrow(result).map((c) =>
- String(typeof c === 'number' ? c : c.userId ?? c),
+ String(typeof c === "number" ? c : (c.userId ?? c)),
);
- return { content: [{ type: 'text', text: ids.length + ' contacts:\n' + ids.join('\n') }] };
+ return {
+ content: [
+ { type: "text", text: ids.length + " contacts:\n" + ids.join("\n") },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'get_contact_ids',
+ "get_contact_ids",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/getDirectChatByContact.ts b/src/lib/mcp/telegram/tools/getDirectChatByContact.ts
index ee2a24852..391ee34c3 100644
--- a/src/lib/mcp/telegram/tools/getDirectChatByContact.ts
+++ b/src/lib/mcp/telegram/tools/getDirectChatByContact.ts
@@ -1,19 +1,19 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { store } from '../../../../store';
-import { selectOrderedChats } from '../../../../store/telegramSelectors';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { store } from "../../../../store";
+import { selectOrderedChats } from "../../../../store/telegramSelectors";
export const tool: MCPTool = {
- name: 'get_direct_chat_by_contact',
- description: 'Get direct message chat with a contact',
+ name: "get_direct_chat_by_contact",
+ description: "Get direct message chat with a contact",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- user_id: { type: 'string', description: 'User ID' },
+ user_id: { type: "string", description: "User ID" },
},
- required: ['user_id'],
+ required: ["user_id"],
},
};
@@ -22,27 +22,42 @@ export async function getDirectChatByContact(
_context: TelegramMCPContext,
): Promise {
try {
- const userId = validateId(args.user_id, 'user_id');
+ const userId = validateId(args.user_id, "user_id");
const state = store.getState();
const chats = selectOrderedChats(state);
const dmChat = chats.find(
- (c) => c.type === 'private' && String(c.id) === String(userId),
+ (c) => c.type === "private" && String(c.id) === String(userId),
);
if (!dmChat) {
- return { content: [{ type: 'text', text: 'No direct chat found with user ' + userId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No direct chat found with user " + userId + ".",
+ },
+ ],
+ };
}
return {
- content: [{
- type: 'text',
- text: 'Chat ID: ' + dmChat.id + ' | Title: ' + (dmChat.title ?? 'DM') + ' | Username: ' + (dmChat.username ?? 'N/A'),
- }],
+ content: [
+ {
+ type: "text",
+ text:
+ "Chat ID: " +
+ dmChat.id +
+ " | Title: " +
+ (dmChat.title ?? "DM") +
+ " | Username: " +
+ (dmChat.username ?? "N/A"),
+ },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'get_direct_chat_by_contact',
+ "get_direct_chat_by_contact",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/getDrafts.ts b/src/lib/mcp/telegram/tools/getDrafts.ts
index 26f092d74..0dd351ec4 100644
--- a/src/lib/mcp/telegram/tools/getDrafts.ts
+++ b/src/lib/mcp/telegram/tools/getDrafts.ts
@@ -1,9 +1,9 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { UpdatesResult } from '../apiResultTypes';
+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 = {
@@ -25,25 +25,29 @@ export async function getDrafts(
const updates = narrow(result);
if (!updates || !updates.updates || updates.updates.length === 0) {
- return { content: [{ type: 'text', text: 'No drafts found.' }] };
+ return { content: [{ type: "text", text: "No drafts found." }] };
}
const lines: string[] = [];
for (const update of updates.updates) {
if (update.draft && update.draft.message) {
- const peerId = update.peer?.userId ?? update.peer?.chatId ?? update.peer?.channelId ?? '?';
- lines.push('Peer ' + peerId + ': ' + update.draft.message);
+ const peerId =
+ update.peer?.userId ??
+ update.peer?.chatId ??
+ update.peer?.channelId ??
+ "?";
+ lines.push("Peer " + peerId + ": " + update.draft.message);
}
}
if (lines.length === 0) {
- return { content: [{ type: 'text', text: 'No drafts found.' }] };
+ return { content: [{ type: "text", text: "No drafts found." }] };
}
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_drafts',
+ "get_drafts",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.DRAFT,
);
diff --git a/src/lib/mcp/telegram/tools/getGifSearch.ts b/src/lib/mcp/telegram/tools/getGifSearch.ts
index ccd28509a..e5025b0ce 100644
--- a/src/lib/mcp/telegram/tools/getGifSearch.ts
+++ b/src/lib/mcp/telegram/tools/getGifSearch.ts
@@ -1,10 +1,10 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { optNumber } from '../args';
-import type { InlineBotResults } from '../apiResultTypes';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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 = {
@@ -14,7 +14,7 @@ export const tool: MCPTool = {
type: "object",
properties: {
query: { type: "string", description: "Search query" },
- limit: { type: 'number', description: 'Max results', default: 10 },
+ limit: { type: "number", description: "Max results", default: 10 },
},
required: ["query"],
},
@@ -25,38 +25,51 @@ export async function getGifSearch(
_context: TelegramMCPContext,
): Promise {
try {
- const query = typeof args.query === 'string' ? args.query : '';
- if (!query) return { content: [{ type: 'text', text: 'query is required' }], isError: true };
- const limit = optNumber(args, 'limit', 10);
+ const query = typeof args.query === "string" ? args.query : "";
+ if (!query)
+ return {
+ content: [{ type: "text", text: "query is required" }],
+ isError: true,
+ };
+ const limit = optNumber(args, "limit", 10);
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
- const bot = await client.getInputEntity('gif');
+ const bot = await client.getInputEntity("gif");
return client.invoke(
new Api.messages.GetInlineBotResults({
bot: toInputUser(bot),
peer: new Api.InputPeerSelf(),
query,
- offset: '',
+ offset: "",
}),
);
});
const results = narrow(result)?.results;
if (!results || !Array.isArray(results) || results.length === 0) {
- return { content: [{ type: 'text', text: 'No GIFs found for: ' + query }] };
+ return {
+ content: [{ type: "text", text: "No GIFs found for: " + query }],
+ };
}
const lines = results.slice(0, limit).map((r, i: number) => {
- const title = r.title ?? r.description ?? 'GIF ' + (i + 1);
- return (i + 1) + '. ' + title;
+ const title = r.title ?? r.description ?? "GIF " + (i + 1);
+ return i + 1 + ". " + title;
});
- return { content: [{ type: 'text', text: lines.length + ' GIFs found:\n' + lines.join('\n') }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: lines.length + " GIFs found:\n" + lines.join("\n"),
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'get_gif_search',
+ "get_gif_search",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MEDIA,
);
diff --git a/src/lib/mcp/telegram/tools/getHistory.ts b/src/lib/mcp/telegram/tools/getHistory.ts
index c8fab3a8f..335441e0f 100644
--- a/src/lib/mcp/telegram/tools/getHistory.ts
+++ b/src/lib/mcp/telegram/tools/getHistory.ts
@@ -1,20 +1,20 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById, getMessages, formatMessage } from '../telegramApi';
-import { validateId } from '../../validation';
-import { optNumber } from '../args';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById, getMessages, formatMessage } from "../telegramApi";
+import { validateId } from "../../validation";
+import { optNumber } from "../args";
export const tool: MCPTool = {
- name: 'get_history',
- description: 'Get message history from a chat',
+ name: "get_history",
+ description: "Get message history from a chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- limit: { type: 'number', description: 'Number of messages', default: 20 },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ limit: { type: "number", description: "Number of messages", default: 20 },
},
- required: ['chat_id'],
+ required: ["chat_id"],
},
};
@@ -23,29 +23,34 @@ export async function getHistory(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const limit = optNumber(args, 'limit', 20);
+ const chatId = validateId(args.chat_id, "chat_id");
+ const limit = optNumber(args, "limit", 20);
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const messages = await getMessages(chatId, limit, 0);
if (!messages || messages.length === 0) {
- return { content: [{ type: 'text', text: 'No messages found in this chat.' }] };
+ return {
+ content: [{ type: "text", text: "No messages found in this chat." }],
+ };
}
const lines = messages.map((msg) => {
const f = formatMessage(msg);
- const from = msg.fromName ?? msg.fromId ?? 'Unknown';
- return `ID: ${f.id} | ${from} | ${f.date} | ${f.text || '[Media/No text]'}`;
+ const from = msg.fromName ?? msg.fromId ?? "Unknown";
+ return `ID: ${f.id} | ${from} | ${f.date} | ${f.text || "[Media/No text]"}`;
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_history',
+ "get_history",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/getLastInteraction.ts b/src/lib/mcp/telegram/tools/getLastInteraction.ts
index 5aacedcbd..674ae3b1a 100644
--- a/src/lib/mcp/telegram/tools/getLastInteraction.ts
+++ b/src/lib/mcp/telegram/tools/getLastInteraction.ts
@@ -1,18 +1,18 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById, getMessages, formatMessage } from '../telegramApi';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById, getMessages, formatMessage } from "../telegramApi";
export const tool: MCPTool = {
- name: 'get_last_interaction',
- description: 'Get the last message exchanged with a user or chat',
+ name: "get_last_interaction",
+ description: "Get the last message exchanged with a user or chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
+ chat_id: { type: "string", description: "Chat ID or username" },
},
- required: ['chat_id'],
+ required: ["chat_id"],
},
};
@@ -21,31 +21,46 @@ export async function getLastInteraction(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
+ const chatId = validateId(args.chat_id, "chat_id");
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
}
const messages = await getMessages(chatId, 1, 0);
if (!messages || messages.length === 0) {
- return { content: [{ type: 'text', text: 'No messages found in this chat.' }] };
+ return {
+ content: [{ type: "text", text: "No messages found in this chat." }],
+ };
}
const msg = messages[0];
const f = formatMessage(msg);
- const from = msg.fromName ?? msg.fromId ?? 'Unknown';
+ const from = msg.fromName ?? msg.fromId ?? "Unknown";
return {
- content: [{
- type: 'text',
- text: 'Last message in ' + (chat.title ?? chatId) + ':\nFrom: ' + from + ' | Date: ' + f.date + ' | ' + (f.text || '[Media/No text]'),
- }],
+ content: [
+ {
+ type: "text",
+ text:
+ "Last message in " +
+ (chat.title ?? chatId) +
+ ":\nFrom: " +
+ from +
+ " | Date: " +
+ f.date +
+ " | " +
+ (f.text || "[Media/No text]"),
+ },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'get_last_interaction',
+ "get_last_interaction",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/getMediaInfo.ts b/src/lib/mcp/telegram/tools/getMediaInfo.ts
index b14f54bbb..f9687298b 100644
--- a/src/lib/mcp/telegram/tools/getMediaInfo.ts
+++ b/src/lib/mcp/telegram/tools/getMediaInfo.ts
@@ -1,8 +1,8 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById, getMessages } from '../telegramApi';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById, getMessages } from "../telegramApi";
export const tool: MCPTool = {
name: "get_media_info",
@@ -22,35 +22,61 @@ export async function getMediaInfo(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
- return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
+ return {
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
+ isError: true,
+ };
}
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const messages = await getMessages(chatId, 200, 0);
- if (!messages) return { content: [{ type: 'text', text: 'No messages found.' }] };
+ if (!messages)
+ return { content: [{ type: "text", text: "No messages found." }] };
const msg = messages.find((m) => String(m.id) === String(messageId));
- if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true };
+ if (!msg)
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Message " + messageId + " not found in cache.",
+ },
+ ],
+ isError: true,
+ };
if (!msg.media) {
- return { content: [{ type: 'text', text: 'No media in message ' + messageId + '.' }] };
+ return {
+ content: [
+ { type: "text", text: "No media in message " + messageId + "." },
+ ],
+ };
}
const info = {
...msg.media,
- type: msg.media.type ?? 'unknown',
+ type: msg.media.type ?? "unknown",
};
- return { content: [{ type: 'text', text: JSON.stringify(info, null, 2) }] };
+ return { content: [{ type: "text", text: JSON.stringify(info, null, 2) }] };
} catch (error) {
return logAndFormatError(
- 'get_media_info',
+ "get_media_info",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MEDIA,
);
diff --git a/src/lib/mcp/telegram/tools/getMessageContext.ts b/src/lib/mcp/telegram/tools/getMessageContext.ts
index 3a1667fdc..111db3fcb 100644
--- a/src/lib/mcp/telegram/tools/getMessageContext.ts
+++ b/src/lib/mcp/telegram/tools/getMessageContext.ts
@@ -1,21 +1,25 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById, getMessages, formatMessage } from '../telegramApi';
-import { validateId } from '../../validation';
-import { optNumber } from '../args';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById, getMessages, formatMessage } from "../telegramApi";
+import { validateId } from "../../validation";
+import { optNumber } from "../args";
export const tool: MCPTool = {
- name: 'get_message_context',
- description: 'Get context around a specific message',
+ name: "get_message_context",
+ description: "Get context around a specific message",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
- limit: { type: 'number', description: 'Number of messages before/after', default: 5 },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
+ limit: {
+ type: "number",
+ description: "Number of messages before/after",
+ default: 5,
+ },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -24,33 +28,48 @@ export async function getMessageContext(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
- const contextSize = optNumber(args, 'limit', 5);
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
+ const contextSize = optNumber(args, "limit", 5);
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be a positive integer' }],
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
isError: true,
};
}
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const allMessages = await getMessages(chatId, 200, 0);
if (!allMessages || allMessages.length === 0) {
- return { content: [{ type: 'text', text: 'No messages found in this chat.' }] };
+ return {
+ content: [{ type: "text", text: "No messages found in this chat." }],
+ };
}
- const targetIndex = allMessages.findIndex((m) => String(m.id) === String(messageId));
+ const targetIndex = allMessages.findIndex(
+ (m) => String(m.id) === String(messageId),
+ );
if (targetIndex === -1) {
return {
- content: [{ type: 'text', text: `Message ${messageId} not found in cached messages.` }],
+ content: [
+ {
+ type: "text",
+ text: `Message ${messageId} not found in cached messages.`,
+ },
+ ],
isError: true,
};
}
@@ -61,15 +80,15 @@ export async function getMessageContext(
const lines = contextMessages.map((msg) => {
const f = formatMessage(msg);
- const from = msg.fromName ?? msg.fromId ?? 'Unknown';
- const marker = String(msg.id) === String(messageId) ? ' >>> ' : ' ';
- return `${marker}ID: ${f.id} | ${from} | ${f.date} | ${f.text || '[Media/No text]'}`;
+ const from = msg.fromName ?? msg.fromId ?? "Unknown";
+ const marker = String(msg.id) === String(messageId) ? " >>> " : " ";
+ return `${marker}ID: ${f.id} | ${from} | ${f.date} | ${f.text || "[Media/No text]"}`;
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_message_context',
+ "get_message_context",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/getMessageReactions.ts b/src/lib/mcp/telegram/tools/getMessageReactions.ts
index 7d3e66fa4..69efde804 100644
--- a/src/lib/mcp/telegram/tools/getMessageReactions.ts
+++ b/src/lib/mcp/telegram/tools/getMessageReactions.ts
@@ -1,23 +1,23 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { UpdatesResult } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+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',
- description: 'Get reactions on a message',
+ name: "get_message_reactions",
+ description: "Get reactions on a message",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -26,15 +26,27 @@ export async function getMessageReactions(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
- return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
+ return {
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
+ isError: true,
+ };
}
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -51,28 +63,49 @@ export async function getMessageReactions(
const updates = narrow(result);
if (!updates || !updates.updates || updates.updates.length === 0) {
- return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No reactions found on message " + messageId + ".",
+ },
+ ],
+ };
}
const lines: string[] = [];
for (const update of updates.updates) {
if (update.reactions && update.reactions.results) {
for (const r of update.reactions.results) {
- const emoji = r.reaction?.emoticon ?? r.reaction?.className ?? '?';
+ const emoji = r.reaction?.emoticon ?? r.reaction?.className ?? "?";
const count = r.count ?? 0;
- lines.push(emoji + ': ' + count);
+ lines.push(emoji + ": " + count);
}
}
}
if (lines.length === 0) {
- return { content: [{ type: 'text', text: 'No reactions found on message ' + messageId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No reactions found on message " + messageId + ".",
+ },
+ ],
+ };
}
- return { content: [{ type: 'text', text: 'Reactions on message ' + messageId + ':\n' + lines.join('\n') }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Reactions on message " + messageId + ":\n" + lines.join("\n"),
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'get_message_reactions',
+ "get_message_reactions",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/getPinnedMessages.ts b/src/lib/mcp/telegram/tools/getPinnedMessages.ts
index 49455280b..c79b97bda 100644
--- a/src/lib/mcp/telegram/tools/getPinnedMessages.ts
+++ b/src/lib/mcp/telegram/tools/getPinnedMessages.ts
@@ -1,21 +1,23 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById, getMessages, formatMessage } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import type { ApiMessage } from '../apiResultTypes';
-import { narrow } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById, getMessages, formatMessage } from "../telegramApi";
+import { validateId } from "../../validation";
+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',
- description: 'Get pinned messages from a chat',
+ name: "get_pinned_messages",
+ description: "Get pinned messages from a chat",
inputSchema: {
- type: 'object',
- properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
- required: ['chat_id'],
+ type: "object",
+ properties: {
+ chat_id: { type: "string", description: "Chat ID or username" },
+ },
+ required: ["chat_id"],
},
};
@@ -24,11 +26,14 @@ export async function getPinnedMessages(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
+ const chatId = validateId(args.chat_id, "chat_id");
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const entity = chat.username ? chat.username : chat.id;
@@ -41,7 +46,7 @@ export async function getPinnedMessages(
const result = await client.invoke(
new Api.messages.Search({
peer: inputPeer,
- q: '',
+ q: "",
filter: new Api.InputMessagesFilterPinned(),
minDate: 0,
maxDate: 0,
@@ -54,11 +59,13 @@ export async function getPinnedMessages(
}),
);
- if ('messages' in result && Array.isArray(result.messages)) {
+ if ("messages" in result && Array.isArray(result.messages)) {
pinnedLines = narrow(result.messages).map((msg) => {
- const id = msg.id ?? '?';
- const text = msg.message ?? '[Media/No text]';
- const date = msg.date ? new Date(msg.date * 1000).toISOString() : 'unknown';
+ const id = msg.id ?? "?";
+ const text = msg.message ?? "[Media/No text]";
+ const date = msg.date
+ ? new Date(msg.date * 1000).toISOString()
+ : "unknown";
return `ID: ${id} | Date: ${date} | ${text}`;
});
}
@@ -69,19 +76,19 @@ export async function getPinnedMessages(
const pinned = allMessages.filter((m) => narrow(m).pinned);
pinnedLines = pinned.map((msg) => {
const f = formatMessage(msg);
- return `ID: ${f.id} | Date: ${f.date} | ${f.text || '[Media/No text]'}`;
+ return `ID: ${f.id} | Date: ${f.date} | ${f.text || "[Media/No text]"}`;
});
}
}
if (pinnedLines.length === 0) {
- return { content: [{ type: 'text', text: 'No pinned messages found.' }] };
+ return { content: [{ type: "text", text: "No pinned messages found." }] };
}
- return { content: [{ type: 'text', text: pinnedLines.join('\n') }] };
+ return { content: [{ type: "text", text: pinnedLines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_pinned_messages',
+ "get_pinned_messages",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/getPrivacySettings.ts b/src/lib/mcp/telegram/tools/getPrivacySettings.ts
index 997d7c110..7fc954c0d 100644
--- a/src/lib/mcp/telegram/tools/getPrivacySettings.ts
+++ b/src/lib/mcp/telegram/tools/getPrivacySettings.ts
@@ -1,9 +1,9 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { PrivacyResult } from '../apiResultTypes';
+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 = {
@@ -12,7 +12,12 @@ export const tool: MCPTool = {
inputSchema: {
type: "object",
properties: {
- key: { type: 'string', description: 'Privacy key: phone_number, last_seen, profile_photo, forwards, phone_call, chat_invite', default: 'last_seen' },
+ key: {
+ type: "string",
+ description:
+ "Privacy key: phone_number, last_seen, profile_photo, forwards, phone_call, chat_invite",
+ default: "last_seen",
+ },
},
},
};
@@ -22,7 +27,7 @@ export async function getPrivacySettings(
_context: TelegramMCPContext,
): Promise {
try {
- const keyStr = typeof args.key === 'string' ? args.key : 'last_seen';
+ const keyStr = typeof args.key === "string" ? args.key : "last_seen";
const client = mtprotoService.getClient();
const keyMap: Record = {
@@ -36,7 +41,19 @@ export async function getPrivacySettings(
const key = keyMap[keyStr];
if (!key) {
- return { content: [{ type: 'text', text: 'Unknown privacy key: ' + keyStr + '. Valid keys: ' + Object.keys(keyMap).join(', ') }], isError: true };
+ return {
+ content: [
+ {
+ type: "text",
+ text:
+ "Unknown privacy key: " +
+ keyStr +
+ ". Valid keys: " +
+ Object.keys(keyMap).join(", "),
+ },
+ ],
+ isError: true,
+ };
}
const result = await mtprotoService.withFloodWaitHandling(async () => {
@@ -45,14 +62,25 @@ export async function getPrivacySettings(
const rules = narrow(result)?.rules;
if (!rules || !Array.isArray(rules)) {
- return { content: [{ type: 'text', text: 'No privacy rules found for ' + keyStr + '.' }] };
+ return {
+ content: [
+ { type: "text", text: "No privacy rules found for " + keyStr + "." },
+ ],
+ };
}
- const lines = rules.map((r) => r.className ?? 'Unknown rule');
- return { content: [{ type: 'text', text: 'Privacy settings for ' + keyStr + ':\n' + lines.join('\n') }] };
+ 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(
- 'get_privacy_settings',
+ "get_privacy_settings",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
diff --git a/src/lib/mcp/telegram/tools/getRecentActions.ts b/src/lib/mcp/telegram/tools/getRecentActions.ts
index 9026f5c89..2bc7e77ce 100644
--- a/src/lib/mcp/telegram/tools/getRecentActions.ts
+++ b/src/lib/mcp/telegram/tools/getRecentActions.ts
@@ -1,13 +1,13 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import { optNumber } from '../args';
-import type { AdminLogResult } from '../apiResultTypes';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+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 = {
@@ -28,14 +28,26 @@ export async function getRecentActions(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const limit = optNumber(args, 'limit', 20);
+ const chatId = validateId(args.chat_id, "chat_id");
+ const limit = optNumber(args, "limit", 20);
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
- if (chat.type !== 'channel' && chat.type !== 'supergroup') {
- return { content: [{ type: 'text', text: 'Recent actions are only available for channels/supergroups.' }], isError: true };
+ if (chat.type !== "channel" && chat.type !== "supergroup") {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Recent actions are only available for channels/supergroups.",
+ },
+ ],
+ isError: true,
+ };
}
const client = mtprotoService.getClient();
@@ -46,7 +58,7 @@ export async function getRecentActions(
return client.invoke(
new Api.channels.GetAdminLog({
channel: toInputChannel(inputChannel),
- q: '',
+ q: "",
maxId: bigInt(0),
minId: bigInt(0),
limit,
@@ -56,19 +68,19 @@ export async function getRecentActions(
const events = narrow(result)?.events;
if (!events || !Array.isArray(events) || events.length === 0) {
- return { content: [{ type: 'text', text: 'No recent actions found.' }] };
+ return { content: [{ type: "text", text: "No recent actions found." }] };
}
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;
+ const date = e.date ? new Date(e.date * 1000).toISOString() : "unknown";
+ const action = e.action?.className ?? "unknown";
+ return date + " | " + action;
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'get_recent_actions',
+ "get_recent_actions",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.ADMIN,
);
diff --git a/src/lib/mcp/telegram/tools/getStickerSets.ts b/src/lib/mcp/telegram/tools/getStickerSets.ts
index 4df931048..eda5236a8 100644
--- a/src/lib/mcp/telegram/tools/getStickerSets.ts
+++ b/src/lib/mcp/telegram/tools/getStickerSets.ts
@@ -1,10 +1,10 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import type { StickerSetsResult } from '../apiResultTypes';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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 = {
@@ -21,22 +21,39 @@ export async function getStickerSets(
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
- return client.invoke(new Api.messages.GetAllStickers({ hash: bigInt(0) }));
+ return client.invoke(
+ new Api.messages.GetAllStickers({ hash: bigInt(0) }),
+ );
});
const sets = narrow(result)?.sets;
if (!sets || !Array.isArray(sets) || sets.length === 0) {
- return { content: [{ type: 'text', text: 'No sticker sets found.' }] };
+ return { content: [{ type: "text", text: "No sticker sets found." }] };
}
const lines = sets.map((s) => {
- return 'ID: ' + s.id + ' | ' + (s.title ?? 'Untitled') + ' (' + (s.count ?? 0) + ' stickers)';
+ return (
+ "ID: " +
+ s.id +
+ " | " +
+ (s.title ?? "Untitled") +
+ " (" +
+ (s.count ?? 0) +
+ " stickers)"
+ );
});
- return { content: [{ type: 'text', text: lines.length + ' sticker sets:\n' + lines.join('\n') }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: lines.length + " sticker sets:\n" + lines.join("\n"),
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'get_sticker_sets',
+ "get_sticker_sets",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MEDIA,
);
diff --git a/src/lib/mcp/telegram/tools/getUserPhotos.ts b/src/lib/mcp/telegram/tools/getUserPhotos.ts
index 37a051f60..0dcd39d67 100644
--- a/src/lib/mcp/telegram/tools/getUserPhotos.ts
+++ b/src/lib/mcp/telegram/tools/getUserPhotos.ts
@@ -1,24 +1,24 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import { optNumber } from '../args';
-import type { ApiPhoto } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
+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',
- description: 'Get profile photos of a user',
+ name: "get_user_photos",
+ description: "Get profile photos of a user",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- user_id: { type: 'string', description: 'User ID' },
- limit: { type: 'number', description: 'Max photos', default: 10 },
+ user_id: { type: "string", description: "User ID" },
+ limit: { type: "number", description: "Max photos", default: 10 },
},
- required: ['user_id'],
+ required: ["user_id"],
},
};
@@ -27,8 +27,8 @@ export async function getUserPhotos(
_context: TelegramMCPContext,
): Promise {
try {
- const userId = validateId(args.user_id, 'user_id');
- const limit = optNumber(args, 'limit', 10);
+ const userId = validateId(args.user_id, "user_id");
+ const limit = optNumber(args, "limit", 10);
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
@@ -43,19 +43,33 @@ export async function getUserPhotos(
);
});
- if (!result || !('photos' in result) || !Array.isArray(result.photos) || result.photos.length === 0) {
- return { content: [{ type: 'text', text: 'No photos found.' }] };
+ if (
+ !result ||
+ !("photos" in result) ||
+ !Array.isArray(result.photos) ||
+ result.photos.length === 0
+ ) {
+ return { content: [{ type: "text", text: "No photos found." }] };
}
const lines = narrow(result.photos).map((photo, i: number) => {
- const date = photo.date ? new Date(photo.date * 1000).toISOString() : 'unknown';
- return 'Photo ' + (i + 1) + ': ID ' + photo.id + ' | Date: ' + date;
+ const date = photo.date
+ ? new Date(photo.date * 1000).toISOString()
+ : "unknown";
+ return "Photo " + (i + 1) + ": ID " + photo.id + " | Date: " + date;
});
- return { content: [{ type: 'text', text: lines.length + ' photos found:\n' + lines.join('\n') }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: lines.length + " photos found:\n" + lines.join("\n"),
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'get_user_photos',
+ "get_user_photos",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
diff --git a/src/lib/mcp/telegram/tools/getUserStatus.ts b/src/lib/mcp/telegram/tools/getUserStatus.ts
index 4f52795e0..77fd8e71b 100644
--- a/src/lib/mcp/telegram/tools/getUserStatus.ts
+++ b/src/lib/mcp/telegram/tools/getUserStatus.ts
@@ -1,21 +1,21 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { ApiUser } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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',
- description: 'Get online status of a user',
+ name: "get_user_status",
+ description: "Get online status of a user",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- user_id: { type: 'string', description: 'User ID' },
+ user_id: { type: "string", description: "User ID" },
},
- required: ['user_id'],
+ required: ["user_id"],
},
};
@@ -24,7 +24,7 @@ export async function getUserStatus(
_context: TelegramMCPContext,
): Promise {
try {
- const userId = validateId(args.user_id, 'user_id');
+ const userId = validateId(args.user_id, "user_id");
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
@@ -35,28 +35,41 @@ export async function getUserStatus(
});
if (!result || !Array.isArray(result) || result.length === 0) {
- return { content: [{ type: 'text', text: 'User ' + userId + ' not found.' }], isError: true };
+ return {
+ content: [{ type: "text", text: "User " + userId + " not found." }],
+ isError: true,
+ };
}
const user = narrow(result[0]);
- const name = [user.firstName, user.lastName].filter(Boolean).join(' ') || 'Unknown';
- let statusText = 'unknown';
+ const name =
+ [user.firstName, user.lastName].filter(Boolean).join(" ") || "Unknown";
+ let statusText = "unknown";
if (user.status) {
const s = user.status;
- if (s.className === 'UserStatusOnline') statusText = 'Online';
- else if (s.className === 'UserStatusOffline')
- statusText = 'Offline (last seen: ' + (s.wasOnline ? new Date(s.wasOnline * 1000).toISOString() : 'unknown') + ')';
- else if (s.className === 'UserStatusRecently') statusText = 'Recently';
- else if (s.className === 'UserStatusLastWeek') statusText = 'Last week';
- else if (s.className === 'UserStatusLastMonth') statusText = 'Last month';
- else statusText = s.className ?? 'unknown';
+ if (s.className === "UserStatusOnline") statusText = "Online";
+ else if (s.className === "UserStatusOffline")
+ statusText =
+ "Offline (last seen: " +
+ (s.wasOnline
+ ? new Date(s.wasOnline * 1000).toISOString()
+ : "unknown") +
+ ")";
+ else if (s.className === "UserStatusRecently") statusText = "Recently";
+ else if (s.className === "UserStatusLastWeek") statusText = "Last week";
+ else if (s.className === "UserStatusLastMonth") statusText = "Last month";
+ else statusText = s.className ?? "unknown";
}
- return { content: [{ type: 'text', text: name + ' (ID: ' + user.id + '): ' + statusText }] };
+ return {
+ content: [
+ { type: "text", text: name + " (ID: " + user.id + "): " + statusText },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'get_user_status',
+ "get_user_status",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/importChatInvite.ts b/src/lib/mcp/telegram/tools/importChatInvite.ts
index c1f66fca2..44fa8c357 100644
--- a/src/lib/mcp/telegram/tools/importChatInvite.ts
+++ b/src/lib/mcp/telegram/tools/importChatInvite.ts
@@ -39,7 +39,8 @@ export async function importChatInvite(
return client.invoke(new Api.messages.ImportChatInvite({ hash }));
});
- const chatTitle = narrow(result)?.chats?.[0]?.title ?? "unknown";
+ const chatTitle =
+ narrow(result)?.chats?.[0]?.title ?? "unknown";
return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] };
} catch (error) {
return logAndFormatError(
diff --git a/src/lib/mcp/telegram/tools/importContacts.ts b/src/lib/mcp/telegram/tools/importContacts.ts
index c0a1605a6..978230ab5 100644
--- a/src/lib/mcp/telegram/tools/importContacts.ts
+++ b/src/lib/mcp/telegram/tools/importContacts.ts
@@ -67,7 +67,8 @@ export async function importContacts(
);
});
- const imported = narrow(result)?.imported?.length ?? 0;
+ const imported =
+ narrow(result)?.imported?.length ?? 0;
return {
content: [
{
diff --git a/src/lib/mcp/telegram/tools/index.ts b/src/lib/mcp/telegram/tools/index.ts
index ad2b338a4..0be429aad 100644
--- a/src/lib/mcp/telegram/tools/index.ts
+++ b/src/lib/mcp/telegram/tools/index.ts
@@ -3,91 +3,154 @@
* Each tool exports a 'tool' definition and a handler function
*/
-export { getChats, tool as getChatsTool } from './getChats';
-export { listChats, tool as listChatsTool } from './listChats';
-export { getChat, tool as getChatTool } from './getChat';
-export { createGroup, tool as createGroupTool } from './createGroup';
-export { inviteToGroup, tool as inviteToGroupTool } from './inviteToGroup';
-export { createChannel, tool as createChannelTool } from './createChannel';
-export { editChatTitle, tool as editChatTitleTool } from './editChatTitle';
-export { deleteChatPhoto, tool as deleteChatPhotoTool } from './deleteChatPhoto';
-export { leaveChat, tool as leaveChatTool } from './leaveChat';
-export { getParticipants, tool as getParticipantsTool } from './getParticipants';
-export { getAdmins, tool as getAdminsTool } from './getAdmins';
-export { getBannedUsers, tool as getBannedUsersTool } from './getBannedUsers';
-export { promoteAdmin, tool as promoteAdminTool } from './promoteAdmin';
-export { demoteAdmin, tool as demoteAdminTool } from './demoteAdmin';
-export { banUser, tool as banUserTool } from './banUser';
-export { unbanUser, tool as unbanUserTool } from './unbanUser';
-export { getInviteLink, tool as getInviteLinkTool } from './getInviteLink';
-export { exportChatInvite, tool as exportChatInviteTool } from './exportChatInvite';
-export { importChatInvite, tool as importChatInviteTool } from './importChatInvite';
-export { joinChatByLink, tool as joinChatByLinkTool } from './joinChatByLink';
-export { subscribePublicChannel, tool as subscribePublicChannelTool } from './subscribePublicChannel';
-export { muteChat, tool as muteChatTool } from './muteChat';
-export { unmuteChat, tool as unmuteChatTool } from './unmuteChat';
-export { archiveChat, tool as archiveChatTool } from './archiveChat';
-export { unarchiveChat, tool as unarchiveChatTool } from './unarchiveChat';
+export { getChats, tool as getChatsTool } from "./getChats";
+export { listChats, tool as listChatsTool } from "./listChats";
+export { getChat, tool as getChatTool } from "./getChat";
+export { createGroup, tool as createGroupTool } from "./createGroup";
+export { inviteToGroup, tool as inviteToGroupTool } from "./inviteToGroup";
+export { createChannel, tool as createChannelTool } from "./createChannel";
+export { editChatTitle, tool as editChatTitleTool } from "./editChatTitle";
+export {
+ deleteChatPhoto,
+ tool as deleteChatPhotoTool,
+} from "./deleteChatPhoto";
+export { leaveChat, tool as leaveChatTool } from "./leaveChat";
+export {
+ getParticipants,
+ tool as getParticipantsTool,
+} from "./getParticipants";
+export { getAdmins, tool as getAdminsTool } from "./getAdmins";
+export { getBannedUsers, tool as getBannedUsersTool } from "./getBannedUsers";
+export { promoteAdmin, tool as promoteAdminTool } from "./promoteAdmin";
+export { demoteAdmin, tool as demoteAdminTool } from "./demoteAdmin";
+export { banUser, tool as banUserTool } from "./banUser";
+export { unbanUser, tool as unbanUserTool } from "./unbanUser";
+export { getInviteLink, tool as getInviteLinkTool } from "./getInviteLink";
+export {
+ exportChatInvite,
+ tool as exportChatInviteTool,
+} from "./exportChatInvite";
+export {
+ importChatInvite,
+ tool as importChatInviteTool,
+} from "./importChatInvite";
+export { joinChatByLink, tool as joinChatByLinkTool } from "./joinChatByLink";
+export {
+ subscribePublicChannel,
+ tool as subscribePublicChannelTool,
+} from "./subscribePublicChannel";
+export { muteChat, tool as muteChatTool } from "./muteChat";
+export { unmuteChat, tool as unmuteChatTool } from "./unmuteChat";
+export { archiveChat, tool as archiveChatTool } from "./archiveChat";
+export { unarchiveChat, tool as unarchiveChatTool } from "./unarchiveChat";
-export { getMessages, tool as getMessagesTool } from './getMessages';
-export { listMessages, tool as listMessagesTool } from './listMessages';
-export { listTopics, tool as listTopicsTool } from './listTopics';
-export { sendMessage, tool as sendMessageTool } from './sendMessage';
-export { replyToMessage, tool as replyToMessageTool } from './replyToMessage';
-export { editMessage, tool as editMessageTool } from './editMessage';
-export { deleteMessage, tool as deleteMessageTool } from './deleteMessage';
-export { forwardMessage, tool as forwardMessageTool } from './forwardMessage';
-export { pinMessage, tool as pinMessageTool } from './pinMessage';
-export { unpinMessage, tool as unpinMessageTool } from './unpinMessage';
-export { markAsRead, tool as markAsReadTool } from './markAsRead';
-export { getMessageContext, tool as getMessageContextTool } from './getMessageContext';
-export { getHistory, tool as getHistoryTool } from './getHistory';
-export { getPinnedMessages, tool as getPinnedMessagesTool } from './getPinnedMessages';
-export { sendReaction, tool as sendReactionTool } from './sendReaction';
-export { removeReaction, tool as removeReactionTool } from './removeReaction';
-export { getMessageReactions, tool as getMessageReactionsTool } from './getMessageReactions';
-export { listInlineButtons, tool as listInlineButtonsTool } from './listInlineButtons';
-export { pressInlineButton, tool as pressInlineButtonTool } from './pressInlineButton';
-export { saveDraft, tool as saveDraftTool } from './saveDraft';
-export { getDrafts, tool as getDraftsTool } from './getDrafts';
-export { clearDraft, tool as clearDraftTool } from './clearDraft';
+export { getMessages, tool as getMessagesTool } from "./getMessages";
+export { listMessages, tool as listMessagesTool } from "./listMessages";
+export { listTopics, tool as listTopicsTool } from "./listTopics";
+export { sendMessage, tool as sendMessageTool } from "./sendMessage";
+export { replyToMessage, tool as replyToMessageTool } from "./replyToMessage";
+export { editMessage, tool as editMessageTool } from "./editMessage";
+export { deleteMessage, tool as deleteMessageTool } from "./deleteMessage";
+export { forwardMessage, tool as forwardMessageTool } from "./forwardMessage";
+export { pinMessage, tool as pinMessageTool } from "./pinMessage";
+export { unpinMessage, tool as unpinMessageTool } from "./unpinMessage";
+export { markAsRead, tool as markAsReadTool } from "./markAsRead";
+export {
+ getMessageContext,
+ tool as getMessageContextTool,
+} from "./getMessageContext";
+export { getHistory, tool as getHistoryTool } from "./getHistory";
+export {
+ getPinnedMessages,
+ tool as getPinnedMessagesTool,
+} from "./getPinnedMessages";
+export { sendReaction, tool as sendReactionTool } from "./sendReaction";
+export { removeReaction, tool as removeReactionTool } from "./removeReaction";
+export {
+ getMessageReactions,
+ tool as getMessageReactionsTool,
+} from "./getMessageReactions";
+export {
+ listInlineButtons,
+ tool as listInlineButtonsTool,
+} from "./listInlineButtons";
+export {
+ pressInlineButton,
+ tool as pressInlineButtonTool,
+} from "./pressInlineButton";
+export { saveDraft, tool as saveDraftTool } from "./saveDraft";
+export { getDrafts, tool as getDraftsTool } from "./getDrafts";
+export { clearDraft, tool as clearDraftTool } from "./clearDraft";
-export { listContacts, tool as listContactsTool } from './listContacts';
-export { searchContacts, tool as searchContactsTool } from './searchContacts';
-export { addContact, tool as addContactTool } from './addContact';
-export { deleteContact, tool as deleteContactTool } from './deleteContact';
-export { blockUser, tool as blockUserTool } from './blockUser';
-export { unblockUser, tool as unblockUserTool } from './unblockUser';
-export { getBlockedUsers, tool as getBlockedUsersTool } from './getBlockedUsers';
+export { listContacts, tool as listContactsTool } from "./listContacts";
+export { searchContacts, tool as searchContactsTool } from "./searchContacts";
+export { addContact, tool as addContactTool } from "./addContact";
+export { deleteContact, tool as deleteContactTool } from "./deleteContact";
+export { blockUser, tool as blockUserTool } from "./blockUser";
+export { unblockUser, tool as unblockUserTool } from "./unblockUser";
+export {
+ getBlockedUsers,
+ tool as getBlockedUsersTool,
+} from "./getBlockedUsers";
-export { getMe, tool as getMeTool } from './getMe';
-export { updateProfile, tool as updateProfileTool } from './updateProfile';
-export { getUserPhotos, tool as getUserPhotosTool } from './getUserPhotos';
-export { getUserStatus, tool as getUserStatusTool } from './getUserStatus';
+export { getMe, tool as getMeTool } from "./getMe";
+export { updateProfile, tool as updateProfileTool } from "./updateProfile";
+export { getUserPhotos, tool as getUserPhotosTool } from "./getUserPhotos";
+export { getUserStatus, tool as getUserStatusTool } from "./getUserStatus";
-export { searchPublicChats, tool as searchPublicChatsTool } from './searchPublicChats';
-export { searchMessages, tool as searchMessagesTool } from './searchMessages';
-export { resolveUsername, tool as resolveUsernameTool } from './resolveUsername';
+export {
+ searchPublicChats,
+ tool as searchPublicChatsTool,
+} from "./searchPublicChats";
+export { searchMessages, tool as searchMessagesTool } from "./searchMessages";
+export {
+ resolveUsername,
+ tool as resolveUsernameTool,
+} from "./resolveUsername";
-export { getMediaInfo, tool as getMediaInfoTool } from './getMediaInfo';
-export { getRecentActions, tool as getRecentActionsTool } from './getRecentActions';
-export { createPoll, tool as createPollTool } from './createPoll';
-export { getBotInfo, tool as getBotInfoTool } from './getBotInfo';
-export { setBotCommands, tool as setBotCommandsTool } from './setBotCommands';
+export { getMediaInfo, tool as getMediaInfoTool } from "./getMediaInfo";
+export {
+ getRecentActions,
+ tool as getRecentActionsTool,
+} from "./getRecentActions";
+export { createPoll, tool as createPollTool } from "./createPoll";
+export { getBotInfo, tool as getBotInfoTool } from "./getBotInfo";
+export { setBotCommands, tool as setBotCommandsTool } from "./setBotCommands";
-export { getPrivacySettings, tool as getPrivacySettingsTool } from './getPrivacySettings';
-export { setPrivacySettings, tool as setPrivacySettingsTool } from './setPrivacySettings';
+export {
+ getPrivacySettings,
+ tool as getPrivacySettingsTool,
+} from "./getPrivacySettings";
+export {
+ setPrivacySettings,
+ tool as setPrivacySettingsTool,
+} from "./setPrivacySettings";
-export { setProfilePhoto, tool as setProfilePhotoTool } from './setProfilePhoto';
-export { deleteProfilePhoto, tool as deleteProfilePhotoTool } from './deleteProfilePhoto';
-export { editChatPhoto, tool as editChatPhotoTool } from './editChatPhoto';
+export {
+ setProfilePhoto,
+ tool as setProfilePhotoTool,
+} from "./setProfilePhoto";
+export {
+ deleteProfilePhoto,
+ tool as deleteProfilePhotoTool,
+} from "./deleteProfilePhoto";
+export { editChatPhoto, tool as editChatPhotoTool } from "./editChatPhoto";
-export { getStickerSets, tool as getStickerSetsTool } from './getStickerSets';
-export { getGifSearch, tool as getGifSearchTool } from './getGifSearch';
+export { getStickerSets, tool as getStickerSetsTool } from "./getStickerSets";
+export { getGifSearch, tool as getGifSearchTool } from "./getGifSearch";
-export { getContactIds, tool as getContactIdsTool } from './getContactIds';
-export { importContacts, tool as importContactsTool } from './importContacts';
-export { exportContacts, tool as exportContactsTool } from './exportContacts';
-export { getDirectChatByContact, tool as getDirectChatByContactTool } from './getDirectChatByContact';
-export { getContactChats, tool as getContactChatsTool } from './getContactChats';
-export { getLastInteraction, tool as getLastInteractionTool } from './getLastInteraction';
+export { getContactIds, tool as getContactIdsTool } from "./getContactIds";
+export { importContacts, tool as importContactsTool } from "./importContacts";
+export { exportContacts, tool as exportContactsTool } from "./exportContacts";
+export {
+ getDirectChatByContact,
+ tool as getDirectChatByContactTool,
+} from "./getDirectChatByContact";
+export {
+ getContactChats,
+ tool as getContactChatsTool,
+} from "./getContactChats";
+export {
+ getLastInteraction,
+ tool as getLastInteractionTool,
+} from "./getLastInteraction";
diff --git a/src/lib/mcp/telegram/tools/joinChatByLink.ts b/src/lib/mcp/telegram/tools/joinChatByLink.ts
index 00162d63e..cb7cd04de 100644
--- a/src/lib/mcp/telegram/tools/joinChatByLink.ts
+++ b/src/lib/mcp/telegram/tools/joinChatByLink.ts
@@ -1,20 +1,24 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { ResultWithChats } from '../apiResultTypes';
-import { narrow } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'Join a chat using an invite link',
+ name: "join_chat_by_link",
+ description: "Join a chat using an invite link",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- link: { type: 'string', description: 'Invite link (e.g. https://t.me/+HASH or https://t.me/joinchat/HASH)' },
+ link: {
+ type: "string",
+ description:
+ "Invite link (e.g. https://t.me/+HASH or https://t.me/joinchat/HASH)",
+ },
},
- required: ['link'],
+ required: ["link"],
},
};
@@ -23,8 +27,12 @@ export async function joinChatByLink(
_context: TelegramMCPContext,
): Promise {
try {
- const link = typeof args.link === 'string' ? args.link : '';
- if (!link) return { content: [{ type: 'text', text: 'link is required' }], isError: true };
+ const link = typeof args.link === "string" ? args.link : "";
+ if (!link)
+ return {
+ content: [{ type: "text", text: "link is required" }],
+ isError: true,
+ };
// Extract hash from link
let hash = link;
@@ -39,11 +47,12 @@ export async function joinChatByLink(
return client.invoke(new Api.messages.ImportChatInvite({ hash }));
});
- const chatTitle = narrow(result)?.chats?.[0]?.title ?? 'unknown';
- return { content: [{ type: 'text', text: `Joined chat: ${chatTitle}` }] };
+ const chatTitle =
+ narrow(result)?.chats?.[0]?.title ?? "unknown";
+ return { content: [{ type: "text", text: `Joined chat: ${chatTitle}` }] };
} catch (error) {
return logAndFormatError(
- 'join_chat_by_link',
+ "join_chat_by_link",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.GROUP,
);
diff --git a/src/lib/mcp/telegram/tools/listContacts.ts b/src/lib/mcp/telegram/tools/listContacts.ts
index 27fb8c36d..1feb6ab6f 100644
--- a/src/lib/mcp/telegram/tools/listContacts.ts
+++ b/src/lib/mcp/telegram/tools/listContacts.ts
@@ -1,15 +1,15 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import bigInt from 'big-integer';
-import type { ApiUser } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'List all contacts in your Telegram account',
- inputSchema: { type: 'object', properties: {} },
+ name: "list_contacts",
+ description: "List all contacts in your Telegram account",
+ inputSchema: { type: "object", properties: {} },
};
export async function listContacts(
@@ -23,25 +23,26 @@ export async function listContacts(
return client.invoke(new Api.contacts.GetContacts({ hash: bigInt(0) }));
});
- if (!result || !('users' in result) || !Array.isArray(result.users)) {
- return { content: [{ type: 'text', text: 'No contacts found.' }] };
+ if (!result || !("users" in result) || !Array.isArray(result.users)) {
+ return { content: [{ type: "text", text: "No contacts found." }] };
}
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}` : '';
+ const name =
+ [u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
+ const username = u.username ? `@${u.username}` : "";
+ const phone = u.phone ? `+${u.phone}` : "";
return `ID: ${u.id} | ${name} ${username} ${phone}`.trim();
});
if (lines.length === 0) {
- return { content: [{ type: 'text', text: 'No contacts found.' }] };
+ return { content: [{ type: "text", text: "No contacts found." }] };
}
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'list_contacts',
+ "list_contacts",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/listInlineButtons.ts b/src/lib/mcp/telegram/tools/listInlineButtons.ts
index 65633f1db..7852fc7f6 100644
--- a/src/lib/mcp/telegram/tools/listInlineButtons.ts
+++ b/src/lib/mcp/telegram/tools/listInlineButtons.ts
@@ -1,10 +1,10 @@
import type { MCPTool, MCPToolResult } from "../../types";
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';
-import { narrow } from '../apiCastHelpers';
+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",
@@ -24,44 +24,82 @@ export async function listInlineButtons(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
- return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
+ return {
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
+ isError: true,
+ };
}
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const messages = await getMessages(chatId, 200, 0);
- if (!messages) return { content: [{ type: 'text', text: 'No messages found.' }] };
+ if (!messages)
+ return { content: [{ type: "text", text: "No messages found." }] };
const found = messages.find((m) => String(m.id) === String(messageId));
const msg = found ? narrow(found) : undefined;
- if (!msg) return { content: [{ type: 'text', text: 'Message ' + messageId + ' not found in cache.' }], isError: true };
+ if (!msg)
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Message " + messageId + " not found in cache.",
+ },
+ ],
+ isError: true,
+ };
if (!msg.replyMarkup || !msg.replyMarkup.rows) {
- return { content: [{ type: 'text', text: 'No inline buttons on message ' + messageId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No inline buttons on message " + messageId + ".",
+ },
+ ],
+ };
}
const lines: string[] = [];
msg.replyMarkup.rows.forEach((row: ReplyMarkupRow, ri: number) => {
if (row.buttons) {
row.buttons.forEach((btn, bi: number) => {
- lines.push('Row ' + ri + ', Button ' + bi + ': "' + (btn.text ?? '?') + '"');
+ lines.push(
+ "Row " + ri + ", Button " + bi + ': "' + (btn.text ?? "?") + '"',
+ );
});
}
});
if (lines.length === 0) {
- return { content: [{ type: 'text', text: 'No inline buttons on message ' + messageId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "No inline buttons on message " + messageId + ".",
+ },
+ ],
+ };
}
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'list_inline_buttons',
+ "list_inline_buttons",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/listTopics.ts b/src/lib/mcp/telegram/tools/listTopics.ts
index f9d596cfc..f64b565a9 100644
--- a/src/lib/mcp/telegram/tools/listTopics.ts
+++ b/src/lib/mcp/telegram/tools/listTopics.ts
@@ -1,17 +1,23 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { ForumTopicsResult } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+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',
- description: 'List topics in a forum chat',
- inputSchema: { type: 'object', properties: { chat_id: { type: 'string', description: 'Chat ID or username' } }, required: ['chat_id'] },
+ name: "list_topics",
+ description: "List topics in a forum chat",
+ inputSchema: {
+ type: "object",
+ properties: {
+ chat_id: { type: "string", description: "Chat ID or username" },
+ },
+ required: ["chat_id"],
+ },
};
export async function listTopics(
@@ -19,13 +25,25 @@ export async function listTopics(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
+ const chatId = validateId(args.chat_id, "chat_id");
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
- if (chat.type !== 'channel' && chat.type !== 'supergroup') {
- return { content: [{ type: 'text', text: 'Forum topics are only available for channels/supergroups.' }], isError: true };
+ if (chat.type !== "channel" && chat.type !== "supergroup") {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Forum topics are only available for channels/supergroups.",
+ },
+ ],
+ isError: true,
+ };
}
const client = mtprotoService.getClient();
@@ -46,17 +64,17 @@ export async function listTopics(
const topics = narrow(result)?.topics;
if (!topics || !Array.isArray(topics) || topics.length === 0) {
- return { content: [{ type: 'text', text: 'No forum topics found.' }] };
+ return { content: [{ type: "text", text: "No forum topics found." }] };
}
const lines = topics.map((t) => {
- return 'ID: ' + t.id + ' | ' + (t.title ?? 'Untitled');
+ return "ID: " + t.id + " | " + (t.title ?? "Untitled");
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'list_topics',
+ "list_topics",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.GROUP,
);
diff --git a/src/lib/mcp/telegram/tools/markAsRead.ts b/src/lib/mcp/telegram/tools/markAsRead.ts
index ac0e43388..c0de621a0 100644
--- a/src/lib/mcp/telegram/tools/markAsRead.ts
+++ b/src/lib/mcp/telegram/tools/markAsRead.ts
@@ -1,17 +1,19 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
export const tool: MCPTool = {
- name: 'mark_as_read',
- description: 'Mark messages as read in a chat',
+ name: "mark_as_read",
+ description: "Mark messages as read in a chat",
inputSchema: {
- type: 'object',
- properties: { chat_id: { type: 'string', description: 'Chat ID or username' } },
- required: ['chat_id'],
+ type: "object",
+ properties: {
+ chat_id: { type: "string", description: "Chat ID or username" },
+ },
+ required: ["chat_id"],
},
};
@@ -20,11 +22,14 @@ export async function markAsRead(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
+ const chatId = validateId(args.chat_id, "chat_id");
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const entity = chat.username ? chat.username : chat.id;
@@ -35,11 +40,13 @@ export async function markAsRead(
});
return {
- content: [{ type: 'text', text: `Messages in chat ${chatId} marked as read.` }],
+ content: [
+ { type: "text", text: `Messages in chat ${chatId} marked as read.` },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'mark_as_read',
+ "mark_as_read",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/muteChat.ts b/src/lib/mcp/telegram/tools/muteChat.ts
index ab7ee06f3..275e1109a 100644
--- a/src/lib/mcp/telegram/tools/muteChat.ts
+++ b/src/lib/mcp/telegram/tools/muteChat.ts
@@ -1,21 +1,25 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
- name: 'mute_chat',
- description: 'Mute notifications for a chat',
+ name: "mute_chat",
+ description: "Mute notifications for a chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- duration: { type: 'number', description: 'Mute duration in seconds (0 = forever)', default: 0 },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ duration: {
+ type: "number",
+ description: "Mute duration in seconds (0 = forever)",
+ default: 0,
+ },
},
- required: ['chat_id'],
+ required: ["chat_id"],
},
};
@@ -24,16 +28,21 @@ export async function muteChat(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const duration = typeof args.duration === 'number' ? args.duration : 0;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const duration = typeof args.duration === "number" ? args.duration : 0;
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
- const muteUntil = duration === 0 ? 2147483647 : Math.floor(Date.now() / 1000) + duration;
+ const muteUntil =
+ duration === 0 ? 2147483647 : Math.floor(Date.now() / 1000) + duration;
await mtprotoService.withFloodWaitHandling(async () => {
const inputPeer = await client.getInputEntity(entity);
@@ -47,10 +56,10 @@ export async function muteChat(
);
});
- return { content: [{ type: 'text', text: `Chat ${chatId} muted.` }] };
+ return { content: [{ type: "text", text: `Chat ${chatId} muted.` }] };
} catch (error) {
return logAndFormatError(
- 'mute_chat',
+ "mute_chat",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CHAT,
);
diff --git a/src/lib/mcp/telegram/tools/notImplemented.ts b/src/lib/mcp/telegram/tools/notImplemented.ts
index 92532a8c4..7d87c2676 100644
--- a/src/lib/mcp/telegram/tools/notImplemented.ts
+++ b/src/lib/mcp/telegram/tools/notImplemented.ts
@@ -1,8 +1,8 @@
-import type { MCPToolResult } from '../../types';
+import type { MCPToolResult } from "../../types";
export function notImplemented(name: string): MCPToolResult {
return {
- content: [{ type: 'text', text: `${name} is not implemented yet.` }],
+ content: [{ type: "text", text: `${name} is not implemented yet.` }],
isError: true,
};
}
diff --git a/src/lib/mcp/telegram/tools/pinMessage.ts b/src/lib/mcp/telegram/tools/pinMessage.ts
index 383184f0b..7e0c67597 100644
--- a/src/lib/mcp/telegram/tools/pinMessage.ts
+++ b/src/lib/mcp/telegram/tools/pinMessage.ts
@@ -1,20 +1,20 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
export const tool: MCPTool = {
- name: 'pin_message',
- description: 'Pin a message in a chat',
+ name: "pin_message",
+ description: "Pin a message in a chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -23,21 +23,27 @@ export async function pinMessage(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be a positive integer' }],
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
isError: true,
};
}
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const entity = chat.username ? chat.username : chat.id;
@@ -48,11 +54,16 @@ export async function pinMessage(
});
return {
- content: [{ type: 'text', text: `Message ${messageId} pinned in chat ${chatId}.` }],
+ content: [
+ {
+ type: "text",
+ text: `Message ${messageId} pinned in chat ${chatId}.`,
+ },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'pin_message',
+ "pin_message",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/pressInlineButton.ts b/src/lib/mcp/telegram/tools/pressInlineButton.ts
index e22b6c9c4..fb8759483 100644
--- a/src/lib/mcp/telegram/tools/pressInlineButton.ts
+++ b/src/lib/mcp/telegram/tools/pressInlineButton.ts
@@ -1,11 +1,11 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { BotCallbackAnswer } from '../apiResultTypes';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+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 = {
@@ -27,17 +27,33 @@ export async function pressInlineButton(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
- const data = typeof args.button_text === 'string' ? args.button_text : '';
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
+ const data = typeof args.button_text === "string" ? args.button_text : "";
if (messageId === undefined) {
- return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
+ return {
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
+ isError: true,
+ };
}
- if (!data) return { content: [{ type: 'text', text: 'button_text is required' }], isError: true };
+ if (!data)
+ return {
+ content: [{ type: "text", text: "button_text is required" }],
+ isError: true,
+ };
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -48,16 +64,18 @@ export async function pressInlineButton(
new Api.messages.GetBotCallbackAnswer({
peer: inputPeer,
msgId: messageId,
- data: Buffer.from(data, 'base64'),
+ data: Buffer.from(data, "base64"),
}),
);
});
- const answer = narrow(result)?.message ?? 'Button pressed (no response message).';
- return { content: [{ type: 'text', text: answer }] };
+ const answer =
+ narrow(result)?.message ??
+ "Button pressed (no response message).";
+ return { content: [{ type: "text", text: answer }] };
} catch (error) {
return logAndFormatError(
- 'press_inline_button',
+ "press_inline_button",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/removeReaction.ts b/src/lib/mcp/telegram/tools/removeReaction.ts
index b920855b0..68b09305e 100644
--- a/src/lib/mcp/telegram/tools/removeReaction.ts
+++ b/src/lib/mcp/telegram/tools/removeReaction.ts
@@ -1,22 +1,22 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
- name: 'remove_reaction',
- description: 'Remove a reaction from a message',
+ name: "remove_reaction",
+ description: "Remove a reaction from a message",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
- reaction: { type: 'string', description: 'Reaction to remove' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
+ reaction: { type: "string", description: "Reaction to remove" },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -25,15 +25,27 @@ export async function removeReaction(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
- return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
+ return {
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
+ isError: true,
+ };
}
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -49,10 +61,17 @@ export async function removeReaction(
);
});
- return { content: [{ type: 'text', text: 'Reaction removed from message ' + messageId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Reaction removed from message " + messageId + ".",
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'remove_reaction',
+ "remove_reaction",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/replyToMessage.ts b/src/lib/mcp/telegram/tools/replyToMessage.ts
index 6dea35620..00507afea 100644
--- a/src/lib/mcp/telegram/tools/replyToMessage.ts
+++ b/src/lib/mcp/telegram/tools/replyToMessage.ts
@@ -34,21 +34,22 @@ export async function replyToMessage(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
- const text = typeof args.text === 'string' ? args.text : '';
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
+ const text = typeof args.text === "string" ? args.text : "";
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be an integer' }],
+ content: [{ type: "text", text: "message_id must be an integer" }],
isError: true,
};
}
if (!text) {
return {
- content: [{ type: 'text', text: 'text is required' }],
+ content: [{ type: "text", text: "text is required" }],
isError: true,
};
}
@@ -66,7 +67,7 @@ export async function replyToMessage(
return {
content: [
{
- type: 'text',
+ type: "text",
text: `Failed to reply to message ${messageId} in chat ${chatId}.`,
},
],
@@ -77,7 +78,7 @@ export async function replyToMessage(
return {
content: [
{
- type: 'text',
+ type: "text",
text: `Replied to message ${messageId} in chat ${chatId}.`,
},
],
diff --git a/src/lib/mcp/telegram/tools/saveDraft.ts b/src/lib/mcp/telegram/tools/saveDraft.ts
index 8c5af6b84..7246f13b0 100644
--- a/src/lib/mcp/telegram/tools/saveDraft.ts
+++ b/src/lib/mcp/telegram/tools/saveDraft.ts
@@ -1,10 +1,10 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
name: "save_draft",
@@ -24,12 +24,20 @@ export async function saveDraft(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const text = typeof args.text === 'string' ? args.text : '';
- if (!text) return { content: [{ type: 'text', text: 'text is required' }], isError: true };
+ const chatId = validateId(args.chat_id, "chat_id");
+ const text = typeof args.text === "string" ? args.text : "";
+ if (!text)
+ return {
+ content: [{ type: "text", text: "text is required" }],
+ isError: true,
+ };
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -44,10 +52,12 @@ export async function saveDraft(
);
});
- return { content: [{ type: 'text', text: 'Draft saved in chat ' + chatId + '.' }] };
+ return {
+ content: [{ type: "text", text: "Draft saved in chat " + chatId + "." }],
+ };
} catch (error) {
return logAndFormatError(
- 'save_draft',
+ "save_draft",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.DRAFT,
);
diff --git a/src/lib/mcp/telegram/tools/searchContacts.ts b/src/lib/mcp/telegram/tools/searchContacts.ts
index 595314200..e62871126 100644
--- a/src/lib/mcp/telegram/tools/searchContacts.ts
+++ b/src/lib/mcp/telegram/tools/searchContacts.ts
@@ -1,21 +1,21 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { optNumber } from '../args';
-import type { ApiUser } from '../apiResultTypes';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'Search contacts by name or username',
+ name: "search_contacts",
+ description: "Search contacts by name or username",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- query: { type: 'string', description: 'Search query' },
- limit: { type: 'number', description: 'Max results', default: 20 },
+ query: { type: "string", description: "Search query" },
+ limit: { type: "number", description: "Max results", default: 20 },
},
- required: ['query'],
+ required: ["query"],
},
};
@@ -24,31 +24,42 @@ export async function searchContacts(
_context: TelegramMCPContext,
): Promise {
try {
- const query = typeof args.query === 'string' ? args.query : '';
+ const query = typeof args.query === "string" ? args.query : "";
if (!query) {
- return { content: [{ type: 'text', text: 'query is required' }], isError: true };
+ return {
+ content: [{ type: "text", text: "query is required" }],
+ isError: true,
+ };
}
- const limit = optNumber(args, 'limit', 20);
+ const limit = optNumber(args, "limit", 20);
const client = mtprotoService.getClient();
const result = await mtprotoService.withFloodWaitHandling(async () => {
return client.invoke(new Api.contacts.Search({ q: query, limit }));
});
- if (!result || !('users' in result) || !Array.isArray(result.users) || result.users.length === 0) {
- return { content: [{ type: 'text', text: `No contacts found for "${query}".` }] };
+ if (
+ !result ||
+ !("users" in result) ||
+ !Array.isArray(result.users) ||
+ result.users.length === 0
+ ) {
+ return {
+ content: [{ type: "text", text: `No contacts found for "${query}".` }],
+ };
}
const lines = result.users.map((u: ApiUser) => {
- const name = [u.firstName, u.lastName].filter(Boolean).join(' ') || 'Unknown';
- const username = u.username ? `@${u.username}` : '';
+ const name =
+ [u.firstName, u.lastName].filter(Boolean).join(" ") || "Unknown";
+ const username = u.username ? `@${u.username}` : "";
return `ID: ${u.id} | ${name} ${username}`.trim();
});
- return { content: [{ type: 'text', text: lines.join('\n') }] };
+ return { content: [{ type: "text", text: lines.join("\n") }] };
} catch (error) {
return logAndFormatError(
- 'search_contacts',
+ "search_contacts",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/sendReaction.ts b/src/lib/mcp/telegram/tools/sendReaction.ts
index bf2ad45fb..0ea4b42d3 100644
--- a/src/lib/mcp/telegram/tools/sendReaction.ts
+++ b/src/lib/mcp/telegram/tools/sendReaction.ts
@@ -1,22 +1,22 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { validateId } from "../../validation";
+import { getChatById } from "../telegramApi";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
- name: 'send_reaction',
- description: 'Send a reaction to a message',
+ name: "send_reaction",
+ description: "Send a reaction to a message",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
- reaction: { type: 'string', description: 'Reaction emoji' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
+ reaction: { type: "string", description: "Reaction emoji" },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -25,16 +25,29 @@ export async function sendReaction(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id) ? args.message_id : undefined;
- const emoji = typeof args.reaction === 'string' ? args.reaction : '\ud83d\udc4d';
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
+ const emoji =
+ typeof args.reaction === "string" ? args.reaction : "\ud83d\udc4d";
if (messageId === undefined) {
- return { content: [{ type: 'text', text: 'message_id must be a positive integer' }], isError: true };
+ return {
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
+ isError: true,
+ };
}
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: 'Chat not found: ' + chatId }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: "Chat not found: " + chatId }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
const entity = chat.username ? chat.username : chat.id;
@@ -50,10 +63,17 @@ export async function sendReaction(
);
});
- return { content: [{ type: 'text', text: 'Reaction ' + emoji + ' sent to message ' + messageId + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Reaction " + emoji + " sent to message " + messageId + ".",
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'send_reaction',
+ "send_reaction",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/setBotCommands.ts b/src/lib/mcp/telegram/tools/setBotCommands.ts
index 7d15d0ac1..83e2a7be5 100644
--- a/src/lib/mcp/telegram/tools/setBotCommands.ts
+++ b/src/lib/mcp/telegram/tools/setBotCommands.ts
@@ -1,9 +1,9 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import type { BotCommandInput } from '../apiResultTypes';
+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",
@@ -24,28 +24,43 @@ export async function setBotCommands(
): Promise {
try {
const cmds = Array.isArray(args.commands) ? args.commands : [];
- if (cmds.length === 0) return { content: [{ type: 'text', text: 'commands array is required' }], isError: true };
+ if (cmds.length === 0)
+ return {
+ content: [{ type: "text", text: "commands array is required" }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
- const botCommands = cmds.map((c: BotCommandInput) =>
- new Api.BotCommand({ command: String(c.command ?? ''), description: String(c.description ?? '') }),
+ const botCommands = cmds.map(
+ (c: BotCommandInput) =>
+ new Api.BotCommand({
+ command: String(c.command ?? ""),
+ description: String(c.description ?? ""),
+ }),
);
await mtprotoService.withFloodWaitHandling(async () => {
await client.invoke(
new Api.bots.SetBotCommands({
scope: new Api.BotCommandScopeDefault(),
- langCode: '',
+ langCode: "",
commands: botCommands,
}),
);
});
- return { content: [{ type: 'text', text: 'Bot commands updated: ' + cmds.length + ' commands.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Bot commands updated: " + cmds.length + " commands.",
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'set_bot_commands',
+ "set_bot_commands",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
diff --git a/src/lib/mcp/telegram/tools/setPrivacySettings.ts b/src/lib/mcp/telegram/tools/setPrivacySettings.ts
index 190dd4fa9..98c84d94c 100644
--- a/src/lib/mcp/telegram/tools/setPrivacySettings.ts
+++ b/src/lib/mcp/telegram/tools/setPrivacySettings.ts
@@ -1,8 +1,8 @@
import type { MCPTool, MCPToolResult } from "../../types";
import type { TelegramMCPContext } from "../types";
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
name: "set_privacy_settings",
@@ -22,11 +22,19 @@ export async function setPrivacySettings(
_context: TelegramMCPContext,
): Promise {
try {
- const keyStr = typeof args.setting === 'string' ? args.setting : '';
- const ruleStr = typeof args.value === 'string' ? args.value : '';
+ const keyStr = typeof args.setting === "string" ? args.setting : "";
+ const ruleStr = typeof args.value === "string" ? args.value : "";
- if (!keyStr) return { content: [{ type: 'text', text: 'setting is required' }], isError: true };
- if (!ruleStr) return { content: [{ type: 'text', text: 'value is required' }], isError: true };
+ if (!keyStr)
+ return {
+ content: [{ type: "text", text: "setting is required" }],
+ isError: true,
+ };
+ if (!ruleStr)
+ return {
+ content: [{ type: "text", text: "value is required" }],
+ isError: true,
+ };
const client = mtprotoService.getClient();
@@ -41,7 +49,10 @@ export async function setPrivacySettings(
const key = keyMap[keyStr];
if (!key) {
- return { content: [{ type: 'text', text: 'Unknown privacy key: ' + keyStr }], isError: true };
+ return {
+ content: [{ type: "text", text: "Unknown privacy key: " + keyStr }],
+ isError: true,
+ };
}
const ruleMap: Record = {
@@ -52,17 +63,35 @@ export async function setPrivacySettings(
const rule = ruleMap[ruleStr];
if (!rule) {
- return { content: [{ type: 'text', text: 'Unknown rule: ' + ruleStr + '. Valid: allow_all, allow_contacts, disallow_all' }], isError: true };
+ return {
+ content: [
+ {
+ type: "text",
+ text:
+ "Unknown rule: " +
+ ruleStr +
+ ". Valid: allow_all, allow_contacts, disallow_all",
+ },
+ ],
+ isError: true,
+ };
}
await mtprotoService.withFloodWaitHandling(async () => {
await client.invoke(new Api.account.SetPrivacy({ key, rules: [rule] }));
});
- return { content: [{ type: 'text', text: 'Privacy setting ' + keyStr + ' set to ' + ruleStr + '.' }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Privacy setting " + keyStr + " set to " + ruleStr + ".",
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'set_privacy_settings',
+ "set_privacy_settings",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
diff --git a/src/lib/mcp/telegram/tools/setProfilePhoto.ts b/src/lib/mcp/telegram/tools/setProfilePhoto.ts
index d31d9e863..952a2724a 100644
--- a/src/lib/mcp/telegram/tools/setProfilePhoto.ts
+++ b/src/lib/mcp/telegram/tools/setProfilePhoto.ts
@@ -7,9 +7,9 @@ export const tool: MCPTool = {
inputSchema: {
type: "object",
properties: {
- file_path: { type: 'string', description: 'Path to the photo file' },
+ file_path: { type: "string", description: "Path to the photo file" },
},
- required: ['file_path'],
+ required: ["file_path"],
},
};
@@ -18,7 +18,12 @@ export async function setProfilePhoto(
_context: TelegramMCPContext,
): Promise {
return {
- content: [{ type: 'text', text: 'set_profile_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.' }],
+ content: [
+ {
+ type: "text",
+ text: "set_profile_photo requires file upload which is not supported via MCP text interface. Use the Telegram client directly.",
+ },
+ ],
isError: true,
};
}
diff --git a/src/lib/mcp/telegram/tools/unbanUser.ts b/src/lib/mcp/telegram/tools/unbanUser.ts
index f780e882d..655e85463 100644
--- a/src/lib/mcp/telegram/tools/unbanUser.ts
+++ b/src/lib/mcp/telegram/tools/unbanUser.ts
@@ -1,22 +1,22 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { getChatById } from '../telegramApi';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { toInputChannel, toInputPeer } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+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',
- description: 'Unban a user from a group or channel',
+ name: "unban_user",
+ description: "Unban a user from a group or channel",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- user_id: { type: 'string', description: 'User ID to unban' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ user_id: { type: "string", description: "User ID to unban" },
},
- required: ['chat_id', 'user_id'],
+ required: ["chat_id", "user_id"],
},
};
@@ -25,14 +25,26 @@ export async function unbanUser(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const userId = validateId(args.user_id, 'user_id');
+ const chatId = validateId(args.chat_id, "chat_id");
+ const userId = validateId(args.user_id, "user_id");
const chat = getChatById(chatId);
- if (!chat) return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ if (!chat)
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
- if (chat.type !== 'channel' && chat.type !== 'supergroup') {
- return { content: [{ type: 'text', text: 'Unban is only available for channels/supergroups.' }], isError: true };
+ if (chat.type !== "channel" && chat.type !== "supergroup") {
+ return {
+ content: [
+ {
+ type: "text",
+ text: "Unban is only available for channels/supergroups.",
+ },
+ ],
+ isError: true,
+ };
}
const client = mtprotoService.getClient();
@@ -50,10 +62,17 @@ export async function unbanUser(
);
});
- return { content: [{ type: 'text', text: `User ${userId} unbanned from ${chat.title ?? chatId}.` }] };
+ return {
+ content: [
+ {
+ type: "text",
+ text: `User ${userId} unbanned from ${chat.title ?? chatId}.`,
+ },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'unban_user',
+ "unban_user",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.ADMIN,
);
diff --git a/src/lib/mcp/telegram/tools/unblockUser.ts b/src/lib/mcp/telegram/tools/unblockUser.ts
index 9fe2057ea..5fc2026bb 100644
--- a/src/lib/mcp/telegram/tools/unblockUser.ts
+++ b/src/lib/mcp/telegram/tools/unblockUser.ts
@@ -1,20 +1,20 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { toInputPeer } from '../apiCastHelpers';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+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',
- description: 'Unblock a user',
+ name: "unblock_user",
+ description: "Unblock a user",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- user_id: { type: 'string', description: 'User ID to unblock' },
+ user_id: { type: "string", description: "User ID to unblock" },
},
- required: ['user_id'],
+ required: ["user_id"],
},
};
@@ -23,7 +23,7 @@ export async function unblockUser(
_context: TelegramMCPContext,
): Promise {
try {
- const userId = validateId(args.user_id, 'user_id');
+ const userId = validateId(args.user_id, "user_id");
const client = mtprotoService.getClient();
await mtprotoService.withFloodWaitHandling(async () => {
@@ -33,10 +33,10 @@ export async function unblockUser(
);
});
- return { content: [{ type: 'text', text: `User ${userId} unblocked.` }] };
+ return { content: [{ type: "text", text: `User ${userId} unblocked.` }] };
} catch (error) {
return logAndFormatError(
- 'unblock_user',
+ "unblock_user",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.CONTACT,
);
diff --git a/src/lib/mcp/telegram/tools/unpinMessage.ts b/src/lib/mcp/telegram/tools/unpinMessage.ts
index 77dd1c2c3..cc0a3c49a 100644
--- a/src/lib/mcp/telegram/tools/unpinMessage.ts
+++ b/src/lib/mcp/telegram/tools/unpinMessage.ts
@@ -1,21 +1,21 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { getChatById } from '../telegramApi';
-import { validateId } from '../../validation';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { getChatById } from "../telegramApi";
+import { validateId } from "../../validation";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
export const tool: MCPTool = {
- name: 'unpin_message',
- description: 'Unpin a message in a chat',
+ name: "unpin_message",
+ description: "Unpin a message in a chat",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- chat_id: { type: 'string', description: 'Chat ID or username' },
- message_id: { type: 'number', description: 'Message ID' },
+ chat_id: { type: "string", description: "Chat ID or username" },
+ message_id: { type: "number", description: "Message ID" },
},
- required: ['chat_id', 'message_id'],
+ required: ["chat_id", "message_id"],
},
};
@@ -24,21 +24,27 @@ export async function unpinMessage(
_context: TelegramMCPContext,
): Promise {
try {
- const chatId = validateId(args.chat_id, 'chat_id');
- const messageId = typeof args.message_id === 'number' && Number.isInteger(args.message_id)
- ? args.message_id
- : undefined;
+ const chatId = validateId(args.chat_id, "chat_id");
+ const messageId =
+ typeof args.message_id === "number" && Number.isInteger(args.message_id)
+ ? args.message_id
+ : undefined;
if (messageId === undefined) {
return {
- content: [{ type: 'text', text: 'message_id must be a positive integer' }],
+ content: [
+ { type: "text", text: "message_id must be a positive integer" },
+ ],
isError: true,
};
}
const chat = getChatById(chatId);
if (!chat) {
- return { content: [{ type: 'text', text: `Chat not found: ${chatId}` }], isError: true };
+ return {
+ content: [{ type: "text", text: `Chat not found: ${chatId}` }],
+ isError: true,
+ };
}
const entity = chat.username ? chat.username : chat.id;
@@ -56,11 +62,16 @@ export async function unpinMessage(
});
return {
- content: [{ type: 'text', text: `Message ${messageId} unpinned in chat ${chatId}.` }],
+ content: [
+ {
+ type: "text",
+ text: `Message ${messageId} unpinned in chat ${chatId}.`,
+ },
+ ],
};
} catch (error) {
return logAndFormatError(
- 'unpin_message',
+ "unpin_message",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.MSG,
);
diff --git a/src/lib/mcp/telegram/tools/updateProfile.ts b/src/lib/mcp/telegram/tools/updateProfile.ts
index 3c0ae897f..b4d2f4c3e 100644
--- a/src/lib/mcp/telegram/tools/updateProfile.ts
+++ b/src/lib/mcp/telegram/tools/updateProfile.ts
@@ -1,19 +1,19 @@
-import type { MCPTool, MCPToolResult } from '../../types';
-import type { TelegramMCPContext } from '../types';
-import { ErrorCategory, logAndFormatError } from '../../errorHandler';
-import { mtprotoService } from '../../../../services/mtprotoService';
-import { Api } from 'telegram';
-import { optString } from '../args';
+import type { MCPTool, MCPToolResult } from "../../types";
+import type { TelegramMCPContext } from "../types";
+import { ErrorCategory, logAndFormatError } from "../../errorHandler";
+import { mtprotoService } from "../../../../services/mtprotoService";
+import { Api } from "telegram";
+import { optString } from "../args";
export const tool: MCPTool = {
- name: 'update_profile',
- description: 'Update your Telegram profile',
+ name: "update_profile",
+ description: "Update your Telegram profile",
inputSchema: {
- type: 'object',
+ type: "object",
properties: {
- first_name: { type: 'string', description: 'New first name' },
- last_name: { type: 'string', description: 'New last name' },
- about: { type: 'string', description: 'New bio/about text' },
+ first_name: { type: "string", description: "New first name" },
+ last_name: { type: "string", description: "New last name" },
+ about: { type: "string", description: "New bio/about text" },
},
},
};
@@ -23,12 +23,20 @@ export async function updateProfile(
_context: TelegramMCPContext,
): Promise {
try {
- const firstName = optString(args, 'first_name');
- const lastName = optString(args, 'last_name');
- const about = optString(args, 'about');
+ const firstName = optString(args, "first_name");
+ const lastName = optString(args, "last_name");
+ const about = optString(args, "about");
if (!firstName && !lastName && !about) {
- return { content: [{ type: 'text', text: 'At least one of first_name, last_name, or about is required.' }], isError: true };
+ return {
+ content: [
+ {
+ type: "text",
+ text: "At least one of first_name, last_name, or about is required.",
+ },
+ ],
+ isError: true,
+ };
}
const client = mtprotoService.getClient();
@@ -44,14 +52,18 @@ export async function updateProfile(
});
const updates: string[] = [];
- if (firstName) updates.push('first_name: ' + firstName);
- if (lastName) updates.push('last_name: ' + lastName);
- if (about) updates.push('about: ' + about);
+ if (firstName) updates.push("first_name: " + firstName);
+ if (lastName) updates.push("last_name: " + lastName);
+ if (about) updates.push("about: " + about);
- return { content: [{ type: 'text', text: 'Profile updated: ' + updates.join(', ') }] };
+ return {
+ content: [
+ { type: "text", text: "Profile updated: " + updates.join(", ") },
+ ],
+ };
} catch (error) {
return logAndFormatError(
- 'update_profile',
+ "update_profile",
error instanceof Error ? error : new Error(String(error)),
ErrorCategory.PROFILE,
);
diff --git a/src/lib/mcp/transport.ts b/src/lib/mcp/transport.ts
index a3deb15c7..5d79c5f83 100644
--- a/src/lib/mcp/transport.ts
+++ b/src/lib/mcp/transport.ts
@@ -3,14 +3,17 @@
* Handles communication between frontend MCP server and backend MCP client
*/
-import type { Socket } from 'socket.io-client';
-import type { MCPRequest, MCPResponse, SocketIOMCPTransport } from './types';
-import { mcpWarn } from './logger';
+import type { Socket } from "socket.io-client";
+import type { MCPRequest, MCPResponse, SocketIOMCPTransport } from "./types";
+import { mcpWarn } from "./logger";
export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
private socket: Socket | null | undefined;
- private requestHandlers = new Map void>();
- private readonly eventPrefix = 'mcp:';
+ private requestHandlers = new Map<
+ string | number,
+ (response: MCPResponse) => void
+ >();
+ private readonly eventPrefix = "mcp:";
private responseHandler = (response: MCPResponse): void => {
const handler = this.requestHandlers.get(response.id);
if (handler) {
@@ -35,7 +38,7 @@ export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
emit(event: string, data: unknown): void {
if (!this.socket?.connected) {
- mcpWarn('Cannot emit MCP event: socket not connected', { event });
+ mcpWarn("Cannot emit MCP event: socket not connected", { event });
return;
}
this.socket.emit(`${this.eventPrefix}${event}`, data);
@@ -53,7 +56,7 @@ export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
async request(request: MCPRequest, timeoutMs = 30000): Promise {
if (!this.socket?.connected) {
- throw new Error('Socket not connected');
+ throw new Error("Socket not connected");
}
return new Promise((resolve, reject) => {
@@ -71,7 +74,7 @@ export class SocketIOMCPTransportImpl implements SocketIOMCPTransport {
}
});
- this.emit('request', request);
+ this.emit("request", request);
});
}
diff --git a/src/lib/mcp/types.ts b/src/lib/mcp/types.ts
index 1fae99607..5dfaf0b3a 100644
--- a/src/lib/mcp/types.ts
+++ b/src/lib/mcp/types.ts
@@ -8,7 +8,7 @@ export interface MCPServerConfig {
}
export interface MCPToolInputSchema {
- type: 'object';
+ type: "object";
properties: Record;
required?: string[];
}
@@ -27,21 +27,21 @@ export interface MCPToolCall {
export interface MCPToolResult {
content: Array<{
- type: 'text';
+ type: "text";
text: string;
}>;
isError?: boolean;
}
export interface MCPRequest {
- jsonrpc: '2.0';
+ jsonrpc: "2.0";
id: string | number;
method: string;
params?: unknown;
}
export interface MCPResponse {
- jsonrpc: '2.0';
+ jsonrpc: "2.0";
id: string | number;
result?: unknown;
error?: {
diff --git a/src/lib/mcp/validation.ts b/src/lib/mcp/validation.ts
index 2ed962eb1..4b0cb8a50 100644
--- a/src/lib/mcp/validation.ts
+++ b/src/lib/mcp/validation.ts
@@ -5,7 +5,7 @@
export class ValidationError extends Error {
constructor(message: string) {
super(message);
- this.name = 'ValidationError';
+ this.name = "ValidationError";
}
}
@@ -13,11 +13,8 @@ export class ValidationError extends Error {
* Validate chat_id or user_id parameter
* Supports integer IDs, string IDs, and usernames
*/
-export function validateId(
- value: unknown,
- paramName: string,
-): number | string {
- if (typeof value === 'number') {
+export function validateId(value: unknown, paramName: string): number | string {
+ if (typeof value === "number") {
if (!Number.isInteger(value) || value < -(2 ** 63) || value > 2 ** 63 - 1) {
throw new ValidationError(
`Invalid ${paramName}: ${value}. ID is out of the valid integer range.`,
@@ -26,7 +23,7 @@ export function validateId(
return value;
}
- if (typeof value === 'string') {
+ if (typeof value === "string") {
const intValue = Number.parseInt(value, 10);
if (!Number.isNaN(intValue) && Number.isFinite(intValue)) {
if (intValue < -(2 ** 63) || intValue > 2 ** 63 - 1) {
@@ -38,7 +35,7 @@ export function validateId(
}
if (/^@?[a-zA-Z0-9_]{5,}$/.test(value)) {
- return value.startsWith('@') ? value : `@${value}`;
+ return value.startsWith("@") ? value : `@${value}`;
}
throw new ValidationError(
@@ -59,9 +56,7 @@ export function validateIdList(
paramName: string,
): Array {
if (!Array.isArray(value)) {
- throw new ValidationError(
- `Invalid ${paramName}: must be an array of IDs.`,
- );
+ throw new ValidationError(`Invalid ${paramName}: must be an array of IDs.`);
}
return value.map((item: unknown, index: number) => {
diff --git a/src/main.tsx b/src/main.tsx
index e3c4ff517..f9b5e4b33 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -7,9 +7,9 @@ import App from "./App";
import "./index.css";
// Deep link listener - lazy import to avoid running before Tauri IPC is ready
-import('./utils/desktopDeepLinkListener').then(m => {
- m.setupDesktopDeepLinkListener().catch(err => {
- console.error('[DeepLink] setup error:', err);
+import("./utils/desktopDeepLinkListener").then((m) => {
+ m.setupDesktopDeepLinkListener().catch((err) => {
+ console.error("[DeepLink] setup error:", err);
});
});
diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx
index ea555cea8..832e72d1c 100644
--- a/src/pages/Home.tsx
+++ b/src/pages/Home.tsx
@@ -1,20 +1,33 @@
-import { useNavigate } from 'react-router-dom';
-import { openUrl } from '../utils/openUrl';
-import { TELEGRAM_BOT_USERNAME } from '../utils/config';
-import ConnectionIndicator from '../components/ConnectionIndicator';
-import TelegramConnectionIndicator from '../components/TelegramConnectionIndicator';
-import GmailConnectionIndicator from '../components/GmailConnectionIndicator';
-import { useUser } from '../hooks/useUser';
+import { useNavigate } from "react-router-dom";
+import { openUrl } from "../utils/openUrl";
+import { TELEGRAM_BOT_USERNAME } from "../utils/config";
+import ConnectionIndicator from "../components/ConnectionIndicator";
+import TelegramConnectionIndicator from "../components/TelegramConnectionIndicator";
+import GmailConnectionIndicator from "../components/GmailConnectionIndicator";
+import { useUser } from "../hooks/useUser";
const Home = () => {
const navigate = useNavigate();
const { user } = useUser();
- const userName = user?.firstName || 'User';
+ const userName = user?.firstName || "User";
// Get current date
const getCurrentDate = () => {
- const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
- const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
+ const days = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
+ const months = [
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ ];
const now = new Date();
return `${days[now.getDay()]}, ${months[now.getMonth()]} ${now.getDate()}`;
};
@@ -22,9 +35,9 @@ const Home = () => {
// Get greeting based on time
const getGreeting = () => {
const hour = new Date().getHours();
- if (hour < 12) return 'Good morning';
- if (hour < 18) return 'Good afternoon';
- return 'Good evening';
+ if (hour < 12) return "Good morning";
+ if (hour < 18) return "Good afternoon";
+ return "Good evening";
};
// Handle Telegram bot link
@@ -33,10 +46,9 @@ const Home = () => {
};
const handleManageConnections = () => {
- navigate('/settings');
+ navigate("/settings");
};
-
return (
- Welcome to AlphaHuman. Your Telegram assistant here to get you 10x more done in your crypto journey.
+ Welcome to AlphaHuman. Your Telegram assistant here to get you 10x
+ more done in your crypto journey.
diff --git a/src/pages/onboarding/Onboarding.tsx b/src/pages/onboarding/Onboarding.tsx
index 91594be4a..e678bc084 100644
--- a/src/pages/onboarding/Onboarding.tsx
+++ b/src/pages/onboarding/Onboarding.tsx
@@ -1,28 +1,30 @@
-import { useState } from 'react';
-import { useNavigate } from 'react-router-dom';
-import { useAppDispatch } from '../../store/hooks';
-import { setOnboarded } from '../../store/authSlice';
-import ProgressIndicator from '../../components/ProgressIndicator';
-import LottieAnimation from '../../components/LottieAnimation';
-import FeaturesStep from './steps/FeaturesStep';
-import PrivacyStep from './steps/PrivacyStep';
-import AnalyticsStep from './steps/AnalyticsStep';
-import ConnectStep from './steps/ConnectStep';
-import GetStartedStep from './steps/GetStartedStep';
+import { useState } from "react";
+import { useNavigate } from "react-router-dom";
+import { useAppDispatch, useAppSelector } from "../../store/hooks";
+import { setOnboardedForUser } from "../../store/authSlice";
+import { userApi } from "../../services/api/userApi";
+import ProgressIndicator from "../../components/ProgressIndicator";
+import LottieAnimation from "../../components/LottieAnimation";
+import FeaturesStep from "./steps/FeaturesStep";
+import PrivacyStep from "./steps/PrivacyStep";
+import AnalyticsStep from "./steps/AnalyticsStep";
+import ConnectStep from "./steps/ConnectStep";
+import GetStartedStep from "./steps/GetStartedStep";
const Onboarding = () => {
const navigate = useNavigate();
const dispatch = useAppDispatch();
+ const user = useAppSelector((state) => state.user.user);
const [currentStep, setCurrentStep] = useState(1);
const totalSteps = 5;
// Lottie animation files for each step
const stepAnimations = [
- '/lottie/wave.json', // Step 1 - Features
- '/lottie/safe3.json', // Step 2 - Privacy
- '/lottie/analytics.json', // Step 3 - Analytics
- '/lottie/connect2.json', // Step 4 - Connect
- '/lottie/trophy.json', // Step 5 - Get Started
+ "/lottie/wave.json", // Step 1 - Features
+ "/lottie/safe3.json", // Step 2 - Privacy
+ "/lottie/analytics.json", // Step 3 - Analytics
+ "/lottie/connect2.json", // Step 4 - Connect
+ "/lottie/trophy.json", // Step 5 - Get Started
];
const handleNext = () => {
@@ -31,9 +33,23 @@ const Onboarding = () => {
}
};
- const handleComplete = () => {
- dispatch(setOnboarded(true));
- navigate('/home');
+ const handleComplete = async () => {
+ try {
+ await userApi.onboardingComplete();
+ } catch (e) {
+ const msg =
+ e &&
+ typeof e === "object" &&
+ "error" in e &&
+ typeof (e as { error: unknown }).error === "string"
+ ? (e as { error: string }).error
+ : "Failed to complete onboarding. Please try again.";
+ throw new Error(msg);
+ }
+ if (user?._id) {
+ dispatch(setOnboardedForUser({ userId: user._id, value: true }));
+ }
+ navigate("/home");
};
const renderStep = () => {
@@ -57,7 +73,11 @@ const Onboarding = () => {
- We collect anonymized usage data to help us improve the app for you and for others. You can choose to skip this.
+ We collect anonymized usage data to help us improve the app for you
+ and for others. You can choose to skip this.
- Share anonymized usage data to help us improve features and performance of the app.
- This helps us improve the app for you and for others.
+ Share anonymized usage data to help us improve features and
+ performance of the app. This helps us improve the app for you
+ and for others.
- We won't collect any usage analytics, ensuring total anonymity. Keep all your data completely private.
+ We won't collect any usage analytics, ensuring total anonymity.
+ Keep all your data completely private.
Your privacy preferences can be updated in your account settings
+
+ You can change this setting anytime
+
+
+ Your privacy preferences can be updated in your account settings
+
diff --git a/src/pages/onboarding/steps/ConnectStep.tsx b/src/pages/onboarding/steps/ConnectStep.tsx
index 1fa83da8d..45db59f00 100644
--- a/src/pages/onboarding/steps/ConnectStep.tsx
+++ b/src/pages/onboarding/steps/ConnectStep.tsx
@@ -1,14 +1,16 @@
-import { useState, useMemo } from 'react';
-import { useAppSelector } from '../../../store/hooks';
-import { selectIsAuthenticated } from '../../../store/telegramSelectors';
-import GoogleIcon from '../../../assets/icons/GoogleIcon';
-import TelegramConnectionModal from '../../../components/TelegramConnectionModal';
-
-import BinanceIcon from '../../../assets/icons/binance.svg';
-import NotionIcon from '../../../assets/icons/notion.svg';
-import TelegramIcon from '../../../assets/icons/telegram.svg';
-import MetamaskIcon from '../../../assets/icons/metamask.svg';
+import { useState } from "react";
+import { useAppSelector } from "../../../store/hooks";
+import {
+ selectIsAuthenticated,
+ selectSessionString,
+} from "../../../store/telegramSelectors";
+import GoogleIcon from "../../../assets/icons/GoogleIcon";
+import TelegramConnectionModal from "../../../components/TelegramConnectionModal";
+import BinanceIcon from "../../../assets/icons/binance.svg";
+import NotionIcon from "../../../assets/icons/notion.svg";
+import TelegramIcon from "../../../assets/icons/telegram.svg";
+import MetamaskIcon from "../../../assets/icons/metamask.svg";
interface ConnectStepProps {
onNext: () => void;
@@ -22,30 +24,19 @@ interface ConnectOption {
comingSoon?: boolean;
}
-// Helper to check if there's a saved session in localStorage
-const hasSavedSession = (): boolean => {
- try {
- return !!localStorage.getItem('telegram_session');
- } catch {
- return false;
- }
-};
-
const ConnectStep = ({ onNext }: ConnectStepProps) => {
const [isTelegramModalOpen, setIsTelegramModalOpen] = useState(false);
const isTelegramAuthenticated = useAppSelector(selectIsAuthenticated);
- const sessionString = useAppSelector((state) => state.telegram.sessionString);
+ const sessionString = useAppSelector(selectSessionString);
- // Check if Telegram account is connected (authenticated or has saved session)
- const isTelegramConnected = useMemo(() => {
- return isTelegramAuthenticated || !!sessionString || hasSavedSession();
- }, [isTelegramAuthenticated, sessionString]);
+ const isTelegramConnected = !!sessionString && isTelegramAuthenticated;
// Check if an account is connected
const isAccountConnected = (accountId: string): boolean => {
- if (accountId === 'telegram') {
+ if (accountId === "telegram") {
return isTelegramConnected;
}
+
// Add other account checks here when implemented
return false;
};
@@ -62,13 +53,13 @@ const ConnectStep = ({ onNext }: ConnectStepProps) => {
// In a real app, this would handle OAuth
console.log(`Connecting to ${provider}`);
- if (provider === 'telegram') {
+ if (provider === "telegram") {
setIsTelegramModalOpen(true);
return;
}
// Don't auto-advance for coming soon items
- if (!connectOptions.find(opt => opt.id === provider)?.comingSoon) {
+ if (!connectOptions.find((opt) => opt.id === provider)?.comingSoon) {
onNext();
}
};
@@ -80,36 +71,36 @@ const ConnectStep = ({ onNext }: ConnectStepProps) => {
const connectOptions: ConnectOption[] = [
{
- id: 'telegram',
- name: 'Telegram',
- description: 'Organize chats, automate messages and get insights.',
+ id: "telegram",
+ name: "Telegram",
+ description: "Organize chats, automate messages and get insights.",
icon: ,
},
{
- id: 'google',
- name: 'Google',
- description: 'Manage emails, contacts and calendar events',
+ id: "google",
+ name: "Google",
+ description: "Manage emails, contacts and calendar events",
icon: ,
comingSoon: true,
},
{
- id: 'notion',
- name: 'Notion',
- description: 'Manage tasks, documents and everything else in your Notion',
+ id: "notion",
+ name: "Notion",
+ description: "Manage tasks, documents and everything else in your Notion",
icon: ,
comingSoon: true,
},
{
- id: 'wallet',
- name: 'Web3 Wallet',
- description: 'Trade the trenches in a safe and secure way.',
+ id: "wallet",
+ name: "Web3 Wallet",
+ description: "Trade the trenches in a safe and secure way.",
icon: ,
comingSoon: true,
},
{
- id: 'exchange',
- name: 'Crypto Trading Exchanges',
- description: 'Connect tand make trades with deep insights.',
+ id: "exchange",
+ name: "Crypto Trading Exchanges",
+ description: "Connect tand make trades with deep insights.",
icon: ,
comingSoon: true,
},
@@ -120,7 +111,8 @@ const ConnectStep = ({ onNext }: ConnectStepProps) => {
Connect Accounts
- The more accounts you connect, the more powerful the intelligence will be.
+ The more accounts you connect, the more powerful the intelligence will
+ be.
All data and credentials are stored
- locally and follows a strict zero-data retention policy so you won't have to worry about anything
- getting leaked.
+
+ 🔒 Remember everything is private & encrypted!
+
+
+ All data and credentials are stored locally and follows a strict
+ zero-data retention policy so you won't have to worry about
+ anything getting leaked.
+
diff --git a/src/pages/onboarding/steps/FeaturesStep.tsx b/src/pages/onboarding/steps/FeaturesStep.tsx
index 7cc58ba91..305063d47 100644
--- a/src/pages/onboarding/steps/FeaturesStep.tsx
+++ b/src/pages/onboarding/steps/FeaturesStep.tsx
@@ -1,4 +1,4 @@
-import PrivacyFeatureCard from '../../../components/PrivacyFeatureCard';
+import PrivacyFeatureCard from "../../../components/PrivacyFeatureCard";
interface FeaturesStepProps {
onNext: () => void;
@@ -7,16 +7,19 @@ interface FeaturesStepProps {
const FeaturesStep = ({ onNext }: FeaturesStepProps) => {
const features = [
{
- title: 'Keep track of Everything',
- description: 'Sometimes your chats, emails, tasks can get a bit too much. Stay on track, organize things and get more done.',
+ title: "Keep track of Everything",
+ description:
+ "Sometimes your chats, emails, tasks can get a bit too much. Stay on track, organize things and get more done.",
},
{
- title: 'Has Infinite Memory & Learns',
- description: 'Missed something? Have a sexy assistant give you exactly what you need, every time.',
+ title: "Has Infinite Memory & Learns",
+ description:
+ "Missed something? Have a sexy assistant give you exactly what you need, every time.",
},
{
- title: 'Trades the Trenches',
- description: 'With it\'s own private wallet, trade or reasearch on any exchange or shitcoin autonomously. Go big or go home.',
+ title: "Trades the Trenches",
+ description:
+ "With it's own private wallet, trade or reasearch on any exchange or shitcoin autonomously. Go big or go home.",
},
];
diff --git a/src/pages/onboarding/steps/GetStartedStep.tsx b/src/pages/onboarding/steps/GetStartedStep.tsx
index b668f452d..1f49cf215 100644
--- a/src/pages/onboarding/steps/GetStartedStep.tsx
+++ b/src/pages/onboarding/steps/GetStartedStep.tsx
@@ -1,16 +1,29 @@
-import { openUrl } from '../../../utils/openUrl';
-import { TELEGRAM_BOT_USERNAME } from '../../../utils/config';
-import ConnectionIndicator from '../../../components/ConnectionIndicator';
+import { useState } from "react";
+import { openUrl } from "../../../utils/openUrl";
+import { TELEGRAM_BOT_USERNAME } from "../../../utils/config";
+import ConnectionIndicator from "../../../components/ConnectionIndicator";
interface GetStartedStepProps {
- onComplete: () => void;
+ onComplete: () => void | Promise;
}
const GetStartedStep = ({ onComplete }: GetStartedStepProps) => {
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState(null);
+
const handleOpenTelegram = async () => {
- // Open Telegram and navigate to home immediately
- await openUrl(`https://t.me/${TELEGRAM_BOT_USERNAME}`);
- onComplete();
+ setError(null);
+ setLoading(true);
+ try {
+ await openUrl(`https://t.me/${TELEGRAM_BOT_USERNAME}`);
+ await onComplete();
+ } catch (e) {
+ setError(
+ e instanceof Error ? e.message : "Something went wrong. Please try again.",
+ );
+ } finally {
+ setLoading(false);
+ }
};
return (
@@ -18,19 +31,24 @@ const GetStartedStep = ({ onComplete }: GetStartedStepProps) => {
You Are Ready, Soldier!
- Alright you're all set up, just message your assistant and you're ready to cook! Remember to keep this tab open to keep the connection alive.
+ Alright you're all set up, just message your assistant and you're
+ ready to cook! Remember to keep this tab open to keep the connection
+ alive.
-
+
+
+ {error && (
+
{error}
+ )}
);
diff --git a/src/pages/onboarding/steps/PrivacyStep.tsx b/src/pages/onboarding/steps/PrivacyStep.tsx
index a9bf0ee22..d67f258ad 100644
--- a/src/pages/onboarding/steps/PrivacyStep.tsx
+++ b/src/pages/onboarding/steps/PrivacyStep.tsx
@@ -1,4 +1,4 @@
-import PrivacyFeatureCard from '../../../components/PrivacyFeatureCard';
+import PrivacyFeatureCard from "../../../components/PrivacyFeatureCard";
interface PrivacyStepProps {
onNext: () => void;
@@ -7,17 +7,20 @@ interface PrivacyStepProps {
const PrivacyStep = ({ onNext }: PrivacyStepProps) => {
const privacyFeatures = [
{
- title: '🔒 Everything is Local & Encrypted',
- description: 'Your data is encrypted (AES-256-GCM) in your device and never stored elsewhere. Your encryption keys never leave your device.',
+ title: "🔒 Everything is Local & Encrypted",
+ description:
+ "Your data is encrypted (AES-256-GCM) in your device and never stored elsewhere. Your encryption keys never leave your device.",
},
{
- title: '🙈 Zero Data Retention',
- description: 'Your queries are processed, immediately discarded and never stored elsewhere. Your data is NEVER used to train AI models. ',
+ title: "🙈 Zero Data Retention",
+ description:
+ "Your queries are processed, immediately discarded and never stored elsewhere. Your data is NEVER used to train AI models. ",
},
{
- title: '🔥 Delete Anytime You Want',
- description: 'You can delete your data and your account anytime you want. Everything will get wiped including AI memories.',
- }
+ title: "🔥 Delete Anytime You Want",
+ description:
+ "You can delete your data and your account anytime you want. Everything will get wiped including AI memories.",
+ },
];
return (
@@ -25,7 +28,8 @@ const PrivacyStep = ({ onNext }: PrivacyStepProps) => {
A Quick Privacy Note
- Since AlphaHuman handles criticial information about you, here's how it handles your data and manages your privacy.
+ Since AlphaHuman handles criticial information about you, here's how
+ it handles your data and manages your privacy.