@overpod/mcp-telegram 1.3.1 → 1.4.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
@@ -1,9 +1,18 @@
1
1
  # MCP Telegram
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@overpod/mcp-telegram)](https://www.npmjs.com/package/@overpod/mcp-telegram)
4
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg)](https://www.typescriptlang.org/)
4
+ [![npm downloads](https://img.shields.io/npm/dm/@overpod/mcp-telegram)](https://www.npmjs.com/package/@overpod/mcp-telegram)
5
+ [![Node.js](https://img.shields.io/badge/Node.js-18%2B-339933.svg?logo=node.js&logoColor=white)](https://nodejs.org/)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9-blue.svg?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
5
7
  [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-1.27-green.svg)](https://modelcontextprotocol.io/)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
9
+ [![mcp-telegram MCP server](https://glama.ai/mcp/servers/overpod/mcp-telegram/badges/score.svg)](https://glama.ai/mcp/servers/overpod/mcp-telegram)
10
+
11
+ > **Hosted version available!** Don't want to self-host? Use [mcp-telegram.com](https://mcp-telegram.com) -- connect Telegram to Claude.ai in 30 seconds with QR code. No API keys needed.
12
+
13
+ <p align="center">
14
+ <img src="assets/demo.gif" alt="MCP Telegram demo — connect and summarize chats in Claude" width="700">
15
+ </p>
7
16
 
8
17
  An MCP (Model Context Protocol) server that connects AI assistants like Claude to Telegram via the MTProto protocol. Unlike bots, this runs as a **userbot** -- it operates under your personal Telegram account using [GramJS](https://github.com/nicedoc/gramjs), giving full access to your chats, contacts, and message history.
9
18
 
@@ -11,7 +20,7 @@ An MCP (Model Context Protocol) server that connects AI assistants like Claude t
11
20
 
12
21
  - **MTProto protocol** -- direct Telegram API access, not the limited Bot API
13
22
  - **Userbot** -- operates as your personal account, not a bot
14
- - **20 tools** -- messaging, chat management, media, contacts, and more
23
+ - **21 tools** -- messaging, chat management, media, contacts, and more
15
24
  - **QR code login** -- authenticate by scanning a QR code in the Telegram app
16
25
  - **Session persistence** -- login once, stay connected across restarts
17
26
  - **Human-readable output** -- sender names are resolved, not just numeric IDs
@@ -174,6 +183,7 @@ const telegramMcp = new MCPClient({
174
183
  | `telegram-mark-as-read` | Mark a chat as read |
175
184
  | `telegram-get-chat-info` | Get detailed info about a chat (name, type, members count, description) |
176
185
  | `telegram-get-chat-members` | List members of a group or channel |
186
+ | `telegram-join-chat` | Join a group or channel by username or invite link |
177
187
  | `telegram-pin-message` | Pin a message in a chat |
178
188
  | `telegram-unpin-message` | Unpin a message in a chat |
179
189
 
@@ -268,6 +278,12 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
268
278
  | `messageId` | number | yes | Message ID to pin |
269
279
  | `silent` | boolean | no | Pin without notification (default: false) |
270
280
 
281
+ ### telegram-join-chat
282
+
283
+ | Parameter | Type | Required | Description |
284
+ |-----------|------|----------|-------------|
285
+ | `target` | string | yes | Username (@group), link (t.me/group), or invite link (t.me/+xxx) |
286
+
271
287
  ### telegram-search-messages
272
288
 
273
289
  | Parameter | Type | Required | Description |
@@ -324,7 +340,7 @@ npm run format # Format code with Biome
324
340
 
325
341
  ```
326
342
  src/
327
- index.ts -- MCP server entry point, 20 tool definitions
343
+ index.ts -- MCP server entry point, 21 tool definitions
328
344
  telegram-client.ts -- TelegramService class (GramJS wrapper)
329
345
  qr-login-cli.ts -- CLI utility for QR code login
330
346
  ```
package/dist/index.js CHANGED
@@ -418,6 +418,31 @@ server.tool("telegram-get-profile", "Get detailed profile info of a Telegram use
418
418
  return { content: [{ type: "text", text: `Error: ${e.message}` }] };
419
419
  }
420
420
  });
421
+ server.tool("telegram-join-chat", "Join a Telegram group or channel by username or invite link", {
422
+ target: z
423
+ .string()
424
+ .describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)"),
425
+ }, async ({ target }) => {
426
+ const err = await requireConnection();
427
+ if (err)
428
+ return { content: [{ type: "text", text: err }] };
429
+ try {
430
+ const result = await telegram.joinChat(target);
431
+ return {
432
+ content: [
433
+ {
434
+ type: "text",
435
+ text: `Joined ${result.type}: ${result.title} (ID: ${result.id})`,
436
+ },
437
+ ],
438
+ };
439
+ }
440
+ catch (e) {
441
+ return {
442
+ content: [{ type: "text", text: `Error: ${e.message}` }],
443
+ };
444
+ }
445
+ });
421
446
  // --- Start ---
422
447
  async function main() {
423
448
  // Try to auto-connect with saved session
@@ -1,13 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import "dotenv/config";
3
- import { exec } from "node:child_process";
4
- import { writeFile } from "node:fs/promises";
5
- import { dirname, join } from "node:path";
6
- import { fileURLToPath } from "node:url";
7
3
  import QRCode from "qrcode";
8
4
  import { TelegramService } from "./telegram-client.js";
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
10
- const QR_IMAGE_PATH = join(__dirname, "..", "qr-login.png");
11
5
  const API_ID = Number(process.env.TELEGRAM_API_ID);
12
6
  const API_HASH = process.env.TELEGRAM_API_HASH;
13
7
  if (!API_ID || !API_HASH) {
@@ -15,10 +9,6 @@ if (!API_ID || !API_HASH) {
15
9
  process.exit(1);
16
10
  }
17
11
  const telegram = new TelegramService(API_ID, API_HASH);
18
- function openFile(path) {
19
- const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
20
- exec(`${cmd} "${path}"`);
21
- }
22
12
  async function main() {
23
13
  // Check if already connected
24
14
  await telegram.loadSession();
@@ -31,16 +21,7 @@ async function main() {
31
21
  console.log("\nStarting Telegram QR login...\n");
32
22
  console.log("Scan the QR code in Telegram app:");
33
23
  console.log(" Settings > Devices > Link Desktop Device\n");
34
- const result = await telegram.startQrLogin(
35
- // onQrDataUrl — save as PNG and open
36
- async (dataUrl) => {
37
- const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
38
- await writeFile(QR_IMAGE_PATH, Buffer.from(base64, "base64"));
39
- console.log(`QR saved: ${QR_IMAGE_PATH}`);
40
- openFile(QR_IMAGE_PATH);
41
- },
42
- // onQrUrl — also show in terminal
43
- async (url) => {
24
+ const result = await telegram.startQrLogin(() => { }, async (url) => {
44
25
  const terminalQr = await QRCode.toString(url, { type: "terminal", small: true });
45
26
  console.log(terminalQr);
46
27
  console.log("Waiting for scan...\n");
@@ -119,4 +119,9 @@ export declare class TelegramService {
119
119
  photo: boolean;
120
120
  lastSeen?: string;
121
121
  }>;
122
+ joinChat(target: string): Promise<{
123
+ id: string;
124
+ title: string;
125
+ type: string;
126
+ }>;
122
127
  }
@@ -123,6 +123,7 @@ export class TelegramService {
123
123
  return false;
124
124
  try {
125
125
  await this.client.invoke(new Api.auth.LogOut());
126
+ await this.client.destroy();
126
127
  this.connected = false;
127
128
  this.sessionString = "";
128
129
  this.client = null;
@@ -199,19 +200,20 @@ export class TelegramService {
199
200
  }
200
201
  if (resolved) {
201
202
  const newSession = client.session.save();
202
- await client.disconnect();
203
- await this.saveSession(newSession);
204
- // Reconnect with new session
203
+ // Adopt the QR login client directly instead of destroy+reconnect
204
+ // This avoids creating a second Telegram session from DC migration auth keys
205
+ this.client = client;
205
206
  this.sessionString = newSession;
206
- await this.connect();
207
+ this.connected = true;
208
+ await this.saveSession(newSession);
207
209
  return { success: true, message: "Telegram login successful" };
208
210
  }
209
- await client.disconnect();
211
+ await client.destroy();
210
212
  return { success: false, message: "QR login timeout" };
211
213
  }
212
214
  catch (err) {
213
215
  try {
214
- await client.disconnect();
216
+ await client.destroy();
215
217
  }
216
218
  catch { }
217
219
  return { success: false, message: `Login failed: ${err.message}` };
@@ -266,7 +268,7 @@ export class TelegramService {
266
268
  throw new Error(`Message ${messageId} not found`);
267
269
  if (!message.media)
268
270
  throw new Error(`Message ${messageId} has no media`);
269
- const buffer = await this.client.downloadMedia(message);
271
+ const buffer = (await this.client.downloadMedia(message));
270
272
  if (!buffer)
271
273
  throw new Error("Failed to download media");
272
274
  const mimeType = this.detectMimeType(buffer, message.media);
@@ -275,9 +277,9 @@ export class TelegramService {
275
277
  /** Detect MIME type from buffer magic bytes, falling back to media metadata */
276
278
  detectMimeType(buffer, media) {
277
279
  // Check magic bytes first
278
- if (buffer[0] === 0xFF && buffer[1] === 0xD8 && buffer[2] === 0xFF)
280
+ if (buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff)
279
281
  return "image/jpeg";
280
- if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4E && buffer[3] === 0x47)
282
+ if (buffer[0] === 0x89 && buffer[1] === 0x50 && buffer[2] === 0x4e && buffer[3] === 0x47)
281
283
  return "image/png";
282
284
  if (buffer[0] === 0x47 && buffer[1] === 0x49 && buffer[2] === 0x46)
283
285
  return "image/gif";
@@ -601,4 +603,35 @@ export class TelegramService {
601
603
  lastSeen,
602
604
  };
603
605
  }
606
+ async joinChat(target) {
607
+ if (!this.client)
608
+ throw new Error("Not connected");
609
+ // Extract invite hash from various link formats
610
+ const inviteMatch = target.match(/(?:t\.me\/\+|t\.me\/joinchat\/|tg:\/\/join\?invite=)([a-zA-Z0-9_-]+)/);
611
+ if (inviteMatch) {
612
+ const result = await this.client.invoke(new Api.messages.ImportChatInvite({ hash: inviteMatch[1] }));
613
+ const chat = result.chats?.[0];
614
+ if (!chat)
615
+ throw new Error("Failed to join via invite link");
616
+ return {
617
+ id: chat.id.toString(),
618
+ title: chat.title ?? "Unknown",
619
+ type: chat.className === "Channel" ? "channel" : "group",
620
+ };
621
+ }
622
+ // Public channel/group by username
623
+ const username = target.replace(/^@/, "").replace(/^https?:\/\/t\.me\//, "");
624
+ const entity = await this.client.getEntity(username);
625
+ if (entity instanceof Api.Channel || entity instanceof Api.Chat) {
626
+ await this.client.invoke(new Api.channels.JoinChannel({
627
+ channel: entity,
628
+ }));
629
+ return {
630
+ id: entity.id.toString(),
631
+ title: entity.title ?? "Unknown",
632
+ type: entity.className === "Channel" ? "channel" : "group",
633
+ };
634
+ }
635
+ throw new Error("Target is not a group or channel. Use username, @username, or invite link.");
636
+ }
604
637
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overpod/mcp-telegram",
3
- "version": "1.3.1",
3
+ "version": "1.4.0",
4
4
  "description": "MCP server for Telegram userbot — 20 tools for messages, media, contacts & more. Built on GramJS/MTProto.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -21,6 +21,7 @@
21
21
  "start": "node dist/index.js",
22
22
  "login": "node dist/qr-login-cli.js",
23
23
  "build": "tsc",
24
+ "typecheck": "tsc --noEmit",
24
25
  "prepublishOnly": "npm run build",
25
26
  "lint": "biome check src/",
26
27
  "lint:fix": "biome check --fix src/",