@overpod/mcp-telegram 1.25.0 → 1.26.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/CHANGELOG.md +35 -0
- package/README.md +35 -5
- package/dist/__tests__/approve-join-request.test.d.ts +1 -0
- package/dist/__tests__/approve-join-request.test.js +107 -0
- package/dist/__tests__/boosts.test.d.ts +1 -0
- package/dist/__tests__/boosts.test.js +310 -0
- package/dist/__tests__/broadcast-stats.test.d.ts +1 -0
- package/dist/__tests__/broadcast-stats.test.js +172 -0
- package/dist/__tests__/business-chat-links.test.d.ts +1 -0
- package/dist/__tests__/business-chat-links.test.js +102 -0
- package/dist/__tests__/get-message-buttons.test.d.ts +1 -0
- package/dist/__tests__/get-message-buttons.test.js +122 -0
- package/dist/__tests__/group-calls.test.d.ts +1 -0
- package/dist/__tests__/group-calls.test.js +503 -0
- package/dist/__tests__/inline-query-send.test.d.ts +1 -0
- package/dist/__tests__/inline-query-send.test.js +94 -0
- package/dist/__tests__/inline-query.test.d.ts +1 -0
- package/dist/__tests__/inline-query.test.js +115 -0
- package/dist/__tests__/megagroup-stats.test.d.ts +1 -0
- package/dist/__tests__/megagroup-stats.test.js +166 -0
- package/dist/__tests__/press-button.test.d.ts +1 -0
- package/dist/__tests__/press-button.test.js +123 -0
- package/dist/__tests__/quick-replies.test.d.ts +1 -0
- package/dist/__tests__/quick-replies.test.js +245 -0
- package/dist/__tests__/set-chat-reactions.test.d.ts +1 -0
- package/dist/__tests__/set-chat-reactions.test.js +129 -0
- package/dist/__tests__/stars-status.test.d.ts +1 -0
- package/dist/__tests__/stars-status.test.js +205 -0
- package/dist/__tests__/stars-transactions.test.d.ts +1 -0
- package/dist/__tests__/stars-transactions.test.js +82 -0
- package/dist/__tests__/stories.test.d.ts +1 -0
- package/dist/__tests__/stories.test.js +361 -0
- package/dist/__tests__/toggle-anti-spam.test.d.ts +1 -0
- package/dist/__tests__/toggle-anti-spam.test.js +80 -0
- package/dist/__tests__/toggle-channel-signatures.test.d.ts +1 -0
- package/dist/__tests__/toggle-channel-signatures.test.js +80 -0
- package/dist/__tests__/toggle-forum-mode.test.d.ts +1 -0
- package/dist/__tests__/toggle-forum-mode.test.js +80 -0
- package/dist/__tests__/toggle-prehistory-hidden.test.d.ts +1 -0
- package/dist/__tests__/toggle-prehistory-hidden.test.js +80 -0
- package/dist/__tests__/updates.test.d.ts +1 -0
- package/dist/__tests__/updates.test.js +221 -0
- package/dist/rate-limiter.d.ts +8 -2
- package/dist/rate-limiter.js +15 -8
- package/dist/telegram-client.d.ts +580 -0
- package/dist/telegram-client.js +1322 -0
- package/dist/tools/account.js +16 -0
- package/dist/tools/boosts.d.ts +3 -0
- package/dist/tools/boosts.js +65 -0
- package/dist/tools/chats.js +150 -0
- package/dist/tools/group-calls.d.ts +4 -0
- package/dist/tools/group-calls.js +77 -0
- package/dist/tools/index.js +10 -0
- package/dist/tools/messages.js +192 -0
- package/dist/tools/quick-replies.d.ts +4 -0
- package/dist/tools/quick-replies.js +58 -0
- package/dist/tools/reactions.js +43 -0
- package/dist/tools/stars.d.ts +4 -0
- package/dist/tools/stars.js +71 -0
- package/dist/tools/stories.d.ts +3 -0
- package/dist/tools/stories.js +107 -0
- package/package.json +1 -1
package/dist/tools/account.js
CHANGED
|
@@ -369,4 +369,20 @@ export function registerAccountTools(server, telegram) {
|
|
|
369
369
|
return fail(e);
|
|
370
370
|
}
|
|
371
371
|
});
|
|
372
|
+
server.registerTool("telegram-get-business-chat-links", {
|
|
373
|
+
description: "List Telegram Business chat links configured for the account (account.GetBusinessChatLinks). Each entry includes the t.me/... link, the prefilled message, optional title (admin-facing label), views count, and entityCount (number of formatting entities in the message). Requires a Telegram Business-enabled account — returns an empty list when the account has none configured. Read-only.",
|
|
374
|
+
inputSchema: {},
|
|
375
|
+
annotations: READ_ONLY,
|
|
376
|
+
}, async () => {
|
|
377
|
+
const err = await requireConnection(telegram);
|
|
378
|
+
if (err)
|
|
379
|
+
return fail(new Error(err));
|
|
380
|
+
try {
|
|
381
|
+
const result = await telegram.getBusinessChatLinks();
|
|
382
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
383
|
+
}
|
|
384
|
+
catch (e) {
|
|
385
|
+
return fail(e);
|
|
386
|
+
}
|
|
387
|
+
});
|
|
372
388
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, sanitize } from "./shared.js";
|
|
3
|
+
export function registerBoostTools(server, telegram) {
|
|
4
|
+
server.registerTool("telegram-get-my-boosts", {
|
|
5
|
+
description: "List the user's premium boost slots (premium.GetMyBoosts). Each entry includes slot index, the peer it currently boosts (if any), the date the boost was applied, expiration timestamp, and cooldownUntilDate (when a slot can be reassigned). Premium users have multiple slots; non-Premium users typically have a single slot. Read-only.",
|
|
6
|
+
inputSchema: {},
|
|
7
|
+
annotations: READ_ONLY,
|
|
8
|
+
}, async () => {
|
|
9
|
+
const err = await requireConnection(telegram);
|
|
10
|
+
if (err)
|
|
11
|
+
return fail(new Error(err));
|
|
12
|
+
try {
|
|
13
|
+
const result = await telegram.getMyBoosts();
|
|
14
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
15
|
+
}
|
|
16
|
+
catch (e) {
|
|
17
|
+
return fail(e);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
server.registerTool("telegram-get-boosts-status", {
|
|
21
|
+
description: "Fetch the boost status of a channel/supergroup (premium.GetBoostsStatus). Returns current boost level, total boosts, progress to next level (currentLevelBoosts/nextLevelBoosts), giftBoosts, premiumAudience ratio, public boostUrl, and whether the current user is boosting (myBoost + myBoostSlots). Also includes any prepaidGiveaways attached to the chat. Read-only.",
|
|
22
|
+
inputSchema: {
|
|
23
|
+
chat: z.string().describe("Channel or supergroup to query — id, @username, or display name fragment"),
|
|
24
|
+
},
|
|
25
|
+
annotations: READ_ONLY,
|
|
26
|
+
}, async ({ chat }) => {
|
|
27
|
+
const err = await requireConnection(telegram);
|
|
28
|
+
if (err)
|
|
29
|
+
return fail(new Error(err));
|
|
30
|
+
try {
|
|
31
|
+
const result = await telegram.getBoostsStatus(chat);
|
|
32
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
33
|
+
}
|
|
34
|
+
catch (e) {
|
|
35
|
+
return fail(e);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
server.registerTool("telegram-get-boosts-list", {
|
|
39
|
+
description: "List the boosts applied to a channel/supergroup (premium.GetBoostsList). Returns paginated boost entries with id, userId (or undefined for anonymous gift boosts), date, expires, flags (gift, giveaway, unclaimed), optional giveawayMsgId, usedGiftSlug, multiplier, and stars. Requires channel admin permissions. Supports pagination via nextOffset and an optional gifts filter to show only gift boosts. Read-only.",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
chat: z.string().describe("Channel or supergroup to query — id, @username, or display name fragment"),
|
|
42
|
+
gifts: z.boolean().optional().describe("If true, return only gift boosts"),
|
|
43
|
+
offset: z.string().optional().describe("Pagination cursor returned as nextOffset from the previous call"),
|
|
44
|
+
limit: z
|
|
45
|
+
.number()
|
|
46
|
+
.int()
|
|
47
|
+
.min(1)
|
|
48
|
+
.max(100)
|
|
49
|
+
.optional()
|
|
50
|
+
.describe("Max boosts to return per page (default 50, max 100)"),
|
|
51
|
+
},
|
|
52
|
+
annotations: READ_ONLY,
|
|
53
|
+
}, async ({ chat, gifts, offset, limit }) => {
|
|
54
|
+
const err = await requireConnection(telegram);
|
|
55
|
+
if (err)
|
|
56
|
+
return fail(new Error(err));
|
|
57
|
+
try {
|
|
58
|
+
const result = await telegram.getBoostsList(chat, { gifts, offset, limit });
|
|
59
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return fail(e);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
package/dist/tools/chats.js
CHANGED
|
@@ -473,6 +473,110 @@ export function registerChatTools(server, telegram) {
|
|
|
473
473
|
return fail(e);
|
|
474
474
|
}
|
|
475
475
|
});
|
|
476
|
+
server.registerTool("telegram-toggle-channel-signatures", {
|
|
477
|
+
description: "Enable or disable author signatures on broadcast channel posts. Channel admin required; not supported for supergroups",
|
|
478
|
+
inputSchema: {
|
|
479
|
+
chatId: z.string().describe("Channel ID or username"),
|
|
480
|
+
enabled: z.boolean().describe("true to enable author signatures, false to disable"),
|
|
481
|
+
},
|
|
482
|
+
annotations: WRITE,
|
|
483
|
+
}, async ({ chatId, enabled }) => {
|
|
484
|
+
const err = await requireConnection(telegram);
|
|
485
|
+
if (err)
|
|
486
|
+
return fail(new Error(err));
|
|
487
|
+
try {
|
|
488
|
+
await telegram.toggleChannelSignatures(chatId, enabled);
|
|
489
|
+
return ok(JSON.stringify({ ok: true, signaturesEnabled: enabled }));
|
|
490
|
+
}
|
|
491
|
+
catch (e) {
|
|
492
|
+
return fail(e);
|
|
493
|
+
}
|
|
494
|
+
});
|
|
495
|
+
server.registerTool("telegram-toggle-anti-spam", {
|
|
496
|
+
description: "Enable or disable aggressive anti-spam filtering in a supergroup. Supergroup only (not broadcast channels); requires admin with ban_users permission",
|
|
497
|
+
inputSchema: {
|
|
498
|
+
chatId: z.string().describe("Supergroup ID or username"),
|
|
499
|
+
enabled: z.boolean().describe("true to enable aggressive anti-spam, false to disable"),
|
|
500
|
+
},
|
|
501
|
+
annotations: WRITE,
|
|
502
|
+
}, async ({ chatId, enabled }) => {
|
|
503
|
+
const err = await requireConnection(telegram);
|
|
504
|
+
if (err)
|
|
505
|
+
return fail(new Error(err));
|
|
506
|
+
try {
|
|
507
|
+
await telegram.toggleAntiSpam(chatId, enabled);
|
|
508
|
+
return ok(`${enabled ? "Enabled" : "Disabled"} aggressive anti-spam in ${chatId}`);
|
|
509
|
+
}
|
|
510
|
+
catch (e) {
|
|
511
|
+
return fail(e);
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
server.registerTool("telegram-toggle-forum-mode", {
|
|
515
|
+
description: "Enable or disable forum/topics mode in a supergroup. Supergroup only; requires creator or admin. " +
|
|
516
|
+
"WARNING: disabling removes ALL existing topics — pass confirm=true to proceed with disable",
|
|
517
|
+
inputSchema: {
|
|
518
|
+
chatId: z.string().describe("Supergroup ID or username"),
|
|
519
|
+
enabled: z.boolean().describe("true to enable forum mode, false to disable"),
|
|
520
|
+
confirm: z
|
|
521
|
+
.boolean()
|
|
522
|
+
.optional()
|
|
523
|
+
.describe("Must be true when disabling (enabled=false) — disabling deletes all existing topics"),
|
|
524
|
+
},
|
|
525
|
+
annotations: DESTRUCTIVE,
|
|
526
|
+
}, async ({ chatId, enabled, confirm }) => {
|
|
527
|
+
const err = await requireConnection(telegram);
|
|
528
|
+
if (err)
|
|
529
|
+
return fail(new Error(err));
|
|
530
|
+
if (!enabled && confirm !== true) {
|
|
531
|
+
return fail(new Error("Disabling forum mode deletes all existing topics. Pass confirm=true to proceed with this destructive action."));
|
|
532
|
+
}
|
|
533
|
+
try {
|
|
534
|
+
await telegram.toggleForumMode(chatId, enabled);
|
|
535
|
+
return ok(`${enabled ? "Enabled" : "Disabled"} forum mode in ${chatId}`);
|
|
536
|
+
}
|
|
537
|
+
catch (e) {
|
|
538
|
+
return fail(e);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
server.registerTool("telegram-toggle-prehistory-hidden", {
|
|
542
|
+
description: "Toggle pre-history visibility for new members in a supergroup. When hidden=true, new joiners cannot see messages posted before they joined. Supergroup only; requires admin",
|
|
543
|
+
inputSchema: {
|
|
544
|
+
chatId: z.string().describe("Supergroup ID or username"),
|
|
545
|
+
hidden: z.boolean().describe("true to hide prior history from new members, false to make it visible"),
|
|
546
|
+
},
|
|
547
|
+
annotations: WRITE,
|
|
548
|
+
}, async ({ chatId, hidden }) => {
|
|
549
|
+
const err = await requireConnection(telegram);
|
|
550
|
+
if (err)
|
|
551
|
+
return fail(new Error(err));
|
|
552
|
+
try {
|
|
553
|
+
await telegram.togglePrehistoryHidden(chatId, hidden);
|
|
554
|
+
return ok(`${hidden ? "Hid" : "Revealed"} prehistory for new members in ${chatId}`);
|
|
555
|
+
}
|
|
556
|
+
catch (e) {
|
|
557
|
+
return fail(e);
|
|
558
|
+
}
|
|
559
|
+
});
|
|
560
|
+
server.registerTool("telegram-approve-join-request", {
|
|
561
|
+
description: "Approve or deny a pending join request for a supergroup or channel (basic groups are not supported). Admin with invite_users permission required",
|
|
562
|
+
inputSchema: {
|
|
563
|
+
chatId: z.string().describe("Chat ID or username where the join request is pending"),
|
|
564
|
+
userId: z.string().describe("User ID or username of the requesting user"),
|
|
565
|
+
approved: z.boolean().describe("true to approve the join request, false to deny"),
|
|
566
|
+
},
|
|
567
|
+
annotations: WRITE,
|
|
568
|
+
}, async ({ chatId, userId, approved }) => {
|
|
569
|
+
const err = await requireConnection(telegram);
|
|
570
|
+
if (err)
|
|
571
|
+
return fail(new Error(err));
|
|
572
|
+
try {
|
|
573
|
+
await telegram.approveChatJoinRequest(chatId, userId, approved);
|
|
574
|
+
return ok(`${approved ? "Approved" : "Denied"} join request from ${userId} in ${chatId}`);
|
|
575
|
+
}
|
|
576
|
+
catch (e) {
|
|
577
|
+
return fail(e);
|
|
578
|
+
}
|
|
579
|
+
});
|
|
476
580
|
server.registerTool("telegram-create-topic", {
|
|
477
581
|
description: "Create a new forum topic in a forum-enabled supergroup",
|
|
478
582
|
inputSchema: {
|
|
@@ -546,6 +650,52 @@ export function registerChatTools(server, telegram) {
|
|
|
546
650
|
return fail(e);
|
|
547
651
|
}
|
|
548
652
|
});
|
|
653
|
+
server.registerTool("telegram-get-broadcast-stats", {
|
|
654
|
+
description: "Get broadcast channel statistics: followers, views/shares/reactions per post & story, notification percent, recent post interactions. Broadcast channels only (use telegram-get-megagroup-stats for supergroups). Admin rights required; some channels may require Telegram Premium to expose stats",
|
|
655
|
+
inputSchema: {
|
|
656
|
+
chatId: z.string().describe("Broadcast channel ID or username"),
|
|
657
|
+
includeGraphs: z
|
|
658
|
+
.boolean()
|
|
659
|
+
.default(false)
|
|
660
|
+
.describe("Include raw graph data for each series (growth, followers, interactions, etc.). Default false — returns only aggregate numbers + metadata"),
|
|
661
|
+
dark: z.boolean().default(false).describe("Prefer dark-theme palette when Telegram renders graphs"),
|
|
662
|
+
},
|
|
663
|
+
annotations: READ_ONLY,
|
|
664
|
+
}, async ({ chatId, includeGraphs, dark }) => {
|
|
665
|
+
const err = await requireConnection(telegram);
|
|
666
|
+
if (err)
|
|
667
|
+
return fail(new Error(err));
|
|
668
|
+
try {
|
|
669
|
+
const stats = await telegram.getBroadcastStats(chatId, { dark, includeGraphs });
|
|
670
|
+
return ok(sanitize(JSON.stringify(stats)));
|
|
671
|
+
}
|
|
672
|
+
catch (e) {
|
|
673
|
+
return fail(e);
|
|
674
|
+
}
|
|
675
|
+
});
|
|
676
|
+
server.registerTool("telegram-get-megagroup-stats", {
|
|
677
|
+
description: "Get supergroup statistics: members, messages, viewers, posters (current vs previous period), top posters/admins/inviters. Supergroups only (use telegram-get-broadcast-stats for broadcast channels). Admin rights required. Telegram rate-limits this endpoint to roughly 1 request per 30 minutes per channel — expect FLOOD_WAIT on rapid repeat calls",
|
|
678
|
+
inputSchema: {
|
|
679
|
+
chatId: z.string().describe("Supergroup ID or username"),
|
|
680
|
+
includeGraphs: z
|
|
681
|
+
.boolean()
|
|
682
|
+
.default(false)
|
|
683
|
+
.describe("Include raw graph data for each series (growth, members, messages, actions, top hours, weekdays, etc.). Default false — returns only aggregate numbers + top lists"),
|
|
684
|
+
dark: z.boolean().default(false).describe("Prefer dark-theme palette when Telegram renders graphs"),
|
|
685
|
+
},
|
|
686
|
+
annotations: READ_ONLY,
|
|
687
|
+
}, async ({ chatId, includeGraphs, dark }) => {
|
|
688
|
+
const err = await requireConnection(telegram);
|
|
689
|
+
if (err)
|
|
690
|
+
return fail(new Error(err));
|
|
691
|
+
try {
|
|
692
|
+
const stats = await telegram.getMegagroupStats(chatId, { dark, includeGraphs });
|
|
693
|
+
return ok(sanitize(JSON.stringify(stats)));
|
|
694
|
+
}
|
|
695
|
+
catch (e) {
|
|
696
|
+
return fail(e);
|
|
697
|
+
}
|
|
698
|
+
});
|
|
549
699
|
server.registerTool("telegram-delete-topic", {
|
|
550
700
|
description: "Delete a forum topic and all its message history",
|
|
551
701
|
inputSchema: {
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { TelegramService } from "../telegram-client.js";
|
|
3
|
+
export declare function isGroupCallsEnabled(): boolean;
|
|
4
|
+
export declare function registerGroupCallTools(server: McpServer, telegram: TelegramService): void;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, sanitize } from "./shared.js";
|
|
3
|
+
export function isGroupCallsEnabled() {
|
|
4
|
+
return process.env.MCP_TELEGRAM_ENABLE_GROUP_CALLS === "1";
|
|
5
|
+
}
|
|
6
|
+
export function registerGroupCallTools(server, telegram) {
|
|
7
|
+
if (!isGroupCallsEnabled())
|
|
8
|
+
return;
|
|
9
|
+
server.registerTool("telegram-get-group-call", {
|
|
10
|
+
description: "Fetch metadata + an optional initial slice of participants for the active group call (voice/video chat) attached to a chat (phone.GetGroupCall). Returns call info (id, accessHash, participantsCount, title, scheduleDate, recordStartDate, streamDcId, flags) plus a participant slice (peer, date, muted/left/self flags, source, volume, video/presentation indicators) and participantsNextOffset. Pass limit:0 (default) for metadata only. Opt-in: register only when MCP_TELEGRAM_ENABLE_GROUP_CALLS=1. Read-only.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
chat: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe("Group/supergroup/channel that currently has an active group call — id, @username, or display name fragment"),
|
|
15
|
+
limit: z
|
|
16
|
+
.number()
|
|
17
|
+
.int()
|
|
18
|
+
.min(0)
|
|
19
|
+
.max(500)
|
|
20
|
+
.optional()
|
|
21
|
+
.describe("Max participants to include (default 0 — metadata only; use telegram-get-group-call-participants for pagination)"),
|
|
22
|
+
},
|
|
23
|
+
annotations: READ_ONLY,
|
|
24
|
+
}, async ({ chat, limit }) => {
|
|
25
|
+
const err = await requireConnection(telegram);
|
|
26
|
+
if (err)
|
|
27
|
+
return fail(new Error(err));
|
|
28
|
+
try {
|
|
29
|
+
const result = await telegram.getGroupCall(chat, { limit });
|
|
30
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return fail(e);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
server.registerTool("telegram-get-group-call-participants", {
|
|
37
|
+
description: "List participants of the active group call (voice/video chat) attached to a chat with pagination (phone.GetGroupParticipants). Looks up the chat's current InputGroupCall automatically, then returns {count, participants[], nextOffset?, version}. Each participant includes peer, date, source, volume, muted/self/left/videoJoined flags, raise-hand rating, about text, and hasVideo/hasPresentation indicators. Pass offset from a prior call's nextOffset to paginate; pass ids (user/channel peers) or sources to filter to specific participants. Opt-in: register only when MCP_TELEGRAM_ENABLE_GROUP_CALLS=1. Read-only.",
|
|
38
|
+
inputSchema: {
|
|
39
|
+
chat: z
|
|
40
|
+
.string()
|
|
41
|
+
.describe("Group/supergroup/channel that currently has an active group call — id, @username, or display name fragment"),
|
|
42
|
+
ids: z
|
|
43
|
+
.array(z.string())
|
|
44
|
+
.max(100)
|
|
45
|
+
.optional()
|
|
46
|
+
.describe("Optional list of participant peer ids/@usernames to filter (max 100)"),
|
|
47
|
+
sources: z
|
|
48
|
+
.array(z.number().int())
|
|
49
|
+
.max(100)
|
|
50
|
+
.optional()
|
|
51
|
+
.describe("Optional list of numeric source ids (audio SSRC) to filter participants"),
|
|
52
|
+
offset: z
|
|
53
|
+
.string()
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Pagination cursor from a previous response's nextOffset; omit for the first page"),
|
|
56
|
+
limit: z
|
|
57
|
+
.number()
|
|
58
|
+
.int()
|
|
59
|
+
.min(1)
|
|
60
|
+
.max(500)
|
|
61
|
+
.optional()
|
|
62
|
+
.describe("Max participants to return in this page (default 100)"),
|
|
63
|
+
},
|
|
64
|
+
annotations: READ_ONLY,
|
|
65
|
+
}, async ({ chat, ids, sources, offset, limit }) => {
|
|
66
|
+
const err = await requireConnection(telegram);
|
|
67
|
+
if (err)
|
|
68
|
+
return fail(new Error(err));
|
|
69
|
+
try {
|
|
70
|
+
const result = await telegram.getGroupCallParticipants(chat, { ids, sources, offset, limit });
|
|
71
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return fail(e);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
import { registerAccountTools } from "./account.js";
|
|
2
2
|
import { registerAuthTools } from "./auth.js";
|
|
3
|
+
import { registerBoostTools } from "./boosts.js";
|
|
3
4
|
import { registerChatTools } from "./chats.js";
|
|
4
5
|
import { registerContactTools } from "./contacts.js";
|
|
5
6
|
import { registerExtraTools } from "./extras.js";
|
|
7
|
+
import { registerGroupCallTools } from "./group-calls.js";
|
|
6
8
|
import { registerMediaTools } from "./media.js";
|
|
7
9
|
import { registerMessageTools } from "./messages.js";
|
|
10
|
+
import { registerQuickRepliesTools } from "./quick-replies.js";
|
|
8
11
|
import { registerReactionTools } from "./reactions.js";
|
|
12
|
+
import { registerStarsTools } from "./stars.js";
|
|
9
13
|
import { registerStickerTools } from "./stickers.js";
|
|
14
|
+
import { registerStoryTools } from "./stories.js";
|
|
10
15
|
export function registerTools(server, telegram) {
|
|
11
16
|
registerAuthTools(server, telegram);
|
|
12
17
|
registerMessageTools(server, telegram);
|
|
@@ -17,4 +22,9 @@ export function registerTools(server, telegram) {
|
|
|
17
22
|
registerExtraTools(server, telegram);
|
|
18
23
|
registerAccountTools(server, telegram);
|
|
19
24
|
registerStickerTools(server, telegram);
|
|
25
|
+
registerStoryTools(server, telegram);
|
|
26
|
+
registerBoostTools(server, telegram);
|
|
27
|
+
registerGroupCallTools(server, telegram);
|
|
28
|
+
registerStarsTools(server, telegram);
|
|
29
|
+
registerQuickRepliesTools(server, telegram);
|
|
20
30
|
}
|
package/dist/tools/messages.js
CHANGED
|
@@ -394,4 +394,196 @@ export function registerMessageTools(server, telegram) {
|
|
|
394
394
|
return fail(e);
|
|
395
395
|
}
|
|
396
396
|
});
|
|
397
|
+
server.registerTool("telegram-inline-query", {
|
|
398
|
+
description: "Query an inline bot (like @gif, @bing) in a chat context and return the compact result list. Returns queryId, cacheTime, and results[{id,type,title?,description?,url?}]. The queryId is typically valid for ~60s and can be passed to telegram-inline-query-send to deliver a chosen result. Bot must be a real bot account",
|
|
399
|
+
inputSchema: {
|
|
400
|
+
bot: z.string().describe("Inline bot username (e.g. @gif) or numeric user ID"),
|
|
401
|
+
chatId: z.string().describe("Chat ID or username providing context for the inline query"),
|
|
402
|
+
query: z.string().describe("Query text the bot should resolve (may be empty string)"),
|
|
403
|
+
offset: z
|
|
404
|
+
.string()
|
|
405
|
+
.optional()
|
|
406
|
+
.describe("Pagination offset returned by a previous call as nextOffset (empty string on first call)"),
|
|
407
|
+
},
|
|
408
|
+
annotations: READ_ONLY,
|
|
409
|
+
}, async ({ bot, chatId, query, offset }) => {
|
|
410
|
+
const err = await requireConnection(telegram);
|
|
411
|
+
if (err)
|
|
412
|
+
return fail(new Error(err));
|
|
413
|
+
try {
|
|
414
|
+
const res = await telegram.getInlineBotResults(bot, chatId, query, offset);
|
|
415
|
+
return ok(sanitize(JSON.stringify(res)));
|
|
416
|
+
}
|
|
417
|
+
catch (e) {
|
|
418
|
+
return fail(e);
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
server.registerTool("telegram-inline-query-send", {
|
|
422
|
+
description: "Send an inline bot result to a chat by queryId + resultId (as returned by telegram-inline-query). The queryId is valid for ~60s after the original query, so call this soon after telegram-inline-query. Returns the sent messageId (0 if not extractable from the update).",
|
|
423
|
+
inputSchema: {
|
|
424
|
+
chatId: z.string().describe("Target chat ID or username to send the result into"),
|
|
425
|
+
queryId: z
|
|
426
|
+
.string()
|
|
427
|
+
.regex(/^\d+$/, "queryId must be a numeric string")
|
|
428
|
+
.describe("queryId from a prior telegram-inline-query call (valid ~60s)"),
|
|
429
|
+
resultId: z.string().describe("id of the chosen result from telegram-inline-query results[]"),
|
|
430
|
+
replyTo: z.number().optional().describe("Message ID to reply to"),
|
|
431
|
+
silent: z.boolean().optional().describe("Send without notification"),
|
|
432
|
+
hideVia: z.boolean().optional().describe("Hide the 'via @bot' label on the sent message"),
|
|
433
|
+
clearDraft: z.boolean().optional().describe("Clear the chat draft after sending"),
|
|
434
|
+
},
|
|
435
|
+
annotations: WRITE,
|
|
436
|
+
}, async ({ chatId, queryId, resultId, replyTo, silent, hideVia, clearDraft }) => {
|
|
437
|
+
const err = await requireConnection(telegram);
|
|
438
|
+
if (err)
|
|
439
|
+
return fail(new Error(err));
|
|
440
|
+
try {
|
|
441
|
+
const { messageId } = await telegram.sendInlineBotResult(chatId, queryId, resultId, {
|
|
442
|
+
replyTo,
|
|
443
|
+
silent,
|
|
444
|
+
hideVia,
|
|
445
|
+
clearDraft,
|
|
446
|
+
});
|
|
447
|
+
const idInfo = messageId ? ` [#${messageId}]` : "";
|
|
448
|
+
return ok(`Inline result ${resultId} sent to ${chatId}${idInfo}`);
|
|
449
|
+
}
|
|
450
|
+
catch (e) {
|
|
451
|
+
return fail(e);
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
server.registerTool("telegram-get-message-buttons", {
|
|
455
|
+
description: "List the inline/reply keyboard buttons on a Telegram message with their (row, col) indices, type (e.g. KeyboardButtonCallback, KeyboardButtonUrl), label and type-specific fields (callback data as base64, url, switchQuery, userId, copyText, etc). Helper for telegram-press-button — call this first to discover indices and filter by type before pressing. Returns markupType='none' and empty buttons when the message has no keyboard",
|
|
456
|
+
inputSchema: {
|
|
457
|
+
chatId: z.string().describe("Chat ID or username where the message lives"),
|
|
458
|
+
messageId: z.number().describe("Message ID whose keyboard to inspect"),
|
|
459
|
+
},
|
|
460
|
+
annotations: READ_ONLY,
|
|
461
|
+
}, async ({ chatId, messageId }) => {
|
|
462
|
+
const err = await requireConnection(telegram);
|
|
463
|
+
if (err)
|
|
464
|
+
return fail(new Error(err));
|
|
465
|
+
try {
|
|
466
|
+
const result = await telegram.getMessageButtons(chatId, messageId);
|
|
467
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
468
|
+
}
|
|
469
|
+
catch (e) {
|
|
470
|
+
return fail(e);
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
server.registerTool("telegram-press-button", {
|
|
474
|
+
description: "Press an inline keyboard callback button on a message. Identify the button by (row, column) from its replyMarkup, or pass raw callback_data as base64. URL, switch-inline, game and 2FA-password buttons are rejected with a clear error. Returns the bot's callback answer: {alert?, hasUrl?, nativeUi?, message?, url?, cacheTime}",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
chatId: z.string().describe("Chat ID or username where the message lives"),
|
|
477
|
+
messageId: z.number().describe("Message ID whose inline button to press"),
|
|
478
|
+
row: z
|
|
479
|
+
.number()
|
|
480
|
+
.int()
|
|
481
|
+
.nonnegative()
|
|
482
|
+
.optional()
|
|
483
|
+
.describe("Button row index (0-based) — required unless data is provided"),
|
|
484
|
+
column: z
|
|
485
|
+
.number()
|
|
486
|
+
.int()
|
|
487
|
+
.nonnegative()
|
|
488
|
+
.optional()
|
|
489
|
+
.describe("Button column index (0-based) — required unless data is provided"),
|
|
490
|
+
data: z.string().optional().describe("Raw callback_data as base64 string (escape hatch — prefer row/column)"),
|
|
491
|
+
},
|
|
492
|
+
annotations: WRITE,
|
|
493
|
+
}, async ({ chatId, messageId, row, column, data }) => {
|
|
494
|
+
const err = await requireConnection(telegram);
|
|
495
|
+
if (err)
|
|
496
|
+
return fail(new Error(err));
|
|
497
|
+
const hasIndex = row !== undefined && column !== undefined;
|
|
498
|
+
if (!hasIndex && data === undefined) {
|
|
499
|
+
return fail(new Error("Provide either both row+column, or data (base64 callback_data)"));
|
|
500
|
+
}
|
|
501
|
+
if (hasIndex && data !== undefined) {
|
|
502
|
+
return fail(new Error("Provide either row+column OR data, not both"));
|
|
503
|
+
}
|
|
504
|
+
try {
|
|
505
|
+
const answer = await telegram.pressButton(chatId, messageId, {
|
|
506
|
+
buttonIndex: hasIndex ? { row: row, column: column } : undefined,
|
|
507
|
+
data,
|
|
508
|
+
});
|
|
509
|
+
return ok(sanitize(JSON.stringify(answer)));
|
|
510
|
+
}
|
|
511
|
+
catch (e) {
|
|
512
|
+
return fail(e);
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
server.registerTool("telegram-get-state", {
|
|
516
|
+
description: "Initialize the polling cursor by fetching the current Telegram updates state {pts, qts, date, seq, unreadCount}. Call once before telegram-get-updates; then persist {pts, qts, date} in your agent state and feed them into telegram-get-updates. The MCP server does NOT store the cursor — you do.",
|
|
517
|
+
inputSchema: {},
|
|
518
|
+
annotations: READ_ONLY,
|
|
519
|
+
}, async () => {
|
|
520
|
+
const err = await requireConnection(telegram);
|
|
521
|
+
if (err)
|
|
522
|
+
return fail(new Error(err));
|
|
523
|
+
try {
|
|
524
|
+
const state = await telegram.getUpdatesState();
|
|
525
|
+
return ok(sanitize(JSON.stringify(state)));
|
|
526
|
+
}
|
|
527
|
+
catch (e) {
|
|
528
|
+
return fail(e);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
server.registerTool("telegram-get-updates", {
|
|
532
|
+
description: "Fetch new messages, deleted messages, and other updates since a previously-known {pts, qts, date} cursor (from telegram-get-state or a prior call). Returns compact newMessages[], deletedMessageIds[], otherUpdates[] (className only), and the new cursor state. isFinal=false means more updates are queued — call again with the returned state. If Telegram reports the gap is too long, a fallback hint is returned suggesting to resync via telegram-read-messages per chat. Cursor is stateless — the agent must persist {pts, qts, date} between calls.",
|
|
533
|
+
inputSchema: {
|
|
534
|
+
pts: z.number().int().describe("Last known pts (from telegram-get-state or prior telegram-get-updates)"),
|
|
535
|
+
qts: z.number().int().describe("Last known qts (secret-chat / encrypted stream cursor; 0 if unknown)"),
|
|
536
|
+
date: z.number().int().describe("Last known date (unix seconds from prior state)"),
|
|
537
|
+
ptsLimit: z
|
|
538
|
+
.number()
|
|
539
|
+
.int()
|
|
540
|
+
.positive()
|
|
541
|
+
.max(1000)
|
|
542
|
+
.optional()
|
|
543
|
+
.describe("Max updates per batch (default 100, capped at 1000)"),
|
|
544
|
+
ptsTotalLimit: z
|
|
545
|
+
.number()
|
|
546
|
+
.int()
|
|
547
|
+
.positive()
|
|
548
|
+
.max(1000)
|
|
549
|
+
.optional()
|
|
550
|
+
.describe("Max total updates across paginated slices (default 1000, capped at 1000)"),
|
|
551
|
+
},
|
|
552
|
+
annotations: READ_ONLY,
|
|
553
|
+
}, async ({ pts, qts, date, ptsLimit, ptsTotalLimit }) => {
|
|
554
|
+
const err = await requireConnection(telegram);
|
|
555
|
+
if (err)
|
|
556
|
+
return fail(new Error(err));
|
|
557
|
+
try {
|
|
558
|
+
const diff = await telegram.getUpdates({ pts, qts, date, ptsLimit, ptsTotalLimit });
|
|
559
|
+
return ok(sanitize(JSON.stringify(diff)));
|
|
560
|
+
}
|
|
561
|
+
catch (e) {
|
|
562
|
+
return fail(e);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
server.registerTool("telegram-get-channel-updates", {
|
|
566
|
+
description: "Fetch new messages and updates for a single channel/supergroup since a known per-channel pts cursor. Separate from the global cursor used by telegram-get-updates. Returns compact newMessages[], otherUpdates[], and new {pts, isFinal, timeout?}. If the channel gap is too long, Telegram returns a dialog snapshot — this tool forwards it and hints to resync via telegram-read-messages. Cursor is stateless — the agent stores pts.",
|
|
567
|
+
inputSchema: {
|
|
568
|
+
chatId: z.string().describe("Channel or supergroup ID or username"),
|
|
569
|
+
pts: z.number().int().describe("Last known per-channel pts"),
|
|
570
|
+
limit: z.number().int().positive().optional().describe("Max updates per batch (default 100)"),
|
|
571
|
+
force: z
|
|
572
|
+
.boolean()
|
|
573
|
+
.optional()
|
|
574
|
+
.describe("Force request updates even if the client hasn't processed previous ones (rarely needed)"),
|
|
575
|
+
},
|
|
576
|
+
annotations: READ_ONLY,
|
|
577
|
+
}, async ({ chatId, pts, limit, force }) => {
|
|
578
|
+
const err = await requireConnection(telegram);
|
|
579
|
+
if (err)
|
|
580
|
+
return fail(new Error(err));
|
|
581
|
+
try {
|
|
582
|
+
const diff = await telegram.getChannelUpdates(chatId, { pts, limit, force });
|
|
583
|
+
return ok(sanitize(JSON.stringify(diff)));
|
|
584
|
+
}
|
|
585
|
+
catch (e) {
|
|
586
|
+
return fail(e);
|
|
587
|
+
}
|
|
588
|
+
});
|
|
397
589
|
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import type { TelegramService } from "../telegram-client.js";
|
|
3
|
+
export declare function isQuickRepliesEnabled(): boolean;
|
|
4
|
+
export declare function registerQuickRepliesTools(server: McpServer, telegram: TelegramService): void;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { fail, ok, READ_ONLY, requireConnection, sanitize } from "./shared.js";
|
|
3
|
+
export function isQuickRepliesEnabled() {
|
|
4
|
+
return process.env.MCP_TELEGRAM_ENABLE_QUICK_REPLIES === "1";
|
|
5
|
+
}
|
|
6
|
+
export function registerQuickRepliesTools(server, telegram) {
|
|
7
|
+
if (!isQuickRepliesEnabled())
|
|
8
|
+
return;
|
|
9
|
+
server.registerTool("telegram-get-quick-replies", {
|
|
10
|
+
description: "Fetch the list of quick-reply shortcuts configured for the user account (messages.GetQuickReplies). Each entry has {shortcutId, shortcut, topMessage, count} — use the shortcutId with telegram-get-quick-reply-messages to inspect the stored messages. Optional `hash` implements Telegram's hash-based diff: pass the last-known aggregate hash as a decimal string and the server may respond with {notModified:true} if nothing changed. Opt-in: register only when MCP_TELEGRAM_ENABLE_QUICK_REPLIES=1. Read-only.",
|
|
11
|
+
inputSchema: {
|
|
12
|
+
hash: z
|
|
13
|
+
.string()
|
|
14
|
+
.regex(/^\d+$/)
|
|
15
|
+
.optional()
|
|
16
|
+
.describe("Aggregate hash from a previous response (decimal string); omit for a fresh fetch"),
|
|
17
|
+
},
|
|
18
|
+
annotations: READ_ONLY,
|
|
19
|
+
}, async ({ hash }) => {
|
|
20
|
+
const err = await requireConnection(telegram);
|
|
21
|
+
if (err)
|
|
22
|
+
return fail(new Error(err));
|
|
23
|
+
try {
|
|
24
|
+
const result = await telegram.getQuickReplies(hash);
|
|
25
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return fail(e);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
server.registerTool("telegram-get-quick-reply-messages", {
|
|
32
|
+
description: "Fetch messages stored under a quick-reply shortcut (messages.GetQuickReplyMessages). Use `shortcutId` from telegram-get-quick-replies. Optional `ids` narrows to specific message ids within the shortcut. Optional `hash` implements Telegram's hash-based diff: pass the last-known aggregate hash as a decimal string — the server may respond with {notModified:true, count} if nothing changed. Returns compact {count, messages[{id, date, text, isService, fromId?, replyToMsgId?}]}. Opt-in: register only when MCP_TELEGRAM_ENABLE_QUICK_REPLIES=1. Read-only.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
shortcutId: z.number().int().nonnegative().describe("Shortcut id from telegram-get-quick-replies"),
|
|
35
|
+
ids: z
|
|
36
|
+
.array(z.number().int().nonnegative())
|
|
37
|
+
.optional()
|
|
38
|
+
.describe("Optional list of message ids to fetch within the shortcut"),
|
|
39
|
+
hash: z
|
|
40
|
+
.string()
|
|
41
|
+
.regex(/^\d+$/)
|
|
42
|
+
.optional()
|
|
43
|
+
.describe("Aggregate hash from a previous response (decimal string); omit for a fresh fetch"),
|
|
44
|
+
},
|
|
45
|
+
annotations: READ_ONLY,
|
|
46
|
+
}, async ({ shortcutId, ids, hash }) => {
|
|
47
|
+
const err = await requireConnection(telegram);
|
|
48
|
+
if (err)
|
|
49
|
+
return fail(new Error(err));
|
|
50
|
+
try {
|
|
51
|
+
const result = await telegram.getQuickReplyMessages(shortcutId, { ids, hash });
|
|
52
|
+
return ok(sanitize(JSON.stringify(result)));
|
|
53
|
+
}
|
|
54
|
+
catch (e) {
|
|
55
|
+
return fail(e);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|