@overpod/mcp-telegram 1.11.0 → 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.
- package/README.md +30 -2
- package/dist/index.js +381 -275
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
[](https://www.npmjs.com/package/@overpod/mcp-telegram)
|
|
4
4
|
[](https://www.npmjs.com/package/@overpod/mcp-telegram)
|
|
5
5
|
[](https://nodejs.org/)
|
|
6
|
-
[](https://www.typescriptlang.org/)
|
|
7
7
|
[](https://modelcontextprotocol.io/)
|
|
8
8
|
[](LICENSE)
|
|
9
9
|
[](https://glama.ai/mcp/servers/overpod/mcp-telegram)
|
|
@@ -208,6 +208,7 @@ const telegramMcp = new MCPClient({
|
|
|
208
208
|
| `telegram-read-messages` | Read recent messages from a chat |
|
|
209
209
|
| `telegram-search-chats` | Search for chats, users, or channels by name |
|
|
210
210
|
| `telegram-search-messages` | Search messages in a chat by text |
|
|
211
|
+
| `telegram-search-global` | Search messages across all public chats and channels |
|
|
211
212
|
| `telegram-get-unread` | Get chats with unread messages; forums show per-topic unread breakdown |
|
|
212
213
|
| `telegram-get-contact-requests` | Get incoming messages from non-contacts with preview |
|
|
213
214
|
|
|
@@ -243,6 +244,8 @@ const telegramMcp = new MCPClient({
|
|
|
243
244
|
|------|-------------|
|
|
244
245
|
| `telegram-get-contacts` | Get your contacts list with phone numbers |
|
|
245
246
|
| `telegram-get-profile` | Get detailed profile info for a user (bio, photo, last seen) |
|
|
247
|
+
| `telegram-get-profile-photo` | Get a user's or chat's profile photo (inline or file) |
|
|
248
|
+
| `telegram-get-reactions` | Get reactions on a specific message with user details |
|
|
246
249
|
|
|
247
250
|
### Media
|
|
248
251
|
|
|
@@ -357,7 +360,8 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
357
360
|
|-----------|------|----------|-------------|
|
|
358
361
|
| `chatId` | string | yes | Chat ID or @username |
|
|
359
362
|
| `messageId` | number | yes | Message ID to react to |
|
|
360
|
-
| `emoji` | string | no |
|
|
363
|
+
| `emoji` | string \| string[] | no | Single emoji `"👍"` or array `["👍","🔥"]`. Omit to remove all reactions |
|
|
364
|
+
| `addToExisting` | boolean | no | Add to existing reactions instead of replacing (default: false) |
|
|
361
365
|
|
|
362
366
|
### telegram-send-scheduled
|
|
363
367
|
|
|
@@ -388,6 +392,15 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
388
392
|
| `query` | string | yes | Search text |
|
|
389
393
|
| `limit` | number | no | Max results (default: 20) |
|
|
390
394
|
|
|
395
|
+
### telegram-search-global
|
|
396
|
+
|
|
397
|
+
| Parameter | Type | Required | Description |
|
|
398
|
+
|-----------|------|----------|-------------|
|
|
399
|
+
| `query` | string | yes | Search text |
|
|
400
|
+
| `limit` | number | no | Max results (default: 20) |
|
|
401
|
+
| `minDate` | number | no | Unix timestamp: only messages after this date |
|
|
402
|
+
| `maxDate` | number | no | Unix timestamp: only messages before this date |
|
|
403
|
+
|
|
391
404
|
### telegram-search-chats
|
|
392
405
|
|
|
393
406
|
| Parameter | Type | Required | Description |
|
|
@@ -414,6 +427,21 @@ Most tools accept `chatId` as a string -- either a numeric ID (e.g., `"-10012345
|
|
|
414
427
|
|-----------|------|----------|-------------|
|
|
415
428
|
| `userId` | string | yes | User ID or @username |
|
|
416
429
|
|
|
430
|
+
### telegram-get-profile-photo
|
|
431
|
+
|
|
432
|
+
| Parameter | Type | Required | Description |
|
|
433
|
+
|-----------|------|----------|-------------|
|
|
434
|
+
| `entityId` | string | yes | User/Chat/Channel ID or username |
|
|
435
|
+
| `savePath` | string | no | Absolute path to save file. If omitted, returns inline base64 image |
|
|
436
|
+
| `size` | `"small"` / `"big"` | no | Photo size: small (160x160) or big (640x640). Default: big |
|
|
437
|
+
|
|
438
|
+
### telegram-get-reactions
|
|
439
|
+
|
|
440
|
+
| Parameter | Type | Required | Description |
|
|
441
|
+
|-----------|------|----------|-------------|
|
|
442
|
+
| `chatId` | string | yes | Chat ID or @username |
|
|
443
|
+
| `messageId` | number | yes | Message ID to get reactions for |
|
|
444
|
+
|
|
417
445
|
### telegram-get-unread
|
|
418
446
|
|
|
419
447
|
| Parameter | Type | Required | Description |
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,10 @@ if (!API_ID || !API_HASH) {
|
|
|
18
18
|
process.exit(1);
|
|
19
19
|
}
|
|
20
20
|
const telegram = new TelegramService(API_ID, API_HASH);
|
|
21
|
+
/** Remove unpaired UTF-16 surrogates that break JSON serialization */
|
|
22
|
+
function sanitize(text) {
|
|
23
|
+
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
24
|
+
}
|
|
21
25
|
/** Format reactions array into compact text like: [👍×5 ❤️×3(me) 🔥×1] */
|
|
22
26
|
function formatReactions(reactions) {
|
|
23
27
|
if (!reactions?.length)
|
|
@@ -36,30 +40,36 @@ async function requireConnection() {
|
|
|
36
40
|
const reason = telegram.lastError ? ` ${telegram.lastError}` : "";
|
|
37
41
|
return `Not connected to Telegram.${reason} Run telegram-login first.`;
|
|
38
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
|
+
}
|
|
39
55
|
// --- Tools ---
|
|
40
|
-
server.
|
|
56
|
+
server.registerTool("telegram-status", { description: "Check Telegram connection status", annotations: READ_ONLY }, async () => {
|
|
41
57
|
if (await telegram.ensureConnected()) {
|
|
42
58
|
try {
|
|
43
59
|
const me = await telegram.getMe();
|
|
44
|
-
return {
|
|
45
|
-
content: [
|
|
46
|
-
{
|
|
47
|
-
type: "text",
|
|
48
|
-
text: `Connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})`,
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
};
|
|
60
|
+
return ok(`Connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})`);
|
|
52
61
|
}
|
|
53
62
|
catch {
|
|
54
|
-
return
|
|
63
|
+
return ok("Connected, but failed to get user info");
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
const reason = telegram.lastError ? ` Reason: ${telegram.lastError}` : "";
|
|
58
|
-
return {
|
|
59
|
-
content: [{ type: "text", text: `Not connected.${reason} Use telegram-login to authenticate via QR code.` }],
|
|
60
|
-
};
|
|
67
|
+
return ok(`Not connected.${reason} Use telegram-login to authenticate via QR code.`);
|
|
61
68
|
});
|
|
62
|
-
server.
|
|
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 () => {
|
|
63
73
|
let qrDataUrl = "";
|
|
64
74
|
let qrRawUrl = "";
|
|
65
75
|
const loginPromise = telegram.startQrLogin((dataUrl) => {
|
|
@@ -73,7 +83,7 @@ server.tool("telegram-login", "Login to Telegram via QR code. Returns QR image.
|
|
|
73
83
|
await new Promise((r) => setTimeout(r, 500));
|
|
74
84
|
}
|
|
75
85
|
if (!qrDataUrl) {
|
|
76
|
-
return
|
|
86
|
+
return fail(new Error("Failed to generate QR code"));
|
|
77
87
|
}
|
|
78
88
|
// Login continues in background
|
|
79
89
|
loginPromise.then((result) => {
|
|
@@ -112,36 +122,44 @@ server.tool("telegram-login", "Login to Telegram via QR code. Returns QR image.
|
|
|
112
122
|
],
|
|
113
123
|
};
|
|
114
124
|
});
|
|
115
|
-
server.
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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,
|
|
121
135
|
}, async ({ chatId, text, replyTo, parseMode, topicId }) => {
|
|
122
136
|
const err = await requireConnection();
|
|
123
137
|
if (err)
|
|
124
|
-
return
|
|
138
|
+
return fail(new Error(err));
|
|
125
139
|
try {
|
|
126
140
|
await telegram.sendMessage(chatId, text, replyTo, parseMode, topicId);
|
|
127
141
|
const dest = topicId ? `topic ${topicId} in ${chatId}` : chatId;
|
|
128
|
-
return
|
|
142
|
+
return ok(`Message sent to ${dest}`);
|
|
129
143
|
}
|
|
130
144
|
catch (e) {
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
});
|
|
134
|
-
server.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
.
|
|
139
|
-
|
|
140
|
-
|
|
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,
|
|
141
159
|
}, async ({ limit, offsetDate, filterType }) => {
|
|
142
160
|
const err = await requireConnection();
|
|
143
161
|
if (err)
|
|
144
|
-
return
|
|
162
|
+
return fail(new Error(err));
|
|
145
163
|
try {
|
|
146
164
|
const dialogs = await telegram.getDialogs(limit, offsetDate, filterType);
|
|
147
165
|
const text = dialogs
|
|
@@ -153,98 +171,118 @@ server.tool("telegram-list-chats", "List Telegram chats", {
|
|
|
153
171
|
return `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${unread}`;
|
|
154
172
|
})
|
|
155
173
|
.join("\n");
|
|
156
|
-
return
|
|
174
|
+
return ok(sanitize(text) || "No chats");
|
|
157
175
|
}
|
|
158
176
|
catch (e) {
|
|
159
|
-
return
|
|
160
|
-
}
|
|
161
|
-
});
|
|
162
|
-
server.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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,
|
|
168
190
|
}, async ({ chatId, limit, offsetId, minDate, maxDate }) => {
|
|
169
191
|
const err = await requireConnection();
|
|
170
192
|
if (err)
|
|
171
|
-
return
|
|
193
|
+
return fail(new Error(err));
|
|
172
194
|
try {
|
|
173
195
|
const messages = await telegram.getMessages(chatId, limit, offsetId, minDate, maxDate);
|
|
174
196
|
const text = messages
|
|
175
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)}`)
|
|
176
198
|
.join("\n\n");
|
|
177
|
-
return
|
|
199
|
+
return ok(sanitize(text) || "No messages");
|
|
178
200
|
}
|
|
179
201
|
catch (e) {
|
|
180
|
-
return
|
|
202
|
+
return fail(e);
|
|
181
203
|
}
|
|
182
204
|
});
|
|
183
|
-
server.
|
|
184
|
-
|
|
185
|
-
|
|
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,
|
|
186
212
|
}, async ({ query, limit }) => {
|
|
187
213
|
const err = await requireConnection();
|
|
188
214
|
if (err)
|
|
189
|
-
return
|
|
215
|
+
return fail(new Error(err));
|
|
190
216
|
try {
|
|
191
217
|
const results = await telegram.searchChats(query, limit);
|
|
192
218
|
const text = results
|
|
193
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)}` : ""}`)
|
|
194
220
|
.join("\n");
|
|
195
|
-
return
|
|
221
|
+
return ok(sanitize(text) || "No results");
|
|
196
222
|
}
|
|
197
223
|
catch (e) {
|
|
198
|
-
return
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
server.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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,
|
|
206
236
|
}, async ({ query, limit, minDate, maxDate }) => {
|
|
207
237
|
const err = await requireConnection();
|
|
208
238
|
if (err)
|
|
209
|
-
return
|
|
239
|
+
return fail(new Error(err));
|
|
210
240
|
try {
|
|
211
241
|
const messages = await telegram.searchGlobal(query, limit, minDate, maxDate);
|
|
212
242
|
const text = messages
|
|
213
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)}`)
|
|
214
244
|
.join("\n\n");
|
|
215
|
-
return
|
|
245
|
+
return ok(sanitize(text) || "No messages found");
|
|
216
246
|
}
|
|
217
247
|
catch (e) {
|
|
218
|
-
return
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
server.
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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,
|
|
227
261
|
}, async ({ chatId, query, limit, minDate, maxDate }) => {
|
|
228
262
|
const err = await requireConnection();
|
|
229
263
|
if (err)
|
|
230
|
-
return
|
|
264
|
+
return fail(new Error(err));
|
|
231
265
|
try {
|
|
232
266
|
const messages = await telegram.searchMessages(chatId, query, limit, minDate, maxDate);
|
|
233
267
|
const text = messages
|
|
234
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)}`)
|
|
235
269
|
.join("\n\n");
|
|
236
|
-
return
|
|
270
|
+
return ok(sanitize(text) || "No messages found");
|
|
237
271
|
}
|
|
238
272
|
catch (e) {
|
|
239
|
-
return
|
|
273
|
+
return fail(e);
|
|
240
274
|
}
|
|
241
275
|
});
|
|
242
|
-
server.
|
|
243
|
-
|
|
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,
|
|
244
282
|
}, async ({ limit }) => {
|
|
245
283
|
const err = await requireConnection();
|
|
246
284
|
if (err)
|
|
247
|
-
return
|
|
285
|
+
return fail(new Error(err));
|
|
248
286
|
try {
|
|
249
287
|
const dialogs = await telegram.getUnreadDialogs(limit);
|
|
250
288
|
const text = dialogs
|
|
@@ -261,83 +299,95 @@ server.tool("telegram-get-unread", "Get unread Telegram chats", {
|
|
|
261
299
|
return line;
|
|
262
300
|
})
|
|
263
301
|
.join("\n");
|
|
264
|
-
return
|
|
302
|
+
return ok(sanitize(text) || "No unread chats");
|
|
265
303
|
}
|
|
266
304
|
catch (e) {
|
|
267
|
-
return
|
|
305
|
+
return fail(e);
|
|
268
306
|
}
|
|
269
307
|
});
|
|
270
|
-
server.
|
|
271
|
-
|
|
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,
|
|
272
312
|
}, async ({ chatId }) => {
|
|
273
313
|
const err = await requireConnection();
|
|
274
314
|
if (err)
|
|
275
|
-
return
|
|
315
|
+
return fail(new Error(err));
|
|
276
316
|
try {
|
|
277
317
|
await telegram.markAsRead(chatId);
|
|
278
|
-
return
|
|
318
|
+
return ok(`Marked ${chatId} as read`);
|
|
279
319
|
}
|
|
280
320
|
catch (e) {
|
|
281
|
-
return
|
|
321
|
+
return fail(e);
|
|
282
322
|
}
|
|
283
323
|
});
|
|
284
|
-
server.
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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,
|
|
288
332
|
}, async ({ fromChatId, toChatId, messageIds }) => {
|
|
289
333
|
const err = await requireConnection();
|
|
290
334
|
if (err)
|
|
291
|
-
return
|
|
335
|
+
return fail(new Error(err));
|
|
292
336
|
try {
|
|
293
337
|
await telegram.forwardMessage(fromChatId, toChatId, messageIds);
|
|
294
|
-
return {
|
|
295
|
-
content: [
|
|
296
|
-
{ type: "text", text: `Forwarded ${messageIds.length} message(s) from ${fromChatId} to ${toChatId}` },
|
|
297
|
-
],
|
|
298
|
-
};
|
|
338
|
+
return ok(`Forwarded ${messageIds.length} message(s) from ${fromChatId} to ${toChatId}`);
|
|
299
339
|
}
|
|
300
340
|
catch (e) {
|
|
301
|
-
return
|
|
341
|
+
return fail(e);
|
|
302
342
|
}
|
|
303
343
|
});
|
|
304
|
-
server.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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,
|
|
308
352
|
}, async ({ chatId, messageId, text }) => {
|
|
309
353
|
const err = await requireConnection();
|
|
310
354
|
if (err)
|
|
311
|
-
return
|
|
355
|
+
return fail(new Error(err));
|
|
312
356
|
try {
|
|
313
357
|
await telegram.editMessage(chatId, messageId, text);
|
|
314
|
-
return
|
|
358
|
+
return ok(`Message ${messageId} edited in ${chatId}`);
|
|
315
359
|
}
|
|
316
360
|
catch (e) {
|
|
317
|
-
return
|
|
361
|
+
return fail(e);
|
|
318
362
|
}
|
|
319
363
|
});
|
|
320
|
-
server.
|
|
321
|
-
|
|
322
|
-
|
|
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,
|
|
323
371
|
}, async ({ chatId, messageIds }) => {
|
|
324
372
|
const err = await requireConnection();
|
|
325
373
|
if (err)
|
|
326
|
-
return
|
|
374
|
+
return fail(new Error(err));
|
|
327
375
|
try {
|
|
328
376
|
await telegram.deleteMessages(chatId, messageIds);
|
|
329
|
-
return
|
|
377
|
+
return ok(`Deleted ${messageIds.length} message(s) in ${chatId}`);
|
|
330
378
|
}
|
|
331
379
|
catch (e) {
|
|
332
|
-
return
|
|
380
|
+
return fail(e);
|
|
333
381
|
}
|
|
334
382
|
});
|
|
335
|
-
server.
|
|
336
|
-
|
|
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,
|
|
337
387
|
}, async ({ chatId }) => {
|
|
338
388
|
const err = await requireConnection();
|
|
339
389
|
if (err)
|
|
340
|
-
return
|
|
390
|
+
return fail(new Error(err));
|
|
341
391
|
try {
|
|
342
392
|
const info = await telegram.getChatInfo(chatId);
|
|
343
393
|
const lines = [
|
|
@@ -349,114 +399,138 @@ server.tool("telegram-get-chat-info", "Get detailed info about a Telegram chat",
|
|
|
349
399
|
...(info.description ? [`Description: ${info.description}`] : []),
|
|
350
400
|
...(info.membersCount != null ? [`Members: ${info.membersCount}`] : []),
|
|
351
401
|
];
|
|
352
|
-
return
|
|
402
|
+
return ok(lines.join("\n"));
|
|
353
403
|
}
|
|
354
404
|
catch (e) {
|
|
355
|
-
return
|
|
405
|
+
return fail(e);
|
|
356
406
|
}
|
|
357
407
|
});
|
|
358
|
-
server.
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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,
|
|
362
416
|
}, async ({ chatId, filePath, caption }) => {
|
|
363
417
|
const err = await requireConnection();
|
|
364
418
|
if (err)
|
|
365
|
-
return
|
|
419
|
+
return fail(new Error(err));
|
|
366
420
|
try {
|
|
367
421
|
await telegram.sendFile(chatId, filePath, caption);
|
|
368
|
-
return
|
|
422
|
+
return ok(`File sent to ${chatId}`);
|
|
369
423
|
}
|
|
370
424
|
catch (e) {
|
|
371
|
-
return
|
|
425
|
+
return fail(e);
|
|
372
426
|
}
|
|
373
427
|
});
|
|
374
|
-
server.
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
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,
|
|
378
436
|
}, async ({ chatId, messageId, downloadPath }) => {
|
|
379
437
|
const err = await requireConnection();
|
|
380
438
|
if (err)
|
|
381
|
-
return
|
|
439
|
+
return fail(new Error(err));
|
|
382
440
|
try {
|
|
383
441
|
const path = await telegram.downloadMedia(chatId, messageId, downloadPath);
|
|
384
|
-
return
|
|
442
|
+
return ok(`Media downloaded to ${path}`);
|
|
385
443
|
}
|
|
386
444
|
catch (e) {
|
|
387
|
-
return
|
|
445
|
+
return fail(e);
|
|
388
446
|
}
|
|
389
447
|
});
|
|
390
|
-
server.
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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,
|
|
394
456
|
}, async ({ chatId, messageId, silent }) => {
|
|
395
457
|
const err = await requireConnection();
|
|
396
458
|
if (err)
|
|
397
|
-
return
|
|
459
|
+
return fail(new Error(err));
|
|
398
460
|
try {
|
|
399
461
|
await telegram.pinMessage(chatId, messageId, silent);
|
|
400
|
-
return
|
|
462
|
+
return ok(`Message ${messageId} pinned in ${chatId}`);
|
|
401
463
|
}
|
|
402
464
|
catch (e) {
|
|
403
|
-
return
|
|
465
|
+
return fail(e);
|
|
404
466
|
}
|
|
405
467
|
});
|
|
406
|
-
server.
|
|
407
|
-
|
|
408
|
-
|
|
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,
|
|
409
475
|
}, async ({ chatId, messageId }) => {
|
|
410
476
|
const err = await requireConnection();
|
|
411
477
|
if (err)
|
|
412
|
-
return
|
|
478
|
+
return fail(new Error(err));
|
|
413
479
|
try {
|
|
414
480
|
await telegram.unpinMessage(chatId, messageId);
|
|
415
|
-
return
|
|
481
|
+
return ok(`Message ${messageId} unpinned in ${chatId}`);
|
|
416
482
|
}
|
|
417
483
|
catch (e) {
|
|
418
|
-
return
|
|
484
|
+
return fail(e);
|
|
419
485
|
}
|
|
420
486
|
});
|
|
421
|
-
server.
|
|
422
|
-
|
|
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,
|
|
423
491
|
}, async ({ limit }) => {
|
|
424
492
|
const err = await requireConnection();
|
|
425
493
|
if (err)
|
|
426
|
-
return
|
|
494
|
+
return fail(new Error(err));
|
|
427
495
|
try {
|
|
428
496
|
const contacts = await telegram.getContacts(limit);
|
|
429
497
|
const text = contacts
|
|
430
498
|
.map((c) => `P ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.phone ? ` +${c.phone}` : ""}`)
|
|
431
499
|
.join("\n");
|
|
432
|
-
return
|
|
500
|
+
return ok(sanitize(text) || "No contacts");
|
|
433
501
|
}
|
|
434
502
|
catch (e) {
|
|
435
|
-
return
|
|
503
|
+
return fail(e);
|
|
436
504
|
}
|
|
437
505
|
});
|
|
438
|
-
server.
|
|
439
|
-
|
|
440
|
-
|
|
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,
|
|
441
513
|
}, async ({ chatId, limit }) => {
|
|
442
514
|
const err = await requireConnection();
|
|
443
515
|
if (err)
|
|
444
|
-
return
|
|
516
|
+
return fail(new Error(err));
|
|
445
517
|
try {
|
|
446
518
|
const members = await telegram.getChatMembers(chatId, limit);
|
|
447
519
|
const text = members.map((m) => `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})`).join("\n");
|
|
448
|
-
return
|
|
520
|
+
return ok(sanitize(text) || "No members found");
|
|
449
521
|
}
|
|
450
522
|
catch (e) {
|
|
451
|
-
return
|
|
523
|
+
return fail(e);
|
|
452
524
|
}
|
|
453
525
|
});
|
|
454
|
-
server.
|
|
455
|
-
|
|
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,
|
|
456
530
|
}, async ({ userId }) => {
|
|
457
531
|
const err = await requireConnection();
|
|
458
532
|
if (err)
|
|
459
|
-
return
|
|
533
|
+
return fail(new Error(err));
|
|
460
534
|
try {
|
|
461
535
|
const profile = await telegram.getProfile(userId);
|
|
462
536
|
const lines = [
|
|
@@ -474,83 +548,87 @@ server.tool("telegram-get-profile", "Get detailed profile info of a Telegram use
|
|
|
474
548
|
...(profile.businessLocation ? [`Business location: ${profile.businessLocation}`] : []),
|
|
475
549
|
...(profile.businessWorkHours ? [`Business hours timezone: ${profile.businessWorkHours}`] : []),
|
|
476
550
|
];
|
|
477
|
-
return
|
|
551
|
+
return ok(lines.join("\n"));
|
|
478
552
|
}
|
|
479
553
|
catch (e) {
|
|
480
|
-
return
|
|
481
|
-
}
|
|
482
|
-
});
|
|
483
|
-
server.
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
.
|
|
488
|
-
|
|
489
|
-
|
|
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,
|
|
490
568
|
}, async ({ entityId, savePath, size }) => {
|
|
491
569
|
const err = await requireConnection();
|
|
492
570
|
if (err)
|
|
493
|
-
return
|
|
571
|
+
return fail(new Error(err));
|
|
494
572
|
try {
|
|
495
573
|
const result = await telegram.downloadProfilePhoto(entityId, {
|
|
496
574
|
isBig: size !== "small",
|
|
497
575
|
savePath,
|
|
498
576
|
});
|
|
499
577
|
if (!result) {
|
|
500
|
-
return
|
|
578
|
+
return ok("No profile photo found");
|
|
501
579
|
}
|
|
502
580
|
if ("filePath" in result) {
|
|
503
|
-
return
|
|
581
|
+
return ok(`Downloaded to: ${result.filePath}`);
|
|
504
582
|
}
|
|
505
583
|
return {
|
|
506
584
|
content: [
|
|
507
585
|
{ type: "image", data: result.buffer.toString("base64"), mimeType: result.mimeType },
|
|
508
|
-
{
|
|
586
|
+
{
|
|
587
|
+
type: "text",
|
|
588
|
+
text: `Profile photo (${(result.buffer.length / 1024).toFixed(0)} KB, ${result.mimeType})`,
|
|
589
|
+
},
|
|
509
590
|
],
|
|
510
591
|
};
|
|
511
592
|
}
|
|
512
593
|
catch (e) {
|
|
513
|
-
return
|
|
594
|
+
return fail(e);
|
|
514
595
|
}
|
|
515
596
|
});
|
|
516
|
-
server.
|
|
517
|
-
|
|
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,
|
|
518
601
|
}, async ({ target }) => {
|
|
519
602
|
const err = await requireConnection();
|
|
520
603
|
if (err)
|
|
521
|
-
return
|
|
604
|
+
return fail(new Error(err));
|
|
522
605
|
try {
|
|
523
606
|
const result = await telegram.joinChat(target);
|
|
524
|
-
return {
|
|
525
|
-
content: [
|
|
526
|
-
{
|
|
527
|
-
type: "text",
|
|
528
|
-
text: `Joined ${result.type}: ${result.title} (ID: ${result.id})`,
|
|
529
|
-
},
|
|
530
|
-
],
|
|
531
|
-
};
|
|
607
|
+
return ok(`Joined ${result.type}: ${result.title} (ID: ${result.id})`);
|
|
532
608
|
}
|
|
533
609
|
catch (e) {
|
|
534
|
-
return
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
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,
|
|
550
628
|
}, async ({ chatId, messageId, emoji, addToExisting }) => {
|
|
551
629
|
const err = await requireConnection();
|
|
552
630
|
if (err)
|
|
553
|
-
return
|
|
631
|
+
return fail(new Error(err));
|
|
554
632
|
try {
|
|
555
633
|
const updated = await telegram.sendReaction(chatId, messageId, emoji, addToExisting);
|
|
556
634
|
const emojiStr = Array.isArray(emoji) ? emoji.join("") : emoji;
|
|
@@ -558,45 +636,53 @@ server.tool("telegram-send-reaction", "Send emoji reaction(s) to a message. Supp
|
|
|
558
636
|
const reactionsInfo = updated
|
|
559
637
|
? ` | Reactions: ${updated.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`).join(" ")}`
|
|
560
638
|
: "";
|
|
561
|
-
return
|
|
639
|
+
return ok(`${action} message ${messageId} in ${chatId}${reactionsInfo}`);
|
|
562
640
|
}
|
|
563
641
|
catch (e) {
|
|
564
|
-
return
|
|
642
|
+
return fail(e);
|
|
565
643
|
}
|
|
566
644
|
});
|
|
567
|
-
server.
|
|
568
|
-
|
|
569
|
-
|
|
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,
|
|
570
652
|
}, async ({ chatId, messageId }) => {
|
|
571
653
|
const err = await requireConnection();
|
|
572
654
|
if (err)
|
|
573
|
-
return
|
|
655
|
+
return fail(new Error(err));
|
|
574
656
|
try {
|
|
575
657
|
const result = await telegram.getMessageReactions(chatId, messageId);
|
|
576
658
|
if (result.reactions.length === 0) {
|
|
577
|
-
return
|
|
659
|
+
return ok(`No reactions on message ${messageId}`);
|
|
578
660
|
}
|
|
579
661
|
const lines = result.reactions.map((r) => {
|
|
580
662
|
const usersStr = r.users.length > 0 ? `: ${r.users.map((u) => u.name).join(", ")}` : "";
|
|
581
663
|
return `${r.emoji} × ${r.count}${usersStr}`;
|
|
582
664
|
});
|
|
583
665
|
lines.push(`\nTotal: ${result.total} reactions`);
|
|
584
|
-
return
|
|
666
|
+
return ok(lines.join("\n"));
|
|
585
667
|
}
|
|
586
668
|
catch (e) {
|
|
587
|
-
return
|
|
588
|
-
}
|
|
589
|
-
});
|
|
590
|
-
server.
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
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,
|
|
596
682
|
}, async ({ chatId, text, scheduleDate, replyTo, parseMode }) => {
|
|
597
683
|
const err = await requireConnection();
|
|
598
684
|
if (err)
|
|
599
|
-
return
|
|
685
|
+
return fail(new Error(err));
|
|
600
686
|
// Resolve 'me'/'self' to Saved Messages
|
|
601
687
|
let target = chatId;
|
|
602
688
|
if (target === "me" || target === "self") {
|
|
@@ -605,47 +691,53 @@ server.tool("telegram-send-scheduled", "Send a scheduled message to a Telegram c
|
|
|
605
691
|
target = me.id;
|
|
606
692
|
}
|
|
607
693
|
catch {
|
|
608
|
-
return
|
|
694
|
+
return fail(new Error("Failed to resolve Saved Messages"));
|
|
609
695
|
}
|
|
610
696
|
}
|
|
611
697
|
try {
|
|
612
698
|
await telegram.sendScheduledMessage(target, text, scheduleDate, replyTo, parseMode);
|
|
613
699
|
const date = new Date(scheduleDate * 1000).toISOString();
|
|
614
|
-
return
|
|
700
|
+
return ok(`Message scheduled for ${date} in ${chatId}`);
|
|
615
701
|
}
|
|
616
702
|
catch (e) {
|
|
617
|
-
return
|
|
618
|
-
}
|
|
619
|
-
});
|
|
620
|
-
server.
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
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,
|
|
627
717
|
}, async ({ chatId, question, answers, multipleChoice, quiz, correctAnswer }) => {
|
|
628
718
|
const err = await requireConnection();
|
|
629
719
|
if (err)
|
|
630
|
-
return
|
|
720
|
+
return fail(new Error(err));
|
|
631
721
|
try {
|
|
632
722
|
const msgId = await telegram.createPoll(chatId, question, answers, { multipleChoice, quiz, correctAnswer });
|
|
633
|
-
return
|
|
723
|
+
return ok(`Poll created in ${chatId}${msgId ? ` (message #${msgId})` : ""}`);
|
|
634
724
|
}
|
|
635
725
|
catch (e) {
|
|
636
|
-
return
|
|
726
|
+
return fail(e);
|
|
637
727
|
}
|
|
638
728
|
});
|
|
639
|
-
server.
|
|
640
|
-
|
|
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,
|
|
641
733
|
}, async ({ limit }) => {
|
|
642
734
|
const err = await requireConnection();
|
|
643
735
|
if (err)
|
|
644
|
-
return
|
|
736
|
+
return fail(new Error(err));
|
|
645
737
|
try {
|
|
646
738
|
const requests = await telegram.getContactRequests(limit);
|
|
647
739
|
if (requests.length === 0) {
|
|
648
|
-
return
|
|
740
|
+
return ok("No contact requests");
|
|
649
741
|
}
|
|
650
742
|
const text = requests
|
|
651
743
|
.map((r) => {
|
|
@@ -656,66 +748,76 @@ server.tool("telegram-get-contact-requests", "Get incoming messages from non-con
|
|
|
656
748
|
return `${tag} ${r.name}${username} (${r.id})${unread}${preview}`;
|
|
657
749
|
})
|
|
658
750
|
.join("\n");
|
|
659
|
-
return
|
|
751
|
+
return ok(sanitize(text));
|
|
660
752
|
}
|
|
661
753
|
catch (e) {
|
|
662
|
-
return
|
|
663
|
-
}
|
|
664
|
-
});
|
|
665
|
-
server.
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
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,
|
|
670
766
|
}, async ({ userId, firstName, lastName, phone }) => {
|
|
671
767
|
const err = await requireConnection();
|
|
672
768
|
if (err)
|
|
673
|
-
return
|
|
769
|
+
return fail(new Error(err));
|
|
674
770
|
try {
|
|
675
771
|
await telegram.addContact(userId, firstName, lastName, phone);
|
|
676
|
-
return {
|
|
677
|
-
content: [{ type: "text", text: `Contact added: ${firstName}${lastName ? ` ${lastName}` : ""} (${userId})` }],
|
|
678
|
-
};
|
|
772
|
+
return ok(`Contact added: ${firstName}${lastName ? ` ${lastName}` : ""} (${userId})`);
|
|
679
773
|
}
|
|
680
774
|
catch (e) {
|
|
681
|
-
return
|
|
775
|
+
return fail(e);
|
|
682
776
|
}
|
|
683
777
|
});
|
|
684
|
-
server.
|
|
685
|
-
|
|
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,
|
|
686
782
|
}, async ({ userId }) => {
|
|
687
783
|
const err = await requireConnection();
|
|
688
784
|
if (err)
|
|
689
|
-
return
|
|
785
|
+
return fail(new Error(err));
|
|
690
786
|
try {
|
|
691
787
|
await telegram.blockUser(userId);
|
|
692
|
-
return
|
|
788
|
+
return ok(`User blocked: ${userId}`);
|
|
693
789
|
}
|
|
694
790
|
catch (e) {
|
|
695
|
-
return
|
|
791
|
+
return fail(e);
|
|
696
792
|
}
|
|
697
793
|
});
|
|
698
|
-
server.
|
|
699
|
-
|
|
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,
|
|
700
798
|
}, async ({ chatId }) => {
|
|
701
799
|
const err = await requireConnection();
|
|
702
800
|
if (err)
|
|
703
|
-
return
|
|
801
|
+
return fail(new Error(err));
|
|
704
802
|
try {
|
|
705
803
|
await telegram.reportSpam(chatId);
|
|
706
|
-
return
|
|
804
|
+
return ok(`Reported as spam: ${chatId}`);
|
|
707
805
|
}
|
|
708
806
|
catch (e) {
|
|
709
|
-
return
|
|
807
|
+
return fail(e);
|
|
710
808
|
}
|
|
711
809
|
});
|
|
712
|
-
server.
|
|
713
|
-
|
|
714
|
-
|
|
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,
|
|
715
817
|
}, async ({ chatId, limit }) => {
|
|
716
818
|
const err = await requireConnection();
|
|
717
819
|
if (err)
|
|
718
|
-
return
|
|
820
|
+
return fail(new Error(err));
|
|
719
821
|
try {
|
|
720
822
|
const topics = await telegram.getForumTopics(chatId, limit);
|
|
721
823
|
const text = topics
|
|
@@ -726,30 +828,34 @@ server.tool("telegram-list-topics", "List forum topics in a Telegram group with
|
|
|
726
828
|
return `# ${t.title} (id: ${t.id})${flagStr}${unread}`;
|
|
727
829
|
})
|
|
728
830
|
.join("\n");
|
|
729
|
-
return
|
|
831
|
+
return ok(sanitize(text) || "No topics found");
|
|
730
832
|
}
|
|
731
833
|
catch (e) {
|
|
732
|
-
return
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
server.
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
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,
|
|
740
846
|
}, async ({ chatId, topicId, limit, offsetId }) => {
|
|
741
847
|
const err = await requireConnection();
|
|
742
848
|
if (err)
|
|
743
|
-
return
|
|
849
|
+
return fail(new Error(err));
|
|
744
850
|
try {
|
|
745
851
|
const messages = await telegram.getTopicMessages(chatId, topicId, limit, offsetId);
|
|
746
852
|
const text = messages
|
|
747
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)}`)
|
|
748
854
|
.join("\n\n");
|
|
749
|
-
return
|
|
855
|
+
return ok(sanitize(text) || "No messages in this topic");
|
|
750
856
|
}
|
|
751
857
|
catch (e) {
|
|
752
|
-
return
|
|
858
|
+
return fail(e);
|
|
753
859
|
}
|
|
754
860
|
});
|
|
755
861
|
// --- Start ---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@overpod/mcp-telegram",
|
|
3
|
-
"version": "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.
|
|
52
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
53
53
|
"dotenv": "^17.3.1",
|
|
54
54
|
"qrcode": "^1.5.4",
|
|
55
55
|
"telegram": "^2.26.22",
|
|
@@ -60,6 +60,6 @@
|
|
|
60
60
|
"@types/node": "^25.5.0",
|
|
61
61
|
"@types/qrcode": "^1.5.6",
|
|
62
62
|
"tsx": "^4.21.0",
|
|
63
|
-
"typescript": "^
|
|
63
|
+
"typescript": "^6.0.2"
|
|
64
64
|
}
|
|
65
65
|
}
|