@overpod/mcp-telegram 1.27.0 → 1.27.1

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/dist/client.d.ts CHANGED
@@ -1,9 +1,19 @@
1
+ import { type Socket } from "node:net";
2
+ export interface IpcClientOptions {
3
+ connectTimeoutMs?: number;
4
+ callTimeoutMs?: number;
5
+ connectFn?: (path: string) => Socket;
6
+ }
1
7
  /** Thin IPC proxy: forwards tool calls to the master process over Unix socket */
2
8
  export declare class IpcClient {
3
9
  private socket;
4
10
  private pending;
5
11
  private buf;
6
12
  private connected;
13
+ private readonly connectTimeoutMs;
14
+ private readonly callTimeoutMs;
15
+ private readonly connectFn;
16
+ constructor(opts?: IpcClientOptions);
7
17
  connect(): Promise<boolean>;
8
18
  isConnected(): boolean;
9
19
  call(tool: string, args: Record<string, unknown>): Promise<unknown>;
package/dist/client.js CHANGED
@@ -15,15 +15,23 @@ export class IpcClient {
15
15
  pending = new Map();
16
16
  buf = "";
17
17
  connected = false;
18
+ connectTimeoutMs;
19
+ callTimeoutMs;
20
+ connectFn;
21
+ constructor(opts = {}) {
22
+ this.connectTimeoutMs = opts.connectTimeoutMs ?? CONNECT_TIMEOUT_MS;
23
+ this.callTimeoutMs = opts.callTimeoutMs ?? IPC_CALL_TIMEOUT_MS;
24
+ this.connectFn = opts.connectFn ?? connect;
25
+ }
18
26
  async connect() {
19
27
  return new Promise((resolve) => {
20
28
  const sock = socketPath();
21
- const s = connect(sock);
29
+ const s = this.connectFn(sock);
22
30
  // One-shot connect timeout — cleared immediately on connect (HIGH-3)
23
31
  const connectTimer = setTimeout(() => {
24
32
  s.destroy();
25
33
  resolve(false);
26
- }, CONNECT_TIMEOUT_MS);
34
+ }, this.connectTimeoutMs);
27
35
  const onConnect = () => {
28
36
  clearTimeout(connectTimer);
29
37
  this.socket = s;
@@ -80,7 +88,7 @@ export class IpcClient {
80
88
  const timer = setTimeout(() => {
81
89
  this.pending.delete(id);
82
90
  reject(new Error(`IPC call timeout: ${tool}`));
83
- }, IPC_CALL_TIMEOUT_MS);
91
+ }, this.callTimeoutMs);
84
92
  this.pending.set(id, { resolve, reject, timer });
85
93
  socket.write(encodeMessage({ id, tool, args }));
86
94
  });
@@ -1,3 +1,10 @@
1
+ /** MCP SDK internal tool registry — field name "handler" confirmed in SDK v1.29.0 */
2
+ export type McpRegisteredTool = {
3
+ handler: (args: Record<string, unknown>, extra: Record<string, unknown>) => Promise<unknown>;
4
+ };
5
+ export interface McpServerInternal {
6
+ _registeredTools: Record<string, McpRegisteredTool>;
7
+ }
1
8
  /** Request from Client → Master */
2
9
  export interface IpcRequest {
3
10
  id: string;
package/dist/master.d.ts CHANGED
@@ -1 +1,4 @@
1
+ import { type Socket } from "node:net";
2
+ import { type McpServerInternal } from "./ipc-protocol.js";
3
+ export declare function handleClient(socket: Socket, mcpServer: McpServerInternal): void;
1
4
  export declare function runMaster(apiId: number, apiHash: string, version: string): Promise<void>;
package/dist/master.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createServer } from "node:net";
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
- import { encodeMessage, parseMessages } from "./ipc-protocol.js";
4
+ import { encodeMessage, parseMessages, } from "./ipc-protocol.js";
5
5
  import { releaseLock, releaseSocket, socketPath } from "./lock.js";
6
6
  import { TelegramService } from "./telegram-client.js";
7
7
  import { registerTools } from "./tools/index.js";
@@ -18,7 +18,7 @@ function cleanup() {
18
18
  process.on("exit", cleanup);
19
19
  process.on("SIGINT", () => process.exit(0));
20
20
  process.on("SIGTERM", () => process.exit(0));
21
- function handleClient(socket, mcpServer) {
21
+ export function handleClient(socket, mcpServer) {
22
22
  let buf = "";
23
23
  // Processing queue — ensures sequential handling even when handler awaits
24
24
  let processing = false;
@@ -60,6 +60,7 @@ export class TelegramService {
60
60
  get sessionDir() {
61
61
  return dirname(this.sessionPath);
62
62
  }
63
+ // ─── Session & Auth ────────────────────────────────────────────────────────
63
64
  getClient() {
64
65
  return this.client;
65
66
  }
@@ -300,6 +301,7 @@ export class TelegramService {
300
301
  return { success: false, message: `Login failed: ${err.message}` };
301
302
  }
302
303
  }
304
+ // ─── Messages ──────────────────────────────────────────────────────────────
303
305
  async getMe() {
304
306
  if (!this.client || !this.connected)
305
307
  throw new Error(NOT_CONNECTED_ERROR);
@@ -410,6 +412,7 @@ export class TelegramService {
410
412
  const resolved = await this.resolvePeer(chatId);
411
413
  await this.client.unpinMessage(resolved, messageId);
412
414
  }
415
+ // ─── Dialogs ───────────────────────────────────────────────────────────────
413
416
  async getDialogs(limit = 20, offsetDate, filterType) {
414
417
  if (!this.client || !this.connected)
415
418
  throw new Error(NOT_CONNECTED_ERROR);
@@ -499,6 +502,7 @@ export class TelegramService {
499
502
  };
500
503
  });
501
504
  }
505
+ // ─── Contacts ──────────────────────────────────────────────────────────────
502
506
  async addContact(userId, firstName, lastName, phone) {
503
507
  if (!this.client || !this.connected)
504
508
  throw new Error(NOT_CONNECTED_ERROR);
@@ -522,6 +526,7 @@ export class TelegramService {
522
526
  const peer = await this.client.getInputEntity(chatId);
523
527
  await this.client.invoke(new Api.messages.ReportSpam({ peer }));
524
528
  }
529
+ // ─── Read state ────────────────────────────────────────────────────────────
525
530
  async markAsRead(chatId) {
526
531
  if (!this.client || !this.connected)
527
532
  throw new Error(NOT_CONNECTED_ERROR);
@@ -786,6 +791,7 @@ export class TelegramService {
786
791
  }
787
792
  }, `sendTyping in ${chatId}`);
788
793
  }
794
+ // ─── Chat lookup & info ────────────────────────────────────────────────────
789
795
  /**
790
796
  * Resolve a chat by ID, username, or display name.
791
797
  * Falls back to searching user's dialogs if getEntity() fails.
@@ -1125,6 +1131,7 @@ export class TelegramService {
1125
1131
  })));
1126
1132
  return results;
1127
1133
  }
1134
+ // ─── Search ────────────────────────────────────────────────────────────────
1128
1135
  async getContacts(limit = 50) {
1129
1136
  if (!this.client || !this.connected)
1130
1137
  throw new Error(NOT_CONNECTED_ERROR);
@@ -1302,6 +1309,7 @@ export class TelegramService {
1302
1309
  businessLocation,
1303
1310
  };
1304
1311
  }
1312
+ // ─── Profiles & Media ──────────────────────────────────────────────────────
1305
1313
  async downloadProfilePhoto(entityId, options) {
1306
1314
  if (!this.client || !this.connected)
1307
1315
  throw new Error(NOT_CONNECTED_ERROR);
@@ -1360,6 +1368,7 @@ export class TelegramService {
1360
1368
  }
1361
1369
  return items.length > 0 ? items : undefined;
1362
1370
  }
1371
+ // ─── Reactions ─────────────────────────────────────────────────────────────
1363
1372
  async sendReaction(chatId, messageId, emoji, addToExisting = false) {
1364
1373
  if (!this.client || !this.connected)
1365
1374
  throw new Error(NOT_CONNECTED_ERROR);
@@ -1496,6 +1505,7 @@ export class TelegramService {
1496
1505
  return out;
1497
1506
  }, "getRecentReactions");
1498
1507
  }
1508
+ // ─── Scheduled & Polls ─────────────────────────────────────────────────────
1499
1509
  async sendScheduledMessage(chatId, text, scheduleDate, replyTo, parseMode) {
1500
1510
  if (!this.client || !this.connected)
1501
1511
  throw new Error(NOT_CONNECTED_ERROR);
@@ -1614,6 +1624,7 @@ export class TelegramService {
1614
1624
  catch { }
1615
1625
  return false;
1616
1626
  }
1627
+ // ─── Chat membership & management ──────────────────────────────────────────
1617
1628
  async joinChat(target) {
1618
1629
  if (!this.client)
1619
1630
  throw new Error(NOT_CONNECTED_ERROR);
@@ -1895,7 +1906,7 @@ export class TelegramService {
1895
1906
  rank: "",
1896
1907
  }));
1897
1908
  }
1898
- // ── New tools: feature parity ──────────────────────────────────────
1909
+ // ─── Chat settings & moderation ────────────────────────────────────────────
1899
1910
  async unblockUser(userId) {
1900
1911
  if (!this.client || !this.connected)
1901
1912
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2133,6 +2144,7 @@ export class TelegramService {
2133
2144
  await this.client?.invoke(new Api.messages.HideChatJoinRequest({ peer: entity, userId: inputUser, approved }));
2134
2145
  }, `approveChatJoinRequest ${chatId}/${userId}`);
2135
2146
  }
2147
+ // ─── Inline bots & buttons ─────────────────────────────────────────────────
2136
2148
  async getInlineBotResults(bot, chatId, query, offset) {
2137
2149
  if (!this.client || !this.connected)
2138
2150
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2305,7 +2317,7 @@ export class TelegramService {
2305
2317
  result = response;
2306
2318
  }
2307
2319
  catch (e) {
2308
- const msg = e.message ?? String(e);
2320
+ const msg = e instanceof Error ? e.message : String(e);
2309
2321
  if (/CHAT_ADMIN_REQUIRED|ADMIN_RANK_INVALID/i.test(msg)) {
2310
2322
  throw new Error("Access denied: channel stats require admin rights (and may require Telegram Premium)");
2311
2323
  }
@@ -2337,7 +2349,7 @@ export class TelegramService {
2337
2349
  result = response;
2338
2350
  }
2339
2351
  catch (e) {
2340
- const msg = e.message ?? String(e);
2352
+ const msg = e instanceof Error ? e.message : String(e);
2341
2353
  if (/CHAT_ADMIN_REQUIRED|ADMIN_RANK_INVALID/i.test(msg)) {
2342
2354
  throw new Error("Access denied: supergroup stats require admin rights");
2343
2355
  }
@@ -2349,6 +2361,7 @@ export class TelegramService {
2349
2361
  return summarizeMegagroupStats(result, options?.includeGraphs === true);
2350
2362
  }, `getMegagroupStats ${chatId}`, { throwOnFloodWait: true });
2351
2363
  }
2364
+ // ─── Stats & updates ───────────────────────────────────────────────────────
2352
2365
  async getUpdatesState() {
2353
2366
  if (!this.client || !this.connected)
2354
2367
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2404,6 +2417,7 @@ export class TelegramService {
2404
2417
  return summarizeChannelDifference(diff, entity.id.toString(), cursor.pts);
2405
2418
  }, `getChannelUpdates ${chatId}`);
2406
2419
  }
2420
+ // ─── Forum topics ──────────────────────────────────────────────────────────
2407
2421
  async createForumTopic(chatId, title, iconColor, iconEmojiId) {
2408
2422
  if (!this.client || !this.connected)
2409
2423
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2477,6 +2491,7 @@ export class TelegramService {
2477
2491
  }));
2478
2492
  }, `deleteForumTopic ${chatId}/${topicId}`);
2479
2493
  }
2494
+ // ─── Invite links & folders ────────────────────────────────────────────────
2480
2495
  async exportInviteLink(chatId, options) {
2481
2496
  if (!this.client || !this.connected)
2482
2497
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2552,6 +2567,7 @@ export class TelegramService {
2552
2567
  const peer = await this.client.getInputEntity(resolved);
2553
2568
  await this.client.invoke(new Api.messages.SetHistoryTTL({ peer, period }));
2554
2569
  }
2570
+ // ─── Account & privacy ─────────────────────────────────────────────────────
2555
2571
  async getActiveSessions() {
2556
2572
  if (!this.client || !this.connected)
2557
2573
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2655,7 +2671,7 @@ export class TelegramService {
2655
2671
  throw new Error(NOT_CONNECTED_ERROR);
2656
2672
  await this.client.invoke(new Api.account.UpdateUsername({ username }));
2657
2673
  }
2658
- // ─── Stickers ──────────────────────────────────────────────
2674
+ // ─── Stickers ──────────────────────────────────────────────────────────────
2659
2675
  async getStickerSet(shortName) {
2660
2676
  if (!this.client || !this.connected)
2661
2677
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2748,6 +2764,7 @@ export class TelegramService {
2748
2764
  });
2749
2765
  }, `sendSticker to ${chatId}`);
2750
2766
  }
2767
+ // ─── Drafts & saved dialogs ────────────────────────────────────────────────
2751
2768
  async saveDraft(chatId, text, replyTo) {
2752
2769
  if (!this.client || !this.connected)
2753
2770
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2943,6 +2960,7 @@ export class TelegramService {
2943
2960
  emoji: emojiMap.get(doc.id.toString()) || "",
2944
2961
  }));
2945
2962
  }
2963
+ // ─── Stories ───────────────────────────────────────────────────────────────
2946
2964
  async getAllStories(options) {
2947
2965
  if (!this.client || !this.connected)
2948
2966
  throw new Error(NOT_CONNECTED_ERROR);
@@ -2999,6 +3017,7 @@ export class TelegramService {
2999
3017
  return summarizeStoryViewsList(response);
3000
3018
  }, `getStoryViewsList ${chatId}/${options.id}`);
3001
3019
  }
3020
+ // ─── Boosts ────────────────────────────────────────────────────────────────
3002
3021
  async getMyBoosts() {
3003
3022
  if (!this.client || !this.connected)
3004
3023
  throw new Error(NOT_CONNECTED_ERROR);
@@ -3046,6 +3065,7 @@ export class TelegramService {
3046
3065
  return summarizeBusinessChatLinks(response);
3047
3066
  }, "getBusinessChatLinks");
3048
3067
  }
3068
+ // ─── Group calls ───────────────────────────────────────────────────────────
3049
3069
  async getGroupCall(chatId, options = {}) {
3050
3070
  if (!this.client || !this.connected)
3051
3071
  throw new Error(NOT_CONNECTED_ERROR);
@@ -3078,6 +3098,7 @@ export class TelegramService {
3078
3098
  return summarizeGroupCallParticipants(response);
3079
3099
  }, `getGroupCallParticipants ${chatId}`);
3080
3100
  }
3101
+ // ─── Stars ─────────────────────────────────────────────────────────────────
3081
3102
  async getStarsStatus(chatId) {
3082
3103
  if (!this.client || !this.connected)
3083
3104
  throw new Error(NOT_CONNECTED_ERROR);
@@ -3108,6 +3129,7 @@ export class TelegramService {
3108
3129
  return summarizeStarsStatus(response);
3109
3130
  }, `getStarsTransactions ${chatId}`);
3110
3131
  }
3132
+ // ─── Quick replies ─────────────────────────────────────────────────────────
3111
3133
  async getQuickReplies(hash) {
3112
3134
  if (!this.client || !this.connected)
3113
3135
  throw new Error(NOT_CONNECTED_ERROR);
@@ -109,12 +109,12 @@ function compactGraph(g) {
109
109
  }
110
110
  return { type: "data", data: parsed, zoomToken: g.zoomToken };
111
111
  }
112
- const any = g;
113
- if (typeof any.token === "string")
114
- return { type: "async", token: any.token };
115
- if (typeof any.error === "string")
116
- return { type: "error", error: any.error };
117
- return { type: "data", data: any.json?.data, zoomToken: any.zoomToken };
112
+ const graph = g;
113
+ if (typeof graph.token === "string")
114
+ return { type: "async", token: graph.token };
115
+ if (typeof graph.error === "string")
116
+ return { type: "error", error: graph.error };
117
+ return { type: "data", data: graph.json?.data, zoomToken: graph.zoomToken };
118
118
  }
119
119
  export function summarizeMegagroupStats(stats, includeGraphs) {
120
120
  const summary = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overpod/mcp-telegram",
3
- "version": "1.27.0",
3
+ "version": "1.27.1",
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",
@@ -27,8 +27,9 @@
27
27
  "lint": "biome check src/",
28
28
  "lint:fix": "biome check --fix src/",
29
29
  "format": "biome format --write src/",
30
- "test": "tsx --test src/**/*.test.ts",
31
- "test:watch": "tsx --test --watch src/**/*.test.ts",
30
+ "test": "tsx --test 'src/**/*.test.ts'",
31
+ "test:watch": "tsx --test --watch 'src/**/*.test.ts'",
32
+ "test:coverage": "c8 --all --src src --exclude 'src/**/*.test.ts' --reporter=text tsx --test 'src/**/*.test.ts'",
32
33
  "docs:dev": "vitepress dev docs",
33
34
  "docs:build": "vitepress build docs",
34
35
  "docs:preview": "vitepress preview docs"
@@ -65,6 +66,7 @@
65
66
  "@biomejs/biome": "^2.4.12",
66
67
  "@types/node": "^25.6.0",
67
68
  "@types/qrcode": "^1.5.6",
69
+ "c8": "^11.0.0",
68
70
  "tsx": "^4.21.0",
69
71
  "typescript": "^6.0.3",
70
72
  "vitepress": "^1.6.4"