# Agents Guide To Telegram

> 教 agent 使用 Telegram / Teach your agent to use Telegram — native rich messages (tables, LaTeX, collapsible blocks), a live progress window for Claude Code, and a field guide of hard-won pitfalls.

- **Type:** MCP server
- **Install:** `agentstack add mcp-circe22-agents-guide-to-telegram`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Circe22](https://agentstack.voostack.com/s/circe22)
- **Installs:** 0
- **Category:** [Communication](https://agentstack.voostack.com/c/communication)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Circe22](https://github.com/Circe22)
- **Source:** https://github.com/Circe22/agents-guide-to-telegram

## Install

```sh
agentstack add mcp-circe22-agents-guide-to-telegram
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# The Agent's Guide to Telegram

**教 agent 使用 Telegram：发原生表格、LaTeX 公式、折叠块、真贴纸，让你在手机上看着它干活——外加一整本真机踩出来的坑谱。**

>  · MIT · 只依赖 `requests`
> 发现 bug 或者 Telegram 又更新了，欢迎开 issue / 提 PR。
> （前名 `tg-rich-mcp`，旧链接与旧 git remote 均自动跳转。名字致敬你猜到的那本书——
> 面对一个陌生星球的 API，手册比勇气有用，Don't Panic.）

Telegram 在 Bot API **10.1**（2026-06-11）加了 Rich Messages，10.2（07-14）补齐发送侧。
官方 telegram 插件的 `reply` 够不着这些，这个包直投 Bot API 把它接进来。

两件东西，可以只用一件：

| 文件 | 是什么 | 通用性 |
|---|---|---|
| `tg_rich_mcp.py` | MCP server，六个工具：发 / 原地改 / 推草稿 / 贴纸挑发 / 贴纸入库 / 按钮选择题（实验性） | ✅ 走**握手式** MCP 的 host（Claude Code、Claude Desktop、Cursor、自己写的 agent）——协议版本见下 |
| `tg_sticker.py` | 贴纸车道：库 / 认领 / 交集挑选 / 各 bot 懒迁移 / 句内标记（挂在上面那个 server 里） | 跟着走 |
| `tg_ask.py` | 按钮问答机制层：inline keyboard + getUpdates 同步等点击（挂在上面那个 server 里） | 跟着走；⚠️ 要**专用 bot**，见「按钮问答要专用 bot」 |
| `tg_progress_hook.py` | 进度窗 hook：每次调工具前推一行 | ⚠️ **仅 Claude Code**（靠它的 PreToolUse 钩子，别的 host 没有这个机制），且需要 Linux / macOS / WSL |
| `tg_sticker_hook.py` | 入站贴纸识别 hook：认识的注入标签（agent 不用看图）、不认识的提醒归档 | ⚠️ **仅 Claude Code**（UserPromptSubmit 钩子）；零网络、fail-silent |
| `secret_redaction.py` | 密钥形态的单一真源，上面的都用它 | 跟着走，别单独删 |
| `sticker-spec/` | 贴纸标记语法的规格真源：共享 golden fixtures，多实现各自跑同一份止漂移（见 COOKBOOK 贴纸章末节） | ✅ 任何实现这套标记语法的都该跑 |
| `test_*.py` | 169 个测试（含 `test_conformance.py` 跑 sticker-spec），`python3 -m unittest discover -v`，1 秒内、不发网络 | — |

外加一份 **[COOKBOOK.md](COOKBOOK.md)** —— Telegram 富消息**能玩什么**的全景清单：
行内公式、剧透、上下标、脚注、锚点跳转、任务清单、表格高级字段、地图、拼贴轮播、
按读者时区渲染的时间……包括你大概率用不上的那些。
列出来不是让你都用，是让你**知道有这条路**——不知道的能力等于不存在。

进度窗长这样，在手机上一行行自己长出来：

```
┌ 正在干活…
│ 📖 Read · server.py
│ 🔍 Grep · handleRequest
│ ⚡ Bash · 跑一遍测试
└ 已经做了 12 步
```

> **它默认会把长得像密钥的摘要替换成「（内容隐去）」。不喜欢这种防御？
> `TG_PROGRESS_REDACT=0` 一把关掉** —— 详见下面「安全闸，以及怎么关」。

> **TL;DR (English)** — Teach your agent to use Telegram. An MCP server exposing Telegram's Rich Message API
> (native tables, LaTeX, collapsible blocks, in-place edits, streaming drafts,
> an emoji-indexed sticker lane the agent curates itself,
> plus experimental synchronous choice questions answered with one button tap)
> to any handshake-based stdio MCP host (protocol 2024-11-05 … 2025-11-25),
> plus a Claude Code hook that streams your agent's tool calls
> into a live Telegram window. Config via `~/.tg-rich-mcp.json`.
> Only dependency: `requests`. Redaction is on by default — `TG_PROGRESS_REDACT=0` disables it.
>
> ⚠️ **Android caveat**: while a streaming draft is active, Telegram Android replaces the
> user's send button with an ellipsis — they cannot send anything, and text already typed
> gets wiped when the input recovers ([bugs.telegram.org/c/62189](https://bugs.telegram.org/c/62189),
> closed by Telegram as expected behaviour). Bot API 10.3 added a partial fix: drafts sent
> with `can_stop` show a Stop button on **up-to-date** clients — pressing it dismisses the
> draft and unlocks the composer — but the bot can't hear the press unless its inbound side
> handles `stopped_message_generation`, and older clients never draw the button.
> The progress hook therefore still defaults to `sendRichMessage` + `editMessageText`
> and deletes the window when done; drafts (frames sent with `can_stop`) are opt-in
> via `TG_PROGRESS_MODE=draft`.

---

## 装

### 1. 配置

```bash
cat > ~/.tg-rich-mcp.json  **想用进度窗 hook 的话，必须用配置文件**（或把变量 export 进编辑器的启动环境）——
> hook 是编辑器另起的进程，拿不到你写在 MCP server 那段 `env` 里的变量。
> 这是最容易卡住的一步，第一次装的人十有八九栽在这。

### 2. 挂 MCP server

`.mcp.json`（或你的 host 对应的配置文件）：

```json
{
  "mcpServers": {
    "tg-rich": {
      "command": "python3",
      "args": ["/绝对路径/tg_rich_mcp.py"]
    }
  }
}
```

只依赖 `requests`（`pip install requests`）。协议是手写的 JSON-RPC over stdio，不需要 mcp SDK。

#### 支持哪几版 MCP 协议

实现的是**握手式**（`initialize` / `notifications/initialized`）的 MCP，协商这四版：

```
2025-11-25 · 2025-06-18 · 2025-03-26 · 2024-11-05
```

客户端要的版本在这里面就回同一个，不在就回最新的那个、由它决定断不断。

> ⚠️ **2026-07-28 那版不支持**，而且不是"再加一个字符串"就能支持的——它把 MCP 改成了
> **无状态**协议：移除 `initialize` 握手，协议版本和客户端能力改为每个请求放进 `_meta`；
> 服务器 MUST 实现 `server/discover`；所有 result 必须带 `resultType`；
> `ping` / `logging/setLevel` 被移除。
> （[Key Changes](https://modelcontextprotocol.io/specification/2026-07-28)）
>
> 好在它留了向后兼容的路：**同时支持新旧两代协议的 stdio 客户端**，会先拿
> `server/discover` 探测、失败再按旧握手回退——本 server 对它回 `method not found`，
> 回退即成立（实测就是这个响应）。但**只实现了 2026-07-28 的客户端不在此列**，
> 它连不上这个 server，这不是 bug，是两代协议的分界。
> 真要支持新协议是另一个工程，欢迎提 PR。

### 3. 挂进度窗 hook（可选，仅 Claude Code）

> ⚠️ **进度窗 hook 需要 Linux / macOS / WSL。** 它用 `fcntl` 给状态文件加锁
> （并发的工具调用会同时写同一个文件），而 `fcntl` 是 Unix-only ——
> **原生 Windows 的 Python 一 import 就报错**。
> Windows 用户请在 WSL 里跑 Claude Code，或者只用 MCP server 那半边（那半边全平台都行）。
>
> Progress hook requires Linux, macOS, or WSL (`fcntl` is Unix-only).
> The MCP server itself runs anywhere.

`.claude/settings.json`：

```json
{
  "hooks": {
    "PreToolUse": [{"hooks": [{"type": "command",
      "command": "python3 /绝对路径/tg_progress_hook.py",
      "timeout": 5}]}],
    "Stop": [{"hooks": [{"type": "command",
      "command": "python3 /绝对路径/tg_progress_hook.py --finish",
      "timeout": 15}]}]
  }
}
```

改完要重开会话才生效。

前一条是每一帧，后一条是收工。只挂前一条也能用，只是窗口会停在最后一帧、不会自己收拾。

### 4. 挂贴纸识别 hook（可选，仅 Claude Code）

收贴纸不该靠 agent「记得」。挂上这个 hook 之后：用户发来**库里认识的**贴纸，
agent 直接收到标题/emoji/标签/描述，不用下载看图；**没见过的**，注入一行提醒
（file_id 已带好），得空调一次 `tg_sticker_import` 就归档。

```json
{
  "hooks": {
    "UserPromptSubmit": [{"hooks": [{"type": "command",
      "command": "python3 /绝对路径/tg_sticker_hook.py",
      "timeout": 5}]}]
  }
}
```

它**零网络**（识别只查本地库，下载留给 import 工具）、fail-silent（自己挂了
最多少一行提示，绝不挡用户说话）。前提：入站消息里有 `attachment_kind="sticker"`
和 `attachment_file_id`（官方 telegram 插件的 tag 格式）；只有 file_id 时靠
import 时记下的各 bot 缓存反查身份，所以**导入过的才认得出**。

#### 它默认怎么干活

**发一条正式消息，然后每帧原地改它**（`sendRichMessage` → `editMessageText`），
收工时把这条消息**撤掉**——聊天记录里一条工具调用都不留。

> ⚠️ **为什么默认不是流式草稿**：`sendRichMessageDraft` 活跃期间，
> **Telegram Android 会把用户的发送键换成省略号，用户发不出消息，
> 而且这期间在输入框里打的字会在恢复时被清空。**
> 官方缺陷记录  已被关闭，称是"当前预期行为"。
> Bot API 10.3 起本 hook 的草稿帧都带 `can_stop`——**新客户端**有停止按钮，
> 按停＝草稿消失+输入框解锁（2026-09-04 Android 实测）。但发布出去的 hook
> 没法预知用户拿的是哪版客户端：**旧客户端不画这颗按钮**、锁死照旧；
> 且 hook 收不到按停事件（`stopped_message_generation` 走收信侧）——
> 好在进度窗瞎推无害，按停后客户端会把同 draft_id 的后续帧直接扔掉。
>
> 长任务里用户最需要插话的时刻（补条件、喊停、纠方向、回答 agent 的提问），
> 恰好就是草稿最活跃的时刻。所以草稿仍只在你显式打开时才走——
> 确认你的用户客户端够新（或在桌面端）再开。
> 桌面端据用户反馈不锁输入框——那是**用户反馈，不是官方的跨平台保证**。

| 想要什么 | 怎么设 |
|---|---|
| 默认（持久窗 + 收工撤掉） | 什么都不用设 |
| 干完把窗口留下来当记录 | `TG_PROGRESS_END=keep` |
| 就要那种流式动画+自动蒸发（帧自带 can_stop；**旧客户端仍锁输入框**） | `TG_PROGRESS_MODE=draft` |
| 换标题 | `TG_PROGRESS_TITLE=…` / `TG_PROGRESS_DONE_TITLE=…` |

**不用改 bot、不用升级什么**——Rich Message 是 Telegram 服务端的能力，
你的 bot 直接调新方法就有。客户端得是支持 10.1 的版本才看得到渲染效果。

---

## 安全闸，以及怎么关

进度窗要把工具调用的摘要发进 Telegram，所以默认带两道闸。
**它们都可以关，而且关得干脆——这是你的机器。**

| 想干什么 | 怎么做 |
|---|---|
| 整个进度窗都不要 | `TG_PROGRESS=0` |
| 要进度窗，但**不要任何脱敏**（摘要原样推） | `TG_PROGRESS_REDACT=0` |
| 想知道哪些东西被隐过 | 看 `~/.tg-progress/redacted.log` |
| 换掉窗口标题 | `TG_PROGRESS_TITLE="你的标题"` / `TG_PROGRESS_DONE_TITLE="…"` |
| 干完别删、留一条记录 | `TG_PROGRESS_END=keep` |
| 要流式草稿（**会锁安卓输入框**） | `TG_PROGRESS_MODE=draft` |

两道闸分别是：

1. **关键词闸** —— 摘要里出现 `token` / `secret` / `password` / `.env` / `id_rsa` … 就整条隐去。
2. **形态闸** —— 认长相不认词：`sk-*`、`AKIA*`、`ghp_*`、`xox?-*`、JWT、PEM 头、
   长 hex / base64、URL 里的 `user:pass@`。
   只有关键词闸是不够的：`deploy sk-live-ABC123XYZ` 一个关键词都没有，照样是把密钥递出去。

另外几处是硬编码的保守取舍（不受开关影响之外的行为，代码里改也就一行）：

- `Bash` 只发 `description`（人话说明），**从不发 `command` 全文**。
- `Grep`/`Glob` 的 pattern 只在长得像普通标识符时才发，否则一个字不说。
- `WebFetch` 的 URL 只留 host + path，丢掉 query / userinfo / fragment。

**关了会怎样**：Bash 的说明、文件名、搜索词会原样发进 Telegram。
如果你的 agent 会碰到真的生产密钥，想清楚再关。

`redacted.log` **刻意不记原文**——记了就等于把密钥抄进另一个文件，闸就白设了。
它只记时间、哪个工具、命中哪道闸、原摘要多长，够回头对账。

---

## 用

### 日常：markdown 一行字

```
tg_rich_send(markdown="## 今日进度\n\n- [x] 修完 bug\n- [ ] 写测试")
```

### 进阶：把它接成 agent 的默认出口（渲染器模式）

「记得挑富消息工具、选对格式」不该是 agent 每条消息的负担。更稳的接法是反过来：
**正式文字回复默认走 `tg_rich_send(markdown=…)`**，把它当渲染器用——
没有 Markdown 语法的消息渲染出来就是普通文本，写了 `**重点**`、列表、代码块的
自动长成原生样式，agent 不用每条都想"这条要不要富格式"。

两条护栏（这个接法实跑出来的，别省）：

1. **降级只认 400 / 404**：Telegram 明确拒收（格式/能力问题）时才退回普通
   `sendMessage` 重发同一段；**网络错、超时、5xx、429 一律原样抛错，不自动补发**——
   这些状态下 Telegram 可能已经收到了第一条，自动补发＝制造重复消息。
2. **只包纯文字出口**：引用回复、附件、贴纸等旁路照走原来的路，别把整条发送链
   都塞进渲染器——包的面越大，降级时要复原的状态越多。

### 长任务：持久进度窗（推荐）

```
tg_rich_send(blocks=[...])        → 返回 message_id
tg_rich_edit(blocks=[...])        ← 每帧原地改；message_id 可省，
                                     默认改本会话最后发的那条（簿记归脚本）
```

`editMessageText` 收 `rich_message`（10.1 加的），所以进度窗**不必**用 30 秒草稿：
发一条正式消息、之后原地编辑，留在聊天记录里、编辑还不响铃。

`tg_rich_draft` 只在你要那种 30 秒动画质感时才用（私聊限定，不进聊天记录，
定稿必须补一条正式消息）。**它会锁住安卓用户的发送框**——新客户端可以
`can_stop: true` 给用户一颗解锁按钮，但旧客户端不画按钮、bot 也收不到按停
事件（完整账见坑 3），所以仍别拿它当长任务的默认进度方案。
进度窗 hook 的两种形态用 `TG_PROGRESS_MODE` 切：`edit`（默认·持久窗）/
`draft`（流式动画·帧自带 can_stop·收工自动消失）——各有拥趸，都留着。

### 表格 / 公式：用 blocks

```json
[
  {"type": "heading", "size": 3, "text": "本周开销"},
  {"type": "table", "is_bordered": true, "is_striped": true,
   "cells": [
     [{"text": "项目", "is_header": true}, {"text": "金额", "is_header": true}],
     [{"text": "服务器"}, {"text": "¥128"}],
     [{"text": "域名"}, {"text": "¥55"}]
   ]},
  {"type": "mathematical_expression", "expression": "\\sum_{i=1}^{n} x_i = 183"},
  {"type": "details", "summary": "明细", "blocks": [
     {"type": "paragraph", "text": "折叠起来只占一行。"}
  ]}
]
```

`mathematical_expression` 的 `expression` 是**裸 LaTeX**，不要包 `$$`。

### 本地图片 / 九宫格：media_paths + attach://

```
tg_rich_send(
  media_paths=["/pics/1.jpg", "/pics/2.jpg", "/pics/3.jpg"],
  blocks=[{"type": "collage", "blocks": [
    {"type": "photo", "photo": {"type": "photo", "media": "attach://f0"}},
    {"type": "photo", "photo": {"type": "photo", "media": "attach://f1"}},
    {"type": "photo", "photo": {"type": "photo", "media": "attach://f2"}}
  ]}]
)
```

- 第 i 个路径＝`attach://f{i}`。`collage` 换成 `slideshow` 就是左右翻页；
  单个 `photo` 块就是普通发图。每个文件 ≤50MB、一条消息最多 50 个。
- 发送成功的返回里带每个媒体的 **file_id**。存下来，下次 `media` 直接填
  file_id 复用，不用重新上传。**复用时整串程序化取用，别看着截断的显示手补
  尾巴**——file_id 彼此长得几乎一样，手打命中纯靠运气（作者试过，侥幸没炸）。
- 文件名形态闸：`.env` / `id_rsa` / `*.pem` / 名字含 token·credential·secret
  之类的文件会被拒，符号链接按**真实目标**检查。会误伤 `my_secret_santa.jpg`
  这种名字——确认无害就设 `TG_RICH_MEDIA_GUARD=0`。

### 贴纸：agent 的脸（两层）

思路、身份三定律和判断标准见 [COOKBOOK「贴纸」一章](COOKBOOK.md)；这里只讲用法。
库默认在 `~/.tg-rich-mcp-stickers/`（`TG_STICKER_DIR` 或配置文件 `sticker_dir`
可改），**空库时两层都零开销、零打扰**。

**第一层：工具对。**

```
tg_sticker_send()                            ← 不带参数＝看馆藏清单
tg_sticker_send(emoji="😾")                  ← 那一池里随机（避开上次刚发的那张）
tg_sticker_send(emoji="💻😾")                ← 交集收窄，通常两个 emoji 就点名一张
tg_sticker_import(file_id="…")               ← getFile 下载归档 → 返回原图路径，看图后……
tg_sticker_import(file_unique_id="…",
                  title="…", emoji="…")      ← ……再来认领入库（emojis 别名越多越容易命中）
```

**第二层：句内标记，渲染器模式的顺风车。** `tg_rich_send` 的 markdown 正文里
写 `（emoji）`，那个位置就发一张库里的真贴纸——写到哪儿，脸跟在哪条后面
（位置即语义）。已经按「渲染器模式」把正式回复路由过来的 agent，什么都不用改
就有了这层。

| 开关 | 默认 | 作用 |
|---|---|---|
| `TG_STICKER_MARKERS` | 开 | `=0` 关掉句内标记层（工具对不受影响） |
| `TG_STICKER_MAX` | 3 | 一条消息最多剥几张，多出来的原样留在正文 |
| `TG_STICKER_DIR` | `~/.tg-rich-mcp-stickers` | 库目录 |

标记层的保守取舍（自用版实跑出来的，别轻易放宽）：括号里出现字母/数字/汉字/
空白＝普通括号话，一律不碰（`（挑眉）`安全）；反引号里不算数——讨论这套语法
本身时不会当场喷贴纸；emoji 不在库/交集为空＝原样留在正文，坏掉的时候只是
一对普通括号，不穿帮；贴纸段发失败**不牵连已送达的文字段、也不自动重试**
（坑 17 的纪律，话已送到、脸没送到只记一笔）。

**孤儿贴纸防护**：脸是贴给它前面那句话的，所以**脸不许先于话出门**——标记
写在句首时贴纸先挂起，第一条正文真送达了才补发；正文发送中途抛错，挂起的脸
永不发送（一张没头没尾的表情比缺一张脸更糟，那是把语气安在一句不存在的话上）。
纯贴纸消息不受此限；贴纸自己发失败依旧不牵连正文。细节与理由见
[COOKBOOK「孤儿脸」一节](COOKBOOK.md)。

**多个 bot 共用一套库**：把两个 server 实例的 `TG_STICKER_DIR` 指到同一个目录
就行。馆藏（原图 + `file_unique_id` + 标签）天然共享；`file_id` 绑定 bot，
所以按 bot 分开缓存在 `file-ids..json`，每个 bot 首次用某张时自动从
归档原图上传、把自己的 file_id 记在自己名下（懒迁移，不用手工逐张重传）。
只有 Telegram 明确回 400 才判 ID 失效；网络错/限流/5xx 都不会触发重复上传。

### 按钮问答：点一下就是答案（tg_ask_choice，实验性）

问对方选择题，不用等 ta 打字：题干+选项变成 inline keyboard，
**工具调用内同步等点击**，直接返回 `{"index": 1, "option": "B", "message_id": 123}`
——不用自己接回流、不用打插件补丁、零落盘。

> ⚠️ **实验性，如实相告**：按钮的显示布局是真机实测过的，但 getUpdates
> 轮询层目前只有单元测试背书——我们自家的 bot 被官方插件占着 getUpdates，
> 没条件跑实弹。用得顺或撞了怪事，都请开 issue 告诉我们。

```
tg_ask_choice(question="午饭吃什么？", options=["面", "饺子", "随便"])
```

布局规则（真机四组对照实测得来的）：

- 选项全部 **≤16 字（中文计，拉丁/数字按半字）** → 文字直接上按钮。
  每行几个自适应：全 ≤3 字一行 5 个（A-E 正好一排），≤8 字一行 2 个，
  再长一行 1 个；`columns` 显式给了听你的。
- **任何一条超线 → 整题自动切「正文列选项全文 + 1️⃣2️⃣3️⃣ 编号按钮」**。
  为什么这么狠：超长按钮文字会被 Telegram **像素级硬剪，连省略号都不给**——
  「先把测试跑绿然后再开始做」在按钮上会变成「先把测试跑绿然」，
  选项含义直接残废。显式 `layout="buttons"|"numbered"` 可以按住不切。
- 私聊只认聊天对面那个人的点击（别人点＝答 Not authorized，题继续等）；
  群里谁点都算，先到先得。
- 默认 `mark_answered=true`：选完原地收按钮、标上「✅ 已选」——**没人再轮询的
  按钮是幽灵按钮**，点了永远转圈。想自己控制选完的样子就传 `false`，
  之后用 `tg_rich_edit` 自己改。
- 超时（默认 600s，上限 3600）明确报错返回，题留在聊天里；超时的卡片
  同样会收按钮（`mark_answered=false` 时不收）。

#### 按钮问答要专用 bot

`getUpdates` 全 Telegram **同一时刻只允许一个消费者**。你的 bot 要是同时
挂着官方 telegram 插件、webhook、或另一个轮询进程，Telegram 回 409，
工具会带着这句话明确报错（不会傻等）。解法：去 @BotFather 给本 server
**单独造一只 bot**，token 写进 `~/.tg-rich-mcp.json`。

顺带的实话：ask 工具轮询期间会把这只 bot 的 `allowed_updates` 收窄到
`callback_query`（Telegram 会记住这个设置）——又一个别和其他消费者
共用 bot 的理由。

### 块速查（全部实测发得出去）

**块级**

| type | 关键字段 |
|---|---|
| `paragraph` | `text` |
| `heading` | `text`, `size`(1-6，1 最大) |
| `pre` | `text`, `language?` |
| `footer` / `divider` | `text` / — |
| `mathematical_expression` | `expression`（裸 LaTeX） |
| `list` | `items[]`（每项 `blocks`，可加 `has_checkbox` / `is_checked`） |
| `blockquote` | `blocks[]`, `credit?` |
| `pullquote` | `text`, `credit?` |
| `table` | `cells[][]`, `is_bordered?`, `is_striped?`, `caption?` |
| `details` | `summary`, `blocks[]`, `is_open?` |
| `anchor` | `name`（配行内 `anchor_link` 做页内跳转） |
| `map` | `location{latitude,longitude}`, `zoom`, `width`, `height` |
| `collage` / `slideshow` | `blocks[]`, `caption?`（caption 是**对象**不是字符串） |
| `photo`/`video`/`audio`/`animation`/`voice_note` | 对应 `InputMedia*` + `caption?` |
| `thinking` | `text` —— **仅 draft 可用** |

**行内**：任何 `text` 字段都能传数组，元素是字符串或 `{type, text}`。
表格单元格的 `text` 同样收数组。

| type | 用处 | 注意 |
|---|---|---|
| `bold` `italic` `underline` `strikethrough` `code` | 基本样式 | |
| `spoiler` | 遮住，点开才看得见 | 答案、剧透 |
| `marked` | 高亮（荧光笔） | |
| `subscript` / `superscript` | 上下标 | 化学式、次方 |
| `mathematical_expression` | **行内公式** | 字段是 `expression`，不是 `text` |
| `url` | 带文字的链接 | 字段 `url` |
| `reference` | 脚注引用 | 配 `anchor` 块 |
| `anchor_link` | 页内跳转 | 字段是 `anchor_name`，不是 `name` |
| `date_time` | 按读者时区渲染 | 字段是 `unix_time` |
| `custom_emoji` | 自定义 emoji | 要 `custom_emoji_id` + `alternative_text` |

表格单元格：`text?`（省略＝不可见）、`is_header?`、`colspan?`、`rowspan?`、
`align`(left/center/right)、`valign`(top/middle/bottom)。

### 配方（给 agent 看的那部分）

这几条同时写进了工具的 `blocks` 参数描述里，不只写在 README。原因是这个包最初的教训：

> blocks 是原样透传的，上面这些块**从第一天起就能用**——但工具描述里没写，
> agent 就不会去试。对它来说，描述里没有的能力等于不存在。

所以描述里给的不是字段清单，是能直接套的形状：

```jsonc
// ① 句子里嵌公式，不用整块打断
{"type":"paragraph","text":["当 ",
  {"type":"mathematical_expression","expression":"x^2-5x+6=0"}," 时…"]}

// ② 答案遮住，点开才见（题卡、剧透）
{"type":"paragraph","text":["答案：",{"type":"spoiler","text":"B"}]}

// ③ 上下标
{"type":"paragraph","text":["H",{"type":"subscript","text":"2"},"O"]}

// ④ 折叠长内容（收起只占一行）
{"type":"details","summary":"展开看细节","blocks":[…]}

// ⑤ 长报告目录跳转
{"type":"anchor","name":"s1"}
{"type":"paragraph","text":[{"type":"anchor_link","text":"跳到第一节","anchor_name":"s1"}]}

// ⑥ 带勾选框的清单
{"type":"list","items":[{"has_checkbox":true,"is_checked":true,
  "blocks":[{"type":"paragraph","text":"做完了"}]}]}

// ⑦ 持久进度窗：send 一条 → 记住 message_id → 每帧 edit 它
```

**你自己加块类型时也照这个来**：往 schema 描述里塞一个能抄的形状，
比列十个字段名管用。

---

## 坑（比代码值钱的部分）

这些是真花时间试出来的，照着躲：

1. **版本别记错**：Rich Messages 首发在 **10.1**，不是 10.2。
   10.2 补的是**发送侧**的 `InputRichBlock*` 全族、`InputRichMessageMedia`、
   `InputMediaVoiceNote`，外加 Ephemeral Messages 和 Communities。
   到处流传的"10.2 支持富消息"不准确。

2. **`html` / `markdown` / `blocks` 三选一**，官方原文是 *Exactly one of the fields...*。
   混着给会被 API 拒收。本 server 在拼包前就拦下来了，报错比 API 的清楚。

3. 🔴 **草稿会锁死安卓用户的发送框——如今锁上配了钥匙，但钥匙在你手里**：
   `sendRichMessageDraft` 活跃期间，Telegram Android 把发送键换成省略号，
   用户**发不出任何消息**，**而且这期间在输入框里打的字，会在恢复时被清空**。
   官方缺陷记录  被 Telegram 关闭称"预期行为"；
   后来 Bot API 10.3（2026-08-24）给出的解法是 `can_stop`：传 True 用户会看到
   一颗停止按钮，按下后草稿消失、发送框解锁（2026-09-04 Android 实测，客户端
   也要够新——旧版根本不画这颗

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Circe22](https://github.com/Circe22)
- **Source:** [Circe22/agents-guide-to-telegram](https://github.com/Circe22/agents-guide-to-telegram)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-circe22-agents-guide-to-telegram
- Seller: https://agentstack.voostack.com/s/circe22
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
