Skip to main content
Glama
Masashi-Ono0611

claude-channels-telegram-mcp

README.md
# claude-channels-telegram-mcp

**The missing outbound layer for Claude Channels. Adds photos, files (PDFs, source code), keyboards, Telegram Stars, and TON tools to Claude Code — safely coexisting with Channels via `CHANNELS_MODE` (no polling conflicts).**

## What This Is

Claude Channels handles inbound Telegram messages and provides `reply`, `react`, and `edit_message` for outbound. That's it. Any richer response — a photo, a file, a button, a payment invoice — requires this toolkit.

**Example: Claude Code coding session, results delivered to Telegram**

| Claude does | Tool |
|---|---|
| Generates a chart or graph | `send_photo` → image in Telegram |
| Edits a source file | `send_document` → file delivered to Telegram |
| Produces a test result report (PDF) | `send_document` → PDF in Telegram |
| Takes a screenshot during web testing | `send_photo` → screenshot in Telegram |

| Claude Channels built-in | This toolkit adds |
|---|---|
| `reply` — plain text response | `send_keyboard` — inline buttons (URL or callback) |
| `react` — emoji reaction | `send_photo`, `send_document` — media |
| `edit_message` — edit sent message | `send_invoice` — Telegram Stars payment |
| *(inbound handled automatically)* | `register_commands`, `set_bot_description` — BotFather setup |
| — | 16 TON blockchain tools incl. TON Connect |

### Why not just use any Bot API MCP?

Other Bot API MCP servers (TONresistor/telegram-mcp, tsgram-mcp, ton-agent-kit) also provide outbound tools — but they all poll `getUpdates` continuously. Running them alongside Claude Channels causes **offset conflicts**: messages get dropped or delivered twice.

`CHANNELS_MODE=true` disables all polling in this toolkit. Channels owns the inbound; this toolkit handles the outbound. No conflicts.

### Primary mode: CHANNELS_MODE

**CHANNELS_MODE** (`CHANNELS_MODE=true`) — the intended use case, alongside Claude Channels:
- Inbound: handled by Channels automatically
- Outbound: all 30 send/TON tools available
- Polling disabled — no offset conflicts with Channels

**Standalone** (no Channels) — also supported, without the Channels plugin:
- Inbound: `get_updates` / `get_pending_messages` available
- All 33 tools available

## MCP Tools — 30 in CHANNELS_MODE / 33 standalone

### Telegram (17 tools)

| Tool | Description |
|------|-------------|
| `send_message` | Send plain text (HTML) messages; supports reply-to for threaded replies |
| `send_keyboard` | Send messages with inline keyboard buttons (URL links or callbacks) |
| `register_commands` | Register `/commands` in BotFather bot menu |
| `set_bot_description` | Set bot profile description |
| `send_photo` | Send photos (URL or local file) with caption |
| `send_document` | Send documents/files with caption |
| `delete_message` | Delete bot's own messages |
| `pin_message` | Pin a message in chat |
| `get_chat_info` | Get chat/user information |
| `send_invoice` | Send Telegram Stars payment invoice |
| `send_chat_action` | Show "typing…" / "sending photo…" status in chat |
| `get_file` | Get download URL for a file from its `file_id` |
| `answer_callback_query` | Respond to inline button presses with toast/alert; closes loading spinner |
| `answer_pre_checkout_query` | Approve or reject a Telegram Stars payment (10 s deadline) |
| `get_updates` | Fetch incoming messages/callbacks via polling *(standalone mode only)* |
| `get_pending_messages` | Simplified view of unread messages *(standalone mode only)* |
| `mark_messages_read` | Advance internal offset to mark updates as processed *(standalone mode only)* |

### TON Blockchain (16 tools)

| Tool | Description |
|------|-------------|
| `get_ton_balance` | Get TON balance for any wallet address |
| `get_jetton_balances` | Get all token (Jetton) holdings |
| `get_token_price` | Real-time token price + 24h/7d change |
| `estimate_swap` | DEX swap output estimation |
| `send_ton` | Execute TON transfer from agent wallet (requires wallet config) |
| `generate_ton_payment_link` | Generate `ton://` + TonKeeper deep link — lets users pay from their own wallet |
| `get_transactions` | Fetch recent TON/Jetton transaction history for any address |
| `transfer_jetton` | Send Jetton tokens (e.g. USDT) from agent wallet (requires wallet config) |
| `get_nft_items` | List NFT items owned by a wallet (name, image, collection) |
| `get_nft_collection` | Get NFT collection metadata (name, description, item count, owner) |
| `resolve_ton_dns` | Resolve a `.ton` or `.t.me` domain to a wallet address |
| `get_staking_info` | List top liquid staking pools (APY, TVL, min stake) sorted by APY |
| `generate_tonconnect_link` | Generate TonKeeper connection link (returns session_id + URL button) |
| `check_tonconnect_session` | Poll session for wallet connection status and address |
| `request_ton_payment` | Request TON payment from connected user wallet (user approves in TonKeeper) |
| `disconnect_wallet` | Disconnect wallet and delete session |

## Quick Start

```bash
git clone https://github.com/Masashi-Ono0611/claude-channels-telegram-mcp.git
cd claude-channels-telegram-mcp && bun install
```

### Configure MCP

Add to your project's `.mcp.json`:

```json
{
  "mcpServers": {
    "telegram-toolkit": {
      "command": "bun",
      "args": ["run", "/path/to/claude-channels-telegram-mcp/src/index.ts"],
      "env": {
        "TELEGRAM_BOT_TOKEN": "<your-bot-token>",
        "TON_API_BASE_URL": "https://testnet.tonapi.io",
        "CHANNELS_MODE": "true"
      }
    }
  }
}
```

### Operating Modes

| Mode | When to use | Tools |
|------|-------------|-------|
| **CHANNELS_MODE** (recommended) | With Claude Channels plugin | 30 tools (polling excluded) |
| **Standalone** | Without Claude Channels | 33 tools (polling included) |

Set `CHANNELS_MODE=true` in your `.mcp.json` env (shown above — already included in the example).

### Environment Variables

```bash
# Required
TELEGRAM_BOT_TOKEN=<bot-token-from-botfather>
CHANNELS_MODE=true   # set to true when using alongside Claude Channels plugin

# TON network (auto-detects testnet/mainnet from URL)
TON_API_BASE_URL=https://testnet.tonapi.io    # testnet (default)
TON_API_BASE_URL=https://tonapi.io            # mainnet
TON_API_KEY=<optional-api-key>               # increases rate limits

# Agent wallet (required for send_ton / transfer_jetton)
AGENT_MNEMONIC_PATH=/path/to/mnemonic.txt
TONCENTER_ENDPOINT=<override-if-needed>      # auto-derived from TON_API_BASE_URL
TONCENTER_API_KEY=<optional-toncenter-key>
```

If TON env vars are not set, TON tools return "not configured" errors. Telegram tools work regardless.

## Architecture

```
Claude Code
  │
  ├── Claude Channels (built-in plugin)
  │   └── reply, react, edit_message (inbound + outbound messaging)
  │
  └── claude-channels-telegram-mcp (this MCP server)
      ├── Telegram: send, keyboards, media, commands, invoice, callbacks, polling
      └── TON: balance, price, swap, transfer, payment links, tx history, jetton, wallet connect
```

**Key design:** In CHANNELS_MODE, only send-only APIs are active — no polling conflicts with Channels. In standalone mode, `get_updates` + `send_message` + `answer_callback_query` provide a full receive-respond loop. TON read tools use tonapi.io; write tools use TonCenter RPC. Network (testnet/mainnet) is auto-detected from `TON_API_BASE_URL`.

### Complete Response Pipeline (standalone)

```
User presses inline button → get_updates → callback_query received
→ answer_callback_query("Processing...") → send_invoice(Stars)
→ User pays → pre_checkout_query received
→ answer_pre_checkout_query(ok=true) → send_message("Payment complete!")
```

### Payment Link Flow (CHANNELS_MODE or standalone)

`generate_ton_payment_link` + `send_keyboard` enables a user-pays flow without exposing the agent wallet:

```
Claude → generate_ton_payment_link → send_keyboard with URL button
User taps button → TonKeeper / any TON wallet opens → user signs & sends
Claude → get_transactions → verify payment received
```

### TON Connect Flow (user wallet)

`generate_tonconnect_link` + `send_keyboard` connects a user's own wallet (TonKeeper) without exposing any agent private key:

```
Claude → generate_tonconnect_link → { session_id, tonkeeper_link }
Claude → send_keyboard with URL button (tonkeeper_link)
User taps → TonKeeper opens → user approves connection
Claude → check_tonconnect_session(session_id) → { status: "connected", address }
Claude → request_ton_payment(session_id, to, "0.5") → user approves in TonKeeper → { boc }
Claude → disconnect_wallet(session_id)
```

Session state is persisted to `~/.ctt/sessions/<session_id>.json`, so connection survives across MCP calls.

### CHANNELS_MODE: What Works and What Doesn't

In CHANNELS_MODE (`CHANNELS_MODE=true`), the three polling tools are disabled at both `ListTools` and `CallTool` level to prevent offset conflicts with the Channels plugin.

**⚠️ `reply` vs `send_message` — not interchangeable**

| | Channels `reply` | toolkit `send_message` |
|---|---|---|
| Responds in conversation thread | ✅ | ❌ (sends a new standalone message) |
| Requires chat_id | ❌ (implicit) | ✅ (must provide) |
| Available in CHANNELS_MODE | ✅ | ✅ |

Use Channels `reply` for all conversational responses. Use `send_message` only for proactive or scheduled sends (e.g. notifications).

**⚠️ Callback buttons don't work in CHANNELS_MODE**

Callback buttons (`callback_data`) require `get_updates` to receive the callback event back from Telegram. Since polling is disabled in CHANNELS_MODE, the callback data is never delivered to Claude.

| Button type | CHANNELS_MODE | Standalone |
|---|---|---|
| URL button (`url` field) — opens a link | ✅ works | ✅ works |
| Callback button (`callback_data`) — returns data to bot | ❌ data never arrives | ✅ works |

In CHANNELS_MODE, use URL buttons for all interactive elements (payment links, TonKeeper connect, external pages).

## Tested

| Tool | Result |
|------|--------|
| `register_commands` | ✅ 5 commands registered |
| `set_bot_description` | ✅ Description set |
| `send_keyboard` | ✅ URL buttons sent |
| `send_photo` | ✅ URL image sent |
| `send_document` | ✅ Local file sent |
| `delete_message` | ✅ Message deleted |
| `pin_message` | ✅ Message pinned |
| `get_chat_info` | ✅ User info retrieved |
| `send_invoice` | ✅ 1 Star invoice sent |
| `get_ton_balance` | ✅ 4.76 TON |
| `get_token_price` | ✅ $1.26 (+2.65%) |
| `get_jetton_balances` | ✅ Jetton holdings listed |
| `estimate_swap` | ✅ Swap estimate returned |
| `send_ton` | ✅ TON transfer confirmed |
| `generate_ton_payment_link` | ✅ ton:// + TonKeeper links generated |
| `get_transactions` | ✅ Tx history fetched |
| `transfer_jetton` | ✅ Jetton transfer confirmed |
| `send_chat_action` | ✅ "typing…" status shown |
| `get_file` | ✅ Download URL returned |
| `get_updates` | ✅ Incoming messages fetched |
| `get_pending_messages` | ✅ Unread messages listed |
| `mark_messages_read` | ✅ Offset advanced |
| `send_message` | ✅ Plain text + reply_to tested |
| `answer_callback_query` | ✅ Toast (show_alert=false) tested |
| `answer_pre_checkout_query` | ✅ Validation guard + 1 Star payment approved |
| `get_nft_items` | ✅ count=0 (testnet wallet, API reachable) |
| `get_nft_collection` | ✅ "Hipo Payout" collection fetched |
| `resolve_ton_dns` | ✅ foundation.ton resolved |
| `get_staking_info` | ✅ 5 pools returned |
| `generate_tonconnect_link` | ✅ TonKeeper link generated |
| `check_tonconnect_session` | ✅ connected + address (testnet, real device) |
| `request_ton_payment` | ✅ 0.01 TON signed + BOC returned (testnet) |
| `disconnect_wallet` | ✅ session disconnected + file deleted |

## License

MIT