telegram-notify-mcp 1.0.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/.env.example ADDED
@@ -0,0 +1,3 @@
1
+ # Copy to your MCP host env — do not commit real values
2
+ TELEGRAM_BOT_TOKEN=
3
+ TELEGRAM_CHAT_ID=
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ehsan Eskandari pour
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # telegram-notify-mcp
2
+
3
+ **MCP connector / server** (stdio) that talks to the [Telegram Bot HTTP API](https://core.telegram.org/bots/api) using **your own** bot from [@BotFather](https://t.me/BotFather) (BYOB — bring your own bot).
4
+
5
+ This package is **not** a Grok Bot agent, chat persona, or hosted SaaS. It is a local Model Context Protocol server you run under Cursor, Grok Bot, or any MCP host. No secrets ship in the repo; configuration is environment-only.
6
+
7
+ **Publisher:** Ehsan Eskandari pour
8
+
9
+ ## How it works
10
+
11
+ 1. You create a Telegram bot with BotFather and receive an HTTP API token.
12
+ 2. You run this MCP server with `TELEGRAM_BOT_TOKEN` (and optionally `TELEGRAM_CHAT_ID`) in the process environment.
13
+ 3. Your MCP host (Cursor / Grok Bot / etc.) lists the tools and can call them over stdio.
14
+ 4. Tools call `api.telegram.org` to verify the bot, discover chat ids, and send notifications.
15
+
16
+ Phase 1 is outbound notify only — no inbound webhooks.
17
+
18
+ ## Tools (Phase 1)
19
+
20
+ | Tool | Purpose |
21
+ |------|---------|
22
+ | `telegram_get_me` | `getMe` — verify token; return bot id/username |
23
+ | `telegram_send_message` | Send text (`chat_id` optional if env default set) |
24
+ | `telegram_notify` | Short completion notification (`title?` + `body`) |
25
+ | `telegram_get_updates` | List recent updates to discover `chat_id` after `/start` |
26
+
27
+ ## BotFather setup
28
+
29
+ 1. Open Telegram and chat with [@BotFather](https://t.me/BotFather).
30
+ 2. Send `/newbot`, choose a display name and a username ending in `bot`.
31
+ 3. Copy the **HTTP API token** BotFather gives you. This is `TELEGRAM_BOT_TOKEN`.
32
+ 4. (Optional) Leave privacy defaults; for personal notify bots this is fine.
33
+
34
+ **Never commit the token.** Put it only in MCP env / your secret store.
35
+
36
+ ## How to get `chat_id`
37
+
38
+ 1. Start this MCP server with `TELEGRAM_BOT_TOKEN` set (no `TELEGRAM_CHAT_ID` yet).
39
+ 2. In Telegram, open your bot and tap **Start** (or send `/start`).
40
+ 3. Call tool `telegram_get_updates` (optional `limit`).
41
+ 4. Read `discovered_chats[].chat_id` from the result.
42
+ 5. Set `TELEGRAM_CHAT_ID` to that value so `telegram_notify` / `telegram_send_message` can omit `chat_id`.
43
+
44
+ Private chats use a numeric id (e.g. `123456789`). Groups/channels may use negative ids.
45
+
46
+ ## Install
47
+
48
+ Requires **Node.js 20+**.
49
+
50
+ ```bash
51
+ npm install telegram-notify-mcp
52
+ # or from a release tarball / local path (GitHub repo not published yet):
53
+ cd /absolute/path/to/telegram-notify-mcp
54
+ npm install
55
+ npm run build
56
+ npm test
57
+ ```
58
+
59
+ Environment variables (see [`.env.example`](./.env.example)):
60
+
61
+ | Variable | Required | Description |
62
+ |----------|----------|-------------|
63
+ | `TELEGRAM_BOT_TOKEN` | Yes (for tool calls) | BotFather HTTP API token |
64
+ | `TELEGRAM_CHAT_ID` | No | Default chat for send/notify |
65
+
66
+ The process starts even if `TELEGRAM_BOT_TOKEN` is missing (so the host can list tools); tool calls then return a clear error until the token is set.
67
+
68
+ ## Add MCP in Cursor / Grok Bot
69
+
70
+ Use a **local** command + env. Example with a built clone:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "telegram-notify": {
76
+ "command": "node",
77
+ "args": [
78
+ "/absolute/path/to/telegram-notify-mcp/dist/index.js"
79
+ ],
80
+ "env": {
81
+ "TELEGRAM_BOT_TOKEN": "123456:ABC-DEF...",
82
+ "TELEGRAM_CHAT_ID": "123456789"
83
+ }
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ Or via `npx` after publishing / linking:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "telegram-notify": {
95
+ "command": "npx",
96
+ "args": [
97
+ "--yes",
98
+ "telegram-notify-mcp"
99
+ ],
100
+ "env": {
101
+ "TELEGRAM_BOT_TOKEN": "123456:ABC-DEF...",
102
+ "TELEGRAM_CHAT_ID": "123456789"
103
+ }
104
+ }
105
+ }
106
+ }
107
+ ```
108
+
109
+ If your host UI has **Add MCP** fields instead of raw JSON:
110
+
111
+ - **command:** `node`
112
+ - **args:** `/absolute/path/to/telegram-notify-mcp/dist/index.js`
113
+ - **env:** `TELEGRAM_BOT_TOKEN`, optional `TELEGRAM_CHAT_ID`
114
+
115
+ ## Example notify flow
116
+
117
+ 1. Configure token (+ optional default chat id) as above; reload MCP.
118
+ 2. `telegram_get_me` → confirm username.
119
+ 3. If needed: message the bot, then `telegram_get_updates` → set `TELEGRAM_CHAT_ID`.
120
+ 4. When a task finishes, call `telegram_notify` with title/body. Example payload:
121
+
122
+ ```json
123
+ {
124
+ "title": "Deploy finished",
125
+ "body": "staging is live; smoke tests passed."
126
+ }
127
+ ```
128
+
129
+ You should receive a Telegram message like:
130
+
131
+ ```text
132
+ ✅ Deploy finished
133
+
134
+ staging is live; smoke tests passed.
135
+ ```
136
+
137
+ See also [`skill-snippet.md`](./skill-snippet.md) for agent-oriented usage notes.
138
+
139
+ ## Security notes
140
+
141
+ - Token grants full control of the bot — treat it like a password.
142
+ - This server only calls `api.telegram.org`; it does not open inbound webhooks in Phase 1.
143
+ - Configuration is **env-only** for the published server. Do not commit tokens or chat ids.
144
+ - No secrets are stored in this repository.
145
+
146
+ ## Contact
147
+
148
+ - **Author:** Ehsan Eskandari pour
149
+ - **Email:** e.eskandaripour7@gmail.com
150
+ - **Website:** https://digitalhand.site
151
+ - **Source:** GitHub repo is not published yet — install from the release tarball or a local path (not `git clone` of a public URL).
152
+
153
+ ## License
154
+
155
+ MIT — see [LICENSE](./LICENSE).
156
+
157
+ ## Roadmap
158
+
159
+ Phase 2 ideas (inbound / control) are sketched in [ROADMAP.md](./ROADMAP.md).
package/ROADMAP.md ADDED
@@ -0,0 +1,21 @@
1
+ # Roadmap
2
+
3
+ ## Phase 1 (this release)
4
+
5
+ - [x] stdio MCP connector/server
6
+ - [x] `telegram_get_me`, `telegram_send_message`, `telegram_notify`, `telegram_get_updates`
7
+ - [x] BYOB BotFather token via env only
8
+ - [x] Unit tests with mocked fetch
9
+ - [ ] Publish to npm (`telegram-notify-mcp`)
10
+ - [ ] Optional Cursor / MCP marketplace plugin listing
11
+
12
+ ## Phase 2 (later) — inbound / control
13
+
14
+ Not implemented yet. Possible directions:
15
+
16
+ - Optional long-poll or webhook receiver so hosts can **read** user replies / commands from Telegram
17
+ - Simple allowlisted control verbs (e.g. `/status`, `/cancel`) mapped to host-side hooks
18
+ - Multi-chat routing and per-chat rate limits
19
+ - Richer notify templates (progress, failure, links) without embedding secrets
20
+
21
+ Phase 2 must remain local-first (no mandatory hosted SaaS) and keep the BotFather BYOB model.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * telegram-notify-mcp — stdio MCP server for Telegram Bot API notifications.
4
+ * Env: TELEGRAM_BOT_TOKEN (required for tool calls), TELEGRAM_CHAT_ID (optional default).
5
+ * Starts cleanly without a token; tools fail with a clear error when invoked.
6
+ * Public release is env-only — no disk/secret-store token loading.
7
+ */
8
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,158 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * telegram-notify-mcp — stdio MCP server for Telegram Bot API notifications.
4
+ * Env: TELEGRAM_BOT_TOKEN (required for tool calls), TELEGRAM_CHAT_ID (optional default).
5
+ * Starts cleanly without a token; tools fail with a clear error when invoked.
6
+ * Public release is env-only — no disk/secret-store token loading.
7
+ */
8
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
+ import { z } from 'zod';
11
+ import { TelegramApiError, TelegramClient, formatNotifyMessage, getDefaultChatId, } from './telegram.js';
12
+ const SERVER_NAME = 'telegram-notify-mcp';
13
+ const SERVER_VERSION = '1.0.0';
14
+ function textResult(text, isError = false) {
15
+ return {
16
+ content: [{ type: 'text', text }],
17
+ isError,
18
+ };
19
+ }
20
+ function errorResult(err) {
21
+ if (err instanceof TelegramApiError) {
22
+ return textResult(err.message, true);
23
+ }
24
+ const msg = err instanceof Error ? err.message : String(err);
25
+ return textResult(msg, true);
26
+ }
27
+ function getClient() {
28
+ return new TelegramClient(process.env.TELEGRAM_BOT_TOKEN);
29
+ }
30
+ function createServer() {
31
+ const server = new McpServer({
32
+ name: SERVER_NAME,
33
+ version: SERVER_VERSION,
34
+ });
35
+ server.tool('telegram_get_me', 'Call Telegram getMe and return the bot id, username, and name. Useful to verify TELEGRAM_BOT_TOKEN.', async () => {
36
+ try {
37
+ const me = await getClient().getMe();
38
+ return textResult(JSON.stringify({
39
+ id: me.id,
40
+ username: me.username ?? null,
41
+ first_name: me.first_name,
42
+ is_bot: me.is_bot,
43
+ }, null, 2));
44
+ }
45
+ catch (err) {
46
+ return errorResult(err);
47
+ }
48
+ });
49
+ server.tool('telegram_send_message', 'Send a text message via the Bot API. chat_id is optional when TELEGRAM_CHAT_ID is set.', {
50
+ text: z.string().min(1).describe('Message text to send'),
51
+ chat_id: z
52
+ .union([z.string(), z.number()])
53
+ .optional()
54
+ .describe('Telegram chat id (falls back to TELEGRAM_CHAT_ID)'),
55
+ parse_mode: z
56
+ .enum(['Markdown', 'MarkdownV2', 'HTML'])
57
+ .optional()
58
+ .describe('Optional Telegram parse_mode'),
59
+ }, async ({ text, chat_id, parse_mode }) => {
60
+ try {
61
+ const chatId = getDefaultChatId(chat_id);
62
+ const msg = await getClient().sendMessage({
63
+ chat_id: chatId,
64
+ text,
65
+ parse_mode,
66
+ });
67
+ return textResult(JSON.stringify({
68
+ ok: true,
69
+ message_id: msg.message_id,
70
+ chat_id: msg.chat.id,
71
+ text: msg.text ?? text,
72
+ }, null, 2));
73
+ }
74
+ catch (err) {
75
+ return errorResult(err);
76
+ }
77
+ });
78
+ server.tool('telegram_notify', 'Send a short completion-style notification (title + body) to the default or explicit chat.', {
79
+ body: z.string().min(1).describe('Notification body / details'),
80
+ title: z.string().optional().describe('Optional short title (e.g. "Build finished")'),
81
+ chat_id: z
82
+ .union([z.string(), z.number()])
83
+ .optional()
84
+ .describe('Telegram chat id (falls back to TELEGRAM_CHAT_ID)'),
85
+ }, async ({ body, title, chat_id }) => {
86
+ try {
87
+ const chatId = getDefaultChatId(chat_id);
88
+ const text = formatNotifyMessage(title, body);
89
+ const msg = await getClient().sendMessage({
90
+ chat_id: chatId,
91
+ text,
92
+ });
93
+ return textResult(JSON.stringify({
94
+ ok: true,
95
+ message_id: msg.message_id,
96
+ chat_id: msg.chat.id,
97
+ text,
98
+ }, null, 2));
99
+ }
100
+ catch (err) {
101
+ return errorResult(err);
102
+ }
103
+ });
104
+ server.tool('telegram_get_updates', 'Fetch recent bot updates so you can discover chat_id after messaging the bot with /start.', {
105
+ limit: z
106
+ .number()
107
+ .int()
108
+ .min(1)
109
+ .max(100)
110
+ .optional()
111
+ .describe('Max updates to return (default 10)'),
112
+ }, async ({ limit }) => {
113
+ try {
114
+ const updates = await getClient().getUpdates({ limit: limit ?? 10 });
115
+ const chats = new Map();
116
+ for (const u of updates) {
117
+ const m = u.message ?? u.edited_message ?? u.channel_post;
118
+ if (!m?.chat)
119
+ continue;
120
+ const c = m.chat;
121
+ const label = c.title ||
122
+ [c.first_name, c.last_name].filter(Boolean).join(' ') ||
123
+ c.username ||
124
+ String(c.id);
125
+ chats.set(String(c.id), { chat_id: c.id, type: c.type, label });
126
+ }
127
+ return textResult(JSON.stringify({
128
+ update_count: updates.length,
129
+ discovered_chats: [...chats.values()],
130
+ updates: updates.map((u) => {
131
+ const m = u.message ?? u.edited_message ?? u.channel_post;
132
+ return {
133
+ update_id: u.update_id,
134
+ chat_id: m?.chat?.id ?? null,
135
+ from: m?.from?.username ?? m?.from?.first_name ?? null,
136
+ text: m?.text ?? null,
137
+ };
138
+ }),
139
+ }, null, 2));
140
+ }
141
+ catch (err) {
142
+ return errorResult(err);
143
+ }
144
+ });
145
+ return server;
146
+ }
147
+ async function main() {
148
+ const server = createServer();
149
+ const transport = new StdioServerTransport();
150
+ await server.connect(transport);
151
+ console.error(`${SERVER_NAME} v${SERVER_VERSION} running on stdio` +
152
+ (process.env.TELEGRAM_BOT_TOKEN ? '' : ' (TELEGRAM_BOT_TOKEN not set — tools will error until configured)'));
153
+ }
154
+ main().catch((err) => {
155
+ console.error('Fatal:', err);
156
+ process.exit(1);
157
+ });
158
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Thin Telegram Bot HTTP API client (api.telegram.org).
3
+ * Uses Node 20+ global fetch. No secrets are hardcoded.
4
+ */
5
+ export declare class TelegramApiError extends Error {
6
+ readonly status?: number | undefined;
7
+ readonly description?: string | undefined;
8
+ constructor(message: string, status?: number | undefined, description?: string | undefined);
9
+ }
10
+ export interface TelegramUser {
11
+ id: number;
12
+ is_bot: boolean;
13
+ first_name: string;
14
+ username?: string;
15
+ can_join_groups?: boolean;
16
+ can_read_all_group_messages?: boolean;
17
+ supports_inline_queries?: boolean;
18
+ }
19
+ export interface TelegramChat {
20
+ id: number;
21
+ type: string;
22
+ title?: string;
23
+ username?: string;
24
+ first_name?: string;
25
+ last_name?: string;
26
+ }
27
+ export interface TelegramMessage {
28
+ message_id: number;
29
+ date: number;
30
+ chat: TelegramChat;
31
+ text?: string;
32
+ from?: TelegramUser;
33
+ }
34
+ export interface TelegramUpdate {
35
+ update_id: number;
36
+ message?: TelegramMessage;
37
+ edited_message?: TelegramMessage;
38
+ channel_post?: TelegramMessage;
39
+ }
40
+ export interface TelegramApiResponse<T> {
41
+ ok: boolean;
42
+ result?: T;
43
+ description?: string;
44
+ error_code?: number;
45
+ }
46
+ export type ParseMode = 'Markdown' | 'MarkdownV2' | 'HTML';
47
+ export interface SendMessageParams {
48
+ chat_id: string | number;
49
+ text: string;
50
+ parse_mode?: ParseMode;
51
+ disable_notification?: boolean;
52
+ }
53
+ export interface GetUpdatesParams {
54
+ limit?: number;
55
+ timeout?: number;
56
+ offset?: number;
57
+ }
58
+ export declare function getDefaultChatId(explicit?: string | number): string | number;
59
+ export declare function formatNotifyMessage(title: string | undefined, body: string): string;
60
+ export declare class TelegramClient {
61
+ private readonly fetchImpl;
62
+ private readonly baseUrl;
63
+ constructor(token?: string | undefined, fetchImpl?: typeof fetch);
64
+ private call;
65
+ getMe(): Promise<TelegramUser>;
66
+ sendMessage(params: SendMessageParams): Promise<TelegramMessage>;
67
+ getUpdates(params?: GetUpdatesParams): Promise<TelegramUpdate[]>;
68
+ }
69
+ export declare function createClientFromEnv(fetchImpl?: typeof fetch): TelegramClient;
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Thin Telegram Bot HTTP API client (api.telegram.org).
3
+ * Uses Node 20+ global fetch. No secrets are hardcoded.
4
+ */
5
+ export class TelegramApiError extends Error {
6
+ status;
7
+ description;
8
+ constructor(message, status, description) {
9
+ super(message);
10
+ this.status = status;
11
+ this.description = description;
12
+ this.name = 'TelegramApiError';
13
+ }
14
+ }
15
+ function requireToken(token) {
16
+ const t = (token ?? '').trim();
17
+ if (!t) {
18
+ throw new TelegramApiError('TELEGRAM_BOT_TOKEN is not set. Create a bot with @BotFather, copy the token, and set TELEGRAM_BOT_TOKEN in the MCP server env.');
19
+ }
20
+ return t;
21
+ }
22
+ export function getDefaultChatId(explicit) {
23
+ if (explicit !== undefined && explicit !== null && String(explicit).trim() !== '') {
24
+ return explicit;
25
+ }
26
+ const fromEnv = (process.env.TELEGRAM_CHAT_ID ?? '').trim();
27
+ if (!fromEnv) {
28
+ throw new TelegramApiError('No chat_id provided and TELEGRAM_CHAT_ID is not set. Message your bot (/start), then call telegram_get_updates to discover your chat id.');
29
+ }
30
+ return fromEnv;
31
+ }
32
+ export function formatNotifyMessage(title, body) {
33
+ const t = (title ?? '').trim();
34
+ const b = body.trim();
35
+ if (t) {
36
+ return `✅ ${t}\n\n${b}`;
37
+ }
38
+ return `✅ ${b}`;
39
+ }
40
+ export class TelegramClient {
41
+ fetchImpl;
42
+ baseUrl;
43
+ constructor(token = process.env.TELEGRAM_BOT_TOKEN, fetchImpl = globalThis.fetch.bind(globalThis)) {
44
+ this.fetchImpl = fetchImpl;
45
+ const t = requireToken(token);
46
+ this.baseUrl = `https://api.telegram.org/bot${t}`;
47
+ }
48
+ async call(method, body) {
49
+ const url = `${this.baseUrl}/${method}`;
50
+ let res;
51
+ try {
52
+ res = await this.fetchImpl(url, {
53
+ method: body ? 'POST' : 'GET',
54
+ headers: body ? { 'Content-Type': 'application/json' } : undefined,
55
+ body: body ? JSON.stringify(body) : undefined,
56
+ });
57
+ }
58
+ catch (err) {
59
+ const msg = err instanceof Error ? err.message : String(err);
60
+ throw new TelegramApiError(`Network error calling Telegram ${method}: ${msg}`);
61
+ }
62
+ let data;
63
+ try {
64
+ data = (await res.json());
65
+ }
66
+ catch {
67
+ throw new TelegramApiError(`Telegram ${method} returned non-JSON (HTTP ${res.status})`, res.status);
68
+ }
69
+ if (!res.ok || !data.ok) {
70
+ throw new TelegramApiError(`Telegram ${method} failed: ${data.description ?? res.statusText ?? 'unknown error'}`, data.error_code ?? res.status, data.description);
71
+ }
72
+ return data.result;
73
+ }
74
+ getMe() {
75
+ return this.call('getMe');
76
+ }
77
+ sendMessage(params) {
78
+ const payload = {
79
+ chat_id: params.chat_id,
80
+ text: params.text,
81
+ };
82
+ if (params.parse_mode)
83
+ payload.parse_mode = params.parse_mode;
84
+ if (params.disable_notification !== undefined) {
85
+ payload.disable_notification = params.disable_notification;
86
+ }
87
+ return this.call('sendMessage', payload);
88
+ }
89
+ getUpdates(params = {}) {
90
+ const payload = {};
91
+ if (params.limit !== undefined)
92
+ payload.limit = params.limit;
93
+ if (params.timeout !== undefined)
94
+ payload.timeout = params.timeout;
95
+ if (params.offset !== undefined)
96
+ payload.offset = params.offset;
97
+ return this.call('getUpdates', Object.keys(payload).length ? payload : undefined);
98
+ }
99
+ }
100
+ export function createClientFromEnv(fetchImpl) {
101
+ return new TelegramClient(process.env.TELEGRAM_BOT_TOKEN, fetchImpl);
102
+ }
103
+ //# sourceMappingURL=telegram.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"telegram.js","sourceRoot":"","sources":["../src/telegram.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IAGvB;IACA;IAHlB,YACE,OAAe,EACC,MAAe,EACf,WAAoB;QAEpC,KAAK,CAAC,OAAO,CAAC,CAAC;QAHC,WAAM,GAAN,MAAM,CAAS;QACf,gBAAW,GAAX,WAAW,CAAS;QAGpC,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AA0DD,SAAS,YAAY,CAAC,KAAyB;IAC7C,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/B,IAAI,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,IAAI,gBAAgB,CACxB,gIAAgI,CACjI,CAAC;IACJ,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,QAA0B;IACzD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAClF,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,MAAM,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC5D,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,gBAAgB,CACxB,0IAA0I,CAC3I,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,mBAAmB,CAAC,KAAyB,EAAE,IAAY;IACzE,MAAM,CAAC,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/B,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IACtB,IAAI,CAAC,EAAE,CAAC;QACN,OAAO,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;IAC1B,CAAC;IACD,OAAO,KAAK,CAAC,EAAE,CAAC;AAClB,CAAC;AAED,MAAM,OAAO,cAAc;IAKN;IAJF,OAAO,CAAS;IAEjC,YACE,QAA4B,OAAO,CAAC,GAAG,CAAC,kBAAkB,EACzC,YAA0B,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC;QAA3D,cAAS,GAAT,SAAS,CAAkD;QAE5E,MAAM,CAAC,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,CAAC,OAAO,GAAG,+BAA+B,CAAC,EAAE,CAAC;IACpD,CAAC;IAEO,KAAK,CAAC,IAAI,CAAI,MAAc,EAAE,IAA8B;QAClE,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,OAAO,IAAI,MAAM,EAAE,CAAC;QACxC,IAAI,GAAa,CAAC;QAClB,IAAI,CAAC;YACH,GAAG,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE;gBAC9B,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;gBAC7B,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,SAAS;gBAClE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;aAC9C,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAC7D,MAAM,IAAI,gBAAgB,CAAC,kCAAkC,MAAM,KAAK,GAAG,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,IAAI,IAA4B,CAAC;QACjC,IAAI,CAAC;YACH,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA2B,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,gBAAgB,CACxB,YAAY,MAAM,4BAA4B,GAAG,CAAC,MAAM,GAAG,EAC3D,GAAG,CAAC,MAAM,CACX,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACxB,MAAM,IAAI,gBAAgB,CACxB,YAAY,MAAM,YAAY,IAAI,CAAC,WAAW,IAAI,GAAG,CAAC,UAAU,IAAI,eAAe,EAAE,EACrF,IAAI,CAAC,UAAU,IAAI,GAAG,CAAC,MAAM,EAC7B,IAAI,CAAC,WAAW,CACjB,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC,MAAW,CAAC;IAC1B,CAAC;IAED,KAAK;QACH,OAAO,IAAI,CAAC,IAAI,CAAe,OAAO,CAAC,CAAC;IAC1C,CAAC;IAED,WAAW,CAAC,MAAyB;QACnC,MAAM,OAAO,GAA4B;YACvC,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,IAAI,EAAE,MAAM,CAAC,IAAI;SAClB,CAAC;QACF,IAAI,MAAM,CAAC,UAAU;YAAE,OAAO,CAAC,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC;QAC9D,IAAI,MAAM,CAAC,oBAAoB,KAAK,SAAS,EAAE,CAAC;YAC9C,OAAO,CAAC,oBAAoB,GAAG,MAAM,CAAC,oBAAoB,CAAC;QAC7D,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,CAAkB,aAAa,EAAE,OAAO,CAAC,CAAC;IAC5D,CAAC;IAED,UAAU,CAAC,SAA2B,EAAE;QACtC,MAAM,OAAO,GAA4B,EAAE,CAAC;QAC5C,IAAI,MAAM,CAAC,KAAK,KAAK,SAAS;YAAE,OAAO,CAAC,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC7D,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,CAAC,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;QACnE,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAChE,OAAO,IAAI,CAAC,IAAI,CAAmB,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACtG,CAAC;CACF;AAED,MAAM,UAAU,mBAAmB,CACjC,SAAwB;IAExB,OAAO,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AACvE,CAAC"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "telegram-notify-mcp",
3
+ "version": "1.0.0",
4
+ "description": "MCP connector/server that sends Telegram notifications via your own BotFather bot (BYOB).",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "telegram-notify-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "test": "node --import tsx --test tests/telegram.test.ts",
14
+ "prepare": "tsc"
15
+ },
16
+ "engines": {
17
+ "node": ">=20"
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "README.md",
22
+ "LICENSE",
23
+ "ROADMAP.md",
24
+ "skill-snippet.md",
25
+ ".env.example"
26
+ ],
27
+ "keywords": [
28
+ "mcp",
29
+ "telegram",
30
+ "notifications",
31
+ "botfather",
32
+ "model-context-protocol"
33
+ ],
34
+ "author": {
35
+ "name": "Ehsan Eskandari pour",
36
+ "email": "e.eskandaripour7@gmail.com",
37
+ "url": "https://digitalhand.site"
38
+ },
39
+ "homepage": "https://digitalhand.site",
40
+ "bugs": {
41
+ "url": "https://digitalhand.site/contact",
42
+ "email": "e.eskandaripour7@gmail.com"
43
+ },
44
+ "license": "MIT",
45
+ "dependencies": {
46
+ "@modelcontextprotocol/sdk": "^1.18.0",
47
+ "zod": "^3.23.8"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^20.14.0",
51
+ "tsx": "^4.19.0",
52
+ "typescript": "^5.6.0"
53
+ }
54
+ }
@@ -0,0 +1,34 @@
1
+ # Skill snippet — Telegram notify
2
+
3
+ Use when the user wants a Telegram ping after work completes, or to verify the bot.
4
+
5
+ This package is an **MCP connector/server**, not a chat agent. Configure it in your MCP host (Cursor, Grok Bot, or similar), then call its tools.
6
+
7
+ ## Preconditions
8
+
9
+ - MCP server `telegram-notify` (or `telegram-notify-mcp`) is configured with `TELEGRAM_BOT_TOKEN`.
10
+ - Prefer `TELEGRAM_CHAT_ID` in env; otherwise pass `chat_id` on each call.
11
+
12
+ ## Discover chat id (once)
13
+
14
+ 1. Ask the user to open the bot in Telegram and send `/start`.
15
+ 2. Call `telegram_get_updates`.
16
+ 3. Read `discovered_chats[].chat_id` and suggest setting `TELEGRAM_CHAT_ID`.
17
+
18
+ ## Notify on completion
19
+
20
+ Call `telegram_notify` with:
21
+
22
+ - `title`: short status (e.g. `Tests passed`, `PR ready`)
23
+ - `body`: one or two lines of detail (paths, URLs, next step)
24
+ - `chat_id`: only if no default env chat
25
+
26
+ Keep messages short. Do not put tokens or passwords in the body.
27
+
28
+ ## Verify bot
29
+
30
+ Call `telegram_get_me` if sends fail or after rotating the token.
31
+
32
+ ## Raw send
33
+
34
+ Use `telegram_send_message` when you need a custom `parse_mode` or non-notify wording.