# 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) ```sql 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//_` - **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