@overpod/mcp-telegram 1.1.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/LICENSE +21 -0
- package/README.md +335 -0
- package/dist/cli.js +9 -0
- package/dist/index.js +424 -0
- package/dist/qr-login-cli.js +58 -0
- package/dist/telegram-client.js +508 -0
- package/package.json +60 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { TelegramService } from "./telegram-client.js";
|
|
7
|
+
// Telegram API credentials from env
|
|
8
|
+
const API_ID = Number(process.env.TELEGRAM_API_ID);
|
|
9
|
+
const API_HASH = process.env.TELEGRAM_API_HASH;
|
|
10
|
+
if (!API_ID || !API_HASH) {
|
|
11
|
+
console.error("[mcp-telegram] TELEGRAM_API_ID and TELEGRAM_API_HASH must be set");
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
const telegram = new TelegramService(API_ID, API_HASH);
|
|
15
|
+
const server = new McpServer({
|
|
16
|
+
name: "mcp-telegram",
|
|
17
|
+
version: "1.0.0",
|
|
18
|
+
});
|
|
19
|
+
/** Try to connect, return error text if failed */
|
|
20
|
+
async function requireConnection() {
|
|
21
|
+
if (await telegram.ensureConnected())
|
|
22
|
+
return null;
|
|
23
|
+
const reason = telegram.lastError ? ` ${telegram.lastError}` : "";
|
|
24
|
+
return `Not connected to Telegram.${reason} Run telegram-login first.`;
|
|
25
|
+
}
|
|
26
|
+
// --- Tools ---
|
|
27
|
+
server.tool("telegram-status", "Check Telegram connection status", {}, async () => {
|
|
28
|
+
if (await telegram.ensureConnected()) {
|
|
29
|
+
try {
|
|
30
|
+
const me = await telegram.getMe();
|
|
31
|
+
return {
|
|
32
|
+
content: [
|
|
33
|
+
{
|
|
34
|
+
type: "text",
|
|
35
|
+
text: `Connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})`,
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return { content: [{ type: "text", text: "Connected, but failed to get user info" }] };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const reason = telegram.lastError ? ` Reason: ${telegram.lastError}` : "";
|
|
45
|
+
return {
|
|
46
|
+
content: [{ type: "text", text: `Not connected.${reason} Use telegram-login to authenticate via QR code.` }],
|
|
47
|
+
};
|
|
48
|
+
});
|
|
49
|
+
server.tool("telegram-login", "Login to Telegram via QR code. Returns QR image. IMPORTANT: pass the entire result to user without modifications.", {}, async () => {
|
|
50
|
+
let qrDataUrl = "";
|
|
51
|
+
const loginPromise = telegram.startQrLogin((dataUrl) => {
|
|
52
|
+
qrDataUrl = dataUrl;
|
|
53
|
+
});
|
|
54
|
+
// Wait for first QR to be generated
|
|
55
|
+
const startTime = Date.now();
|
|
56
|
+
while (!qrDataUrl && Date.now() - startTime < 15000) {
|
|
57
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
58
|
+
}
|
|
59
|
+
if (!qrDataUrl) {
|
|
60
|
+
return { content: [{ type: "text", text: "Failed to generate QR code" }] };
|
|
61
|
+
}
|
|
62
|
+
// Login continues in background
|
|
63
|
+
loginPromise.then((result) => {
|
|
64
|
+
if (result.success) {
|
|
65
|
+
console.error("[mcp-telegram] Login successful");
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.error(`[mcp-telegram] Login failed: ${result.message}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
// Return as MCP image content + markdown image as fallback
|
|
72
|
+
const base64 = qrDataUrl.replace(/^data:image\/png;base64,/, "");
|
|
73
|
+
return {
|
|
74
|
+
content: [
|
|
75
|
+
{
|
|
76
|
+
type: "image",
|
|
77
|
+
data: base64,
|
|
78
|
+
mimeType: "image/png",
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: "text",
|
|
82
|
+
text: `Scan QR in Telegram: Settings → Devices → Link Desktop Device.\n\nIf image not visible: \n\nAfter scanning, check with telegram-status.`,
|
|
83
|
+
},
|
|
84
|
+
],
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
server.tool("telegram-send-message", "Send a message to a Telegram chat", {
|
|
88
|
+
chatId: z.string().describe("Chat ID or username (e.g. @username or numeric ID)"),
|
|
89
|
+
text: z.string().describe("Message text"),
|
|
90
|
+
replyTo: z.number().optional().describe("Message ID to reply to"),
|
|
91
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
|
|
92
|
+
}, async ({ chatId, text, replyTo, parseMode }) => {
|
|
93
|
+
const err = await requireConnection();
|
|
94
|
+
if (err)
|
|
95
|
+
return { content: [{ type: "text", text: err }] };
|
|
96
|
+
try {
|
|
97
|
+
await telegram.sendMessage(chatId, text, replyTo, parseMode);
|
|
98
|
+
return { content: [{ type: "text", text: `Message sent to ${chatId}` }] };
|
|
99
|
+
}
|
|
100
|
+
catch (e) {
|
|
101
|
+
return { content: [{ type: "text", text: `Send error: ${e.message}` }] };
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
server.tool("telegram-list-chats", "List Telegram chats", {
|
|
105
|
+
limit: z.number().default(20).describe("Number of chats to return"),
|
|
106
|
+
offsetDate: z.number().optional().describe("Unix timestamp offset for pagination"),
|
|
107
|
+
filterType: z.enum(["private", "group", "channel"]).optional().describe("Filter by chat type"),
|
|
108
|
+
}, async ({ limit, offsetDate, filterType }) => {
|
|
109
|
+
const err = await requireConnection();
|
|
110
|
+
if (err)
|
|
111
|
+
return { content: [{ type: "text", text: err }] };
|
|
112
|
+
try {
|
|
113
|
+
const dialogs = await telegram.getDialogs(limit, offsetDate, filterType);
|
|
114
|
+
const text = dialogs
|
|
115
|
+
.map((d) => `${d.type === "group" ? "G" : d.type === "channel" ? "C" : "P"} ${d.name} (${d.id}) ${d.unreadCount > 0 ? `[${d.unreadCount} unread]` : ""}`)
|
|
116
|
+
.join("\n");
|
|
117
|
+
return { content: [{ type: "text", text: text || "No chats" }] };
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
server.tool("telegram-read-messages", "Read recent messages from a Telegram chat", {
|
|
124
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
125
|
+
limit: z.number().default(10).describe("Number of messages to return"),
|
|
126
|
+
offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
|
|
127
|
+
minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
|
|
128
|
+
maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
|
|
129
|
+
}, async ({ chatId, limit, offsetId, minDate, maxDate }) => {
|
|
130
|
+
const err = await requireConnection();
|
|
131
|
+
if (err)
|
|
132
|
+
return { content: [{ type: "text", text: err }] };
|
|
133
|
+
try {
|
|
134
|
+
const messages = await telegram.getMessages(chatId, limit, offsetId, minDate, maxDate);
|
|
135
|
+
const text = messages
|
|
136
|
+
.map((m) => `[${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
137
|
+
.join("\n\n");
|
|
138
|
+
return { content: [{ type: "text", text: text || "No messages" }] };
|
|
139
|
+
}
|
|
140
|
+
catch (e) {
|
|
141
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
server.tool("telegram-search-chats", "Search for Telegram chats/users/channels by name or username", {
|
|
145
|
+
query: z.string().describe("Search query (name or username)"),
|
|
146
|
+
limit: z.number().default(10).describe("Max results"),
|
|
147
|
+
}, async ({ query, limit }) => {
|
|
148
|
+
const err = await requireConnection();
|
|
149
|
+
if (err)
|
|
150
|
+
return { content: [{ type: "text", text: err }] };
|
|
151
|
+
try {
|
|
152
|
+
const results = await telegram.searchChats(query, limit);
|
|
153
|
+
const text = results
|
|
154
|
+
.map((c) => `${c.type === "group" ? "G" : c.type === "channel" ? "C" : "P"} ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})`)
|
|
155
|
+
.join("\n");
|
|
156
|
+
return { content: [{ type: "text", text: text || "No results" }] };
|
|
157
|
+
}
|
|
158
|
+
catch (e) {
|
|
159
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
server.tool("telegram-search-messages", "Search messages in a Telegram chat by text", {
|
|
163
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
164
|
+
query: z.string().describe("Search text"),
|
|
165
|
+
limit: z.number().default(20).describe("Max results"),
|
|
166
|
+
minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
|
|
167
|
+
maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
|
|
168
|
+
}, async ({ chatId, query, limit, minDate, maxDate }) => {
|
|
169
|
+
const err = await requireConnection();
|
|
170
|
+
if (err)
|
|
171
|
+
return { content: [{ type: "text", text: err }] };
|
|
172
|
+
try {
|
|
173
|
+
const messages = await telegram.searchMessages(chatId, query, limit, minDate, maxDate);
|
|
174
|
+
const text = messages
|
|
175
|
+
.map((m) => `[${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}`)
|
|
176
|
+
.join("\n\n");
|
|
177
|
+
return { content: [{ type: "text", text: text || "No messages found" }] };
|
|
178
|
+
}
|
|
179
|
+
catch (e) {
|
|
180
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
server.tool("telegram-get-unread", "Get unread Telegram chats", {
|
|
184
|
+
limit: z.number().default(20).describe("Number of unread chats to return"),
|
|
185
|
+
}, async ({ limit }) => {
|
|
186
|
+
const err = await requireConnection();
|
|
187
|
+
if (err)
|
|
188
|
+
return { content: [{ type: "text", text: err }] };
|
|
189
|
+
try {
|
|
190
|
+
const dialogs = await telegram.getUnreadDialogs(limit);
|
|
191
|
+
const text = dialogs
|
|
192
|
+
.map((d) => `${d.type === "group" ? "G" : d.type === "channel" ? "C" : "P"} ${d.name} (${d.id}) [${d.unreadCount} unread]`)
|
|
193
|
+
.join("\n");
|
|
194
|
+
return { content: [{ type: "text", text: text || "No unread chats" }] };
|
|
195
|
+
}
|
|
196
|
+
catch (e) {
|
|
197
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
server.tool("telegram-mark-as-read", "Mark a Telegram chat as read", {
|
|
201
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
202
|
+
}, async ({ chatId }) => {
|
|
203
|
+
const err = await requireConnection();
|
|
204
|
+
if (err)
|
|
205
|
+
return { content: [{ type: "text", text: err }] };
|
|
206
|
+
try {
|
|
207
|
+
await telegram.markAsRead(chatId);
|
|
208
|
+
return { content: [{ type: "text", text: `Marked ${chatId} as read` }] };
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
server.tool("telegram-forward-message", "Forward messages between Telegram chats", {
|
|
215
|
+
fromChatId: z.string().describe("Source chat ID or username"),
|
|
216
|
+
toChatId: z.string().describe("Destination chat ID or username"),
|
|
217
|
+
messageIds: z.array(z.number()).describe("Array of message IDs to forward"),
|
|
218
|
+
}, async ({ fromChatId, toChatId, messageIds }) => {
|
|
219
|
+
const err = await requireConnection();
|
|
220
|
+
if (err)
|
|
221
|
+
return { content: [{ type: "text", text: err }] };
|
|
222
|
+
try {
|
|
223
|
+
await telegram.forwardMessage(fromChatId, toChatId, messageIds);
|
|
224
|
+
return {
|
|
225
|
+
content: [
|
|
226
|
+
{ type: "text", text: `Forwarded ${messageIds.length} message(s) from ${fromChatId} to ${toChatId}` },
|
|
227
|
+
],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
catch (e) {
|
|
231
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
server.tool("telegram-edit-message", "Edit a sent message in Telegram", {
|
|
235
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
236
|
+
messageId: z.number().describe("ID of the message to edit"),
|
|
237
|
+
text: z.string().describe("New message text"),
|
|
238
|
+
}, async ({ chatId, messageId, text }) => {
|
|
239
|
+
const err = await requireConnection();
|
|
240
|
+
if (err)
|
|
241
|
+
return { content: [{ type: "text", text: err }] };
|
|
242
|
+
try {
|
|
243
|
+
await telegram.editMessage(chatId, messageId, text);
|
|
244
|
+
return { content: [{ type: "text", text: `Message ${messageId} edited in ${chatId}` }] };
|
|
245
|
+
}
|
|
246
|
+
catch (e) {
|
|
247
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
server.tool("telegram-delete-message", "Delete messages in a Telegram chat", {
|
|
251
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
252
|
+
messageIds: z.array(z.number()).describe("Array of message IDs to delete"),
|
|
253
|
+
}, async ({ chatId, messageIds }) => {
|
|
254
|
+
const err = await requireConnection();
|
|
255
|
+
if (err)
|
|
256
|
+
return { content: [{ type: "text", text: err }] };
|
|
257
|
+
try {
|
|
258
|
+
await telegram.deleteMessages(chatId, messageIds);
|
|
259
|
+
return { content: [{ type: "text", text: `Deleted ${messageIds.length} message(s) in ${chatId}` }] };
|
|
260
|
+
}
|
|
261
|
+
catch (e) {
|
|
262
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
server.tool("telegram-get-chat-info", "Get detailed info about a Telegram chat", {
|
|
266
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
267
|
+
}, async ({ chatId }) => {
|
|
268
|
+
const err = await requireConnection();
|
|
269
|
+
if (err)
|
|
270
|
+
return { content: [{ type: "text", text: err }] };
|
|
271
|
+
try {
|
|
272
|
+
const info = await telegram.getChatInfo(chatId);
|
|
273
|
+
const lines = [
|
|
274
|
+
`Name: ${info.name}`,
|
|
275
|
+
`ID: ${info.id}`,
|
|
276
|
+
`Type: ${info.type}`,
|
|
277
|
+
...(info.username ? [`Username: @${info.username}`] : []),
|
|
278
|
+
...(info.description ? [`Description: ${info.description}`] : []),
|
|
279
|
+
...(info.membersCount != null ? [`Members: ${info.membersCount}`] : []),
|
|
280
|
+
];
|
|
281
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
282
|
+
}
|
|
283
|
+
catch (e) {
|
|
284
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
server.tool("telegram-send-file", "Send a file to a Telegram chat", {
|
|
288
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
289
|
+
filePath: z.string().describe("Absolute path to file"),
|
|
290
|
+
caption: z.string().optional().describe("File caption"),
|
|
291
|
+
}, async ({ chatId, filePath, caption }) => {
|
|
292
|
+
const err = await requireConnection();
|
|
293
|
+
if (err)
|
|
294
|
+
return { content: [{ type: "text", text: err }] };
|
|
295
|
+
try {
|
|
296
|
+
await telegram.sendFile(chatId, filePath, caption);
|
|
297
|
+
return { content: [{ type: "text", text: `File sent to ${chatId}` }] };
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
return { content: [{ type: "text", text: `Send file error: ${e.message}` }] };
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
server.tool("telegram-download-media", "Download media from a Telegram message", {
|
|
304
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
305
|
+
messageId: z.number().describe("Message ID containing media"),
|
|
306
|
+
downloadPath: z.string().describe("Absolute path to save file"),
|
|
307
|
+
}, async ({ chatId, messageId, downloadPath }) => {
|
|
308
|
+
const err = await requireConnection();
|
|
309
|
+
if (err)
|
|
310
|
+
return { content: [{ type: "text", text: err }] };
|
|
311
|
+
try {
|
|
312
|
+
const path = await telegram.downloadMedia(chatId, messageId, downloadPath);
|
|
313
|
+
return { content: [{ type: "text", text: `Media downloaded to ${path}` }] };
|
|
314
|
+
}
|
|
315
|
+
catch (e) {
|
|
316
|
+
return { content: [{ type: "text", text: `Download error: ${e.message}` }] };
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
server.tool("telegram-pin-message", "Pin a message in a Telegram chat", {
|
|
320
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
321
|
+
messageId: z.number().describe("Message ID to pin"),
|
|
322
|
+
silent: z.boolean().default(false).describe("Pin without notification"),
|
|
323
|
+
}, async ({ chatId, messageId, silent }) => {
|
|
324
|
+
const err = await requireConnection();
|
|
325
|
+
if (err)
|
|
326
|
+
return { content: [{ type: "text", text: err }] };
|
|
327
|
+
try {
|
|
328
|
+
await telegram.pinMessage(chatId, messageId, silent);
|
|
329
|
+
return { content: [{ type: "text", text: `Message ${messageId} pinned in ${chatId}` }] };
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
return { content: [{ type: "text", text: `Pin error: ${e.message}` }] };
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
server.tool("telegram-unpin-message", "Unpin a message in a Telegram chat", {
|
|
336
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
337
|
+
messageId: z.number().describe("Message ID to unpin"),
|
|
338
|
+
}, async ({ chatId, messageId }) => {
|
|
339
|
+
const err = await requireConnection();
|
|
340
|
+
if (err)
|
|
341
|
+
return { content: [{ type: "text", text: err }] };
|
|
342
|
+
try {
|
|
343
|
+
await telegram.unpinMessage(chatId, messageId);
|
|
344
|
+
return { content: [{ type: "text", text: `Message ${messageId} unpinned in ${chatId}` }] };
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
return { content: [{ type: "text", text: `Unpin error: ${e.message}` }] };
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
server.tool("telegram-get-contacts", "Get Telegram contacts list", {
|
|
351
|
+
limit: z.number().default(50).describe("Number of contacts to return"),
|
|
352
|
+
}, async ({ limit }) => {
|
|
353
|
+
const err = await requireConnection();
|
|
354
|
+
if (err)
|
|
355
|
+
return { content: [{ type: "text", text: err }] };
|
|
356
|
+
try {
|
|
357
|
+
const contacts = await telegram.getContacts(limit);
|
|
358
|
+
const text = contacts
|
|
359
|
+
.map((c) => `P ${c.name}${c.username ? ` (@${c.username})` : ""} (${c.id})${c.phone ? ` +${c.phone}` : ""}`)
|
|
360
|
+
.join("\n");
|
|
361
|
+
return { content: [{ type: "text", text: text || "No contacts" }] };
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
server.tool("telegram-get-chat-members", "Get members of a Telegram group or channel", {
|
|
368
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
369
|
+
limit: z.number().default(50).describe("Number of members to return"),
|
|
370
|
+
}, async ({ chatId, limit }) => {
|
|
371
|
+
const err = await requireConnection();
|
|
372
|
+
if (err)
|
|
373
|
+
return { content: [{ type: "text", text: err }] };
|
|
374
|
+
try {
|
|
375
|
+
const members = await telegram.getChatMembers(chatId, limit);
|
|
376
|
+
const text = members.map((m) => `${m.name}${m.username ? ` (@${m.username})` : ""} (${m.id})`).join("\n");
|
|
377
|
+
return { content: [{ type: "text", text: text || "No members found" }] };
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
server.tool("telegram-get-profile", "Get detailed profile info of a Telegram user", {
|
|
384
|
+
userId: z.string().describe("User ID or username"),
|
|
385
|
+
}, async ({ userId }) => {
|
|
386
|
+
const err = await requireConnection();
|
|
387
|
+
if (err)
|
|
388
|
+
return { content: [{ type: "text", text: err }] };
|
|
389
|
+
try {
|
|
390
|
+
const profile = await telegram.getProfile(userId);
|
|
391
|
+
const lines = [
|
|
392
|
+
`Name: ${profile.name}`,
|
|
393
|
+
`ID: ${profile.id}`,
|
|
394
|
+
...(profile.username ? [`Username: @${profile.username}`] : []),
|
|
395
|
+
...(profile.phone ? [`Phone: +${profile.phone}`] : []),
|
|
396
|
+
...(profile.bio ? [`Bio: ${profile.bio}`] : []),
|
|
397
|
+
`Photo: ${profile.photo ? "yes" : "no"}`,
|
|
398
|
+
...(profile.lastSeen ? [`Last seen: ${profile.lastSeen}`] : []),
|
|
399
|
+
];
|
|
400
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
401
|
+
}
|
|
402
|
+
catch (e) {
|
|
403
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }] };
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
// --- Start ---
|
|
407
|
+
async function main() {
|
|
408
|
+
// Try to auto-connect with saved session
|
|
409
|
+
await telegram.loadSession();
|
|
410
|
+
if (await telegram.connect()) {
|
|
411
|
+
const me = await telegram.getMe();
|
|
412
|
+
console.error(`[mcp-telegram] Auto-connected as @${me.username}`);
|
|
413
|
+
}
|
|
414
|
+
else if (telegram.lastError) {
|
|
415
|
+
console.error(`[mcp-telegram] ${telegram.lastError}`);
|
|
416
|
+
}
|
|
417
|
+
const transport = new StdioServerTransport();
|
|
418
|
+
await server.connect(transport);
|
|
419
|
+
console.error("[mcp-telegram] MCP server running on stdio");
|
|
420
|
+
}
|
|
421
|
+
main().catch((err) => {
|
|
422
|
+
console.error("[mcp-telegram] Fatal:", err);
|
|
423
|
+
process.exit(1);
|
|
424
|
+
});
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import { exec } from "node:child_process";
|
|
4
|
+
import { writeFile } from "node:fs/promises";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import QRCode from "qrcode";
|
|
8
|
+
import { TelegramService } from "./telegram-client.js";
|
|
9
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const QR_IMAGE_PATH = join(__dirname, "..", "qr-login.png");
|
|
11
|
+
const API_ID = Number(process.env.TELEGRAM_API_ID);
|
|
12
|
+
const API_HASH = process.env.TELEGRAM_API_HASH;
|
|
13
|
+
if (!API_ID || !API_HASH) {
|
|
14
|
+
console.error("Set TELEGRAM_API_ID and TELEGRAM_API_HASH in .env file");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
const telegram = new TelegramService(API_ID, API_HASH);
|
|
18
|
+
function openFile(path) {
|
|
19
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
20
|
+
exec(`${cmd} "${path}"`);
|
|
21
|
+
}
|
|
22
|
+
async function main() {
|
|
23
|
+
// Check if already connected
|
|
24
|
+
await telegram.loadSession();
|
|
25
|
+
if (await telegram.connect()) {
|
|
26
|
+
const me = await telegram.getMe();
|
|
27
|
+
console.log(`\nAlready connected as ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})\n`);
|
|
28
|
+
await telegram.disconnect();
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
console.log("\nStarting Telegram QR login...\n");
|
|
32
|
+
console.log("Scan the QR code in Telegram app:");
|
|
33
|
+
console.log(" Settings > Devices > Link Desktop Device\n");
|
|
34
|
+
const result = await telegram.startQrLogin(
|
|
35
|
+
// onQrDataUrl — save as PNG and open
|
|
36
|
+
async (dataUrl) => {
|
|
37
|
+
const base64 = dataUrl.replace(/^data:image\/png;base64,/, "");
|
|
38
|
+
await writeFile(QR_IMAGE_PATH, Buffer.from(base64, "base64"));
|
|
39
|
+
console.log(`QR saved: ${QR_IMAGE_PATH}`);
|
|
40
|
+
openFile(QR_IMAGE_PATH);
|
|
41
|
+
},
|
|
42
|
+
// onQrUrl — also show in terminal
|
|
43
|
+
async (url) => {
|
|
44
|
+
const terminalQr = await QRCode.toString(url, { type: "terminal", small: true });
|
|
45
|
+
console.log(terminalQr);
|
|
46
|
+
console.log("Waiting for scan...\n");
|
|
47
|
+
});
|
|
48
|
+
if (result.success) {
|
|
49
|
+
console.log("Login successful!");
|
|
50
|
+
const me = await telegram.getMe();
|
|
51
|
+
console.log(` Account: ${me.firstName ?? ""} (@${me.username ?? "unknown"}, id: ${me.id})\n`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log(`Error: ${result.message}\n`);
|
|
55
|
+
}
|
|
56
|
+
await telegram.disconnect();
|
|
57
|
+
}
|
|
58
|
+
main().catch(console.error);
|