telegram-notify-mcp 1.0.0 → 2.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 +4 -0
- package/ARCHITECTURE-PHASE2.md +324 -0
- package/README.md +63 -23
- package/ROADMAP.md +17 -9
- package/dist/index.d.ts +3 -1
- package/dist/index.js +143 -11
- package/dist/index.js.map +1 -1
- package/dist/routing.d.ts +49 -0
- package/dist/routing.js +146 -0
- package/dist/routing.js.map +1 -0
- package/dist/store.d.ts +56 -0
- package/dist/store.js +126 -0
- package/dist/store.js.map +1 -0
- package/dist/telegram.d.ts +1 -0
- package/dist/telegram.js.map +1 -1
- package/package.json +4 -3
- package/skill-snippet.md +53 -5
package/.env.example
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
1
|
# Copy to your MCP host env — do not commit real values
|
|
2
2
|
TELEGRAM_BOT_TOKEN=
|
|
3
3
|
TELEGRAM_CHAT_ID=
|
|
4
|
+
# Optional comma-separated inbound allowlist (defaults to TELEGRAM_CHAT_ID if unset)
|
|
5
|
+
# TELEGRAM_CHAT_ALLOWLIST=123456789,987654321
|
|
6
|
+
# Optional local store directory for mappings + pending commands (default: ./.telegram-notify-data)
|
|
7
|
+
# TELEGRAM_NOTIFY_DATA_DIR=
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# Architecture — Phase 2: Bidirectional Telegram ↔ MCP Host
|
|
2
|
+
|
|
3
|
+
Publisher-neutral design notes for **`telegram-notify-mcp`**. Phase 1 already ships outbound notify tools over stdio MCP. Phase 2 adds **inbound** text commands from Telegram that route back to the **same agent** that sent a given notify. Voice/audio is deferred to Phase 2b after a text MVP.
|
|
4
|
+
|
|
5
|
+
This document describes architecture only. It is not an implementation plan with code.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Goals & non-goals
|
|
10
|
+
|
|
11
|
+
### Goals
|
|
12
|
+
|
|
13
|
+
- When the host sends a Telegram notify, the user can **reply in Telegram** and command the **same** Grok Bot / MCP-host agent that originated that notify.
|
|
14
|
+
- Every outbound notify must carry (or be mapped to) a stable **`source_agent_id`** so replies route only to that agent.
|
|
15
|
+
- Inbound text should support **full natural-language commands**, equivalent in intent to typing in the in-app chat box (subject to host delivery APIs).
|
|
16
|
+
- Keep **BYOB** (bring-your-own BotFather token) via process env; no mandatory hosted SaaS.
|
|
17
|
+
- Remain local-first: mapping store and pending queue live with the MCP process (or a small colocated bridge).
|
|
18
|
+
- Preserve **agent isolation**: a reply to agent A’s message must never wake agent B.
|
|
19
|
+
|
|
20
|
+
### Non-goals
|
|
21
|
+
|
|
22
|
+
- Replacing the in-app chat UI or becoming a full Telegram client.
|
|
23
|
+
- Executing shell, file writes, or arbitrary code **inside** the MCP server based on Telegram text.
|
|
24
|
+
- Shipping or collecting BotFather tokens in the package; marketplace “plugin secret UI” is separate future work.
|
|
25
|
+
- Native voice STT inside the MCP package (host transcribes after download).
|
|
26
|
+
- Multi-tenant hosted bridging, public webhooks on the open internet without the user’s own infra, or group-admin bot features.
|
|
27
|
+
- Guaranteeing delivery if the MCP host process is stopped (queue persists locally; delivery waits for a poller).
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## 2. Actors
|
|
32
|
+
|
|
33
|
+
| Actor | Role |
|
|
34
|
+
|-------|------|
|
|
35
|
+
| **User (Telegram)** | Receives notifies; replies with text (MVP) or later voice. Only allowlisted chat(s) may command. |
|
|
36
|
+
| **BotFather bot** | User-owned Telegram bot; HTTP API token stays in env (`TELEGRAM_BOT_TOKEN`). |
|
|
37
|
+
| **MCP server (`telegram-notify-mcp`)** | stdio tools: outbound notify, mapping persistence, pending-command queue, ack, optional getFile metadata. Does **not** wake agents by itself. |
|
|
38
|
+
| **MCP host / Grok Bot agents** | Multiple concurrent agents may share one MCP server instance. Each agent has an id used as `source_agent_id`. Host provides SendToAgent / channel / routine hooks. |
|
|
39
|
+
| **Optional bridge** | Host skill/routine or small daemon that polls Telegram (or receives webhook), feeds updates into MCP queue tools, then delivers pending commands to the correct agent. |
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## 3. Why MCP alone is not enough for push-inbound
|
|
44
|
+
|
|
45
|
+
MCP tools over **stdio** are **pull**: the host invokes a tool, the server returns a result, then the turn ends. There is no standard MCP mechanism for the server to asynchronously push “user replied on Telegram” into a sleeping agent.
|
|
46
|
+
|
|
47
|
+
Telegram inbound is either:
|
|
48
|
+
|
|
49
|
+
- **Long-poll** `getUpdates`, or
|
|
50
|
+
- **Webhook** HTTP callbacks,
|
|
51
|
+
|
|
52
|
+
both of which require a process that is awake and can **deliver** into the host’s agent runtime.
|
|
53
|
+
|
|
54
|
+
Grok Bot-style **wake / `[inbound]`** (or equivalent host channels) are **host-side** concerns. The MCP package can:
|
|
55
|
+
|
|
56
|
+
1. Record outbound message → agent mappings,
|
|
57
|
+
2. Ingest updates into a **pending commands** queue,
|
|
58
|
+
3. Expose `list` / `get` / `ack` tools,
|
|
59
|
+
|
|
60
|
+
but something on the host (routine, skill, or bridge daemon) must poll those tools (or call Telegram and then the tools) and invoke **SendToAgent** (or the host’s equivalent) for the mapped `source_agent_id`.
|
|
61
|
+
|
|
62
|
+
**Implication:** Phase 2 is a **two-layer** design. Shipping only new MCP tools without a host poller does not complete the user story.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 4. Recommended architecture — two layers
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
┌─────────────┐ Bot API ┌──────────────────────┐ stdio ┌─────────────────┐
|
|
70
|
+
│ Telegram │ ◀──────────────▶ │ telegram-notify-mcp │ ◀────────────▶ │ MCP host │
|
|
71
|
+
│ (user) │ │ (tools + local DB) │ │ agents + │
|
|
72
|
+
└─────────────┘ └──────────────────────┘ │ routine/bridge │
|
|
73
|
+
└────────┬────────┘
|
|
74
|
+
│
|
|
75
|
+
SendToAgent / channel
|
|
76
|
+
│
|
|
77
|
+
┌────────┴────────┐
|
|
78
|
+
│ target agent │
|
|
79
|
+
│ (by source_id) │
|
|
80
|
+
└─────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Layer A — MCP package
|
|
84
|
+
|
|
85
|
+
- Extend outbound notify to require **`source_agent_id`** (optional display name).
|
|
86
|
+
- Persist mapping: Telegram `message_id` + `chat_id` → agent metadata.
|
|
87
|
+
- Ingest allowlisted inbound text (and later voice file refs) into a **pending commands** queue.
|
|
88
|
+
- Expose tools to list/get/ack pending commands; no agent wake logic inside MCP.
|
|
89
|
+
|
|
90
|
+
### Layer B — Host / bridge
|
|
91
|
+
|
|
92
|
+
- Prefer a **Grok Bot routine or skill** that periodically:
|
|
93
|
+
|
|
94
|
+
1. Ensures Telegram updates are pulled (either host calls `telegram_get_updates` / ingest helper, or MCP does opportunistic poll on tool call — see open questions),
|
|
95
|
+
2. Calls `telegram_list_pending_commands` (or get),
|
|
96
|
+
3. Delivers each item to the mapped agent via host API,
|
|
97
|
+
4. Acks successful delivery.
|
|
98
|
+
|
|
99
|
+
- Alternative: a small local daemon with the same poll → deliver → ack loop if the host has no scheduler.
|
|
100
|
+
|
|
101
|
+
**Do not** put BotFather tokens in Telegram message bodies or in agent prompts.
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## 5. Outbound notify changes
|
|
106
|
+
|
|
107
|
+
### Tool contract (conceptual)
|
|
108
|
+
|
|
109
|
+
`telegram_notify` (and optionally `telegram_send_message`) gains:
|
|
110
|
+
|
|
111
|
+
| Field | Required | Purpose |
|
|
112
|
+
|-------|----------|---------|
|
|
113
|
+
| `source_agent_id` | **Yes** (Phase 2) | Stable id of the originating agent |
|
|
114
|
+
| `source_agent_name` | No | Short human label for footer / debugging |
|
|
115
|
+
| existing `title` / `body` / `chat_id` | as today | Unchanged semantics |
|
|
116
|
+
|
|
117
|
+
### User-visible attribution
|
|
118
|
+
|
|
119
|
+
Append a **short footer** on the Telegram message, for example:
|
|
120
|
+
|
|
121
|
+
```text
|
|
122
|
+
— from agent <source_agent_name or truncated source_agent_id>
|
|
123
|
+
Reply to this message to continue with this agent.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Optional later: Telegram reply keyboard / force-reply hints. Footer alone is enough for MVP clarity.
|
|
127
|
+
|
|
128
|
+
### Mapping store
|
|
129
|
+
|
|
130
|
+
On successful `sendMessage`, persist:
|
|
131
|
+
|
|
132
|
+
```text
|
|
133
|
+
{ telegram_message_id, chat_id } → {
|
|
134
|
+
source_agent_id,
|
|
135
|
+
source_agent_name?,
|
|
136
|
+
conversation_hint?, // optional host conversation / thread id
|
|
137
|
+
created_at
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Storage: local file or embedded store beside the MCP process (path configurable). Entries may TTL/expire; see open questions.
|
|
142
|
+
|
|
143
|
+
Routing key is **`reply_to_message.message_id` + `chat.id`**, not the free-text footer (footer is UX only).
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 6. Inbound path MVP (text)
|
|
148
|
+
|
|
149
|
+
1. **Prefer Telegram reply** to the notify message. If `message.reply_to_message` is present, look up mapping; if found, enqueue a pending command for that `source_agent_id`.
|
|
150
|
+
2. **Fallback:** if there is no reply context, accept an explicit `/to <agent_id_or_name> …` only when unambiguous; otherwise reject with a short Telegram error reply (optional) or drop with log.
|
|
151
|
+
3. **Allowlist:** only updates from configured `TELEGRAM_CHAT_ID` (or an allowlist env) may create commands. Other chats are ignored.
|
|
152
|
+
4. Payload to the agent is the user’s natural-language text (plus optional metadata: telegram message id, timestamp, reply-to notify id). Treat it like an in-app user message, not a restricted verb DSL — verbs like `/status` may still be conveniences but are not required for MVP.
|
|
153
|
+
5. After enqueue, the host poller delivers and **acks**; MCP does not interpret command semantics.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 7. Delivery into Grok Bot (ranked options)
|
|
158
|
+
|
|
159
|
+
| Rank | Option | Notes |
|
|
160
|
+
|------|--------|-------|
|
|
161
|
+
| **A (recommended)** | Host **routine/skill** polls `telegram_list_pending_commands` / get, then **SendToAgent** (or host channel) for `source_agent_id`, then `telegram_ack_command` | Works with BYOB MCP today; no product webhook dependency |
|
|
162
|
+
| **B** | Webhook trigger if the host supports HTTP wake endpoints | Faster; needs user-exposed HTTPS and host support |
|
|
163
|
+
| **C** | Native Telegram channel if the product adds one later | Best UX long-term; out of package scope |
|
|
164
|
+
|
|
165
|
+
**Recommendation for BYOB MCP today: A.**
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 8. New MCP tools sketch (names + purpose only)
|
|
170
|
+
|
|
171
|
+
| Tool | Purpose |
|
|
172
|
+
|------|---------|
|
|
173
|
+
| `telegram_notify` | Extended: require `source_agent_id`; optional `source_agent_name`; write mapping; footer |
|
|
174
|
+
| `telegram_send_message` | Same attribution/mapping when used as a notify-equivalent |
|
|
175
|
+
| `telegram_list_pending_commands` | List unacked pending commands (filter by agent optional) |
|
|
176
|
+
| `telegram_get_pending_commands` | Alias or fetch+claim variant for pollers (exact semantics TBD) |
|
|
177
|
+
| `telegram_ack_command` | Mark command delivered / drop after host success or poison |
|
|
178
|
+
| `telegram_ingest_updates` | Optional: pull `getUpdates`, apply allowlist + mapping, enqueue (keeps poll logic in one place) |
|
|
179
|
+
| `telegram_get_file` | Phase 2b: resolve Telegram `file_id` → download path/URL metadata for host STT |
|
|
180
|
+
|
|
181
|
+
Phase 1 tools (`telegram_get_me`, `telegram_get_updates`) remain for setup and diagnostics.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 9. Data model — pending commands queue
|
|
186
|
+
|
|
187
|
+
Logical record (illustrative fields):
|
|
188
|
+
|
|
189
|
+
```text
|
|
190
|
+
PendingCommand {
|
|
191
|
+
id: string // opaque local id
|
|
192
|
+
source_agent_id: string
|
|
193
|
+
source_agent_name?: string
|
|
194
|
+
conversation_hint?: string
|
|
195
|
+
chat_id: string | number
|
|
196
|
+
telegram_message_id: number
|
|
197
|
+
reply_to_telegram_message_id?: number
|
|
198
|
+
text: string // user command text (MVP)
|
|
199
|
+
voice_file_id?: string // Phase 2b
|
|
200
|
+
created_at: string // ISO-8601
|
|
201
|
+
status: "pending" | "delivered" | "acked" | "rejected"
|
|
202
|
+
error?: string
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Mapping table (outbound):
|
|
207
|
+
|
|
208
|
+
```text
|
|
209
|
+
OutboundMapping {
|
|
210
|
+
chat_id
|
|
211
|
+
telegram_message_id
|
|
212
|
+
source_agent_id
|
|
213
|
+
source_agent_name?
|
|
214
|
+
conversation_hint?
|
|
215
|
+
created_at
|
|
216
|
+
}
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Concurrency: ack should be idempotent; list should not lose items if the poller crashes before ack (at-least-once delivery; host should tolerate duplicates or use `id` for dedupe).
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
223
|
+
## 10. Security
|
|
224
|
+
|
|
225
|
+
- **Token:** `TELEGRAM_BOT_TOKEN` only via env / secret store; never log full token; never put token in Telegram bodies or tool results shown to the user.
|
|
226
|
+
- **Chat allowlist:** inbound commands only from configured chat id(s); reject others silently or with minimal log.
|
|
227
|
+
- **No shell from Telegram inside MCP:** the server only enqueues text/metadata; the **host agent** decides what to do under its normal policies.
|
|
228
|
+
- **Rate limits:** per-chat and global caps on enqueue and outbound replies to avoid loops / floods.
|
|
229
|
+
- **Secrets in bodies:** notify templates must not embed tokens, API keys, or private paths.
|
|
230
|
+
- **Agent isolation:** routing strictly by mapping / `/to`; never broadcast a command to all agents.
|
|
231
|
+
- **Least privilege:** bridge/routine only needs MCP tool access + host SendToAgent; it should not re-export the bot token to agents’ prompts.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## 11. Voice — Phase 2b
|
|
236
|
+
|
|
237
|
+
After text MVP:
|
|
238
|
+
|
|
239
|
+
1. User replies with a voice message (preferably as reply-to notify).
|
|
240
|
+
2. Ingest stores `voice_file_id` (and duration/mime if available) on the pending command; `text` may be empty.
|
|
241
|
+
3. Host (or bridge) calls `telegram_get_file`, downloads bytes locally, **transcribes** with a host-chosen STT service.
|
|
242
|
+
4. Host writes transcript into the same delivery path (SendToAgent) as text commands, then acks.
|
|
243
|
+
|
|
244
|
+
MCP does not embed a cloud STT vendor; transcription is host-side so BYOB and privacy choices stay with the operator.
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## 12. Sequence diagrams
|
|
249
|
+
|
|
250
|
+
### Outbound notify
|
|
251
|
+
|
|
252
|
+
```mermaid
|
|
253
|
+
sequenceDiagram
|
|
254
|
+
participant Agent as Source agent
|
|
255
|
+
participant Host as MCP host
|
|
256
|
+
participant MCP as telegram-notify-mcp
|
|
257
|
+
participant TG as Telegram Bot API
|
|
258
|
+
participant User as User Telegram
|
|
259
|
+
|
|
260
|
+
Agent->>Host: request notify (body, source_agent_id)
|
|
261
|
+
Host->>MCP: telegram_notify(...)
|
|
262
|
+
MCP->>TG: sendMessage (body + footer)
|
|
263
|
+
TG-->>MCP: message_id, chat_id
|
|
264
|
+
MCP->>MCP: persist mapping message_id->source_agent_id
|
|
265
|
+
MCP-->>Host: ok + message_id
|
|
266
|
+
TG-->>User: notification visible
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Inbound reply → agent
|
|
270
|
+
|
|
271
|
+
```mermaid
|
|
272
|
+
sequenceDiagram
|
|
273
|
+
participant User as User Telegram
|
|
274
|
+
participant TG as Telegram Bot API
|
|
275
|
+
participant MCP as telegram-notify-mcp
|
|
276
|
+
participant Bridge as Host routine / bridge
|
|
277
|
+
participant Agent as Mapped agent
|
|
278
|
+
|
|
279
|
+
User->>TG: reply text to notify message
|
|
280
|
+
Bridge->>MCP: telegram_ingest_updates / getUpdates path
|
|
281
|
+
MCP->>TG: getUpdates (if not webhook)
|
|
282
|
+
TG-->>MCP: update with reply_to_message
|
|
283
|
+
MCP->>MCP: allowlist check + mapping lookup
|
|
284
|
+
MCP->>MCP: enqueue PendingCommand
|
|
285
|
+
Bridge->>MCP: telegram_list_pending_commands
|
|
286
|
+
MCP-->>Bridge: [{ id, source_agent_id, text, ... }]
|
|
287
|
+
Bridge->>Agent: SendToAgent / channel (natural language)
|
|
288
|
+
Bridge->>MCP: telegram_ack_command(id)
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
## 13. MVP milestones
|
|
294
|
+
|
|
295
|
+
| Milestone | Scope |
|
|
296
|
+
|-----------|--------|
|
|
297
|
+
| **M1** | Mapping store + `source_agent_id` on notify + footer attribution |
|
|
298
|
+
| **M2** | Pending commands queue + list/get/ack (+ optional ingest) tools |
|
|
299
|
+
| **M3** | Grok Bot routine/skill (or daemon) poll → SendToAgent → ack |
|
|
300
|
+
| **M4** | Voice Phase 2b: `telegram_get_file` + host transcription into same queue |
|
|
301
|
+
|
|
302
|
+
Exit criteria for text MVP: reply-to a notify in the allowlisted chat reliably wakes **only** the originating agent with the user’s full text.
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## 14. Open questions for the publisher
|
|
307
|
+
|
|
308
|
+
1. **Mapping TTL / retention** — How long to keep outbound mappings and pending rows? Bound disk use vs. late replies.
|
|
309
|
+
2. **Who calls `getUpdates`?** — Host-only vs. `telegram_ingest_updates` inside MCP on each poll tick (single consumer offset / `offset` management).
|
|
310
|
+
3. **Claim vs. list** — Soft-claim (`delivered`) before SendToAgent to reduce double-delivery races across multiple pollers.
|
|
311
|
+
4. **`conversation_hint` schema** — Opaque string vs. structured host conversation id; required or optional for MVP.
|
|
312
|
+
5. **Failure UX** — Auto-reply on Telegram when mapping missing / agent unknown / rate limited?
|
|
313
|
+
6. **Multi-instance MCP** — If two host processes share one bot token, how is update offset and queue ownership coordinated? (Recommend single consumer.)
|
|
314
|
+
7. **Webhook mode** — Document as optional bridge feature only, or first-class in package later?
|
|
315
|
+
8. **Marketplace secret UI** — Explicitly out of band; any interim docs for Cursor/Grok env wiring only?
|
|
316
|
+
9. **Name collision** — Unscoped npm name vs. existing third-party scoped packages; confirm publish name/scope before M3 docs ship install lines.
|
|
317
|
+
10. **Host API stability** — Exact SendToAgent / routine primitives available to third-party skills; may require bridge daemon if APIs are not public.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## Related
|
|
322
|
+
|
|
323
|
+
- Phase 1 tools and BYOB setup: [README.md](./README.md)
|
|
324
|
+
- High-level phases: [ROADMAP.md](./ROADMAP.md)
|
package/README.md
CHANGED
|
@@ -2,27 +2,57 @@
|
|
|
2
2
|
|
|
3
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
4
|
|
|
5
|
-
This package is **not** a
|
|
5
|
+
This package is **not** a 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
6
|
|
|
7
|
-
**Publisher:** Ehsan Eskandari pour
|
|
7
|
+
**Publisher:** Ehsan Eskandari pour
|
|
8
|
+
**Version:** 2.0.0 (Phase 2 text MVP — breaking: `source_agent_id` required on `telegram_notify`)
|
|
8
9
|
|
|
9
10
|
## How it works
|
|
10
11
|
|
|
11
12
|
1. You create a Telegram bot with BotFather and receive an HTTP API token.
|
|
12
13
|
2. You run this MCP server with `TELEGRAM_BOT_TOKEN` (and optionally `TELEGRAM_CHAT_ID`) in the process environment.
|
|
13
|
-
3. Your MCP host
|
|
14
|
-
4.
|
|
14
|
+
3. Your MCP host lists the tools and can call them over stdio.
|
|
15
|
+
4. **Outbound:** notify tools send messages, append an agent footer, and persist `message_id → source_agent_id` mappings locally.
|
|
16
|
+
5. **Inbound (Phase 2):** a host poller calls `telegram_poll_inbound` → `telegram_list_pending_commands` → delivers via the host’s **SendToAgent** (or equivalent) → `telegram_ack_command`.
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
MCP alone cannot wake agents asynchronously (stdio is pull-only). See [ARCHITECTURE-PHASE2.md](./ARCHITECTURE-PHASE2.md).
|
|
17
19
|
|
|
18
|
-
## Tools
|
|
20
|
+
## Tools
|
|
21
|
+
|
|
22
|
+
### Phase 1 (still available)
|
|
19
23
|
|
|
20
24
|
| Tool | Purpose |
|
|
21
25
|
|------|---------|
|
|
22
26
|
| `telegram_get_me` | `getMe` — verify token; return bot id/username |
|
|
23
|
-
| `telegram_send_message` | Send text (`chat_id` optional if env default set) |
|
|
24
|
-
| `
|
|
25
|
-
|
|
27
|
+
| `telegram_send_message` | Send text (`chat_id` optional if env default set). Optional `source_agent_id` / `source_agent_name` for footer + mapping |
|
|
28
|
+
| `telegram_get_updates` | List recent updates to discover `chat_id` after `/start` (does **not** enqueue commands) |
|
|
29
|
+
|
|
30
|
+
### Phase 2 — notify + inbound queue
|
|
31
|
+
|
|
32
|
+
| Tool | Purpose |
|
|
33
|
+
|------|---------|
|
|
34
|
+
| `telegram_notify` | Completion notification (`title?` + `body`). **Requires** `source_agent_id`; optional `source_agent_name`. Appends footer; persists mapping |
|
|
35
|
+
| `telegram_poll_inbound` | `getUpdates` + allowlist + route into pending queue; returns summary counts |
|
|
36
|
+
| `telegram_list_pending_commands` | List pending commands (optional `source_agent_id` filter) |
|
|
37
|
+
| `telegram_ack_command` | Ack/delete by `command_id` after host delivery |
|
|
38
|
+
|
|
39
|
+
### Inbound routing rules
|
|
40
|
+
|
|
41
|
+
1. Only messages from allowlisted chats (`TELEGRAM_CHAT_ALLOWLIST` or `TELEGRAM_CHAT_ID`) are considered.
|
|
42
|
+
2. If the message is a **Telegram reply** to a mapped outbound `message_id`, enqueue for that `source_agent_id`.
|
|
43
|
+
3. Else if text starts with `/to <agent_id> <command…>`, enqueue for that agent (fallback).
|
|
44
|
+
4. Else **ignore** (counted as `ignored_unroutable` in the poll result). No shell execution inside MCP.
|
|
45
|
+
|
|
46
|
+
## Host poller responsibilities (M3 bridge)
|
|
47
|
+
|
|
48
|
+
A host **routine / skill / daemon** must periodically:
|
|
49
|
+
|
|
50
|
+
1. Call `telegram_poll_inbound` (fills the queue; advances update offset).
|
|
51
|
+
2. Call `telegram_list_pending_commands`.
|
|
52
|
+
3. For each command, deliver `text` to the agent identified by `source_agent_id` via **SendToAgent** (or the host’s equivalent channel).
|
|
53
|
+
4. Call `telegram_ack_command` with that command’s `id` after successful delivery.
|
|
54
|
+
|
|
55
|
+
Without this loop, pending commands sit on disk and no agent wakes. See [`skill-snippet.md`](./skill-snippet.md) for a copy-paste host-bridge sketch.
|
|
26
56
|
|
|
27
57
|
## BotFather setup
|
|
28
58
|
|
|
@@ -39,7 +69,7 @@ Phase 1 is outbound notify only — no inbound webhooks.
|
|
|
39
69
|
2. In Telegram, open your bot and tap **Start** (or send `/start`).
|
|
40
70
|
3. Call tool `telegram_get_updates` (optional `limit`).
|
|
41
71
|
4. Read `discovered_chats[].chat_id` from the result.
|
|
42
|
-
5. Set `TELEGRAM_CHAT_ID` to that value so
|
|
72
|
+
5. Set `TELEGRAM_CHAT_ID` to that value so send/notify can omit `chat_id`, and inbound allowlisting works.
|
|
43
73
|
|
|
44
74
|
Private chats use a numeric id (e.g. `123456789`). Groups/channels may use negative ids.
|
|
45
75
|
|
|
@@ -61,10 +91,14 @@ Environment variables (see [`.env.example`](./.env.example)):
|
|
|
61
91
|
| Variable | Required | Description |
|
|
62
92
|
|----------|----------|-------------|
|
|
63
93
|
| `TELEGRAM_BOT_TOKEN` | Yes (for tool calls) | BotFather HTTP API token |
|
|
64
|
-
| `TELEGRAM_CHAT_ID` | No | Default chat for send/notify |
|
|
94
|
+
| `TELEGRAM_CHAT_ID` | No | Default chat for send/notify; also used as inbound allowlist |
|
|
95
|
+
| `TELEGRAM_CHAT_ALLOWLIST` | No | Comma-separated chat ids allowed for inbound commands |
|
|
96
|
+
| `TELEGRAM_NOTIFY_DATA_DIR` | No | Directory for mapping + pending JSON (default `./.telegram-notify-data`) |
|
|
65
97
|
|
|
66
98
|
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
99
|
|
|
100
|
+
Local data under `.telegram-notify-data/` is **gitignored** and **not** included in the npm `files` list.
|
|
101
|
+
|
|
68
102
|
## Add MCP in Cursor / Grok Bot
|
|
69
103
|
|
|
70
104
|
Use a **local** command + env. Example with a built clone:
|
|
@@ -110,38 +144,44 @@ If your host UI has **Add MCP** fields instead of raw JSON:
|
|
|
110
144
|
|
|
111
145
|
- **command:** `node`
|
|
112
146
|
- **args:** `/absolute/path/to/telegram-notify-mcp/dist/index.js`
|
|
113
|
-
- **env:** `TELEGRAM_BOT_TOKEN`, optional `TELEGRAM_CHAT_ID`
|
|
147
|
+
- **env:** `TELEGRAM_BOT_TOKEN`, optional `TELEGRAM_CHAT_ID` / `TELEGRAM_CHAT_ALLOWLIST` / `TELEGRAM_NOTIFY_DATA_DIR`
|
|
114
148
|
|
|
115
|
-
## Example notify flow
|
|
149
|
+
## Example notify flow (Phase 2)
|
|
116
150
|
|
|
117
|
-
1. Configure token (+
|
|
151
|
+
1. Configure token (+ default chat id) as above; reload MCP.
|
|
118
152
|
2. `telegram_get_me` → confirm username.
|
|
119
|
-
3.
|
|
120
|
-
4. When a task finishes, call `telegram_notify` with title/body. Example payload:
|
|
153
|
+
3. When a task finishes, call `telegram_notify` with **required** `source_agent_id`:
|
|
121
154
|
|
|
122
155
|
```json
|
|
123
156
|
{
|
|
124
157
|
"title": "Deploy finished",
|
|
125
|
-
"body": "staging is live; smoke tests passed."
|
|
158
|
+
"body": "staging is live; smoke tests passed.",
|
|
159
|
+
"source_agent_id": "agent-deploy-1",
|
|
160
|
+
"source_agent_name": "Deploy helper"
|
|
126
161
|
}
|
|
127
162
|
```
|
|
128
163
|
|
|
129
|
-
|
|
164
|
+
Telegram shows something like:
|
|
130
165
|
|
|
131
166
|
```text
|
|
132
167
|
✅ Deploy finished
|
|
133
168
|
|
|
134
169
|
staging is live; smoke tests passed.
|
|
170
|
+
|
|
171
|
+
— from agent Deploy helper
|
|
172
|
+
Reply to this message to continue with this agent.
|
|
135
173
|
```
|
|
136
174
|
|
|
137
|
-
|
|
175
|
+
4. User **replies** to that message in Telegram (or sends `/to agent-deploy-1 …`).
|
|
176
|
+
5. Host poller: `telegram_poll_inbound` → list → SendToAgent → `telegram_ack_command`.
|
|
138
177
|
|
|
139
178
|
## Security notes
|
|
140
179
|
|
|
141
|
-
- Token grants full control of the bot — treat it like a password.
|
|
142
|
-
- This server
|
|
180
|
+
- Token grants full control of the bot — treat it like a password. **Do not log tokens.**
|
|
181
|
+
- This server does **not** execute shell or arbitrary code from Telegram text; it only enqueues text/metadata for the host.
|
|
182
|
+
- Inbound commands are restricted to the chat allowlist.
|
|
143
183
|
- Configuration is **env-only** for the published server. Do not commit tokens or chat ids.
|
|
144
|
-
-
|
|
184
|
+
- Mapping/pending data stays local; do not publish `.telegram-notify-data` in releases.
|
|
145
185
|
|
|
146
186
|
## Contact
|
|
147
187
|
|
|
@@ -156,4 +196,4 @@ MIT — see [LICENSE](./LICENSE).
|
|
|
156
196
|
|
|
157
197
|
## Roadmap
|
|
158
198
|
|
|
159
|
-
|
|
199
|
+
Milestones and remaining work: [ROADMAP.md](./ROADMAP.md). Architecture detail: [ARCHITECTURE-PHASE2.md](./ARCHITECTURE-PHASE2.md).
|
package/ROADMAP.md
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
# Roadmap
|
|
2
2
|
|
|
3
|
-
## Phase 1
|
|
3
|
+
## Phase 1
|
|
4
4
|
|
|
5
5
|
- [x] stdio MCP connector/server
|
|
6
6
|
- [x] `telegram_get_me`, `telegram_send_message`, `telegram_notify`, `telegram_get_updates`
|
|
7
7
|
- [x] BYOB BotFather token via env only
|
|
8
8
|
- [x] Unit tests with mocked fetch
|
|
9
|
-
- [
|
|
9
|
+
- [x] Publish to npm (`telegram-notify-mcp`) — 1.0.0 shipped; 2.0.0 Phase 2 text MVP
|
|
10
10
|
- [ ] Optional Cursor / MCP marketplace plugin listing
|
|
11
11
|
|
|
12
|
-
## Phase 2
|
|
12
|
+
## Phase 2 — inbound / control (text MVP)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Architecture: [ARCHITECTURE-PHASE2.md](./ARCHITECTURE-PHASE2.md).
|
|
15
15
|
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
-
|
|
16
|
+
- [x] **M1** — Mapping store + required `source_agent_id` on `telegram_notify` + footer attribution
|
|
17
|
+
- [x] **M2** — Pending commands queue + `telegram_poll_inbound` / `telegram_list_pending_commands` / `telegram_ack_command`; reply + `/to` routing; chat allowlist
|
|
18
|
+
- [x] **M3** — Host routine/skill (or daemon) poll → SendToAgent → ack (documented; host-provided)
|
|
19
|
+
- [ ] **M4** — Voice Phase 2b: `telegram_get_file` + host transcription into same queue
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
Notes:
|
|
22
|
+
|
|
23
|
+
- Two layers: MCP tools + mapping/pending queue; host routine/bridge delivers to the originating agent
|
|
24
|
+
- Unroutable allowlisted inbound text (no reply mapping and no `/to <agent_id> …`) is **ignored** (counted in poll summary)
|
|
25
|
+
- Delivery: recommend host poll of pending-command tools + SendToAgent (webhook / native channel later)
|
|
26
|
+
- Voice/audio after text MVP (Phase 2b / M4)
|
|
27
|
+
- BYOB token remains env-only (marketplace secret UI is separate)
|
|
28
|
+
|
|
29
|
+
Phase 2 remains local-first (no mandatory hosted SaaS) and keeps the BotFather BYOB model.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
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)
|
|
4
|
+
* Env: TELEGRAM_BOT_TOKEN (required for tool calls), TELEGRAM_CHAT_ID (optional default),
|
|
5
|
+
* TELEGRAM_CHAT_ALLOWLIST (optional inbound), TELEGRAM_NOTIFY_DATA_DIR (optional store path).
|
|
5
6
|
* Starts cleanly without a token; tools fail with a clear error when invoked.
|
|
6
7
|
* Public release is env-only — no disk/secret-store token loading.
|
|
8
|
+
* Phase 2: outbound mapping + inbound pending queue (host must poll + SendToAgent + ack).
|
|
7
9
|
*/
|
|
8
10
|
export {};
|