Project · Telegram Mcp Connector

Claude Telegram Bot — High-Level Design

Give Bob a way to reach Claude from his phone: DM a Telegram bot, get an answer. Rather than a bespoke service, this uses the official **telegram channel plugin** for **Claude Code** — a full Claude Code session runs headless on the [[gcp-mcp-standalone/README|broker VM]], and the plugin bridges Telegram DMs into that session. It is deliberately **locked to read-only ops** (disk/memory/uptime style questions), not a general shell.

type hldstatus activemcp · telegram · claude-code · architecture

Architecture

flowchart LR
  PHONE[Bob's Telegram]
  API[(api.telegram.org)]
  subgraph GCP[gcp-mcp-standalone VM]
    subgraph SVC[systemd: claude-telegram · user claudebot · MemoryMax 768M]
      TMUX[tmux tgchan] --> CLAUDE[claude --channels<br/>plugin:telegram]
      CLAUDE -->|spawns MCP server| BUN[bun server.ts<br/>grammy long-poll]
    end
    FW[[iptables: claudebot<br/>blocked from metadata server]]
  end
  PHONE <--> API
  BUN <-->|long-poll getUpdates / sendMessage| API
  BUN <-->|stdio MCP: reply tool + injected msgs| CLAUDE

Message flow

  1. bun server.ts (the plugin's MCP server, using the grammy Bot API library) holds a long-poll to Telegram — idle = a network wait, no model, no tokens.
  2. A DM arrives. The server checks the sender against the allowlist (access.json); an unknown sender in pairing policy gets a 6-char pairing code instead of service.
  3. An allowlisted message is injected into the Claude Code session over stdio.
  4. Claude acts — only commands on its allow-list run without a human to approve (there is none); anything else is refused — and calls the plugin's reply tool.
  5. The server sends the reply back via sendMessage. Tokens are consumed only for this turn.

Components

  • Isolated user claudebot — own home, no sudo; iptables OUTPUT rule (persisted by claudebot-metadata-block.service) blocks it from 169.254.169.254, so it cannot use the VM's service account to reach GCP-Secret-Manager or any GCP API.
  • claude-telegram.service — oneshot systemd unit launching claude --channels plugin:telegram@claude-plugins-official inside tmux; MemoryMax=768M (2 G swap on the box absorbs spikes).
  • telegram channel plugin 0.0.6 — installed and enabled (it installs disabled by default — the channel banner shows but the MCP server never starts until enabled); its server.ts runs on bun, deps pre-installed so the server spawns within Claude's MCP startup timeout.
  • AuthTELEGRAM_BOT_TOKEN (BotFather) + CLAUDE_CODE_OAUTH_TOKEN (claude setup-token, a Claude subscription) + NOTION_BROKER_KEY (broker API key) in ~/.config/telegram.env (mode 600).
  • notion-broker MCP server — user-scope in ~/.claude.json, HTTP to http://127.0.0.1:8002/proxy/notion_api/ (localhost, on-box) with header-auth (X-Broker-Key + X-App-Id: my_company:app1). Lets the bot read Notion via the broker without ever seeing the raw Notion token. Note the trailing slash — the bare path 307-redirects and stalls the MCP client at startup.

Security

  • Read-only allow-list (~/.claude/settings.json): Read/LS/Grep/Glob + a fixed set of harmless shell commands (df free uptime date whoami hostname uname cal echo) + the telegram reply tool + read-only Notion tools via the broker (search, fetch, query_data_source, get_block_children, get_comments, get_page_property, get_users — no create/update/archive). Default permission mode, no bypass — anything off-list is refused, because a headless channel has no human to approve it. This is the core boundary: the bot can read Notion and answer questions, but cannot modify it.
  • Sender allowlist — pairing captures the numeric user ID; policy then switched to allowlist so strangers can't even get pairing replies. Only Bob's ID is served.
  • Blast radiusclaudebot is unprivileged, metadata-blocked (no GCP creds), memory capped, and cannot read the broker's .env/secrets (not on its allow-list, and it runs as a different user from the broker container's files). Worst realistic case: it answers read-only status questions to the one allowlisted account.
  • Token exposure note — if a bot token is ever pasted in plaintext, rotate it via BotFather → Revoke.

Cost

Negligible: runs in the existing e2-small (already ~$17/mo, see gcp-mcp-standalone/HLD); Telegram Bot API is free; Claude usage draws on the existing subscription and only when a message is actually handled (idle = 0 tokens).

Compiled from wiki/projects/telegram-mcp-connector/HLD.md · git is the source of truth