@overpod/mcp-telegram 1.16.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -204,302 +204,13 @@ const telegramMcp = new MCPClient({
204
204
  });
205
205
  ```
206
206
 
207
- ## Available Tools
207
+ ## Tools
208
208
 
209
- ### Connection
210
-
211
- | Tool | Description |
212
- |------|-------------|
213
- | `telegram-status` | Check connection status and current account info |
214
- | `telegram-login` | Authenticate via QR code |
209
+ All tools are auto-discoverable via MCP — your AI client will see the full list with parameters and descriptions when connected.
215
210
 
216
- ### Messaging
211
+ **Categories:** authentication, messaging (send, edit, delete, forward, schedule, polls), reading (chats, messages, search, unread), forum topics, group management (create, edit, invite, kick, ban, admin, leave), contacts & moderation, user profiles, reactions, media.
217
212
 
218
- | Tool | Description |
219
- |------|-------------|
220
- | `telegram-send-message` | Send a text message to a chat |
221
- | `telegram-send-file` | Send a file (photo, document, video, etc.) to a chat |
222
- | `telegram-send-reaction` | Send or remove an emoji reaction on a message |
223
- | `telegram-send-scheduled` | Schedule a message for future delivery |
224
- | `telegram-create-poll` | Create a poll (multiple choice or quiz mode) |
225
- | `telegram-edit-message` | Edit a previously sent message |
226
- | `telegram-delete-message` | Delete messages in a chat |
227
- | `telegram-forward-message` | Forward messages between chats |
228
-
229
- ### Reading
230
-
231
- | Tool | Description |
232
- |------|-------------|
233
- | `telegram-list-chats` | List recent dialogs with unread counts, bot/contact markers |
234
- | `telegram-read-messages` | Read recent messages from a chat |
235
- | `telegram-search-chats` | Search for chats, users, or channels by name |
236
- | `telegram-search-messages` | Search messages in a chat by text |
237
- | `telegram-search-global` | Search messages across all public chats and channels |
238
- | `telegram-get-unread` | Get chats with unread messages; forums show per-topic unread breakdown |
239
- | `telegram-get-contact-requests` | Get incoming messages from non-contacts with preview |
240
-
241
- ### Forum Topics
242
-
243
- | Tool | Description |
244
- |------|-------------|
245
- | `telegram-list-topics` | List forum topics in a group with unread counts and status |
246
- | `telegram-read-topic-messages` | Read messages from a specific forum topic |
247
-
248
- ### Chat Management
249
-
250
- | Tool | Description |
251
- |------|-------------|
252
- | `telegram-mark-as-read` | Mark a chat as read |
253
- | `telegram-get-chat-info` | Get detailed info about a chat (name, type, members, bot/contact status) |
254
- | `telegram-get-chat-members` | List members of a group or channel |
255
- | `telegram-join-chat` | Join a group or channel by username or invite link |
256
- | `telegram-pin-message` | Pin a message in a chat |
257
- | `telegram-unpin-message` | Unpin a message in a chat |
258
-
259
- ### Contacts & Moderation
260
-
261
- | Tool | Description |
262
- |------|-------------|
263
- | `telegram-add-contact` | Add a user to your contacts (accept contact request) |
264
- | `telegram-block-user` | Block a user from sending you messages |
265
- | `telegram-report-spam` | Report a chat as spam to Telegram |
266
-
267
- ### User Info
268
-
269
- | Tool | Description |
270
- |------|-------------|
271
- | `telegram-get-contacts` | Get your contacts list with phone numbers |
272
- | `telegram-get-profile` | Get detailed profile info for a user (bio, photo, last seen) |
273
- | `telegram-get-profile-photo` | Get a user's or chat's profile photo (inline or file) |
274
- | `telegram-get-reactions` | Get reactions on a specific message with user details |
275
-
276
- ### Media
277
-
278
- | Tool | Description |
279
- |------|-------------|
280
- | `telegram-download-media` | Download media from a message to a local file |
281
-
282
- ## Tool Parameters
283
-
284
- ### Common patterns
285
-
286
- Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-1001234567890"`) or a username (e.g., `"@username"`).
287
-
288
- ### telegram-send-message
289
-
290
- | Parameter | Type | Required | Description |
291
- |-----------|------|----------|-------------|
292
- | `chatId` | string | yes | Chat ID or @username |
293
- | `text` | string | yes | Message text |
294
- | `replyTo` | number | no | Message ID to reply to |
295
- | `parseMode` | `"md"` / `"html"` | no | Message formatting mode |
296
- | `topicId` | number | no | Forum topic ID to send into (for groups with Topics) |
297
-
298
- ### telegram-list-topics
299
-
300
- | Parameter | Type | Required | Description |
301
- |-----------|------|----------|-------------|
302
- | `chatId` | string | yes | Chat ID or @username (group with Topics enabled) |
303
- | `limit` | number | no | Max topics to return (default: 100) |
304
-
305
- ### telegram-read-topic-messages
306
-
307
- | Parameter | Type | Required | Description |
308
- |-----------|------|----------|-------------|
309
- | `chatId` | string | yes | Chat ID or @username |
310
- | `topicId` | number | yes | Topic ID (from `telegram-list-topics`) |
311
- | `limit` | number | no | Number of messages (default: 20) |
312
- | `offsetId` | number | no | Message ID for pagination |
313
-
314
- ### telegram-list-chats
315
-
316
- | Parameter | Type | Required | Description |
317
- |-----------|------|----------|-------------|
318
- | `limit` | number | no | Number of chats to return (default: 20) |
319
- | `offsetDate` | number | no | Unix timestamp for pagination |
320
- | `filterType` | `"private"` / `"group"` / `"channel"` / `"contact_requests"` | no | Filter by chat type |
321
-
322
- ### telegram-read-messages
323
-
324
- | Parameter | Type | Required | Description |
325
- |-----------|------|----------|-------------|
326
- | `chatId` | string | yes | Chat ID or @username |
327
- | `limit` | number | no | Number of messages (default: 10) |
328
- | `offsetId` | number | no | Message ID for pagination |
329
-
330
- ### telegram-send-file
331
-
332
- | Parameter | Type | Required | Description |
333
- |-----------|------|----------|-------------|
334
- | `chatId` | string | yes | Chat ID or @username |
335
- | `filePath` | string | yes | Absolute path to the file |
336
- | `caption` | string | no | File caption |
337
-
338
- ### telegram-download-media
339
-
340
- | Parameter | Type | Required | Description |
341
- |-----------|------|----------|-------------|
342
- | `chatId` | string | yes | Chat ID or @username |
343
- | `messageId` | number | yes | Message ID containing media |
344
- | `downloadPath` | string | yes | Absolute path to save the file |
345
-
346
- ### telegram-forward-message
347
-
348
- | Parameter | Type | Required | Description |
349
- |-----------|------|----------|-------------|
350
- | `fromChatId` | string | yes | Source chat ID or @username |
351
- | `toChatId` | string | yes | Destination chat ID or @username |
352
- | `messageIds` | number[] | yes | Array of message IDs to forward |
353
-
354
- ### telegram-edit-message
355
-
356
- | Parameter | Type | Required | Description |
357
- |-----------|------|----------|-------------|
358
- | `chatId` | string | yes | Chat ID or @username |
359
- | `messageId` | number | yes | ID of the message to edit |
360
- | `text` | string | yes | New message text |
361
-
362
- ### telegram-delete-message
363
-
364
- | Parameter | Type | Required | Description |
365
- |-----------|------|----------|-------------|
366
- | `chatId` | string | yes | Chat ID or @username |
367
- | `messageIds` | number[] | yes | Array of message IDs to delete |
368
-
369
- ### telegram-pin-message
370
-
371
- | Parameter | Type | Required | Description |
372
- |-----------|------|----------|-------------|
373
- | `chatId` | string | yes | Chat ID or @username |
374
- | `messageId` | number | yes | Message ID to pin |
375
- | `silent` | boolean | no | Pin without notification (default: false) |
376
-
377
- ### telegram-join-chat
378
-
379
- | Parameter | Type | Required | Description |
380
- |-----------|------|----------|-------------|
381
- | `target` | string | yes | Username (@group), link (t.me/group), or invite link (t.me/+xxx) |
382
-
383
- ### telegram-send-reaction
384
-
385
- | Parameter | Type | Required | Description |
386
- |-----------|------|----------|-------------|
387
- | `chatId` | string | yes | Chat ID or @username |
388
- | `messageId` | number | yes | Message ID to react to |
389
- | `emoji` | string \| string[] | no | Single emoji `"👍"` or array `["👍","🔥"]`. Omit to remove all reactions |
390
- | `addToExisting` | boolean | no | Add to existing reactions instead of replacing (default: false) |
391
-
392
- ### telegram-send-scheduled
393
-
394
- | Parameter | Type | Required | Description |
395
- |-----------|------|----------|-------------|
396
- | `chatId` | string | yes | Chat ID or @username (use `"me"` or `"self"` for Saved Messages) |
397
- | `text` | string | yes | Message text |
398
- | `scheduleDate` | number | yes | Unix timestamp when to send (must be in the future) |
399
- | `replyTo` | number | no | Message ID to reply to |
400
- | `parseMode` | `"md"` / `"html"` | no | Message formatting mode |
401
-
402
- ### telegram-create-poll
403
-
404
- | Parameter | Type | Required | Description |
405
- |-----------|------|----------|-------------|
406
- | `chatId` | string | yes | Chat ID or @username |
407
- | `question` | string | yes | Poll question |
408
- | `answers` | string[] | yes | Answer options (2-10 items) |
409
- | `multipleChoice` | boolean | no | Allow multiple answers (default: false) |
410
- | `quiz` | boolean | no | Quiz mode with one correct answer (default: false) |
411
- | `correctAnswer` | number | no | Index of correct answer, 0-based (required for quiz mode) |
412
-
413
- ### telegram-search-messages
414
-
415
- | Parameter | Type | Required | Description |
416
- |-----------|------|----------|-------------|
417
- | `chatId` | string | yes | Chat ID or @username |
418
- | `query` | string | yes | Search text |
419
- | `limit` | number | no | Max results (default: 20) |
420
-
421
- ### telegram-search-global
422
-
423
- | Parameter | Type | Required | Description |
424
- |-----------|------|----------|-------------|
425
- | `query` | string | yes | Search text |
426
- | `limit` | number | no | Max results (default: 20) |
427
- | `minDate` | number | no | Unix timestamp: only messages after this date |
428
- | `maxDate` | number | no | Unix timestamp: only messages before this date |
429
-
430
- ### telegram-search-chats
431
-
432
- | Parameter | Type | Required | Description |
433
- |-----------|------|----------|-------------|
434
- | `query` | string | yes | Search query (name or username) |
435
- | `limit` | number | no | Max results (default: 10) |
436
-
437
- ### telegram-get-chat-members
438
-
439
- | Parameter | Type | Required | Description |
440
- |-----------|------|----------|-------------|
441
- | `chatId` | string | yes | Chat ID or @username |
442
- | `limit` | number | no | Number of members (default: 50) |
443
-
444
- ### telegram-get-contacts
445
-
446
- | Parameter | Type | Required | Description |
447
- |-----------|------|----------|-------------|
448
- | `limit` | number | no | Number of contacts (default: 50) |
449
-
450
- ### telegram-get-profile
451
-
452
- | Parameter | Type | Required | Description |
453
- |-----------|------|----------|-------------|
454
- | `userId` | string | yes | User ID or @username |
455
-
456
- ### telegram-get-profile-photo
457
-
458
- | Parameter | Type | Required | Description |
459
- |-----------|------|----------|-------------|
460
- | `entityId` | string | yes | User/Chat/Channel ID or username |
461
- | `savePath` | string | no | Absolute path to save file. If omitted, returns inline base64 image |
462
- | `size` | `"small"` / `"big"` | no | Photo size: small (160x160) or big (640x640). Default: big |
463
-
464
- ### telegram-get-reactions
465
-
466
- | Parameter | Type | Required | Description |
467
- |-----------|------|----------|-------------|
468
- | `chatId` | string | yes | Chat ID or @username |
469
- | `messageId` | number | yes | Message ID to get reactions for |
470
-
471
- ### telegram-get-unread
472
-
473
- | Parameter | Type | Required | Description |
474
- |-----------|------|----------|-------------|
475
- | `limit` | number | no | Number of unread chats (default: 20) |
476
-
477
- ### telegram-get-contact-requests
478
-
479
- | Parameter | Type | Required | Description |
480
- |-----------|------|----------|-------------|
481
- | `limit` | number | no | Number of contact requests (default: 20) |
482
-
483
- ### telegram-add-contact
484
-
485
- | Parameter | Type | Required | Description |
486
- |-----------|------|----------|-------------|
487
- | `userId` | string | yes | User ID or @username to add |
488
- | `firstName` | string | yes | First name for the contact |
489
- | `lastName` | string | no | Last name for the contact |
490
- | `phone` | string | no | Phone number for the contact |
491
-
492
- ### telegram-block-user
493
-
494
- | Parameter | Type | Required | Description |
495
- |-----------|------|----------|-------------|
496
- | `userId` | string | yes | User ID or @username to block |
497
-
498
- ### telegram-report-spam
499
-
500
- | Parameter | Type | Required | Description |
501
- |-----------|------|----------|-------------|
502
- | `chatId` | string | yes | Chat ID or @username to report |
213
+ > **Tip**: Ask your AI assistant *"What Telegram tools are available?"* to get the current list with parameters.
503
214
 
504
215
  ## Development
505
216
 
@@ -517,9 +228,18 @@ npm run format # Format code with Biome
517
228
 
518
229
  ```
519
230
  src/
520
- index.ts -- MCP server entry point, tool definitions
231
+ index.ts -- MCP server entry point
521
232
  telegram-client.ts -- TelegramService class (GramJS wrapper)
522
233
  qr-login-cli.ts -- CLI utility for QR code login
234
+ tools/ -- Modular tool definitions
235
+ auth.ts -- Connection & login
236
+ messages.ts -- Send, read, search, edit, delete, forward
237
+ chats.ts -- Chat listing, group management, admin
238
+ contacts.ts -- Contacts, profiles, moderation
239
+ media.ts -- Files, photos
240
+ reactions.ts -- Reactions
241
+ extras.ts -- Pin, schedule, polls, topics
242
+ shared.ts -- Shared utilities
523
243
  ```
524
244
 
525
245
  ## Tech Stack
@@ -85,6 +85,16 @@ export declare class TelegramService {
85
85
  forwardMessage(fromChatId: string, toChatId: string, messageIds: number[]): Promise<void>;
86
86
  editMessage(chatId: string, messageId: number, newText: string): Promise<void>;
87
87
  deleteMessages(chatId: string, messageIds: number[]): Promise<void>;
88
+ /**
89
+ * Resolve a chat by ID, username, or display name.
90
+ * Falls back to searching user's dialogs if getEntity() fails.
91
+ */
92
+ resolveChat(chatId: string): Promise<any>;
93
+ /**
94
+ * Resolve chatId to a peer string that GramJS methods accept.
95
+ * Handles display names by searching dialogs.
96
+ */
97
+ private resolvePeer;
88
98
  getChatInfo(chatId: string): Promise<{
89
99
  id: string;
90
100
  name: string;
@@ -172,7 +182,15 @@ export declare class TelegramService {
172
182
  id: string;
173
183
  name: string;
174
184
  username?: string;
185
+ role: string;
175
186
  }>>;
187
+ getMyRole(chatId: string): Promise<{
188
+ role: string;
189
+ chatId: string;
190
+ chatName: string;
191
+ }>;
192
+ private getParticipantUserId;
193
+ private getParticipantRole;
176
194
  getProfile(userId: string): Promise<{
177
195
  id: string;
178
196
  name: string;
@@ -299,9 +299,10 @@ export class TelegramService {
299
299
  async sendMessage(chatId, text, replyTo, parseMode, topicId) {
300
300
  if (!this.client || !this.connected)
301
301
  throw new Error("Not connected");
302
+ const resolved = await this.resolvePeer(chatId);
302
303
  if (topicId) {
303
304
  // Forum topics require raw API call with InputReplyToMessage
304
- const peer = await this.client.getInputEntity(chatId);
305
+ const peer = await this.client.getInputEntity(resolved);
305
306
  await this.client.invoke(new Api.messages.SendMessage({
306
307
  peer,
307
308
  message: text,
@@ -313,7 +314,7 @@ export class TelegramService {
313
314
  }));
314
315
  }
315
316
  else {
316
- await this.client.sendMessage(chatId, {
317
+ await this.client.sendMessage(resolved, {
317
318
  message: text,
318
319
  ...(replyTo ? { replyTo } : {}),
319
320
  ...(parseMode ? { parseMode: parseMode === "html" ? "html" : "md" } : {}),
@@ -323,12 +324,14 @@ export class TelegramService {
323
324
  async sendFile(chatId, filePath, caption) {
324
325
  if (!this.client || !this.connected)
325
326
  throw new Error("Not connected");
326
- await this.client.sendFile(chatId, { file: filePath, caption });
327
+ const resolved = await this.resolvePeer(chatId);
328
+ await this.client.sendFile(resolved, { file: filePath, caption });
327
329
  }
328
330
  async downloadMedia(chatId, messageId, downloadPath) {
329
331
  if (!this.client || !this.connected)
330
332
  throw new Error("Not connected");
331
- const messages = await this.client.getMessages(chatId, { ids: [messageId] });
333
+ const resolved = await this.resolvePeer(chatId);
334
+ const messages = await this.client.getMessages(resolved, { ids: [messageId] });
332
335
  const message = messages[0];
333
336
  if (!message)
334
337
  throw new Error(`Message ${messageId} not found`);
@@ -343,7 +346,8 @@ export class TelegramService {
343
346
  async downloadMediaAsBuffer(chatId, messageId) {
344
347
  if (!this.client || !this.connected)
345
348
  throw new Error("Not connected");
346
- const messages = await this.client.getMessages(chatId, { ids: [messageId] });
349
+ const resolved = await this.resolvePeer(chatId);
350
+ const messages = await this.client.getMessages(resolved, { ids: [messageId] });
347
351
  const message = messages[0];
348
352
  if (!message)
349
353
  throw new Error(`Message ${messageId} not found`);
@@ -378,12 +382,14 @@ export class TelegramService {
378
382
  async pinMessage(chatId, messageId, silent = false) {
379
383
  if (!this.client || !this.connected)
380
384
  throw new Error("Not connected");
381
- await this.client.pinMessage(chatId, messageId, { notify: !silent });
385
+ const resolved = await this.resolvePeer(chatId);
386
+ await this.client.pinMessage(resolved, messageId, { notify: !silent });
382
387
  }
383
388
  async unpinMessage(chatId, messageId) {
384
389
  if (!this.client || !this.connected)
385
390
  throw new Error("Not connected");
386
- await this.client.unpinMessage(chatId, messageId);
391
+ const resolved = await this.resolvePeer(chatId);
392
+ await this.client.unpinMessage(resolved, messageId);
387
393
  }
388
394
  async getDialogs(limit = 20, offsetDate, filterType) {
389
395
  if (!this.client || !this.connected)
@@ -505,22 +511,70 @@ export class TelegramService {
505
511
  async forwardMessage(fromChatId, toChatId, messageIds) {
506
512
  if (!this.client || !this.connected)
507
513
  throw new Error("Not connected");
508
- await this.client.forwardMessages(toChatId, { messages: messageIds, fromPeer: fromChatId });
514
+ const resolvedFrom = await this.resolvePeer(fromChatId);
515
+ const resolvedTo = await this.resolvePeer(toChatId);
516
+ await this.client.forwardMessages(resolvedTo, { messages: messageIds, fromPeer: resolvedFrom });
509
517
  }
510
518
  async editMessage(chatId, messageId, newText) {
511
519
  if (!this.client || !this.connected)
512
520
  throw new Error("Not connected");
513
- await this.client.editMessage(chatId, { message: messageId, text: newText });
521
+ const resolved = await this.resolvePeer(chatId);
522
+ await this.client.editMessage(resolved, { message: messageId, text: newText });
514
523
  }
515
524
  async deleteMessages(chatId, messageIds) {
516
525
  if (!this.client || !this.connected)
517
526
  throw new Error("Not connected");
518
- await this.client.deleteMessages(chatId, messageIds, { revoke: true });
527
+ const resolved = await this.resolvePeer(chatId);
528
+ await this.client.deleteMessages(resolved, messageIds, { revoke: true });
529
+ }
530
+ /**
531
+ * Resolve a chat by ID, username, or display name.
532
+ * Falls back to searching user's dialogs if getEntity() fails.
533
+ */
534
+ // biome-ignore lint: GramJS has no proper entity union type
535
+ async resolveChat(chatId) {
536
+ if (!this.client)
537
+ throw new Error("Not connected");
538
+ // First try direct resolve (numeric ID, username, phone)
539
+ try {
540
+ return await this.client.getEntity(chatId);
541
+ }
542
+ catch {
543
+ // Fall through to dialog search
544
+ }
545
+ // Search dialogs by display name
546
+ const dialogs = await this.client.getDialogs({ limit: 100 });
547
+ const query = chatId.toLowerCase();
548
+ // Exact match first
549
+ const exact = dialogs.find((d) => d.title?.toLowerCase() === query);
550
+ if (exact?.entity)
551
+ return exact.entity;
552
+ // Partial match
553
+ const partial = dialogs.filter((d) => d.title?.toLowerCase().includes(query));
554
+ if (partial.length === 1 && partial[0].entity)
555
+ return partial[0].entity;
556
+ if (partial.length > 1) {
557
+ const matches = partial.map((d) => ` ${d.title} (${d.entity?.id?.toString() ?? "?"})`).join("\n");
558
+ throw new Error(`Multiple chats match "${chatId}". Use the numeric ID instead:\n${matches}`);
559
+ }
560
+ throw new Error(`Cannot find chat "${chatId}". Use a numeric ID, @username, or run telegram-search-chats to find it.`);
561
+ }
562
+ /**
563
+ * Resolve chatId to a peer string that GramJS methods accept.
564
+ * Handles display names by searching dialogs.
565
+ */
566
+ // biome-ignore lint: GramJS has no proper entity union type
567
+ async resolvePeer(chatId) {
568
+ // Numeric IDs and @usernames work directly
569
+ if (/^-?\d+$/.test(chatId) || chatId.startsWith("@"))
570
+ return chatId;
571
+ // Everything else — resolve via dialogs
572
+ return this.resolveChat(chatId);
519
573
  }
520
574
  async getChatInfo(chatId) {
521
575
  if (!this.client || !this.connected)
522
576
  throw new Error("Not connected");
523
- const entity = await this.client.getEntity(chatId);
577
+ const entity = await this.resolveChat(chatId);
524
578
  if (entity instanceof Api.User) {
525
579
  const parts = [entity.firstName, entity.lastName].filter(Boolean);
526
580
  return {
@@ -628,12 +682,13 @@ export class TelegramService {
628
682
  async getMessages(chatId, limit = 10, offsetId, minDate, maxDate) {
629
683
  if (!this.client || !this.connected)
630
684
  throw new Error("Not connected");
685
+ const resolved = await this.resolvePeer(chatId);
631
686
  const opts = {
632
687
  limit,
633
688
  ...(offsetId ? { offsetId } : {}),
634
689
  ...(maxDate ? { offsetDate: maxDate } : {}),
635
690
  };
636
- const messages = await this.client.getMessages(chatId, opts);
691
+ const messages = await this.client.getMessages(resolved, opts);
637
692
  let filtered = messages;
638
693
  if (minDate) {
639
694
  filtered = filtered.filter((m) => (m.date ?? 0) >= minDate);
@@ -781,7 +836,8 @@ export class TelegramService {
781
836
  async searchMessages(chatId, query, limit = 20, minDate, maxDate) {
782
837
  if (!this.client || !this.connected)
783
838
  throw new Error("Not connected");
784
- const messages = await this.client.getMessages(chatId, {
839
+ const resolved = await this.resolvePeer(chatId);
840
+ const messages = await this.client.getMessages(resolved, {
785
841
  search: query,
786
842
  limit,
787
843
  ...(maxDate ? { offsetDate: maxDate } : {}),
@@ -823,19 +879,99 @@ export class TelegramService {
823
879
  async getChatMembers(chatId, limit = 50) {
824
880
  if (!this.client || !this.connected)
825
881
  throw new Error("Not connected");
826
- const participants = await this.client.getParticipants(chatId, { limit });
827
- const members = [];
828
- for (const p of participants) {
829
- if (p instanceof Api.User) {
830
- const parts = [p.firstName, p.lastName].filter(Boolean);
831
- members.push({
832
- id: p.id.toString(),
882
+ const entity = await this.resolveChat(chatId);
883
+ if (entity instanceof Api.Channel) {
884
+ const result = await this.client.invoke(new Api.channels.GetParticipants({
885
+ channel: entity,
886
+ filter: new Api.ChannelParticipantsRecent(),
887
+ offset: 0,
888
+ limit,
889
+ hash: bigInt.zero,
890
+ }));
891
+ if (!(result instanceof Api.channels.ChannelParticipants))
892
+ return [];
893
+ const userMap = new Map();
894
+ for (const u of result.users) {
895
+ if (u instanceof Api.User)
896
+ userMap.set(u.id.toString(), u);
897
+ }
898
+ return result.participants.map((p) => {
899
+ const userId = this.getParticipantUserId(p);
900
+ const user = userMap.get(userId);
901
+ const parts = user ? [user.firstName, user.lastName].filter(Boolean) : [];
902
+ return {
903
+ id: userId,
833
904
  name: parts.join(" ") || "Unknown",
834
- username: p.username ?? undefined,
835
- });
905
+ username: user?.username ?? undefined,
906
+ role: this.getParticipantRole(p),
907
+ };
908
+ });
909
+ }
910
+ // Basic group — use getParticipants (no role info available)
911
+ const participants = await this.client.getParticipants(entity, { limit });
912
+ return participants
913
+ .filter((p) => p instanceof Api.User)
914
+ .map((p) => {
915
+ const parts = [p.firstName, p.lastName].filter(Boolean);
916
+ return {
917
+ id: p.id.toString(),
918
+ name: parts.join(" ") || "Unknown",
919
+ username: p.username ?? undefined,
920
+ role: "member",
921
+ };
922
+ });
923
+ }
924
+ async getMyRole(chatId) {
925
+ if (!this.client || !this.connected)
926
+ throw new Error("Not connected");
927
+ const entity = await this.resolveChat(chatId);
928
+ const me = await this.getMe();
929
+ if (entity instanceof Api.Channel) {
930
+ const result = await this.client.invoke(new Api.channels.GetParticipant({ channel: entity, participant: new Api.InputUserSelf() }));
931
+ return {
932
+ role: this.getParticipantRole(result.participant),
933
+ chatId: entity.id.toString(),
934
+ chatName: entity.title ?? "Unknown",
935
+ };
936
+ }
937
+ if (entity instanceof Api.Chat) {
938
+ // Basic group — check if creator
939
+ if (entity.creator) {
940
+ return { role: "creator", chatId: entity.id.toString(), chatName: entity.title ?? "Unknown" };
941
+ }
942
+ if (entity.adminRights) {
943
+ return { role: "admin", chatId: entity.id.toString(), chatName: entity.title ?? "Unknown" };
836
944
  }
945
+ return { role: "member", chatId: entity.id.toString(), chatName: entity.title ?? "Unknown" };
946
+ }
947
+ if (entity instanceof Api.User) {
948
+ return { role: "user", chatId: entity.id.toString(), chatName: me.username ?? "self" };
837
949
  }
838
- return members;
950
+ return { role: "unknown", chatId: chatId, chatName: "Unknown" };
951
+ }
952
+ getParticipantUserId(p) {
953
+ if (p instanceof Api.ChannelParticipantCreator)
954
+ return p.userId.toString();
955
+ if (p instanceof Api.ChannelParticipantAdmin)
956
+ return p.userId.toString();
957
+ if (p instanceof Api.ChannelParticipantSelf)
958
+ return p.userId.toString();
959
+ if (p instanceof Api.ChannelParticipantBanned)
960
+ return p.peer?.userId?.toString() ?? "0";
961
+ if (p instanceof Api.ChannelParticipant)
962
+ return p.userId.toString();
963
+ return "0";
964
+ }
965
+ getParticipantRole(p) {
966
+ if (p instanceof Api.ChannelParticipantCreator)
967
+ return "creator";
968
+ if (p instanceof Api.ChannelParticipantAdmin)
969
+ return "admin";
970
+ if (p instanceof Api.ChannelParticipantBanned)
971
+ return "banned";
972
+ if (p instanceof Api.ChannelParticipantLeft)
973
+ return "left";
974
+ return "member";
839
975
  }
840
976
  async getProfile(userId) {
841
977
  if (!this.client || !this.connected)
@@ -951,13 +1087,14 @@ export class TelegramService {
951
1087
  async sendReaction(chatId, messageId, emoji, addToExisting = false) {
952
1088
  if (!this.client || !this.connected)
953
1089
  throw new Error("Not connected");
954
- const peer = await this.client.getInputEntity(chatId);
1090
+ const resolved = await this.resolvePeer(chatId);
1091
+ const peer = await this.client.getInputEntity(resolved);
955
1092
  const reactionList = [];
956
1093
  if (emoji) {
957
1094
  const emojis = Array.isArray(emoji) ? emoji : [emoji];
958
1095
  if (addToExisting) {
959
1096
  // Fetch current reactions to preserve them
960
- const msgs = await this.client.getMessages(chatId, { ids: [messageId] });
1097
+ const msgs = await this.client.getMessages(resolved, { ids: [messageId] });
961
1098
  const msg = msgs[0];
962
1099
  if (msg?.reactions?.results) {
963
1100
  for (const r of msg.reactions.results) {
@@ -990,9 +1127,10 @@ export class TelegramService {
990
1127
  async getMessageReactions(chatId, messageId) {
991
1128
  if (!this.client || !this.connected)
992
1129
  throw new Error("Not connected");
993
- const peer = await this.client.getInputEntity(chatId);
1130
+ const resolved = await this.resolvePeer(chatId);
1131
+ const peer = await this.client.getInputEntity(resolved);
994
1132
  // First get the message to know which reactions exist
995
- const msgs = await this.client.getMessages(chatId, { ids: [messageId] });
1133
+ const msgs = await this.client.getMessages(resolved, { ids: [messageId] });
996
1134
  const msg = msgs[0];
997
1135
  if (!msg?.reactions?.results?.length) {
998
1136
  return { reactions: [], total: 0 };
@@ -1044,7 +1182,8 @@ export class TelegramService {
1044
1182
  async sendScheduledMessage(chatId, text, scheduleDate, replyTo, parseMode) {
1045
1183
  if (!this.client || !this.connected)
1046
1184
  throw new Error("Not connected");
1047
- await this.client.sendMessage(chatId, {
1185
+ const resolved = await this.resolvePeer(chatId);
1186
+ await this.client.sendMessage(resolved, {
1048
1187
  message: text,
1049
1188
  schedule: scheduleDate,
1050
1189
  ...(replyTo ? { replyTo } : {}),
@@ -1090,7 +1229,7 @@ export class TelegramService {
1090
1229
  async getForumTopics(chatId, limit = 100) {
1091
1230
  if (!this.client || !this.connected)
1092
1231
  throw new Error("Not connected");
1093
- const entity = await this.client.getEntity(chatId);
1232
+ const entity = await this.resolveChat(chatId);
1094
1233
  if (!(entity instanceof Api.Channel))
1095
1234
  throw new Error("Forum topics are only available in supergroups");
1096
1235
  const result = await this.client.invoke(new Api.channels.GetForumTopics({
@@ -1149,7 +1288,7 @@ export class TelegramService {
1149
1288
  if (!this.client || !this.connected)
1150
1289
  throw new Error("Not connected");
1151
1290
  try {
1152
- const entity = await this.client.getEntity(chatId);
1291
+ const entity = await this.resolveChat(chatId);
1153
1292
  if (entity instanceof Api.Channel) {
1154
1293
  return Boolean(entity.forum);
1155
1294
  }
@@ -1265,7 +1404,7 @@ export class TelegramService {
1265
1404
  async inviteToGroup(chatId, users) {
1266
1405
  if (!this.client)
1267
1406
  throw new Error("Not connected");
1268
- const entity = await this.client.getEntity(chatId);
1407
+ const entity = await this.resolveChat(chatId);
1269
1408
  const invited = [];
1270
1409
  const failed = [];
1271
1410
  for (const u of users) {
@@ -1293,7 +1432,7 @@ export class TelegramService {
1293
1432
  async kickUser(chatId, userId) {
1294
1433
  if (!this.client)
1295
1434
  throw new Error("Not connected");
1296
- const entity = await this.client.getEntity(chatId);
1435
+ const entity = await this.resolveChat(chatId);
1297
1436
  const user = await this.client.getEntity(userId);
1298
1437
  if (!(user instanceof Api.User))
1299
1438
  throw new Error("Target is not a user");
@@ -1318,7 +1457,7 @@ export class TelegramService {
1318
1457
  async banUser(chatId, userId) {
1319
1458
  if (!this.client)
1320
1459
  throw new Error("Not connected");
1321
- const entity = await this.client.getEntity(chatId);
1460
+ const entity = await this.resolveChat(chatId);
1322
1461
  const user = await this.client.getEntity(userId);
1323
1462
  if (!(user instanceof Api.User))
1324
1463
  throw new Error("Target is not a user");
@@ -1334,7 +1473,7 @@ export class TelegramService {
1334
1473
  async unbanUser(chatId, userId) {
1335
1474
  if (!this.client)
1336
1475
  throw new Error("Not connected");
1337
- const entity = await this.client.getEntity(chatId);
1476
+ const entity = await this.resolveChat(chatId);
1338
1477
  const user = await this.client.getEntity(userId);
1339
1478
  if (!(user instanceof Api.User))
1340
1479
  throw new Error("Target is not a user");
@@ -1350,7 +1489,7 @@ export class TelegramService {
1350
1489
  async editGroup(chatId, options) {
1351
1490
  if (!this.client)
1352
1491
  throw new Error("Not connected");
1353
- const entity = await this.client.getEntity(chatId);
1492
+ const entity = await this.resolveChat(chatId);
1354
1493
  if (options.title) {
1355
1494
  if (entity instanceof Api.Channel) {
1356
1495
  await this.client.invoke(new Api.channels.EditTitle({ channel: entity, title: options.title }));
@@ -1380,7 +1519,7 @@ export class TelegramService {
1380
1519
  async leaveGroup(chatId) {
1381
1520
  if (!this.client)
1382
1521
  throw new Error("Not connected");
1383
- const entity = await this.client.getEntity(chatId);
1522
+ const entity = await this.resolveChat(chatId);
1384
1523
  if (entity instanceof Api.Channel) {
1385
1524
  await this.client.invoke(new Api.channels.LeaveChannel({ channel: entity }));
1386
1525
  }
@@ -1397,7 +1536,7 @@ export class TelegramService {
1397
1536
  async setAdmin(chatId, userId, options) {
1398
1537
  if (!this.client)
1399
1538
  throw new Error("Not connected");
1400
- const entity = await this.client.getEntity(chatId);
1539
+ const entity = await this.resolveChat(chatId);
1401
1540
  if (!(entity instanceof Api.Channel))
1402
1541
  throw new Error("Set admin is only supported for supergroups and channels");
1403
1542
  const user = await this.client.getEntity(userId);
@@ -1423,7 +1562,7 @@ export class TelegramService {
1423
1562
  async removeAdmin(chatId, userId) {
1424
1563
  if (!this.client)
1425
1564
  throw new Error("Not connected");
1426
- const entity = await this.client.getEntity(chatId);
1565
+ const entity = await this.resolveChat(chatId);
1427
1566
  if (!(entity instanceof Api.Channel))
1428
1567
  throw new Error("Remove admin is only supported for supergroups and channels");
1429
1568
  const user = await this.client.getEntity(userId);
@@ -93,13 +93,36 @@ export function registerChatTools(server, telegram) {
93
93
  return fail(new Error(err));
94
94
  try {
95
95
  const members = await telegram.getChatMembers(chatId, limit);
96
- const text = members.map((m) => `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})`).join("\n");
96
+ const text = members
97
+ .map((m) => {
98
+ const role = m.role !== "member" ? ` [${m.role}]` : "";
99
+ return `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})${role}`;
100
+ })
101
+ .join("\n");
97
102
  return ok(sanitize(text) || "No members found");
98
103
  }
99
104
  catch (e) {
100
105
  return fail(e);
101
106
  }
102
107
  });
108
+ server.registerTool("telegram-get-my-role", {
109
+ description: "Get the current user's role in a chat (creator, admin, or member)",
110
+ inputSchema: {
111
+ chatId: z.string().describe("Chat ID or username"),
112
+ },
113
+ annotations: READ_ONLY,
114
+ }, async ({ chatId }) => {
115
+ const err = await requireConnection(telegram);
116
+ if (err)
117
+ return fail(new Error(err));
118
+ try {
119
+ const result = await telegram.getMyRole(chatId);
120
+ return ok(`Role: ${result.role}\nChat: ${result.chatName} (${result.chatId})`);
121
+ }
122
+ catch (e) {
123
+ return fail(e);
124
+ }
125
+ });
103
126
  server.registerTool("telegram-create-group", {
104
127
  description: "Create a new Telegram group or supergroup",
105
128
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overpod/mcp-telegram",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "MCP server for Telegram userbot — messages, media, reactions, polls & more. Built on GramJS/MTProto.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",