From 520bfdb74c8fb1d40404cef4c2eb94ec3a4aee90 Mon Sep 17 00:00:00 2001
From: Steven Enamakel <31011319+senamakel@users.noreply.github.com>
Date: Thu, 29 Jan 2026 07:38:34 +0530
Subject: [PATCH] Fix/telegram login (#4)
* Add UserProvider to App component and enhance user data fetching logic
- Integrated UserProvider into the App component to manage user state.
- Updated UserProvider to fetch current user data on token availability and handle token expiration by clearing the token on fetch failure.
- Improved user data fetching logic for better error handling and user experience.
* Enhance Telegram connection handling and error management
- Added logic to handle Telegram account mismatch, providing user feedback and resetting connection state.
- Improved user verification during authentication to ensure the logged-in account matches the Telegram account.
- Refactored connection status checks and error handling for better user experience.
- Introduced a method to clear session and disconnect when account mismatch occurs.
- Updated polling mechanism for authentication status to ensure timely updates.
This update improves the robustness of the Telegram connection process and enhances user feedback during authentication failures.
* ran prettier
* Refactor socket and auth state management for user-specific handling
- Updated socket state management to support multiple users by introducing user-specific selectors and actions.
- Refactored connection status handling in the socket service to dispatch user-specific status updates.
- Enhanced onboarding state management to track completion per user, allowing for more granular control.
- Introduced new selectors for socket and auth states to improve readability and maintainability.
- Updated relevant components to utilize the new user-specific state management.
This update improves the application's ability to handle multiple users and enhances the overall user experience during onboarding and socket connections.
* Enhance MTProto client session management and introduce user-specific session handling
- Updated the MTProtoService to manage sessions per user by storing session data under a user-specific key.
- Modified the initialize method to accept a userId parameter, allowing for user-specific session initialization and management.
- Improved session loading and saving logic to ensure proper handling of user sessions.
- Added a utility function to generate session keys based on userId for better maintainability.
This update enhances the application's ability to handle multiple user sessions effectively.
* Refactor Telegram state management for user-specific handling
- Introduced user-specific state management in the Telegram store, allowing for better handling of multiple users.
- Updated selectors to retrieve state based on the current user, improving data encapsulation and reducing global state dependencies.
- Enhanced reducers and thunks to ensure actions are dispatched with user context, maintaining user-specific data integrity.
- Modified persistence configuration to store Telegram state scoped by user, ensuring isolated state management.
This update significantly improves the application's ability to manage multiple user sessions effectively and enhances overall user experience.
* Enhance MTProtoService connection handling with user-specific initialization
- Updated the checkConnection method to accept an optional userId parameter, allowing for user-specific connection initialization.
- Modified the connection logic to ensure proper initialization only occurs when a userId is provided, improving session management.
- Refactored related thunks to use a consistent naming convention for userId parameters, enhancing code clarity.
This update improves the handling of user sessions in the MTProtoService, aligning with recent enhancements in user-specific state management.
* Refactor Telegram connection handling for user-specific management
- Updated the TelegramConnectionModal to utilize user-specific identifiers for connection status and authentication processes, enhancing session management.
- Refactored related components and selectors to ensure userId is consistently passed and utilized, improving clarity and maintainability.
- Enhanced the ConnectionsPanel and ConnectStep to check for saved sessions based on userId, allowing for better handling of multiple user connections.
This update significantly improves the application's ability to manage user-specific Telegram connections and enhances the overall user experience.
* Refactor Telegram connection logic to simplify session management
- Removed unnecessary userId checks and localStorage interactions from ConnectionsPanel and ConnectStep components, streamlining the connection status determination.
- Updated MTProtoService to manage session data through Redux instead of localStorage, enhancing consistency and maintainability.
- Improved session loading and saving logic to focus solely on Redux state, ensuring better integration with user-specific session handling.
This update enhances the clarity and efficiency of the Telegram connection management process, aligning with recent improvements in user-specific state management.
* Update CLAUDE.md to clarify localStorage usage and emphasize Redux for state management
- Revised documentation to discourage the use of localStorage and sessionStorage for app state, advocating for Redux and Redux Persist instead.
- Added guidelines for removing existing localStorage usage and migrating relevant data to Redux.
- Updated service layer documentation to reflect changes in session management, specifying that session data is now stored in Redux rather than localStorage.
- Enhanced clarity on deep link handling and the prevention of infinite reload loops.
This update aligns with recent improvements in user-specific state management and reinforces best practices for state handling.
* Enhance onboarding process with error handling and API integration
- Updated the Onboarding component to include an API call for marking onboarding as complete, with error handling to provide user feedback.
- Refactored the GetStartedStep component to support asynchronous completion handling, including loading state and error messages.
- Introduced a new onboardingComplete method in the userApi service to facilitate the onboarding completion process.
This update improves the user experience during onboarding by ensuring proper error management and feedback.
* Enhance TelegramConnectionModal with socket connection management and improved initialization flow
- Integrated socket connection handling in the TelegramConnectionModal to ensure the socket is connected when the modal opens, enhancing real-time features.
- Refactored the initialization logic to streamline the connection process, including user verification and error handling.
- Introduced a reference to track the QR code flow state, preventing multiple initiations and improving user experience during authentication.
This update improves the reliability of the Telegram connection process and enhances user feedback during the onboarding experience.
* Refactor Telegram connection checks for improved logic consistency
- Updated the ConnectionsPanel and ConnectStep components to refine the logic for determining if a Telegram connection is established, ensuring that both session string and authentication status are required for a valid connection.
- Simplified className definitions in the UI components for better readability and maintainability.
This update enhances the clarity of connection status checks and improves the overall user experience during the onboarding process.
* Refactor Telegram authentication flow and improve error handling
- Simplified the TelegramLoginButton component by removing complex authentication logic and replacing it with a direct link to the Telegram bot for login.
- Enhanced the Login page to consume a login token from the URL, providing better error handling and user feedback during the authentication process.
- Introduced a new API service for consuming login tokens, ensuring secure retrieval of JWT tokens from the backend.
This update streamlines the authentication experience and improves error management, enhancing overall user experience during login.
---
CLAUDE.md | 14 +-
src/App.tsx | 31 +-
src/AppRoutes.tsx | 30 +-
src/assets/icons/GmailIcon.tsx | 4 +-
src/assets/icons/GoogleIcon.tsx | 20 +-
src/components/ConnectionIndicator.tsx | 33 +-
src/components/DefaultRedirect.tsx | 7 +-
src/components/DesignSystemShowcase.tsx | 179 +++++--
src/components/GmailConnectionIndicator.tsx | 18 +-
src/components/LottieAnimation.tsx | 15 +-
src/components/PrivacyFeatureCard.tsx | 9 +-
src/components/ProgressIndicator.tsx | 7 +-
src/components/ProtectedRoute.tsx | 11 +-
src/components/PublicRoute.tsx | 9 +-
.../TelegramConnectionIndicator.tsx | 28 +-
src/components/TelegramConnectionModal.tsx | 470 ++++++++++++------
src/components/TelegramLoginButton.tsx | 294 +----------
src/components/TypewriterGreeting.tsx | 17 +-
src/components/settings/SettingsHome.tsx | 235 ++++++---
src/components/settings/SettingsLayout.tsx | 18 +-
src/components/settings/SettingsModal.tsx | 26 +-
.../components/SettingsBackButton.tsx | 6 +-
.../settings/components/SettingsHeader.tsx | 47 +-
.../settings/components/SettingsMenuItem.tsx | 29 +-
.../components/SettingsPanelLayout.tsx | 16 +-
.../settings/hooks/useSettingsAnimation.ts | 31 +-
.../settings/hooks/useSettingsNavigation.ts | 56 ++-
.../settings/panels/AdvancedPanel.tsx | 34 +-
.../settings/panels/BillingPanel.tsx | 24 +-
.../settings/panels/ConnectionsPanel.tsx | 125 ++---
.../settings/panels/MessagingPanel.tsx | 27 +-
.../settings/panels/PrivacyPanel.tsx | 27 +-
.../settings/panels/ProfilePanel.tsx | 24 +-
src/data/countries.ts | 44 +-
src/hooks/useSocket.ts | 30 +-
src/hooks/useUser.ts | 6 +-
src/lib/mcp/errorHandler.ts | 58 +--
src/lib/mcp/index.ts | 10 +-
src/lib/mcp/logger.ts | 17 +-
src/lib/mcp/telegram/index.ts | 22 +-
src/lib/mcp/telegram/server.ts | 4 +-
src/lib/mcp/telegram/telegramApi.ts | 60 ++-
src/lib/mcp/telegram/toolActionParser.ts | 218 ++++----
src/lib/mcp/telegram/tools/addContact.ts | 59 ++-
src/lib/mcp/telegram/tools/blockUser.ts | 32 +-
src/lib/mcp/telegram/tools/clearDraft.ts | 28 +-
src/lib/mcp/telegram/tools/createChannel.ts | 3 +-
src/lib/mcp/telegram/tools/createPoll.ts | 56 ++-
src/lib/mcp/telegram/tools/deleteContact.ts | 35 +-
src/lib/mcp/telegram/tools/deleteMessage.ts | 48 +-
.../mcp/telegram/tools/deleteProfilePhoto.ts | 35 +-
src/lib/mcp/telegram/tools/editChatPhoto.ts | 11 +-
src/lib/mcp/telegram/tools/editMessage.ts | 54 +-
.../mcp/telegram/tools/exportChatInvite.ts | 63 ++-
src/lib/mcp/telegram/tools/forwardMessage.ts | 62 ++-
src/lib/mcp/telegram/tools/getBlockedUsers.ts | 46 +-
src/lib/mcp/telegram/tools/getBotInfo.ts | 40 +-
src/lib/mcp/telegram/tools/getChat.ts | 38 +-
src/lib/mcp/telegram/tools/getChats.ts | 38 +-
src/lib/mcp/telegram/tools/getContactChats.ts | 35 +-
src/lib/mcp/telegram/tools/getContactIds.ts | 34 +-
.../telegram/tools/getDirectChatByContact.ts | 53 +-
src/lib/mcp/telegram/tools/getDrafts.ts | 24 +-
src/lib/mcp/telegram/tools/getGifSearch.ts | 45 +-
src/lib/mcp/telegram/tools/getHistory.ts | 45 +-
.../mcp/telegram/tools/getLastInteraction.ts | 53 +-
src/lib/mcp/telegram/tools/getMediaInfo.ts | 52 +-
.../mcp/telegram/tools/getMessageContext.ts | 75 +--
.../mcp/telegram/tools/getMessageReactions.ts | 81 ++-
.../mcp/telegram/tools/getPinnedMessages.ts | 59 ++-
.../mcp/telegram/tools/getPrivacySettings.ts | 50 +-
.../mcp/telegram/tools/getRecentActions.ts | 52 +-
src/lib/mcp/telegram/tools/getStickerSets.ts | 37 +-
src/lib/mcp/telegram/tools/getUserPhotos.ts | 60 ++-
src/lib/mcp/telegram/tools/getUserStatus.ts | 63 ++-
.../mcp/telegram/tools/importChatInvite.ts | 3 +-
src/lib/mcp/telegram/tools/importContacts.ts | 3 +-
src/lib/mcp/telegram/tools/index.ts | 221 +++++---
src/lib/mcp/telegram/tools/joinChatByLink.ts | 43 +-
src/lib/mcp/telegram/tools/listContacts.ts | 37 +-
.../mcp/telegram/tools/listInlineButtons.ts | 70 ++-
src/lib/mcp/telegram/tools/listTopics.ts | 56 ++-
src/lib/mcp/telegram/tools/markAsRead.ts | 37 +-
src/lib/mcp/telegram/tools/muteChat.ts | 47 +-
src/lib/mcp/telegram/tools/notImplemented.ts | 4 +-
src/lib/mcp/telegram/tools/pinMessage.ts | 51 +-
.../mcp/telegram/tools/pressInlineButton.ts | 50 +-
src/lib/mcp/telegram/tools/removeReaction.ts | 59 ++-
src/lib/mcp/telegram/tools/replyToMessage.ts | 19 +-
src/lib/mcp/telegram/tools/saveDraft.ts | 32 +-
src/lib/mcp/telegram/tools/searchContacts.ts | 55 +-
src/lib/mcp/telegram/tools/sendReaction.ts | 62 ++-
src/lib/mcp/telegram/tools/setBotCommands.ts | 35 +-
.../mcp/telegram/tools/setPrivacySettings.ts | 51 +-
src/lib/mcp/telegram/tools/setProfilePhoto.ts | 11 +-
src/lib/mcp/telegram/tools/unbanUser.ts | 61 ++-
src/lib/mcp/telegram/tools/unblockUser.ts | 30 +-
src/lib/mcp/telegram/tools/unpinMessage.ts | 53 +-
src/lib/mcp/telegram/tools/updateProfile.ts | 54 +-
src/lib/mcp/transport.ts | 19 +-
src/lib/mcp/types.ts | 8 +-
src/lib/mcp/validation.ts | 17 +-
src/main.tsx | 6 +-
src/pages/Home.tsx | 67 ++-
src/pages/Login.tsx | 62 ++-
src/pages/Welcome.tsx | 9 +-
src/pages/onboarding/Onboarding.tsx | 60 ++-
src/pages/onboarding/steps/AnalyticsStep.tsx | 87 ++--
src/pages/onboarding/steps/ConnectStep.tsx | 102 ++--
src/pages/onboarding/steps/FeaturesStep.tsx | 17 +-
src/pages/onboarding/steps/GetStartedStep.tsx | 44 +-
src/pages/onboarding/steps/PrivacyStep.tsx | 22 +-
src/providers/SocketProvider.tsx | 17 +-
src/providers/TelegramProvider.tsx | 112 +++--
src/providers/UserProvider.tsx | 24 +-
src/services/api/authApi.ts | 22 +
src/services/api/userApi.ts | 19 +-
src/services/apiClient.ts | 56 ++-
src/services/mtprotoService.ts | 200 +++++---
src/services/socketService.ts | 30 +-
src/store/authSelectors.ts | 7 +
src/store/authSlice.ts | 35 +-
src/store/hooks.ts | 4 +-
src/store/index.ts | 41 +-
src/store/socketSelectors.ts | 19 +
src/store/socketSlice.ts | 61 ++-
src/store/telegram/extraReducers.ts | 171 ++++---
src/store/telegram/index.ts | 23 +-
src/store/telegram/reducers.ts | 330 ++++++------
src/store/telegram/thunks.ts | 90 ++--
src/store/telegram/types.ts | 23 +-
src/store/telegramSelectors.ts | 168 ++++---
src/store/userSlice.ts | 16 +-
src/styles/theme.css | 205 ++++----
src/types/api.ts | 8 +-
src/types/onboarding.ts | 2 +-
src/utils/deeplink.ts | 21 +-
src/utils/desktopDeepLinkListener.ts | 36 +-
src/utils/openUrl.ts | 6 +-
139 files changed, 4365 insertions(+), 2901 deletions(-)
create mode 100644 src/services/api/authApi.ts
create mode 100644 src/store/authSelectors.ts
create mode 100644 src/store/socketSelectors.ts
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.