@overpod/mcp-telegram 1.12.0 → 1.13.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/dist/index.js +2 -838
- package/dist/telegram-client.js +1 -1
- package/dist/tools/auth.d.ts +3 -0
- package/dist/tools/auth.js +72 -0
- package/dist/tools/chats.d.ts +3 -0
- package/dist/tools/chats.js +121 -0
- package/dist/tools/contacts.d.ts +3 -0
- package/dist/tools/contacts.js +135 -0
- package/dist/tools/extras.d.ts +3 -0
- package/dist/tools/extras.js +155 -0
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.js +16 -0
- package/dist/tools/media.d.ts +3 -0
- package/dist/tools/media.js +84 -0
- package/dist/tools/messages.d.ts +3 -0
- package/dist/tools/messages.js +208 -0
- package/dist/tools/reactions.d.ts +3 -0
- package/dist/tools/reactions.js +63 -0
- package/dist/tools/shared.d.ts +40 -0
- package/dist/tools/shared.js +30 -0
- package/package.json +1 -1
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { DESTRUCTIVE, fail, formatReactions, ok, READ_ONLY, requireConnection, sanitize, WRITE } from "./shared.js";
|
|
3
|
+
export function registerMessageTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-send-message", {
|
|
5
|
+
description: "Send a message to a Telegram chat",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
chatId: z.string().describe("Chat ID or username (e.g. @username or numeric ID)"),
|
|
8
|
+
text: z.string().describe("Message text"),
|
|
9
|
+
replyTo: z.number().optional().describe("Message ID to reply to"),
|
|
10
|
+
parseMode: z.enum(["md", "html"]).optional().describe("Message format: md (Markdown) or html"),
|
|
11
|
+
topicId: z.number().optional().describe("Forum topic ID to send message into (for groups with Topics enabled)"),
|
|
12
|
+
},
|
|
13
|
+
annotations: WRITE,
|
|
14
|
+
}, async ({ chatId, text, replyTo, parseMode, topicId }) => {
|
|
15
|
+
const err = await requireConnection(telegram);
|
|
16
|
+
if (err)
|
|
17
|
+
return fail(new Error(err));
|
|
18
|
+
try {
|
|
19
|
+
await telegram.sendMessage(chatId, text, replyTo, parseMode, topicId);
|
|
20
|
+
const dest = topicId ? `topic ${topicId} in ${chatId}` : chatId;
|
|
21
|
+
return ok(`Message sent to ${dest}`);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
return fail(e);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
server.registerTool("telegram-read-messages", {
|
|
28
|
+
description: "Read recent messages from a Telegram chat with sender names, dates, media info, and reactions",
|
|
29
|
+
inputSchema: {
|
|
30
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
31
|
+
limit: z.number().default(10).describe("Number of messages to return"),
|
|
32
|
+
offsetId: z.number().optional().describe("Message ID to start from (for pagination)"),
|
|
33
|
+
minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
|
|
34
|
+
maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
|
|
35
|
+
},
|
|
36
|
+
annotations: READ_ONLY,
|
|
37
|
+
}, async ({ chatId, limit, offsetId, minDate, maxDate }) => {
|
|
38
|
+
const err = await requireConnection(telegram);
|
|
39
|
+
if (err)
|
|
40
|
+
return fail(new Error(err));
|
|
41
|
+
try {
|
|
42
|
+
const messages = await telegram.getMessages(chatId, limit, offsetId, minDate, maxDate);
|
|
43
|
+
const text = messages
|
|
44
|
+
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
45
|
+
.join("\n\n");
|
|
46
|
+
return ok(sanitize(text) || "No messages");
|
|
47
|
+
}
|
|
48
|
+
catch (e) {
|
|
49
|
+
return fail(e);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
server.registerTool("telegram-search-messages", {
|
|
53
|
+
description: "Search messages in a specific Telegram chat by text",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
56
|
+
query: z.string().describe("Search text"),
|
|
57
|
+
limit: z.number().default(20).describe("Max results"),
|
|
58
|
+
minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
|
|
59
|
+
maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
|
|
60
|
+
},
|
|
61
|
+
annotations: READ_ONLY,
|
|
62
|
+
}, async ({ chatId, query, limit, minDate, maxDate }) => {
|
|
63
|
+
const err = await requireConnection(telegram);
|
|
64
|
+
if (err)
|
|
65
|
+
return fail(new Error(err));
|
|
66
|
+
try {
|
|
67
|
+
const messages = await telegram.searchMessages(chatId, query, limit, minDate, maxDate);
|
|
68
|
+
const text = messages
|
|
69
|
+
.map((m) => `[#${m.id}] [${m.date}] ${m.sender}: ${m.text}${m.media ? ` [${m.media.type}${m.media.fileName ? `: ${m.media.fileName}` : ""}]` : ""}${formatReactions(m.reactions)}`)
|
|
70
|
+
.join("\n\n");
|
|
71
|
+
return ok(sanitize(text) || "No messages found");
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return fail(e);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
server.registerTool("telegram-search-global", {
|
|
78
|
+
description: "Search messages globally across all public Telegram chats and channels",
|
|
79
|
+
inputSchema: {
|
|
80
|
+
query: z.string().describe("Search text"),
|
|
81
|
+
limit: z.number().default(20).describe("Max results"),
|
|
82
|
+
minDate: z.number().optional().describe("Unix timestamp: only messages after this date"),
|
|
83
|
+
maxDate: z.number().optional().describe("Unix timestamp: only messages before this date"),
|
|
84
|
+
},
|
|
85
|
+
annotations: READ_ONLY,
|
|
86
|
+
}, async ({ query, limit, minDate, maxDate }) => {
|
|
87
|
+
const err = await requireConnection(telegram);
|
|
88
|
+
if (err)
|
|
89
|
+
return fail(new Error(err));
|
|
90
|
+
try {
|
|
91
|
+
const messages = await telegram.searchGlobal(query, limit, minDate, maxDate);
|
|
92
|
+
const text = messages
|
|
93
|
+
.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)}`)
|
|
94
|
+
.join("\n\n");
|
|
95
|
+
return ok(sanitize(text) || "No messages found");
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return fail(e);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
server.registerTool("telegram-edit-message", {
|
|
102
|
+
description: "Edit a previously sent message in Telegram",
|
|
103
|
+
inputSchema: {
|
|
104
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
105
|
+
messageId: z.number().describe("ID of the message to edit"),
|
|
106
|
+
text: z.string().describe("New message text"),
|
|
107
|
+
},
|
|
108
|
+
annotations: WRITE,
|
|
109
|
+
}, async ({ chatId, messageId, text }) => {
|
|
110
|
+
const err = await requireConnection(telegram);
|
|
111
|
+
if (err)
|
|
112
|
+
return fail(new Error(err));
|
|
113
|
+
try {
|
|
114
|
+
await telegram.editMessage(chatId, messageId, text);
|
|
115
|
+
return ok(`Message ${messageId} edited in ${chatId}`);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return fail(e);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
server.registerTool("telegram-delete-message", {
|
|
122
|
+
description: "Delete messages in a Telegram chat",
|
|
123
|
+
inputSchema: {
|
|
124
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
125
|
+
messageIds: z.array(z.number()).describe("Array of message IDs to delete"),
|
|
126
|
+
},
|
|
127
|
+
annotations: DESTRUCTIVE,
|
|
128
|
+
}, async ({ chatId, messageIds }) => {
|
|
129
|
+
const err = await requireConnection(telegram);
|
|
130
|
+
if (err)
|
|
131
|
+
return fail(new Error(err));
|
|
132
|
+
try {
|
|
133
|
+
await telegram.deleteMessages(chatId, messageIds);
|
|
134
|
+
return ok(`Deleted ${messageIds.length} message(s) in ${chatId}`);
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
return fail(e);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
server.registerTool("telegram-forward-message", {
|
|
141
|
+
description: "Forward messages between Telegram chats",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
fromChatId: z.string().describe("Source chat ID or username"),
|
|
144
|
+
toChatId: z.string().describe("Destination chat ID or username"),
|
|
145
|
+
messageIds: z.array(z.number()).describe("Array of message IDs to forward"),
|
|
146
|
+
},
|
|
147
|
+
annotations: WRITE,
|
|
148
|
+
}, async ({ fromChatId, toChatId, messageIds }) => {
|
|
149
|
+
const err = await requireConnection(telegram);
|
|
150
|
+
if (err)
|
|
151
|
+
return fail(new Error(err));
|
|
152
|
+
try {
|
|
153
|
+
await telegram.forwardMessage(fromChatId, toChatId, messageIds);
|
|
154
|
+
return ok(`Forwarded ${messageIds.length} message(s) from ${fromChatId} to ${toChatId}`);
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
return fail(e);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
server.registerTool("telegram-get-unread", {
|
|
161
|
+
description: "Get chats with unread messages. Forums show per-topic unread breakdown",
|
|
162
|
+
inputSchema: {
|
|
163
|
+
limit: z.number().default(20).describe("Number of unread chats to return"),
|
|
164
|
+
},
|
|
165
|
+
annotations: READ_ONLY,
|
|
166
|
+
}, async ({ limit }) => {
|
|
167
|
+
const err = await requireConnection(telegram);
|
|
168
|
+
if (err)
|
|
169
|
+
return fail(new Error(err));
|
|
170
|
+
try {
|
|
171
|
+
const dialogs = await telegram.getUnreadDialogs(limit);
|
|
172
|
+
const text = dialogs
|
|
173
|
+
.map((d) => {
|
|
174
|
+
const prefix = d.type === "group" ? "G" : d.type === "channel" ? "C" : "P";
|
|
175
|
+
const botTag = d.isBot ? " [bot]" : "";
|
|
176
|
+
const contactTag = d.type === "private" && d.isContact === false ? " [not in contacts]" : "";
|
|
177
|
+
const forumTag = d.forum ? " [forum]" : "";
|
|
178
|
+
let line = `${prefix} ${d.name} (${d.id})${botTag}${contactTag}${forumTag} [${d.unreadCount} unread]`;
|
|
179
|
+
if (d.topics && d.topics.length > 0) {
|
|
180
|
+
const topicLines = d.topics.map((t) => ` # ${t.title} [${t.unreadCount} unread]`);
|
|
181
|
+
line += `\n${topicLines.join("\n")}`;
|
|
182
|
+
}
|
|
183
|
+
return line;
|
|
184
|
+
})
|
|
185
|
+
.join("\n");
|
|
186
|
+
return ok(sanitize(text) || "No unread chats");
|
|
187
|
+
}
|
|
188
|
+
catch (e) {
|
|
189
|
+
return fail(e);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
server.registerTool("telegram-mark-as-read", {
|
|
193
|
+
description: "Mark a Telegram chat as read",
|
|
194
|
+
inputSchema: { chatId: z.string().describe("Chat ID or username") },
|
|
195
|
+
annotations: WRITE,
|
|
196
|
+
}, async ({ chatId }) => {
|
|
197
|
+
const err = await requireConnection(telegram);
|
|
198
|
+
if (err)
|
|
199
|
+
return fail(new Error(err));
|
|
200
|
+
try {
|
|
201
|
+
await telegram.markAsRead(chatId);
|
|
202
|
+
return ok(`Marked ${chatId} as read`);
|
|
203
|
+
}
|
|
204
|
+
catch (e) {
|
|
205
|
+
return fail(e);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, WRITE } from "./shared.js";
|
|
3
|
+
export function registerReactionTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-send-reaction", {
|
|
5
|
+
description: "Send emoji reaction(s) to a message. Supports multiple reactions and adding to existing ones. Omit emoji to remove all reactions",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
8
|
+
messageId: z.number().describe("Message ID to react to"),
|
|
9
|
+
emoji: z
|
|
10
|
+
.union([z.string(), z.array(z.string())])
|
|
11
|
+
.optional()
|
|
12
|
+
.describe("Reaction emoji(s): single '👍' or array ['👍','🔥']. Omit to remove all reactions"),
|
|
13
|
+
addToExisting: z
|
|
14
|
+
.boolean()
|
|
15
|
+
.default(false)
|
|
16
|
+
.describe("If true, add reaction(s) to existing ones instead of replacing"),
|
|
17
|
+
},
|
|
18
|
+
annotations: WRITE,
|
|
19
|
+
}, async ({ chatId, messageId, emoji, addToExisting }) => {
|
|
20
|
+
const err = await requireConnection(telegram);
|
|
21
|
+
if (err)
|
|
22
|
+
return fail(new Error(err));
|
|
23
|
+
try {
|
|
24
|
+
const updated = await telegram.sendReaction(chatId, messageId, emoji, addToExisting);
|
|
25
|
+
const emojiStr = Array.isArray(emoji) ? emoji.join("") : emoji;
|
|
26
|
+
const action = emoji ? `Reacted ${emojiStr} to` : "Removed reactions from";
|
|
27
|
+
const reactionsInfo = updated
|
|
28
|
+
? ` | Reactions: ${updated.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`).join(" ")}`
|
|
29
|
+
: "";
|
|
30
|
+
return ok(`${action} message ${messageId} in ${chatId}${reactionsInfo}`);
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return fail(e);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
server.registerTool("telegram-get-reactions", {
|
|
37
|
+
description: "Get detailed reaction info for a message: which reactions, counts, and who reacted (when visible)",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
chatId: z.string().describe("Chat ID or username"),
|
|
40
|
+
messageId: z.number().describe("Message ID to get reactions for"),
|
|
41
|
+
},
|
|
42
|
+
annotations: READ_ONLY,
|
|
43
|
+
}, async ({ chatId, messageId }) => {
|
|
44
|
+
const err = await requireConnection(telegram);
|
|
45
|
+
if (err)
|
|
46
|
+
return fail(new Error(err));
|
|
47
|
+
try {
|
|
48
|
+
const result = await telegram.getMessageReactions(chatId, messageId);
|
|
49
|
+
if (result.reactions.length === 0) {
|
|
50
|
+
return ok(`No reactions on message ${messageId}`);
|
|
51
|
+
}
|
|
52
|
+
const lines = result.reactions.map((r) => {
|
|
53
|
+
const usersStr = r.users.length > 0 ? `: ${r.users.map((u) => u.name).join(", ")}` : "";
|
|
54
|
+
return `${r.emoji} × ${r.count}${usersStr}`;
|
|
55
|
+
});
|
|
56
|
+
lines.push(`\nTotal: ${result.total} reactions`);
|
|
57
|
+
return ok(lines.join("\n"));
|
|
58
|
+
}
|
|
59
|
+
catch (e) {
|
|
60
|
+
return fail(e);
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { TelegramService } from "../telegram-client.js";
|
|
2
|
+
/** MCP tool annotation presets */
|
|
3
|
+
export declare const READ_ONLY: {
|
|
4
|
+
readonly readOnlyHint: true;
|
|
5
|
+
readonly openWorldHint: true;
|
|
6
|
+
};
|
|
7
|
+
export declare const WRITE: {
|
|
8
|
+
readonly readOnlyHint: false;
|
|
9
|
+
readonly openWorldHint: true;
|
|
10
|
+
};
|
|
11
|
+
export declare const DESTRUCTIVE: {
|
|
12
|
+
readonly readOnlyHint: false;
|
|
13
|
+
readonly destructiveHint: true;
|
|
14
|
+
readonly openWorldHint: true;
|
|
15
|
+
};
|
|
16
|
+
/** Helper: success response */
|
|
17
|
+
export declare function ok(text: string): {
|
|
18
|
+
content: {
|
|
19
|
+
type: "text";
|
|
20
|
+
text: string;
|
|
21
|
+
}[];
|
|
22
|
+
};
|
|
23
|
+
/** Helper: error response with isError flag */
|
|
24
|
+
export declare function fail(e: unknown): {
|
|
25
|
+
content: {
|
|
26
|
+
type: "text";
|
|
27
|
+
text: string;
|
|
28
|
+
}[];
|
|
29
|
+
isError: true;
|
|
30
|
+
};
|
|
31
|
+
/** Remove unpaired UTF-16 surrogates that break JSON serialization */
|
|
32
|
+
export declare function sanitize(text: string): string;
|
|
33
|
+
/** Format reactions array into compact text like: [👍×5 ❤️×3(me) 🔥×1] */
|
|
34
|
+
export declare function formatReactions(reactions?: {
|
|
35
|
+
emoji: string;
|
|
36
|
+
count: number;
|
|
37
|
+
me: boolean;
|
|
38
|
+
}[]): string;
|
|
39
|
+
/** Try to connect, return error text if failed */
|
|
40
|
+
export declare function requireConnection(telegram: TelegramService): Promise<string | null>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** MCP tool annotation presets */
|
|
2
|
+
export const READ_ONLY = { readOnlyHint: true, openWorldHint: true };
|
|
3
|
+
export const WRITE = { readOnlyHint: false, openWorldHint: true };
|
|
4
|
+
export const DESTRUCTIVE = { readOnlyHint: false, destructiveHint: true, openWorldHint: true };
|
|
5
|
+
/** Helper: success response */
|
|
6
|
+
export function ok(text) {
|
|
7
|
+
return { content: [{ type: "text", text }] };
|
|
8
|
+
}
|
|
9
|
+
/** Helper: error response with isError flag */
|
|
10
|
+
export function fail(e) {
|
|
11
|
+
return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
|
|
12
|
+
}
|
|
13
|
+
/** Remove unpaired UTF-16 surrogates that break JSON serialization */
|
|
14
|
+
export function sanitize(text) {
|
|
15
|
+
return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
16
|
+
}
|
|
17
|
+
/** Format reactions array into compact text like: [👍×5 ❤️×3(me) 🔥×1] */
|
|
18
|
+
export function formatReactions(reactions) {
|
|
19
|
+
if (!reactions?.length)
|
|
20
|
+
return "";
|
|
21
|
+
const parts = reactions.map((r) => `${r.emoji}×${r.count}${r.me ? "(me)" : ""}`);
|
|
22
|
+
return ` [${parts.join(" ")}]`;
|
|
23
|
+
}
|
|
24
|
+
/** Try to connect, return error text if failed */
|
|
25
|
+
export async function requireConnection(telegram) {
|
|
26
|
+
if (await telegram.ensureConnected())
|
|
27
|
+
return null;
|
|
28
|
+
const reason = telegram.lastError ? ` ${telegram.lastError}` : "";
|
|
29
|
+
return `Not connected to Telegram.${reason} Run telegram-login first.`;
|
|
30
|
+
}
|
package/package.json
CHANGED