@ebowwa/mcp-telegram 0.1.4
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/auth-signin.js +182 -0
- package/auth-signin.ts +122 -0
- package/auth-verify.js +234 -0
- package/auth-verify.ts +180 -0
- package/auth.js +154 -0
- package/auth.ts +98 -0
- package/bun.lock +302 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +807 -0
- package/dist/index.js.map +1 -0
- package/package.json +52 -0
- package/src/index.js +918 -0
- package/src/index.ts +907 -0
- package/tg.sh +44 -0
- package/tsconfig.json +20 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,907 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @ebowwa/telegram-mcp - Telegram MTProto Client MCP Server
|
|
4
|
+
*
|
|
5
|
+
* Full-featured Telegram user account integration using GramJS:
|
|
6
|
+
* - Connect/authenticate with API ID and hash
|
|
7
|
+
* - Send messages to users, groups, channels
|
|
8
|
+
* - Get dialogs (chat list)
|
|
9
|
+
* - Get messages from chats
|
|
10
|
+
* - Get entity info (users, chats, channels)
|
|
11
|
+
*
|
|
12
|
+
* Prerequisites:
|
|
13
|
+
* 1. Get API ID and API Hash from https://my.telegram.org/apps
|
|
14
|
+
* 2. First connection requires phone number + verification code
|
|
15
|
+
* 3. Session string is saved for subsequent connections
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
19
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
20
|
+
import {
|
|
21
|
+
CallToolRequestSchema,
|
|
22
|
+
ListToolsRequestSchema,
|
|
23
|
+
} from "@modelcontextprotocol/sdk/types.js";
|
|
24
|
+
import { TelegramClient } from "telegram";
|
|
25
|
+
import { StringSession } from "telegram/sessions";
|
|
26
|
+
import { Api } from "telegram";
|
|
27
|
+
import bigInt from "big-integer";
|
|
28
|
+
import { homedir } from "os";
|
|
29
|
+
import { join } from "path";
|
|
30
|
+
import { mkdirSync, existsSync, writeFileSync, readFileSync } from "fs";
|
|
31
|
+
import { execSync } from "child_process";
|
|
32
|
+
|
|
33
|
+
// ==============
|
|
34
|
+
// Configuration
|
|
35
|
+
// ==============
|
|
36
|
+
|
|
37
|
+
const SESSION_DIR = join(homedir(), ".telegram-mcp");
|
|
38
|
+
const SESSION_FILE = join(SESSION_DIR, "session.txt");
|
|
39
|
+
|
|
40
|
+
// Ensure session directory exists
|
|
41
|
+
if (!existsSync(SESSION_DIR)) {
|
|
42
|
+
mkdirSync(SESSION_DIR, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Global client instance
|
|
46
|
+
let client: TelegramClient | null = null;
|
|
47
|
+
let sessionString: string = "";
|
|
48
|
+
|
|
49
|
+
// ==============
|
|
50
|
+
// Helper Functions
|
|
51
|
+
// ==============
|
|
52
|
+
|
|
53
|
+
async function loadSession(): Promise<string> {
|
|
54
|
+
if (existsSync(SESSION_FILE)) {
|
|
55
|
+
return readFileSync(SESSION_FILE, "utf-8").trim();
|
|
56
|
+
}
|
|
57
|
+
return "";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function saveSession(session: string): Promise<void> {
|
|
61
|
+
writeFileSync(SESSION_FILE, session, "utf-8");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function getClient(): Promise<TelegramClient> {
|
|
65
|
+
if (client && client.connected) {
|
|
66
|
+
return client;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Auto-connect using saved session
|
|
70
|
+
const apiId = parseInt(process.env.TELEGRAM_API_ID || "0");
|
|
71
|
+
const apiHash = process.env.TELEGRAM_API_HASH;
|
|
72
|
+
|
|
73
|
+
if (!apiId || !apiHash) {
|
|
74
|
+
throw new Error("TELEGRAM_API_ID and TELEGRAM_API_HASH environment variables required.");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const savedSession = await loadSession();
|
|
78
|
+
if (!savedSession) {
|
|
79
|
+
throw new Error("No saved session. Run telegram_connect with phone number first.");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const session = new StringSession(savedSession);
|
|
83
|
+
client = new TelegramClient(session, apiId, apiHash, {
|
|
84
|
+
connectionRetries: 5,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await client.connect();
|
|
88
|
+
|
|
89
|
+
if (!(await client.checkAuthorization())) {
|
|
90
|
+
client = null;
|
|
91
|
+
throw new Error("Session expired. Run telegram_connect again.");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return client;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function formatEntity(entity: any): any {
|
|
98
|
+
if (!entity) return null;
|
|
99
|
+
|
|
100
|
+
const base = {
|
|
101
|
+
id: entity.id?.toString(),
|
|
102
|
+
className: entity.className,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (entity.className === "User") {
|
|
106
|
+
return {
|
|
107
|
+
...base,
|
|
108
|
+
firstName: entity.firstName,
|
|
109
|
+
lastName: entity.lastName,
|
|
110
|
+
username: entity.username,
|
|
111
|
+
phone: entity.phone,
|
|
112
|
+
bot: entity.bot,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (entity.className === "Chat" || entity.className === "Channel") {
|
|
117
|
+
return {
|
|
118
|
+
...base,
|
|
119
|
+
title: entity.title,
|
|
120
|
+
username: entity.username,
|
|
121
|
+
megagroup: entity.megagroup,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return base;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function formatMessage(message: any): any {
|
|
129
|
+
if (!message) return null;
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
id: message.id,
|
|
133
|
+
date: message.date ? new Date(message.date * 1000).toISOString() : null,
|
|
134
|
+
message: message.message,
|
|
135
|
+
fromId: message.fromId?.userId?.toString() || message.fromId?.toString(),
|
|
136
|
+
peerId: message.peerId?.userId?.toString() || message.peerId?.chatId?.toString() || message.peerId?.channelId?.toString(),
|
|
137
|
+
out: message.out,
|
|
138
|
+
media: message.media?.className,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ==============
|
|
143
|
+
// MCP Server
|
|
144
|
+
// ==============
|
|
145
|
+
|
|
146
|
+
const server = new Server(
|
|
147
|
+
{
|
|
148
|
+
name: "@ebowwa/telegram-mcp",
|
|
149
|
+
version: "0.1.0",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
capabilities: {
|
|
153
|
+
tools: {},
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// List available tools
|
|
159
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
160
|
+
return {
|
|
161
|
+
tools: [
|
|
162
|
+
// Connection Management
|
|
163
|
+
{
|
|
164
|
+
name: "telegram_connect",
|
|
165
|
+
description: "Connect to Telegram with API credentials. Requires TELEGRAM_API_ID and TELEGRAM_API_HASH env vars. First connection needs phone/code verification.",
|
|
166
|
+
inputSchema: {
|
|
167
|
+
type: "object",
|
|
168
|
+
properties: {
|
|
169
|
+
phoneNumber: {
|
|
170
|
+
type: "string",
|
|
171
|
+
description: "Phone number with country code (e.g., +1234567890). Required for first connection.",
|
|
172
|
+
},
|
|
173
|
+
code: {
|
|
174
|
+
type: "string",
|
|
175
|
+
description: "Verification code received via Telegram. Required after initial phone submission.",
|
|
176
|
+
},
|
|
177
|
+
password: {
|
|
178
|
+
type: "string",
|
|
179
|
+
description: "2FA password if enabled on account.",
|
|
180
|
+
},
|
|
181
|
+
sessionString: {
|
|
182
|
+
type: "string",
|
|
183
|
+
description: "Existing session string to restore (optional, overrides saved session).",
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
name: "telegram_disconnect",
|
|
190
|
+
description: "Disconnect from Telegram and clear the session.",
|
|
191
|
+
inputSchema: {
|
|
192
|
+
type: "object",
|
|
193
|
+
properties: {},
|
|
194
|
+
},
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: "telegram_get_session",
|
|
198
|
+
description: "Get the current session string for backup/restore purposes.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
type: "object",
|
|
201
|
+
properties: {},
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: "telegram_get_me",
|
|
206
|
+
description: "Get information about the currently authenticated user.",
|
|
207
|
+
inputSchema: {
|
|
208
|
+
type: "object",
|
|
209
|
+
properties: {},
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "telegram_restart",
|
|
214
|
+
description: "Restart the Telegram MCP systemd service. Useful for applying config changes or recovering from errors.",
|
|
215
|
+
inputSchema: {
|
|
216
|
+
type: "object",
|
|
217
|
+
properties: {
|
|
218
|
+
serviceName: {
|
|
219
|
+
type: "string",
|
|
220
|
+
description: "Systemd service name (default: telegram-mcp)",
|
|
221
|
+
},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
|
|
226
|
+
// Messaging
|
|
227
|
+
{
|
|
228
|
+
name: "telegram_send_message",
|
|
229
|
+
description: "Send a message to a user, group, or channel.",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
entity: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description: "Username, phone, or entity ID to send message to (e.g., 'username', '+1234567890', '123456789')",
|
|
236
|
+
},
|
|
237
|
+
message: {
|
|
238
|
+
type: "string",
|
|
239
|
+
description: "Message text to send",
|
|
240
|
+
},
|
|
241
|
+
parseMode: {
|
|
242
|
+
type: "string",
|
|
243
|
+
enum: ["md", "html", ""],
|
|
244
|
+
description: "Parse mode for formatting (md=Markdown, html=HTML)",
|
|
245
|
+
},
|
|
246
|
+
replyTo: {
|
|
247
|
+
type: "number",
|
|
248
|
+
description: "Message ID to reply to (optional)",
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
required: ["entity", "message"],
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "telegram_get_dialogs",
|
|
256
|
+
description: "Get list of all chats/conversations (dialogs).",
|
|
257
|
+
inputSchema: {
|
|
258
|
+
type: "object",
|
|
259
|
+
properties: {
|
|
260
|
+
limit: {
|
|
261
|
+
type: "number",
|
|
262
|
+
description: "Maximum number of dialogs to return (default: 100)",
|
|
263
|
+
},
|
|
264
|
+
offsetDate: {
|
|
265
|
+
type: "number",
|
|
266
|
+
description: "Offset date for pagination",
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
name: "telegram_get_messages",
|
|
273
|
+
description: "Get messages from a chat/conversation.",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
type: "object",
|
|
276
|
+
properties: {
|
|
277
|
+
entity: {
|
|
278
|
+
type: "string",
|
|
279
|
+
description: "Username, phone, or entity ID of the chat",
|
|
280
|
+
},
|
|
281
|
+
limit: {
|
|
282
|
+
type: "number",
|
|
283
|
+
description: "Maximum number of messages to return (default: 100)",
|
|
284
|
+
},
|
|
285
|
+
offsetId: {
|
|
286
|
+
type: "number",
|
|
287
|
+
description: "Message ID to start from (for pagination)",
|
|
288
|
+
},
|
|
289
|
+
minId: {
|
|
290
|
+
type: "number",
|
|
291
|
+
description: "Minimum message ID to fetch",
|
|
292
|
+
},
|
|
293
|
+
maxId: {
|
|
294
|
+
type: "number",
|
|
295
|
+
description: "Maximum message ID to fetch",
|
|
296
|
+
},
|
|
297
|
+
reverse: {
|
|
298
|
+
type: "boolean",
|
|
299
|
+
description: "Fetch messages in reverse order (oldest first)",
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
required: ["entity"],
|
|
303
|
+
},
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
name: "telegram_get_entity",
|
|
307
|
+
description: "Get information about a user, chat, or channel by username or ID.",
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: "object",
|
|
310
|
+
properties: {
|
|
311
|
+
entity: {
|
|
312
|
+
type: "string",
|
|
313
|
+
description: "Username, phone, or entity ID to look up",
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
required: ["entity"],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: "telegram_mark_read",
|
|
321
|
+
description: "Mark messages as read in a chat.",
|
|
322
|
+
inputSchema: {
|
|
323
|
+
type: "object",
|
|
324
|
+
properties: {
|
|
325
|
+
entity: {
|
|
326
|
+
type: "string",
|
|
327
|
+
description: "Username, phone, or entity ID of the chat",
|
|
328
|
+
},
|
|
329
|
+
maxId: {
|
|
330
|
+
type: "number",
|
|
331
|
+
description: "Maximum message ID to mark as read (default: latest)",
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
required: ["entity"],
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: "telegram_delete_messages",
|
|
339
|
+
description: "Delete messages from a chat.",
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
entity: {
|
|
344
|
+
type: "string",
|
|
345
|
+
description: "Username, phone, or entity ID of the chat",
|
|
346
|
+
},
|
|
347
|
+
messageIds: {
|
|
348
|
+
type: "array",
|
|
349
|
+
items: { type: "number" },
|
|
350
|
+
description: "Array of message IDs to delete",
|
|
351
|
+
},
|
|
352
|
+
revoke: {
|
|
353
|
+
type: "boolean",
|
|
354
|
+
description: "Delete for both parties (default: true)",
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
required: ["entity", "messageIds"],
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
name: "telegram_edit_message",
|
|
362
|
+
description: "Edit a sent message.",
|
|
363
|
+
inputSchema: {
|
|
364
|
+
type: "object",
|
|
365
|
+
properties: {
|
|
366
|
+
entity: {
|
|
367
|
+
type: "string",
|
|
368
|
+
description: "Username, phone, or entity ID of the chat",
|
|
369
|
+
},
|
|
370
|
+
messageId: {
|
|
371
|
+
type: "number",
|
|
372
|
+
description: "ID of the message to edit",
|
|
373
|
+
},
|
|
374
|
+
message: {
|
|
375
|
+
type: "string",
|
|
376
|
+
description: "New message text",
|
|
377
|
+
},
|
|
378
|
+
parseMode: {
|
|
379
|
+
type: "string",
|
|
380
|
+
enum: ["md", "html", ""],
|
|
381
|
+
description: "Parse mode for formatting",
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
required: ["entity", "messageId", "message"],
|
|
385
|
+
},
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: "telegram_forward_messages",
|
|
389
|
+
description: "Forward messages to another chat.",
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
properties: {
|
|
393
|
+
fromEntity: {
|
|
394
|
+
type: "string",
|
|
395
|
+
description: "Source chat username or ID",
|
|
396
|
+
},
|
|
397
|
+
toEntity: {
|
|
398
|
+
type: "string",
|
|
399
|
+
description: "Destination chat username or ID",
|
|
400
|
+
},
|
|
401
|
+
messageIds: {
|
|
402
|
+
type: "array",
|
|
403
|
+
items: { type: "number" },
|
|
404
|
+
description: "Array of message IDs to forward",
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
required: ["fromEntity", "toEntity", "messageIds"],
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
|
|
411
|
+
// Contacts & Users
|
|
412
|
+
{
|
|
413
|
+
name: "telegram_get_contacts",
|
|
414
|
+
description: "Get list of contacts.",
|
|
415
|
+
inputSchema: {
|
|
416
|
+
type: "object",
|
|
417
|
+
properties: {
|
|
418
|
+
limit: {
|
|
419
|
+
type: "number",
|
|
420
|
+
description: "Maximum number of contacts to return",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Chats & Channels
|
|
427
|
+
{
|
|
428
|
+
name: "telegram_create_group",
|
|
429
|
+
description: "Create a new group chat.",
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: "object",
|
|
432
|
+
properties: {
|
|
433
|
+
title: {
|
|
434
|
+
type: "string",
|
|
435
|
+
description: "Group title",
|
|
436
|
+
},
|
|
437
|
+
users: {
|
|
438
|
+
type: "array",
|
|
439
|
+
items: { type: "string" },
|
|
440
|
+
description: "Array of user usernames or IDs to add",
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
required: ["title", "users"],
|
|
444
|
+
},
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
name: "telegram_get_participants",
|
|
448
|
+
description: "Get participants of a group or channel.",
|
|
449
|
+
inputSchema: {
|
|
450
|
+
type: "object",
|
|
451
|
+
properties: {
|
|
452
|
+
entity: {
|
|
453
|
+
type: "string",
|
|
454
|
+
description: "Group/channel username or ID",
|
|
455
|
+
},
|
|
456
|
+
limit: {
|
|
457
|
+
type: "number",
|
|
458
|
+
description: "Maximum number of participants to return",
|
|
459
|
+
},
|
|
460
|
+
},
|
|
461
|
+
required: ["entity"],
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
// Handle tool calls
|
|
469
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
470
|
+
const { name, arguments: args } = request.params;
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
switch (name) {
|
|
474
|
+
// ==================
|
|
475
|
+
// Connection
|
|
476
|
+
// ==================
|
|
477
|
+
case "telegram_connect": {
|
|
478
|
+
const apiId = parseInt(process.env.TELEGRAM_API_ID || "0");
|
|
479
|
+
const apiHash = process.env.TELEGRAM_API_HASH;
|
|
480
|
+
|
|
481
|
+
if (!apiId || !apiHash) {
|
|
482
|
+
throw new Error("TELEGRAM_API_ID and TELEGRAM_API_HASH environment variables required. Get them from https://my.telegram.org/apps");
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
// Use provided session or load saved one
|
|
486
|
+
const savedSession = (args?.sessionString as string) || await loadSession();
|
|
487
|
+
const session = new StringSession(savedSession);
|
|
488
|
+
|
|
489
|
+
client = new TelegramClient(session, apiId, apiHash, {
|
|
490
|
+
connectionRetries: 5,
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
const phoneNumber = args?.phoneNumber as string | undefined;
|
|
494
|
+
const code = args?.code as string | undefined;
|
|
495
|
+
const password = args?.password as string | undefined;
|
|
496
|
+
|
|
497
|
+
// Start the client with auth options
|
|
498
|
+
const startOptions: any = {
|
|
499
|
+
onError: (err: Error) => console.error("Telegram auth error:", err),
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
if (phoneNumber) {
|
|
503
|
+
startOptions.phoneNumber = async () => phoneNumber;
|
|
504
|
+
}
|
|
505
|
+
if (code) {
|
|
506
|
+
startOptions.phoneCode = async () => code;
|
|
507
|
+
}
|
|
508
|
+
if (password) {
|
|
509
|
+
startOptions.password = async () => password;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
await client.start(startOptions);
|
|
513
|
+
|
|
514
|
+
// Save session for future use
|
|
515
|
+
sessionString = client.session.save() as unknown as string;
|
|
516
|
+
await saveSession(sessionString);
|
|
517
|
+
|
|
518
|
+
const me = await client.getMe();
|
|
519
|
+
|
|
520
|
+
return {
|
|
521
|
+
content: [{
|
|
522
|
+
type: "text",
|
|
523
|
+
text: JSON.stringify({
|
|
524
|
+
success: true,
|
|
525
|
+
message: "Connected to Telegram",
|
|
526
|
+
user: formatEntity(me),
|
|
527
|
+
sessionString: sessionString,
|
|
528
|
+
}, null, 2),
|
|
529
|
+
}],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
case "telegram_disconnect": {
|
|
534
|
+
if (client) {
|
|
535
|
+
await client.disconnect();
|
|
536
|
+
client = null;
|
|
537
|
+
sessionString = "";
|
|
538
|
+
}
|
|
539
|
+
return {
|
|
540
|
+
content: [{
|
|
541
|
+
type: "text",
|
|
542
|
+
text: JSON.stringify({ success: true, message: "Disconnected from Telegram" }),
|
|
543
|
+
}],
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
case "telegram_get_session": {
|
|
548
|
+
if (!sessionString && existsSync(SESSION_FILE)) {
|
|
549
|
+
sessionString = await loadSession();
|
|
550
|
+
}
|
|
551
|
+
return {
|
|
552
|
+
content: [{
|
|
553
|
+
type: "text",
|
|
554
|
+
text: JSON.stringify({ sessionString: sessionString || null }),
|
|
555
|
+
}],
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
case "telegram_get_me": {
|
|
560
|
+
const c = await getClient();
|
|
561
|
+
const me = await c.getMe();
|
|
562
|
+
return {
|
|
563
|
+
content: [{
|
|
564
|
+
type: "text",
|
|
565
|
+
text: JSON.stringify(formatEntity(me), null, 2),
|
|
566
|
+
}],
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
case "telegram_restart": {
|
|
571
|
+
const serviceName = (args?.serviceName as string) || "telegram-mcp";
|
|
572
|
+
try {
|
|
573
|
+
// Restart the systemd service
|
|
574
|
+
execSync(`sudo systemctl restart ${serviceName}`, {
|
|
575
|
+
encoding: "utf-8",
|
|
576
|
+
timeout: 30000,
|
|
577
|
+
});
|
|
578
|
+
|
|
579
|
+
return {
|
|
580
|
+
content: [{
|
|
581
|
+
type: "text",
|
|
582
|
+
text: JSON.stringify({
|
|
583
|
+
success: true,
|
|
584
|
+
message: `Systemd service '${serviceName}' restarted successfully`,
|
|
585
|
+
serviceName,
|
|
586
|
+
}, null, 2),
|
|
587
|
+
}],
|
|
588
|
+
};
|
|
589
|
+
} catch (error: any) {
|
|
590
|
+
// Check if systemctl is available (not on macOS)
|
|
591
|
+
if (error.message.includes("systemctl: command not found")) {
|
|
592
|
+
return {
|
|
593
|
+
content: [{
|
|
594
|
+
type: "text",
|
|
595
|
+
text: JSON.stringify({
|
|
596
|
+
error: true,
|
|
597
|
+
message: "systemctl not found. This command is only available on Linux systems with systemd.",
|
|
598
|
+
hint: "On macOS, use 'brew services restart' or manual process management.",
|
|
599
|
+
}, null, 2),
|
|
600
|
+
}],
|
|
601
|
+
isError: true,
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
throw error;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// ==================
|
|
609
|
+
// Messaging
|
|
610
|
+
// ==================
|
|
611
|
+
case "telegram_send_message": {
|
|
612
|
+
if (!args?.entity || !args?.message) {
|
|
613
|
+
throw new Error("entity and message are required");
|
|
614
|
+
}
|
|
615
|
+
const c = await getClient();
|
|
616
|
+
const entity = await c.getInputEntity(args.entity as string);
|
|
617
|
+
|
|
618
|
+
const result = await c.sendMessage(entity, {
|
|
619
|
+
message: args.message as string,
|
|
620
|
+
parseMode: args.parseMode as any,
|
|
621
|
+
replyTo: args.replyTo as number,
|
|
622
|
+
});
|
|
623
|
+
|
|
624
|
+
return {
|
|
625
|
+
content: [{
|
|
626
|
+
type: "text",
|
|
627
|
+
text: JSON.stringify({
|
|
628
|
+
success: true,
|
|
629
|
+
message: formatMessage(result),
|
|
630
|
+
}, null, 2),
|
|
631
|
+
}],
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
case "telegram_get_dialogs": {
|
|
636
|
+
const c = await getClient();
|
|
637
|
+
const limit = (args?.limit as number) || 100;
|
|
638
|
+
|
|
639
|
+
const dialogs = await c.getDialogs({ limit });
|
|
640
|
+
|
|
641
|
+
const formatted = dialogs.map((d: any) => ({
|
|
642
|
+
id: d.id?.toString(),
|
|
643
|
+
name: d.name,
|
|
644
|
+
unreadCount: d.unreadCount,
|
|
645
|
+
archived: d.archived,
|
|
646
|
+
pinned: d.pinned,
|
|
647
|
+
entity: formatEntity(d.entity),
|
|
648
|
+
}));
|
|
649
|
+
|
|
650
|
+
return {
|
|
651
|
+
content: [{
|
|
652
|
+
type: "text",
|
|
653
|
+
text: JSON.stringify(formatted, null, 2),
|
|
654
|
+
}],
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
case "telegram_get_messages": {
|
|
659
|
+
if (!args?.entity) {
|
|
660
|
+
throw new Error("entity is required");
|
|
661
|
+
}
|
|
662
|
+
const c = await getClient();
|
|
663
|
+
const entity = await c.getInputEntity(args.entity as string);
|
|
664
|
+
|
|
665
|
+
const messages = await c.getMessages(entity, {
|
|
666
|
+
limit: (args.limit as number) || 100,
|
|
667
|
+
offsetId: args.offsetId as number,
|
|
668
|
+
minId: args.minId as number,
|
|
669
|
+
maxId: args.maxId as number,
|
|
670
|
+
reverse: args.reverse as boolean,
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
const formatted = messages.map(formatMessage);
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
content: [{
|
|
677
|
+
type: "text",
|
|
678
|
+
text: JSON.stringify({
|
|
679
|
+
total: messages.total,
|
|
680
|
+
messages: formatted,
|
|
681
|
+
}, null, 2),
|
|
682
|
+
}],
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
case "telegram_get_entity": {
|
|
687
|
+
if (!args?.entity) {
|
|
688
|
+
throw new Error("entity is required");
|
|
689
|
+
}
|
|
690
|
+
const c = await getClient();
|
|
691
|
+
const entity = await c.getEntity(args.entity as string);
|
|
692
|
+
return {
|
|
693
|
+
content: [{
|
|
694
|
+
type: "text",
|
|
695
|
+
text: JSON.stringify(formatEntity(entity), null, 2),
|
|
696
|
+
}],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
case "telegram_mark_read": {
|
|
701
|
+
if (!args?.entity) {
|
|
702
|
+
throw new Error("entity is required");
|
|
703
|
+
}
|
|
704
|
+
const c = await getClient();
|
|
705
|
+
const entity = await c.getInputEntity(args.entity as string);
|
|
706
|
+
|
|
707
|
+
await c.invoke(
|
|
708
|
+
new Api.messages.ReadHistory({
|
|
709
|
+
peer: entity,
|
|
710
|
+
maxId: (args.maxId as number) || 0,
|
|
711
|
+
})
|
|
712
|
+
);
|
|
713
|
+
|
|
714
|
+
return {
|
|
715
|
+
content: [{
|
|
716
|
+
type: "text",
|
|
717
|
+
text: JSON.stringify({ success: true, message: "Messages marked as read" }),
|
|
718
|
+
}],
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
case "telegram_delete_messages": {
|
|
723
|
+
if (!args?.entity || !args?.messageIds) {
|
|
724
|
+
throw new Error("entity and messageIds are required");
|
|
725
|
+
}
|
|
726
|
+
const c = await getClient();
|
|
727
|
+
const entity = await c.getInputEntity(args.entity as string);
|
|
728
|
+
|
|
729
|
+
await c.deleteMessages(
|
|
730
|
+
entity,
|
|
731
|
+
args.messageIds as number[],
|
|
732
|
+
{ revoke: args.revoke !== false }
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
return {
|
|
736
|
+
content: [{
|
|
737
|
+
type: "text",
|
|
738
|
+
text: JSON.stringify({ success: true, message: "Messages deleted" }),
|
|
739
|
+
}],
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
case "telegram_edit_message": {
|
|
744
|
+
if (!args?.entity || !args?.messageId || !args?.message) {
|
|
745
|
+
throw new Error("entity, messageId, and message are required");
|
|
746
|
+
}
|
|
747
|
+
const c = await getClient();
|
|
748
|
+
const entity = await c.getInputEntity(args.entity as string);
|
|
749
|
+
|
|
750
|
+
const result = await c.editMessage(entity, {
|
|
751
|
+
message: args.messageId as number,
|
|
752
|
+
text: args.message as string,
|
|
753
|
+
parseMode: args.parseMode as any,
|
|
754
|
+
});
|
|
755
|
+
|
|
756
|
+
return {
|
|
757
|
+
content: [{
|
|
758
|
+
type: "text",
|
|
759
|
+
text: JSON.stringify({
|
|
760
|
+
success: true,
|
|
761
|
+
message: formatMessage(result),
|
|
762
|
+
}, null, 2),
|
|
763
|
+
}],
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
case "telegram_forward_messages": {
|
|
768
|
+
if (!args?.fromEntity || !args?.toEntity || !args?.messageIds) {
|
|
769
|
+
throw new Error("fromEntity, toEntity, and messageIds are required");
|
|
770
|
+
}
|
|
771
|
+
const c = await getClient();
|
|
772
|
+
const fromEntity = await c.getInputEntity(args.fromEntity as string);
|
|
773
|
+
const toEntity = await c.getInputEntity(args.toEntity as string);
|
|
774
|
+
|
|
775
|
+
const result = await c.forwardMessages(toEntity, {
|
|
776
|
+
messages: args.messageIds as number[],
|
|
777
|
+
fromPeer: fromEntity,
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
return {
|
|
781
|
+
content: [{
|
|
782
|
+
type: "text",
|
|
783
|
+
text: JSON.stringify({
|
|
784
|
+
success: true,
|
|
785
|
+
forwarded: result.length,
|
|
786
|
+
}, null, 2),
|
|
787
|
+
}],
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
// ==================
|
|
792
|
+
// Contacts
|
|
793
|
+
// ==================
|
|
794
|
+
case "telegram_get_contacts": {
|
|
795
|
+
const c = await getClient();
|
|
796
|
+
const result = await c.invoke(
|
|
797
|
+
new Api.contacts.GetContacts({
|
|
798
|
+
hash: bigInt(0),
|
|
799
|
+
})
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
const contacts = (result as any).users?.map(formatEntity) || [];
|
|
803
|
+
|
|
804
|
+
return {
|
|
805
|
+
content: [{
|
|
806
|
+
type: "text",
|
|
807
|
+
text: JSON.stringify(contacts, null, 2),
|
|
808
|
+
}],
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// ==================
|
|
813
|
+
// Groups & Channels
|
|
814
|
+
// ==================
|
|
815
|
+
case "telegram_create_group": {
|
|
816
|
+
if (!args?.title || !args?.users) {
|
|
817
|
+
throw new Error("title and users are required");
|
|
818
|
+
}
|
|
819
|
+
const c = await getClient();
|
|
820
|
+
|
|
821
|
+
const users = await Promise.all(
|
|
822
|
+
(args.users as string[]).map((u: string) => c.getInputEntity(u))
|
|
823
|
+
);
|
|
824
|
+
|
|
825
|
+
const result = await c.invoke(
|
|
826
|
+
new Api.messages.CreateChat({
|
|
827
|
+
users: users,
|
|
828
|
+
title: args.title as string,
|
|
829
|
+
})
|
|
830
|
+
);
|
|
831
|
+
|
|
832
|
+
return {
|
|
833
|
+
content: [{
|
|
834
|
+
type: "text",
|
|
835
|
+
text: JSON.stringify({
|
|
836
|
+
success: true,
|
|
837
|
+
chat: formatEntity((result as any).chats?.[0]),
|
|
838
|
+
}, null, 2),
|
|
839
|
+
}],
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
case "telegram_get_participants": {
|
|
844
|
+
if (!args?.entity) {
|
|
845
|
+
throw new Error("entity is required");
|
|
846
|
+
}
|
|
847
|
+
const c = await getClient();
|
|
848
|
+
const entity = await c.getInputEntity(args.entity as string);
|
|
849
|
+
|
|
850
|
+
const result = await c.invoke(
|
|
851
|
+
new Api.channels.GetParticipants({
|
|
852
|
+
channel: entity,
|
|
853
|
+
filter: new Api.ChannelParticipantsRecent(),
|
|
854
|
+
limit: (args.limit as number) || 100,
|
|
855
|
+
offset: 0,
|
|
856
|
+
})
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
const participants = (result as any).participants?.map((p: any) => ({
|
|
860
|
+
userId: p.userId?.toString(),
|
|
861
|
+
date: p.date ? new Date(p.date * 1000).toISOString() : null,
|
|
862
|
+
className: p.className,
|
|
863
|
+
})) || [];
|
|
864
|
+
|
|
865
|
+
return {
|
|
866
|
+
content: [{
|
|
867
|
+
type: "text",
|
|
868
|
+
text: JSON.stringify({
|
|
869
|
+
total: (result as any).count,
|
|
870
|
+
participants,
|
|
871
|
+
}, null, 2),
|
|
872
|
+
}],
|
|
873
|
+
};
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
default:
|
|
877
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
878
|
+
}
|
|
879
|
+
} catch (error: any) {
|
|
880
|
+
return {
|
|
881
|
+
content: [{
|
|
882
|
+
type: "text",
|
|
883
|
+
text: JSON.stringify({
|
|
884
|
+
error: true,
|
|
885
|
+
message: error.message,
|
|
886
|
+
stack: error.stack,
|
|
887
|
+
}, null, 2),
|
|
888
|
+
}],
|
|
889
|
+
isError: true,
|
|
890
|
+
};
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
|
|
894
|
+
// ==============
|
|
895
|
+
// Start Server
|
|
896
|
+
// ==============
|
|
897
|
+
|
|
898
|
+
async function main() {
|
|
899
|
+
const transport = new StdioServerTransport();
|
|
900
|
+
await server.connect(transport);
|
|
901
|
+
console.error("Telegram MCP server running on stdio");
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
main().catch((error) => {
|
|
905
|
+
console.error("Fatal error:", error);
|
|
906
|
+
process.exit(1);
|
|
907
|
+
});
|