@overpod/mcp-telegram 1.11.1 → 1.12.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.
Files changed (2) hide show
  1. package/dist/index.js +377 -275
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -40,30 +40,36 @@ async function requireConnection() {
40
40
  const reason = telegram.lastError ? ` ${telegram.lastError}` : "";
41
41
  return `Not connected to Telegram.${reason} Run telegram-login first.`;
42
42
  }
43
+ /** MCP tool annotation presets */
44
+ const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
45
+ const WRITE = { readOnlyHint: false, openWorldHint: true };
46
+ const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
47
+ /** Helper: success response */
48
+ function ok(text) {
49
+ return { content: [{ type: "text", text }] };
50
+ }
51
+ /** Helper: error response with isError flag */
52
+ function fail(e) {
53
+ return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
54
+ }
43
55
  // --- Tools ---
44
- server.tool("telegram-status", "Check Telegram connection status", {}, async () => {
56
+ server.registerTool("telegram-status", { description: "Check Telegram connection status", annotations: READ_ONLY }, async () => {
45
57
  if (await telegram.ensureConnected()) {
46
58
  try {
47
59
  const me = await telegram.getMe();
48
- return {
49
- content: [
50
- {
51
- type: "text",
52
- text: `Connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})`,
53
- },
54
- ],
55
- };
60
+ return ok(`Connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})`);
56
61
  }
57
62
  catch {
58
- return { content: [{ type: "text", text: "Connected, but failed to get user info" }] };
63
+ return ok("Connected, but failed to get user info");
59
64
  }
60
65
  }
61
66
  const reason = telegram.lastError ? ` Reason: ${telegram.lastError}` : "";
62
- return {
63
- content: [{ type: "text", text: `Not connected.${reason} Use telegram-login to authenticate via QR code.` }],
64
- };
67
+ return ok(`Not connected.${reason} Use telegram-login to authenticate via QR code.`);
65
68
  });
66
- server.tool("telegram-login", "Login to Telegram via QR code. Returns QR image. IMPORTANT: pass the entire result to user without modifications.", {}, async () => {
69
+ server.registerTool("telegram-login", {
70
+ description: "Login to Telegram via QR code. Returns QR image. IMPORTANT: pass the entire result to user without modifications.",
71
+ annotations: WRITE,
72
+ }, async () => {
67
73
  let qrDataUrl = "";
68
74
  let qrRawUrl = "";
69
75
  const loginPromise = telegram.startQrLogin((dataUrl) => {
@@ -77,7 +83,7 @@ server.tool("telegram-login", "Login to Telegram via QR code. Returns QR image.
77
83
  await new Promise((r) => setTimeout(r, 500));
78
84
  }
79
85
  if (!qrDataUrl) {
80
- return { content: [{ type: "text", text: "Failed to generate QR code" }] };
86
+ return fail(new Error("Failed to generate QR code"));
81
87
  }
82
88
  // Login continues in background
83
89
  loginPromise.then((result) => {
@@ -116,36 +122,44 @@ server.tool("telegram-login", "Login to Telegram via QR code. Returns QR image.
116
122
  ],
117
123
  };
118
124
  });
119
- server.tool("telegram-send-message", "Send a message to a Telegram chat", {
120
- chatId: z.string().describe("Chat ID or username (e.g. @username or numeric ID)"),
121
- text: z.string().describe("Message text"),
122
- replyTo: z.number().optional().describe("Message ID to reply to"),
123
- parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
124
- topicId: z.number().optional().describe("Forum topic ID to send message into (for groups with Topics enabled)"),
125
+ server.registerTool("telegram-send-message", {
126
+ description: "Send a message to a Telegram chat",
127
+ inputSchema: {
128
+ chatId: z.string().describe("Chat ID or username (e.g. @username or numeric ID)"),
129
+ text: z.string().describe("Message text"),
130
+ replyTo: z.number().optional().describe("Message ID to reply to"),
131
+ parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
132
+ topicId: z.number().optional().describe("Forum topic ID to send message into (for groups with Topics enabled)"),
133
+ },
134
+ annotations: WRITE,
125
135
  }, async ({ chatId, text, replyTo, parseMode, topicId }) => {
126
136
  const err = await requireConnection();
127
137
  if (err)
128
- return { content: [{ type: "text", text: err }] };
138
+ return fail(new Error(err));
129
139
  try {
130
140
  await telegram.sendMessage(chatId, text, replyTo, parseMode, topicId);
131
141
  const dest = topicId ? `topic ${topicId} in ${chatId}` : chatId;
132
- return { content: [{ type: "text", text: `Message sent to ${dest}` }] };
142
+ return ok(`Message sent to ${dest}`);
133
143
  }
134
144
  catch (e) {
135
- return { content: [{ type: "text", text: `Send error: ${e.message}` }] };
136
- }
137
- });
138
- server.tool("telegram-list-chats", "List Telegram chats", {
139
- limit: z.number().default(20).describe("Number of chats to return"),
140
- offsetDate: z.number().optional().describe("Unix timestamp offset for pagination"),
141
- filterType: z
142
- .enum(["private", "group", "channel", "contact_requests"])
143
- .optional()
144
- .describe("Filter by chat type. 'contact_requests' shows only private chats from non-contacts"),
145
+ return fail(e);
146
+ }
147
+ });
148
+ server.registerTool("telegram-list-chats", {
149
+ description: "List Telegram chats with unread counts, type indicators, and contact status",
150
+ inputSchema: {
151
+ limit: z.number().default(20).describe("Number of chats to return"),
152
+ offsetDate: z.number().optional().describe("Unix timestamp offset for pagination"),
153
+ filterType: z
154
+ .enum(["private", "group", "channel", "contact_requests"])
155
+ .optional()
156
+ .describe("Filter by chat type. 'contact_requests' shows only private chats from non-contacts"),
157
+ },
158
+ annotations: READ_ONLY,
145
159
  }, async ({ limit, offsetDate, filterType }) => {
146
160
  const err = await requireConnection();
147
161
  if (err)
148
- return { content: [{ type: "text", text: err }] };
162
+ return fail(new Error(err));
149
163
  try {
150
164
  const dialogs = await telegram.getDialogs(limit, offsetDate, filterType);
151
165
  const text = dialogs
@@ -157,98 +171,118 @@ server.tool("telegram-list-chats", "List Telegram chats", {
157
171
  return `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${unread}`;
158
172
  })
159
173
  .join("\n");
160
- return { content: [{ type: "text", text: sanitize(text) || "No chats" }] };
174
+ return ok(sanitize(text) || "No chats");
161
175
  }
162
176
  catch (e) {
163
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
164
- }
165
- });
166
- server.tool("telegram-read-messages", "Read recent messages from a Telegram chat", {
167
- chatId: z.string().describe("Chat ID or username"),
168
- limit: z.number().default(10).describe("Number of messages to return"),
169
- offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
170
- minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
171
- maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
177
+ return fail(e);
178
+ }
179
+ });
180
+ server.registerTool("telegram-read-messages", {
181
+ description: "Read recent messages from a Telegram chat with sender names, dates, media info, and reactions",
182
+ inputSchema: {
183
+ chatId: z.string().describe("Chat ID or username"),
184
+ limit: z.number().default(10).describe("Number of messages to return"),
185
+ offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
186
+ minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
187
+ maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
188
+ },
189
+ annotations: READ_ONLY,
172
190
  }, async ({ chatId, limit, offsetId, minDate, maxDate }) => {
173
191
  const err = await requireConnection();
174
192
  if (err)
175
- return { content: [{ type: "text", text: err }] };
193
+ return fail(new Error(err));
176
194
  try {
177
195
  const messages = await telegram.getMessages(chatId, limit, offsetId, minDate, maxDate);
178
196
  const text = messages
179
197
  .map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
180
198
  .join("\n\n");
181
- return { content: [{ type: "text", text: sanitize(text) || "No messages" }] };
199
+ return ok(sanitize(text) || "No messages");
182
200
  }
183
201
  catch (e) {
184
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
202
+ return fail(e);
185
203
  }
186
204
  });
187
- server.tool("telegram-search-chats", "Search for Telegram chats/users/channels by name or username", {
188
- query: z.string().describe("Search query (name or username)"),
189
- limit: z.number().default(10).describe("Max results"),
205
+ server.registerTool("telegram-search-chats", {
206
+ description: "Search for Telegram chats, users, or channels by name or username. Returns description and member count",
207
+ inputSchema: {
208
+ query: z.string().describe("Search query (name or username)"),
209
+ limit: z.number().default(10).describe("Max results"),
210
+ },
211
+ annotations: READ_ONLY,
190
212
  }, async ({ query, limit }) => {
191
213
  const err = await requireConnection();
192
214
  if (err)
193
- return { content: [{ type: "text", text: err }] };
215
+ return fail(new Error(err));
194
216
  try {
195
217
  const results = await telegram.searchChats(query, limit);
196
218
  const text = results
197
219
  .map((c) => `${c.type === "group" ? "G" : c.type === "channel" ? "C" : "P"} ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.membersCount ? ` [${c.membersCount} members]` : ""}${c.description ? ` — ${c.description.split("\n")[0].slice(0, 100)}` : ""}`)
198
220
  .join("\n");
199
- return { content: [{ type: "text", text: sanitize(text) || "No results" }] };
221
+ return ok(sanitize(text) || "No results");
200
222
  }
201
223
  catch (e) {
202
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
203
- }
204
- });
205
- server.tool("telegram-search-global", "Search messages globally across all public Telegram chats and channels", {
206
- query: z.string().describe("Search text"),
207
- limit: z.number().default(20).describe("Max results"),
208
- minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
209
- maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
224
+ return fail(e);
225
+ }
226
+ });
227
+ server.registerTool("telegram-search-global", {
228
+ description: "Search messages globally across all public Telegram chats and channels",
229
+ inputSchema: {
230
+ query: z.string().describe("Search text"),
231
+ limit: z.number().default(20).describe("Max results"),
232
+ minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
233
+ maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
234
+ },
235
+ annotations: READ_ONLY,
210
236
  }, async ({ query, limit, minDate, maxDate }) => {
211
237
  const err = await requireConnection();
212
238
  if (err)
213
- return { content: [{ type: "text", text: err }] };
239
+ return fail(new Error(err));
214
240
  try {
215
241
  const messages = await telegram.searchGlobal(query, limit, minDate, maxDate);
216
242
  const text = messages
217
243
  .map((m) => `[#${m.id}] [${m.date}] [${m.chat.type === "channel" ? "C" : m.chat.type === "group" ? "G" : "P"} ${m.chat.name}${m.chat.username ? ` @${m.chat.username}` : ""}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
218
244
  .join("\n\n");
219
- return { content: [{ type: "text", text: sanitize(text) || "No messages found" }] };
245
+ return ok(sanitize(text) || "No messages found");
220
246
  }
221
247
  catch (e) {
222
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
223
- }
224
- });
225
- server.tool("telegram-search-messages", "Search messages in a Telegram chat by text", {
226
- chatId: z.string().describe("Chat ID or username"),
227
- query: z.string().describe("Search text"),
228
- limit: z.number().default(20).describe("Max results"),
229
- minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
230
- maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
248
+ return fail(e);
249
+ }
250
+ });
251
+ server.registerTool("telegram-search-messages", {
252
+ description: "Search messages in a specific Telegram chat by text",
253
+ inputSchema: {
254
+ chatId: z.string().describe("Chat ID or username"),
255
+ query: z.string().describe("Search text"),
256
+ limit: z.number().default(20).describe("Max results"),
257
+ minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
258
+ maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
259
+ },
260
+ annotations: READ_ONLY,
231
261
  }, async ({ chatId, query, limit, minDate, maxDate }) => {
232
262
  const err = await requireConnection();
233
263
  if (err)
234
- return { content: [{ type: "text", text: err }] };
264
+ return fail(new Error(err));
235
265
  try {
236
266
  const messages = await telegram.searchMessages(chatId, query, limit, minDate, maxDate);
237
267
  const text = messages
238
268
  .map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
239
269
  .join("\n\n");
240
- return { content: [{ type: "text", text: sanitize(text) || "No messages found" }] };
270
+ return ok(sanitize(text) || "No messages found");
241
271
  }
242
272
  catch (e) {
243
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
273
+ return fail(e);
244
274
  }
245
275
  });
246
- server.tool("telegram-get-unread", "Get unread Telegram chats", {
247
- limit: z.number().default(20).describe("Number of unread chats to return"),
276
+ server.registerTool("telegram-get-unread", {
277
+ description: "Get chats with unread messages. Forums show per-topic unread breakdown",
278
+ inputSchema: {
279
+ limit: z.number().default(20).describe("Number of unread chats to return"),
280
+ },
281
+ annotations: READ_ONLY,
248
282
  }, async ({ limit }) => {
249
283
  const err = await requireConnection();
250
284
  if (err)
251
- return { content: [{ type: "text", text: err }] };
285
+ return fail(new Error(err));
252
286
  try {
253
287
  const dialogs = await telegram.getUnreadDialogs(limit);
254
288
  const text = dialogs
@@ -265,83 +299,95 @@ server.tool("telegram-get-unread", "Get unread Telegram chats", {
265
299
  return line;
266
300
  })
267
301
  .join("\n");
268
- return { content: [{ type: "text", text: sanitize(text) || "No unread chats" }] };
302
+ return ok(sanitize(text) || "No unread chats");
269
303
  }
270
304
  catch (e) {
271
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
305
+ return fail(e);
272
306
  }
273
307
  });
274
- server.tool("telegram-mark-as-read", "Mark a Telegram chat as read", {
275
- chatId: z.string().describe("Chat ID or username"),
308
+ server.registerTool("telegram-mark-as-read", {
309
+ description: "Mark a Telegram chat as read",
310
+ inputSchema: { chatId: z.string().describe("Chat ID or username") },
311
+ annotations: WRITE,
276
312
  }, async ({ chatId }) => {
277
313
  const err = await requireConnection();
278
314
  if (err)
279
- return { content: [{ type: "text", text: err }] };
315
+ return fail(new Error(err));
280
316
  try {
281
317
  await telegram.markAsRead(chatId);
282
- return { content: [{ type: "text", text: `Marked ${chatId} as read` }] };
318
+ return ok(`Marked ${chatId} as read`);
283
319
  }
284
320
  catch (e) {
285
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
321
+ return fail(e);
286
322
  }
287
323
  });
288
- server.tool("telegram-forward-message", "Forward messages between Telegram chats", {
289
- fromChatId: z.string().describe("Source chat ID or username"),
290
- toChatId: z.string().describe("Destination chat ID or username"),
291
- messageIds: z.array(z.number()).describe("Array of message IDs to forward"),
324
+ server.registerTool("telegram-forward-message", {
325
+ description: "Forward messages between Telegram chats",
326
+ inputSchema: {
327
+ fromChatId: z.string().describe("Source chat ID or username"),
328
+ toChatId: z.string().describe("Destination chat ID or username"),
329
+ messageIds: z.array(z.number()).describe("Array of message IDs to forward"),
330
+ },
331
+ annotations: WRITE,
292
332
  }, async ({ fromChatId, toChatId, messageIds }) => {
293
333
  const err = await requireConnection();
294
334
  if (err)
295
- return { content: [{ type: "text", text: err }] };
335
+ return fail(new Error(err));
296
336
  try {
297
337
  await telegram.forwardMessage(fromChatId, toChatId, messageIds);
298
- return {
299
- content: [
300
- { type: "text", text: `Forwarded ${messageIds.length} message(s) from ${fromChatId} to ${toChatId}` },
301
- ],
302
- };
338
+ return ok(`Forwarded ${messageIds.length} message(s) from ${fromChatId} to ${toChatId}`);
303
339
  }
304
340
  catch (e) {
305
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
341
+ return fail(e);
306
342
  }
307
343
  });
308
- server.tool("telegram-edit-message", "Edit a sent message in Telegram", {
309
- chatId: z.string().describe("Chat ID or username"),
310
- messageId: z.number().describe("ID of the message to edit"),
311
- text: z.string().describe("New message text"),
344
+ server.registerTool("telegram-edit-message", {
345
+ description: "Edit a previously sent message in Telegram",
346
+ inputSchema: {
347
+ chatId: z.string().describe("Chat ID or username"),
348
+ messageId: z.number().describe("ID of the message to edit"),
349
+ text: z.string().describe("New message text"),
350
+ },
351
+ annotations: WRITE,
312
352
  }, async ({ chatId, messageId, text }) => {
313
353
  const err = await requireConnection();
314
354
  if (err)
315
- return { content: [{ type: "text", text: err }] };
355
+ return fail(new Error(err));
316
356
  try {
317
357
  await telegram.editMessage(chatId, messageId, text);
318
- return { content: [{ type: "text", text: `Message ${messageId} edited in ${chatId}` }] };
358
+ return ok(`Message ${messageId} edited in ${chatId}`);
319
359
  }
320
360
  catch (e) {
321
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
361
+ return fail(e);
322
362
  }
323
363
  });
324
- server.tool("telegram-delete-message", "Delete messages in a Telegram chat", {
325
- chatId: z.string().describe("Chat ID or username"),
326
- messageIds: z.array(z.number()).describe("Array of message IDs to delete"),
364
+ server.registerTool("telegram-delete-message", {
365
+ description: "Delete messages in a Telegram chat",
366
+ inputSchema: {
367
+ chatId: z.string().describe("Chat ID or username"),
368
+ messageIds: z.array(z.number()).describe("Array of message IDs to delete"),
369
+ },
370
+ annotations: DESTRUCTIVE,
327
371
  }, async ({ chatId, messageIds }) => {
328
372
  const err = await requireConnection();
329
373
  if (err)
330
- return { content: [{ type: "text", text: err }] };
374
+ return fail(new Error(err));
331
375
  try {
332
376
  await telegram.deleteMessages(chatId, messageIds);
333
- return { content: [{ type: "text", text: `Deleted ${messageIds.length} message(s) in ${chatId}` }] };
377
+ return ok(`Deleted ${messageIds.length} message(s) in ${chatId}`);
334
378
  }
335
379
  catch (e) {
336
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
380
+ return fail(e);
337
381
  }
338
382
  });
339
- server.tool("telegram-get-chat-info", "Get detailed info about a Telegram chat", {
340
- chatId: z.string().describe("Chat ID or username"),
383
+ server.registerTool("telegram-get-chat-info", {
384
+ description: "Get detailed info about a Telegram chat including name, type, members, description, and forum status",
385
+ inputSchema: { chatId: z.string().describe("Chat ID or username") },
386
+ annotations: READ_ONLY,
341
387
  }, async ({ chatId }) => {
342
388
  const err = await requireConnection();
343
389
  if (err)
344
- return { content: [{ type: "text", text: err }] };
390
+ return fail(new Error(err));
345
391
  try {
346
392
  const info = await telegram.getChatInfo(chatId);
347
393
  const lines = [
@@ -353,114 +399,138 @@ server.tool("telegram-get-chat-info", "Get detailed info about a Telegram chat",
353
399
  ...(info.description ? [`Description: ${info.description}`] : []),
354
400
  ...(info.membersCount != null ? [`Members: ${info.membersCount}`] : []),
355
401
  ];
356
- return { content: [{ type: "text", text: lines.join("\n") }] };
402
+ return ok(lines.join("\n"));
357
403
  }
358
404
  catch (e) {
359
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
405
+ return fail(e);
360
406
  }
361
407
  });
362
- server.tool("telegram-send-file", "Send a file to a Telegram chat", {
363
- chatId: z.string().describe("Chat ID or username"),
364
- filePath: z.string().describe("Absolute path to file"),
365
- caption: z.string().optional().describe("File caption"),
408
+ server.registerTool("telegram-send-file", {
409
+ description: "Send a file (photo, document, video, etc.) to a Telegram chat",
410
+ inputSchema: {
411
+ chatId: z.string().describe("Chat ID or username"),
412
+ filePath: z.string().describe("Absolute path to file"),
413
+ caption: z.string().optional().describe("File caption"),
414
+ },
415
+ annotations: WRITE,
366
416
  }, async ({ chatId, filePath, caption }) => {
367
417
  const err = await requireConnection();
368
418
  if (err)
369
- return { content: [{ type: "text", text: err }] };
419
+ return fail(new Error(err));
370
420
  try {
371
421
  await telegram.sendFile(chatId, filePath, caption);
372
- return { content: [{ type: "text", text: `File sent to ${chatId}` }] };
422
+ return ok(`File sent to ${chatId}`);
373
423
  }
374
424
  catch (e) {
375
- return { content: [{ type: "text", text: `Send file error: ${e.message}` }] };
425
+ return fail(e);
376
426
  }
377
427
  });
378
- server.tool("telegram-download-media", "Download media from a Telegram message", {
379
- chatId: z.string().describe("Chat ID or username"),
380
- messageId: z.number().describe("Message ID containing media"),
381
- downloadPath: z.string().describe("Absolute path to save file"),
428
+ server.registerTool("telegram-download-media", {
429
+ description: "Download media from a Telegram message to a local file",
430
+ inputSchema: {
431
+ chatId: z.string().describe("Chat ID or username"),
432
+ messageId: z.number().describe("Message ID containing media"),
433
+ downloadPath: z.string().describe("Absolute path to save file"),
434
+ },
435
+ annotations: READ_ONLY,
382
436
  }, async ({ chatId, messageId, downloadPath }) => {
383
437
  const err = await requireConnection();
384
438
  if (err)
385
- return { content: [{ type: "text", text: err }] };
439
+ return fail(new Error(err));
386
440
  try {
387
441
  const path = await telegram.downloadMedia(chatId, messageId, downloadPath);
388
- return { content: [{ type: "text", text: `Media downloaded to ${path}` }] };
442
+ return ok(`Media downloaded to ${path}`);
389
443
  }
390
444
  catch (e) {
391
- return { content: [{ type: "text", text: `Download error: ${e.message}` }] };
445
+ return fail(e);
392
446
  }
393
447
  });
394
- server.tool("telegram-pin-message", "Pin a message in a Telegram chat", {
395
- chatId: z.string().describe("Chat ID or username"),
396
- messageId: z.number().describe("Message ID to pin"),
397
- silent: z.boolean().default(false).describe("Pin without notification"),
448
+ server.registerTool("telegram-pin-message", {
449
+ description: "Pin a message in a Telegram chat",
450
+ inputSchema: {
451
+ chatId: z.string().describe("Chat ID or username"),
452
+ messageId: z.number().describe("Message ID to pin"),
453
+ silent: z.boolean().default(false).describe("Pin without notification"),
454
+ },
455
+ annotations: WRITE,
398
456
  }, async ({ chatId, messageId, silent }) => {
399
457
  const err = await requireConnection();
400
458
  if (err)
401
- return { content: [{ type: "text", text: err }] };
459
+ return fail(new Error(err));
402
460
  try {
403
461
  await telegram.pinMessage(chatId, messageId, silent);
404
- return { content: [{ type: "text", text: `Message ${messageId} pinned in ${chatId}` }] };
462
+ return ok(`Message ${messageId} pinned in ${chatId}`);
405
463
  }
406
464
  catch (e) {
407
- return { content: [{ type: "text", text: `Pin error: ${e.message}` }] };
465
+ return fail(e);
408
466
  }
409
467
  });
410
- server.tool("telegram-unpin-message", "Unpin a message in a Telegram chat", {
411
- chatId: z.string().describe("Chat ID or username"),
412
- messageId: z.number().describe("Message ID to unpin"),
468
+ server.registerTool("telegram-unpin-message", {
469
+ description: "Unpin a message in a Telegram chat",
470
+ inputSchema: {
471
+ chatId: z.string().describe("Chat ID or username"),
472
+ messageId: z.number().describe("Message ID to unpin"),
473
+ },
474
+ annotations: WRITE,
413
475
  }, async ({ chatId, messageId }) => {
414
476
  const err = await requireConnection();
415
477
  if (err)
416
- return { content: [{ type: "text", text: err }] };
478
+ return fail(new Error(err));
417
479
  try {
418
480
  await telegram.unpinMessage(chatId, messageId);
419
- return { content: [{ type: "text", text: `Message ${messageId} unpinned in ${chatId}` }] };
481
+ return ok(`Message ${messageId} unpinned in ${chatId}`);
420
482
  }
421
483
  catch (e) {
422
- return { content: [{ type: "text", text: `Unpin error: ${e.message}` }] };
484
+ return fail(e);
423
485
  }
424
486
  });
425
- server.tool("telegram-get-contacts", "Get Telegram contacts list", {
426
- limit: z.number().default(50).describe("Number of contacts to return"),
487
+ server.registerTool("telegram-get-contacts", {
488
+ description: "Get your Telegram contacts list with phone numbers",
489
+ inputSchema: { limit: z.number().default(50).describe("Number of contacts to return") },
490
+ annotations: READ_ONLY,
427
491
  }, async ({ limit }) => {
428
492
  const err = await requireConnection();
429
493
  if (err)
430
- return { content: [{ type: "text", text: err }] };
494
+ return fail(new Error(err));
431
495
  try {
432
496
  const contacts = await telegram.getContacts(limit);
433
497
  const text = contacts
434
498
  .map((c) => `P ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.phone ? ` +${c.phone}` : ""}`)
435
499
  .join("\n");
436
- return { content: [{ type: "text", text: sanitize(text) || "No contacts" }] };
500
+ return ok(sanitize(text) || "No contacts");
437
501
  }
438
502
  catch (e) {
439
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
503
+ return fail(e);
440
504
  }
441
505
  });
442
- server.tool("telegram-get-chat-members", "Get members of a Telegram group or channel", {
443
- chatId: z.string().describe("Chat ID or username"),
444
- limit: z.number().default(50).describe("Number of members to return"),
506
+ server.registerTool("telegram-get-chat-members", {
507
+ description: "Get members of a Telegram group or channel",
508
+ inputSchema: {
509
+ chatId: z.string().describe("Chat ID or username"),
510
+ limit: z.number().default(50).describe("Number of members to return"),
511
+ },
512
+ annotations: READ_ONLY,
445
513
  }, async ({ chatId, limit }) => {
446
514
  const err = await requireConnection();
447
515
  if (err)
448
- return { content: [{ type: "text", text: err }] };
516
+ return fail(new Error(err));
449
517
  try {
450
518
  const members = await telegram.getChatMembers(chatId, limit);
451
519
  const text = members.map((m) => `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})`).join("\n");
452
- return { content: [{ type: "text", text: sanitize(text) || "No members found" }] };
520
+ return ok(sanitize(text) || "No members found");
453
521
  }
454
522
  catch (e) {
455
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
523
+ return fail(e);
456
524
  }
457
525
  });
458
- server.tool("telegram-get-profile", "Get detailed profile info of a Telegram user including bio, birthday, business info and more", {
459
- userId: z.string().describe("User ID or username"),
526
+ server.registerTool("telegram-get-profile", {
527
+ description: "Get detailed profile info of a Telegram user including bio, birthday, premium status, business info and more",
528
+ inputSchema: { userId: z.string().describe("User ID or username") },
529
+ annotations: READ_ONLY,
460
530
  }, async ({ userId }) => {
461
531
  const err = await requireConnection();
462
532
  if (err)
463
- return { content: [{ type: "text", text: err }] };
533
+ return fail(new Error(err));
464
534
  try {
465
535
  const profile = await telegram.getProfile(userId);
466
536
  const lines = [
@@ -478,83 +548,87 @@ server.tool("telegram-get-profile", "Get detailed profile info of a Telegram use
478
548
  ...(profile.businessLocation ? [`Business location: ${profile.businessLocation}`] : []),
479
549
  ...(profile.businessWorkHours ? [`Business hours timezone: ${profile.businessWorkHours}`] : []),
480
550
  ];
481
- return { content: [{ type: "text", text: lines.join("\n") }] };
551
+ return ok(lines.join("\n"));
482
552
  }
483
553
  catch (e) {
484
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
485
- }
486
- });
487
- server.tool("telegram-get-profile-photo", "Download profile photo of a Telegram user, group, or channel. Returns inline image or saves to file", {
488
- entityId: z.string().describe("User/Chat/Channel ID or username"),
489
- savePath: z.string().optional().describe("Absolute path to save file. If omitted, returns inline base64 image"),
490
- size: z
491
- .enum(["small", "big"])
492
- .optional()
493
- .describe("Photo size: 'small' (160x160) or 'big' (640x640). Default: big"),
554
+ return fail(e);
555
+ }
556
+ });
557
+ server.registerTool("telegram-get-profile-photo", {
558
+ description: "Download profile photo of a Telegram user, group, or channel. Returns inline image or saves to file",
559
+ inputSchema: {
560
+ entityId: z.string().describe("User/Chat/Channel ID or username"),
561
+ savePath: z.string().optional().describe("Absolute path to save file. If omitted, returns inline base64 image"),
562
+ size: z
563
+ .enum(["small", "big"])
564
+ .optional()
565
+ .describe("Photo size: 'small' (160x160) or 'big' (640x640). Default: big"),
566
+ },
567
+ annotations: READ_ONLY,
494
568
  }, async ({ entityId, savePath, size }) => {
495
569
  const err = await requireConnection();
496
570
  if (err)
497
- return { content: [{ type: "text", text: err }] };
571
+ return fail(new Error(err));
498
572
  try {
499
573
  const result = await telegram.downloadProfilePhoto(entityId, {
500
574
  isBig: size !== "small",
501
575
  savePath,
502
576
  });
503
577
  if (!result) {
504
- return { content: [{ type: "text", text: "No profile photo found" }] };
578
+ return ok("No profile photo found");
505
579
  }
506
580
  if ("filePath" in result) {
507
- return { content: [{ type: "text", text: `Downloaded to: ${result.filePath}` }] };
581
+ return ok(`Downloaded to: ${result.filePath}`);
508
582
  }
509
583
  return {
510
584
  content: [
511
585
  { type: "image", data: result.buffer.toString("base64"), mimeType: result.mimeType },
512
- { type: "text", text: `Profile photo (${(result.buffer.length / 1024).toFixed(0)} KB, ${result.mimeType})` },
586
+ {
587
+ type: "text",
588
+ text: `Profile photo (${(result.buffer.length / 1024).toFixed(0)} KB, ${result.mimeType})`,
589
+ },
513
590
  ],
514
591
  };
515
592
  }
516
593
  catch (e) {
517
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
594
+ return fail(e);
518
595
  }
519
596
  });
520
- server.tool("telegram-join-chat", "Join a Telegram group or channel by username or invite link", {
521
- target: z.string().describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)"),
597
+ server.registerTool("telegram-join-chat", {
598
+ description: "Join a Telegram group or channel by username or invite link",
599
+ inputSchema: { target: z.string().describe("Username (@group), link (t.me/group), or invite link (t.me/+xxx)") },
600
+ annotations: WRITE,
522
601
  }, async ({ target }) => {
523
602
  const err = await requireConnection();
524
603
  if (err)
525
- return { content: [{ type: "text", text: err }] };
604
+ return fail(new Error(err));
526
605
  try {
527
606
  const result = await telegram.joinChat(target);
528
- return {
529
- content: [
530
- {
531
- type: "text",
532
- text: `Joined ${result.type}: ${result.title} (ID: ${result.id})`,
533
- },
534
- ],
535
- };
607
+ return ok(`Joined ${result.type}: ${result.title} (ID: ${result.id})`);
536
608
  }
537
609
  catch (e) {
538
- return {
539
- content: [{ type: "text", text: `Error: ${e.message}` }],
540
- };
541
- }
542
- });
543
- server.tool("telegram-send-reaction", "Send emoji reaction(s) to a message. Supports multiple reactions and adding to existing ones. Omit emoji to remove all reactions", {
544
- chatId: z.string().describe("Chat ID or username"),
545
- messageId: z.number().describe("Message ID to react to"),
546
- emoji: z
547
- .union([z.string(), z.array(z.string())])
548
- .optional()
549
- .describe("Reaction emoji(s): single '👍' or array ['👍','🔥']. Omit to remove all reactions"),
550
- addToExisting: z
551
- .boolean()
552
- .default(false)
553
- .describe("If true, add reaction(s) to existing ones instead of replacing"),
610
+ return fail(e);
611
+ }
612
+ });
613
+ server.registerTool("telegram-send-reaction", {
614
+ description: "Send emoji reaction(s) to a message. Supports multiple reactions and adding to existing ones. Omit emoji to remove all reactions",
615
+ inputSchema: {
616
+ chatId: z.string().describe("Chat ID or username"),
617
+ messageId: z.number().describe("Message ID to react to"),
618
+ emoji: z
619
+ .union([z.string(), z.array(z.string())])
620
+ .optional()
621
+ .describe("Reaction emoji(s): single '👍' or array ['👍','🔥']. Omit to remove all reactions"),
622
+ addToExisting: z
623
+ .boolean()
624
+ .default(false)
625
+ .describe("If true, add reaction(s) to existing ones instead of replacing"),
626
+ },
627
+ annotations: WRITE,
554
628
  }, async ({ chatId, messageId, emoji, addToExisting }) => {
555
629
  const err = await requireConnection();
556
630
  if (err)
557
- return { content: [{ type: "text", text: err }] };
631
+ return fail(new Error(err));
558
632
  try {
559
633
  const updated = await telegram.sendReaction(chatId, messageId, emoji, addToExisting);
560
634
  const emojiStr = Array.isArray(emoji) ? emoji.join("") : emoji;
@@ -562,45 +636,53 @@ server.tool("telegram-send-reaction", "Send emoji reaction(s) to a message. Supp
562
636
  const reactionsInfo = updated
563
637
  ? ` | Reactions: ${updated.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`).join(" ")}`
564
638
  : "";
565
- return { content: [{ type: "text", text: `${action} message ${messageId} in ${chatId}${reactionsInfo}` }] };
639
+ return ok(`${action} message ${messageId} in ${chatId}${reactionsInfo}`);
566
640
  }
567
641
  catch (e) {
568
- return { content: [{ type: "text", text: `Reaction error: ${e.message}` }] };
642
+ return fail(e);
569
643
  }
570
644
  });
571
- server.tool("telegram-get-reactions", "Get detailed reaction info for a message: which reactions, counts, and who reacted (when visible)", {
572
- chatId: z.string().describe("Chat ID or username"),
573
- messageId: z.number().describe("Message ID to get reactions for"),
645
+ server.registerTool("telegram-get-reactions", {
646
+ description: "Get detailed reaction info for a message: which reactions, counts, and who reacted (when visible)",
647
+ inputSchema: {
648
+ chatId: z.string().describe("Chat ID or username"),
649
+ messageId: z.number().describe("Message ID to get reactions for"),
650
+ },
651
+ annotations: READ_ONLY,
574
652
  }, async ({ chatId, messageId }) => {
575
653
  const err = await requireConnection();
576
654
  if (err)
577
- return { content: [{ type: "text", text: err }] };
655
+ return fail(new Error(err));
578
656
  try {
579
657
  const result = await telegram.getMessageReactions(chatId, messageId);
580
658
  if (result.reactions.length === 0) {
581
- return { content: [{ type: "text", text: `No reactions on message ${messageId}` }] };
659
+ return ok(`No reactions on message ${messageId}`);
582
660
  }
583
661
  const lines = result.reactions.map((r) => {
584
662
  const usersStr = r.users.length > 0 ? `: ${r.users.map((u) => u.name).join(", ")}` : "";
585
663
  return `${r.emoji} × ${r.count}${usersStr}`;
586
664
  });
587
665
  lines.push(`\nTotal: ${result.total} reactions`);
588
- return { content: [{ type: "text", text: lines.join("\n") }] };
666
+ return ok(lines.join("\n"));
589
667
  }
590
668
  catch (e) {
591
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
592
- }
593
- });
594
- server.tool("telegram-send-scheduled", "Send a scheduled message to a Telegram chat. The message will be delivered at the specified time by Telegram servers", {
595
- chatId: z.string().describe("Chat ID or username (use 'me' or 'self' for Saved Messages)"),
596
- text: z.string().describe("Message text"),
597
- scheduleDate: z.number().describe("Unix timestamp when to send the message (must be in the future)"),
598
- replyTo: z.number().optional().describe("Message ID to reply to"),
599
- parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
669
+ return fail(e);
670
+ }
671
+ });
672
+ server.registerTool("telegram-send-scheduled", {
673
+ description: "Send a scheduled message to a Telegram chat. The message will be delivered at the specified time by Telegram servers",
674
+ inputSchema: {
675
+ chatId: z.string().describe("Chat ID or username (use 'me' or 'self' for Saved Messages)"),
676
+ text: z.string().describe("Message text"),
677
+ scheduleDate: z.number().describe("Unix timestamp when to send the message (must be in the future)"),
678
+ replyTo: z.number().optional().describe("Message ID to reply to"),
679
+ parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
680
+ },
681
+ annotations: WRITE,
600
682
  }, async ({ chatId, text, scheduleDate, replyTo, parseMode }) => {
601
683
  const err = await requireConnection();
602
684
  if (err)
603
- return { content: [{ type: "text", text: err }] };
685
+ return fail(new Error(err));
604
686
  // Resolve 'me'/'self' to Saved Messages
605
687
  let target = chatId;
606
688
  if (target === "me" || target === "self") {
@@ -609,47 +691,53 @@ server.tool("telegram-send-scheduled", "Send a scheduled message to a Telegram c
609
691
  target = me.id;
610
692
  }
611
693
  catch {
612
- return { content: [{ type: "text", text: "Failed to resolve Saved Messages" }] };
694
+ return fail(new Error("Failed to resolve Saved Messages"));
613
695
  }
614
696
  }
615
697
  try {
616
698
  await telegram.sendScheduledMessage(target, text, scheduleDate, replyTo, parseMode);
617
699
  const date = new Date(scheduleDate * 1000).toISOString();
618
- return { content: [{ type: "text", text: `Message scheduled for ${date} in ${chatId}` }] };
700
+ return ok(`Message scheduled for ${date} in ${chatId}`);
619
701
  }
620
702
  catch (e) {
621
- return { content: [{ type: "text", text: `Schedule error: ${e.message}` }] };
622
- }
623
- });
624
- server.tool("telegram-create-poll", "Create a poll in a Telegram chat", {
625
- chatId: z.string().describe("Chat ID or username"),
626
- question: z.string().describe("Poll question"),
627
- answers: z.array(z.string()).min(2).max(10).describe("Answer options (2-10)"),
628
- multipleChoice: z.boolean().default(false).describe("Allow multiple answers"),
629
- quiz: z.boolean().default(false).describe("Quiz mode (one correct answer)"),
630
- correctAnswer: z.number().optional().describe("Index of correct answer (0-based, required for quiz mode)"),
703
+ return fail(e);
704
+ }
705
+ });
706
+ server.registerTool("telegram-create-poll", {
707
+ description: "Create a poll in a Telegram chat (multiple choice or quiz mode)",
708
+ inputSchema: {
709
+ chatId: z.string().describe("Chat ID or username"),
710
+ question: z.string().describe("Poll question"),
711
+ answers: z.array(z.string()).min(2).max(10).describe("Answer options (2-10)"),
712
+ multipleChoice: z.boolean().default(false).describe("Allow multiple answers"),
713
+ quiz: z.boolean().default(false).describe("Quiz mode (one correct answer)"),
714
+ correctAnswer: z.number().optional().describe("Index of correct answer (0-based, required for quiz mode)"),
715
+ },
716
+ annotations: WRITE,
631
717
  }, async ({ chatId, question, answers, multipleChoice, quiz, correctAnswer }) => {
632
718
  const err = await requireConnection();
633
719
  if (err)
634
- return { content: [{ type: "text", text: err }] };
720
+ return fail(new Error(err));
635
721
  try {
636
722
  const msgId = await telegram.createPoll(chatId, question, answers, { multipleChoice, quiz, correctAnswer });
637
- return { content: [{ type: "text", text: `Poll created in ${chatId}${msgId ? ` (message #${msgId})` : ""}` }] };
723
+ return ok(`Poll created in ${chatId}${msgId ? ` (message #${msgId})` : ""}`);
638
724
  }
639
725
  catch (e) {
640
- return { content: [{ type: "text", text: `Poll error: ${e.message}` }] };
726
+ return fail(e);
641
727
  }
642
728
  });
643
- server.tool("telegram-get-contact-requests", "Get incoming messages from non-contacts (contact requests). Shows who messaged you without being in your contacts, with message preview", {
644
- limit: z.number().default(20).describe("Number of contact requests to return"),
729
+ server.registerTool("telegram-get-contact-requests", {
730
+ description: "Get incoming messages from non-contacts (contact requests). Shows who messaged you without being in your contacts, with message preview",
731
+ inputSchema: { limit: z.number().default(20).describe("Number of contact requests to return") },
732
+ annotations: READ_ONLY,
645
733
  }, async ({ limit }) => {
646
734
  const err = await requireConnection();
647
735
  if (err)
648
- return { content: [{ type: "text", text: err }] };
736
+ return fail(new Error(err));
649
737
  try {
650
738
  const requests = await telegram.getContactRequests(limit);
651
739
  if (requests.length === 0) {
652
- return { content: [{ type: "text", text: "No contact requests" }] };
740
+ return ok("No contact requests");
653
741
  }
654
742
  const text = requests
655
743
  .map((r) => {
@@ -660,66 +748,76 @@ server.tool("telegram-get-contact-requests", "Get incoming messages from non-con
660
748
  return `${tag} ${r.name}${username} (${r.id})${unread}${preview}`;
661
749
  })
662
750
  .join("\n");
663
- return { content: [{ type: "text", text: sanitize(text) }] };
751
+ return ok(sanitize(text));
664
752
  }
665
753
  catch (e) {
666
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
667
- }
668
- });
669
- server.tool("telegram-add-contact", "Add a user to your Telegram contacts. Use this to accept contact requests from non-contacts", {
670
- userId: z.string().describe("User ID or username to add"),
671
- firstName: z.string().describe("First name for the contact"),
672
- lastName: z.string().optional().describe("Last name for the contact"),
673
- phone: z.string().optional().describe("Phone number for the contact"),
754
+ return fail(e);
755
+ }
756
+ });
757
+ server.registerTool("telegram-add-contact", {
758
+ description: "Add a user to your Telegram contacts. Use this to accept contact requests from non-contacts",
759
+ inputSchema: {
760
+ userId: z.string().describe("User ID or username to add"),
761
+ firstName: z.string().describe("First name for the contact"),
762
+ lastName: z.string().optional().describe("Last name for the contact"),
763
+ phone: z.string().optional().describe("Phone number for the contact"),
764
+ },
765
+ annotations: WRITE,
674
766
  }, async ({ userId, firstName, lastName, phone }) => {
675
767
  const err = await requireConnection();
676
768
  if (err)
677
- return { content: [{ type: "text", text: err }] };
769
+ return fail(new Error(err));
678
770
  try {
679
771
  await telegram.addContact(userId, firstName, lastName, phone);
680
- return {
681
- content: [{ type: "text", text: `Contact added: ${firstName}${lastName ? ` ${lastName}` : ""} (${userId})` }],
682
- };
772
+ return ok(`Contact added: ${firstName}${lastName ? ` ${lastName}` : ""} (${userId})`);
683
773
  }
684
774
  catch (e) {
685
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
775
+ return fail(e);
686
776
  }
687
777
  });
688
- server.tool("telegram-block-user", "Block a Telegram user. Blocked users cannot send you messages", {
689
- userId: z.string().describe("User ID or username to block"),
778
+ server.registerTool("telegram-block-user", {
779
+ description: "Block a Telegram user. Blocked users cannot send you messages",
780
+ inputSchema: { userId: z.string().describe("User ID or username to block") },
781
+ annotations: WRITE,
690
782
  }, async ({ userId }) => {
691
783
  const err = await requireConnection();
692
784
  if (err)
693
- return { content: [{ type: "text", text: err }] };
785
+ return fail(new Error(err));
694
786
  try {
695
787
  await telegram.blockUser(userId);
696
- return { content: [{ type: "text", text: `User blocked: ${userId}` }] };
788
+ return ok(`User blocked: ${userId}`);
697
789
  }
698
790
  catch (e) {
699
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
791
+ return fail(e);
700
792
  }
701
793
  });
702
- server.tool("telegram-report-spam", "Report a chat as spam to Telegram", {
703
- chatId: z.string().describe("Chat ID or username to report"),
794
+ server.registerTool("telegram-report-spam", {
795
+ description: "Report a chat as spam to Telegram",
796
+ inputSchema: { chatId: z.string().describe("Chat ID or username to report") },
797
+ annotations: WRITE,
704
798
  }, async ({ chatId }) => {
705
799
  const err = await requireConnection();
706
800
  if (err)
707
- return { content: [{ type: "text", text: err }] };
801
+ return fail(new Error(err));
708
802
  try {
709
803
  await telegram.reportSpam(chatId);
710
- return { content: [{ type: "text", text: `Reported as spam: ${chatId}` }] };
804
+ return ok(`Reported as spam: ${chatId}`);
711
805
  }
712
806
  catch (e) {
713
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
807
+ return fail(e);
714
808
  }
715
809
  });
716
- server.tool("telegram-list-topics", "List forum topics in a Telegram group with Topics enabled. Shows topic names, unread counts, and status", {
717
- chatId: z.string().describe("Chat ID or username of a group with Topics enabled"),
718
- limit: z.number().default(100).describe("Max topics to return"),
810
+ server.registerTool("telegram-list-topics", {
811
+ description: "List forum topics in a Telegram group with Topics enabled. Shows topic names, unread counts, and status",
812
+ inputSchema: {
813
+ chatId: z.string().describe("Chat ID or username of a group with Topics enabled"),
814
+ limit: z.number().default(100).describe("Max topics to return"),
815
+ },
816
+ annotations: READ_ONLY,
719
817
  }, async ({ chatId, limit }) => {
720
818
  const err = await requireConnection();
721
819
  if (err)
722
- return { content: [{ type: "text", text: err }] };
820
+ return fail(new Error(err));
723
821
  try {
724
822
  const topics = await telegram.getForumTopics(chatId, limit);
725
823
  const text = topics
@@ -730,30 +828,34 @@ server.tool("telegram-list-topics", "List forum topics in a Telegram group with
730
828
  return `# ${t.title} (id: ${t.id})${flagStr}${unread}`;
731
829
  })
732
830
  .join("\n");
733
- return { content: [{ type: "text", text: sanitize(text) || "No topics found" }] };
831
+ return ok(sanitize(text) || "No topics found");
734
832
  }
735
833
  catch (e) {
736
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
737
- }
738
- });
739
- server.tool("telegram-read-topic-messages", "Read messages from a specific forum topic in a Telegram group", {
740
- chatId: z.string().describe("Chat ID or username"),
741
- topicId: z.number().describe("Topic ID (get from telegram-list-topics)"),
742
- limit: z.number().default(20).describe("Number of messages to return"),
743
- offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
834
+ return fail(e);
835
+ }
836
+ });
837
+ server.registerTool("telegram-read-topic-messages", {
838
+ description: "Read messages from a specific forum topic in a Telegram group",
839
+ inputSchema: {
840
+ chatId: z.string().describe("Chat ID or username"),
841
+ topicId: z.number().describe("Topic ID (get from telegram-list-topics)"),
842
+ limit: z.number().default(20).describe("Number of messages to return"),
843
+ offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
844
+ },
845
+ annotations: READ_ONLY,
744
846
  }, async ({ chatId, topicId, limit, offsetId }) => {
745
847
  const err = await requireConnection();
746
848
  if (err)
747
- return { content: [{ type: "text", text: err }] };
849
+ return fail(new Error(err));
748
850
  try {
749
851
  const messages = await telegram.getTopicMessages(chatId, topicId, limit, offsetId);
750
852
  const text = messages
751
853
  .map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
752
854
  .join("\n\n");
753
- return { content: [{ type: "text", text: sanitize(text) || "No messages in this topic" }] };
855
+ return ok(sanitize(text) || "No messages in this topic");
754
856
  }
755
857
  catch (e) {
756
- return { content: [{ type: "text", text: `Error: ${e.message}` }] };
858
+ return fail(e);
757
859
  }
758
860
  });
759
861
  // --- Start ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@overpod/mcp-telegram",
3
- "version": "1.11.1",
3
+ "version": "1.12.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",
@@ -49,7 +49,7 @@
49
49
  "url": "https://github.com/overpod/mcp-telegram/issues"
50
50
  },
51
51
  "dependencies": {
52
- "@modelcontextprotocol/sdk": "^1.27.1",
52
+ "@modelcontextprotocol/sdk": "^1.28.0",
53
53
  "dotenv": "^17.3.1",
54
54
  "qrcode": "^1.5.4",
55
55
  "telegram": "^2.26.22",