telegram-bot-mcp/docs/2026-03-30-nexus-mcp-bridge-design.md
Mikkel Georgsen 1cb16e6e8f feat: MCP bridge - Telegram group logger + FastMCP HTTP server
Single-process Python app that:
- Runs a Telegram bot in a group chat, logging all messages/files to libsql
- Exposes send_message, pull_updates, queue_status MCP tools over HTTP
- Downloads and stores file attachments with Telegram file_id + local path
- Accessible via NetBird mesh at mgmt.mg:8321 (no auth needed)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:56:05 +00:00

8.7 KiB

Nexus MCP Bridge — Design Spec

Date: 2026-03-30 Status: Approved (user greenlit Approach 1, all design questions resolved)


Summary

A single-process Python application that:

  1. Runs a Telegram bot in a group chat, logging all messages/files to libsql
  2. Exposes an MCP server (FastMCP over HTTP) on the NetBird mesh for claude.ai to query

This lets claude.ai dispatch tasks to the homelab agent and pull conversation updates — without Mikkel being the copy-paste middleman.


Design Decisions

Question Decision Rationale
Telegram conflict New bot + group chat Each bot has own token, no polling conflict
What to capture Everything (all participants + files) Full replay capability
File storage Download to disk + store file_id Durability + convenience
Auth None (NetBird mesh is trust boundary) Only accessible from enrolled peers
Database libsql embedded (local file) Single process, no extra service
Scope Single group chat (homelab) for v1 Nail core loop, extend later
Bot personality Attributed relay [claude.ai] ... Clear attribution in group
Architecture Single process (FastMCP + telegram bot) Simple, systemd restart covers failures

Architecture

┌──────────────┐     HTTP (NetBird)     ┌─────────────────────────────┐
│  claude.ai   │ ◄──────────────────── │  MCP Bridge Process         │
│  MCP client  │ ────────────────────► │                             │
└──────────────┘                       │  ┌─────────┐  ┌──────────┐ │
                                       │  │ FastMCP  │  │ Telegram │ │
                                       │  │ HTTP     │  │ Bot      │ │
                                       │  │ Server   │  │ (polling)│ │
                                       │  └────┬─────┘  └────┬─────┘ │
                                       │       │              │       │
                                       │       └──────┬───────┘       │
                                       │              │               │
                                       │        ┌─────▼─────┐        │
                                       │        │  libsql   │        │
                                       │        │  (embed)  │        │
                                       │        └───────────┘        │
                                       │                             │
                                       │  media/  (downloaded files) │
                                       └─────────────────────────────┘
                                              │
                                     Telegram Group Chat
                                       ┌─────▼─────┐
                                       │ Mikkel    │
                                       │ Homelab ♦ │  (existing bot)
                                       │ MCP ♦     │  (new bot)
                                       └───────────┘

Database Schema (libsql)

CREATE TABLE messages (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    telegram_message_id INTEGER NOT NULL,
    chat_id INTEGER NOT NULL,
    sender_type TEXT NOT NULL,          -- 'user', 'homelab_bot', 'mcp_bot', 'unknown'
    sender_id INTEGER,
    sender_name TEXT,
    content TEXT,                       -- message text (nullable for media-only)
    reply_to_message_id INTEGER,       -- telegram reply reference
    has_attachment INTEGER DEFAULT 0,
    created_at TEXT NOT NULL,           -- ISO 8601
    UNIQUE(chat_id, telegram_message_id)
);

CREATE TABLE attachments (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    message_id INTEGER NOT NULL REFERENCES messages(id),
    file_type TEXT NOT NULL,            -- 'photo', 'document', 'video', 'voice', 'sticker'
    file_id TEXT NOT NULL,              -- telegram file_id
    file_unique_id TEXT NOT NULL,       -- telegram file_unique_id
    file_name TEXT,                     -- original filename
    mime_type TEXT,
    file_size INTEGER,
    local_path TEXT,                    -- path under media/
    caption TEXT,
    created_at TEXT NOT NULL
);

CREATE TABLE outbound_queue (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    chat_id INTEGER NOT NULL,
    content TEXT NOT NULL,
    attribution TEXT DEFAULT 'claude.ai', -- prefix for the message
    status TEXT DEFAULT 'pending',      -- 'pending', 'sent', 'failed'
    created_at TEXT NOT NULL,
    sent_at TEXT
);

-- Index for delta queries
CREATE INDEX idx_messages_created_at ON messages(created_at);
CREATE INDEX idx_messages_chat_id ON messages(chat_id);
CREATE INDEX idx_outbound_status ON outbound_queue(status);

MCP Tools

send_message

Send a message to the homelab group chat, attributed as [claude.ai].

Input:  { "message": "Fix the nexus.mg DNS record to 100.79.65.206" }
Output: { "sent": true, "id": 42 }

pull_updates

Get conversation messages since a cursor (message ID or timestamp).
Returns messages from all participants with attachment metadata.

Input:  { "since_id": 150 }  or  { "since": "2026-03-30T01:00:00Z" }  or  {}
Output: {
  "messages": [
    {
      "id": 151,
      "sender": "mikkel",
      "sender_type": "user",
      "content": "Can you check the DNS?",
      "attachments": [],
      "created_at": "2026-03-30T01:02:15Z"
    },
    {
      "id": 152,
      "sender": "homelab_bot",
      "sender_type": "homelab_bot",
      "content": "Checking Technitium... record found.",
      "attachments": [{"file_type": "photo", "file_name": "dns-screenshot.png"}],
      "created_at": "2026-03-30T01:02:45Z"
    }
  ],
  "cursor": 152
}

queue_status

Current state summary.

Input:  {}
Output: {
  "total_messages": 152,
  "last_message_at": "2026-03-30T01:02:45Z",
  "last_sender": "homelab_bot",
  "pending_outbound": 0
}

Telegram Bot Behavior

  • Joins group chat with Mikkel + existing homelab bot
  • Logs everything: text, photos, documents, voice, stickers, replies
  • Downloads attachments to media/<YYYY-MM-DD>/<file_unique_id>_<filename>
  • Sends outbound messages prefixed with [claude.ai] in bold
  • No commands — this bot is a silent logger + relay, not interactive
  • Ignores private messages — only operates in the configured group

File Structure

~/repos/telegram-bot-mcp/
├── docs/                          # Specs and design docs
├── mcp_bridge/
│   ├── __init__.py
│   ├── __main__.py                # Entry point: runs both bot + MCP server
│   ├── config.py                  # Configuration (env vars, paths)
│   ├── db.py                      # libsql database layer
│   ├── telegram_bot.py            # Telegram bot (polling, logging, sending)
│   ├── mcp_server.py              # FastMCP tool definitions
│   └── models.py                  # Shared data models
├── media/                         # Downloaded attachments
├── data/                          # libsql database file
├── credentials                    # BOT_TOKEN, GROUP_CHAT_ID (created during setup)
├── requirements.txt
├── heartbeat.sh
└── README.md

Configuration

Environment/file based:

  • credentials file: MCP_BOT_TOKEN=..., GROUP_CHAT_ID=..., HOMELAB_BOT_ID=...
  • MCP server binds to 0.0.0.0:8321 (accessible via NetBird at mgmt.mg:8321)
  • Database at data/bridge.db
  • Media at media/

Deployment

  • systemd user service (mcp-bridge.service)
  • Uses project-local venv at .venv/
  • Restart=always, RestartSec=5
  • Bind to 0.0.0.0:8321 (NetBird interface)

Setup Steps (for user)

  1. Create new Telegram bot via BotFather → get token
  2. Create Telegram group → add Mikkel + homelab bot + MCP bot
  3. Get group chat ID (bot will log it on first message)
  4. Fill in credentials file
  5. systemctl --user enable --now mcp-bridge
  6. Add MCP URL http://mgmt.mg:8321/mcp in claude.ai settings

Future Extensions

  • Multi-group support (per-project chats with target parameter)
  • Session tracking (detect agent restarts)
  • File content search across attachments
  • Message threading/reply chain reconstruction