Skip to Content
Telegram MCPPlan & PhasesOperations And Strategy

Operations & Strategy

The operational shape and overall strategic decisions for the project: testing strategy, deployment runbook, tech stack, and the still-open design questions. This document is the authoritative ops reference until ../dev/ and ../testing/ get populated as the code stabilizes.


Testing Strategy

  • Unit tests (vitest): pure utilities — normalize.ts, validation, role/status checks, error mappers, event schema parsing. Per-phase unit-test cases are listed in each phase-n-*.md.
  • Integration tests: real local Postgres in a Docker container (or pg-mem for fast suites). Cover user approval flow, memory upsert/read, job state transitions, chat-history pagination.
  • MCP contract tests: spin up the MCP server in-process, drive via the official MCP test client; verify each tool’s input/output shape and error codes for happy + sad paths.
  • Inngest local dev: npx inngest-cli dev against /api/inngest. Trigger events from tests; assert side effects in Postgres.
  • Manual E2E (Phase 6): MCP Inspector connected to https://bot.example.com/mcp with bearer token; exercise every tool against a personal test bot.
  • CI: npm test runs unit + integration on every push. Inngest function tests run against a local dev server in CI.

Deployment Runbook

One-time VPS setup

  1. Ubuntu 24.04 LTS, non-root sudo user; SSH key-only, root login disabled.
  2. Packages:
    • nginx and certbot + python3-certbot-nginx from the Ubuntu archive.
    • Postgres from the PGDG repo (Ubuntu 24.04’s archive ships 16; we run Postgres 18).
    • Node.js LTS from NodeSource (currently Node 22).
  3. Service user: useradd --system --shell /usr/sbin/nologin --home-dir /var/lib/telegram-mcp --create-home telegram-mcp.
  4. Dirs: /opt/telegram-mcp (code), /etc/telegram-mcp (env, mode 750, owned root:telegram-mcp), /var/lib/telegram-mcp (runtime state if any).
  5. Postgres: create DB telegram_mcp and a role with password; record DSN in env.
  6. DNS: A record bot.<domain> → VPS public IP.
  7. Firewall (ufw): allow 22, 80, 443.
  8. TLS cert: sudo certbot --nginx -d bot.<domain> once nginx site config below is in place. Renewal is handled automatically by the certbot.timer systemd unit installed with the package.

nginx server block (shape, ops/nginx-bot.conf)

# Redirect plain HTTP → HTTPS server { listen 80; listen [::]:80; server_name bot.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name bot.example.com; # certbot --nginx fills in the cert + key paths and ssl_* directives; # the lines below are placeholders showing what to expect. ssl_certificate /etc/letsencrypt/live/bot.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/bot.example.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; access_log /var/log/nginx/bot.access.log; error_log /var/log/nginx/bot.error.log; gzip on; gzip_types text/plain application/json application/javascript text/css; # Allow modest webhook payloads (photos/docs flow via file_id, not bytes). client_max_body_size 2m; location / { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # MCP streamable HTTP at /mcp uses long-lived server→client streams. # Disable response buffering and bump the upstream read timeout so the # connection survives idle periods between agent messages and pushes. proxy_buffering off; proxy_read_timeout 3600s; proxy_send_timeout 3600s; } }

systemd unit (shape)

[Unit] Description=Telegram MCP Server After=network.target postgresql.service [Service] Type=simple User=telegram-mcp Group=telegram-mcp WorkingDirectory=/opt/telegram-mcp EnvironmentFile=/etc/telegram-mcp/env ExecStart=/usr/bin/node dist/index.js Restart=on-failure RestartSec=5 NoNewPrivileges=true ProtectSystem=strict ProtectHome=true PrivateTmp=true [Install] WantedBy=multi-user.target

Inngest Cloud setup

  1. Create app; copy INNGEST_EVENT_KEY + INNGEST_SIGNING_KEY into /etc/telegram-mcp/env.
  2. In the dashboard, register endpoint https://bot.example.com/api/inngest.
  3. After first deploy, Inngest auto-syncs the function list on its next request.

Telegram setup

  1. /newbot in BotFather → bot token into env.
  2. Programmatically call setWebhook with url=https://bot.example.com/telegram/webhook&secret_token=$TELEGRAM_WEBHOOK_SECRET. Done by a small npm run register-webhook script in this repo.
  3. Optional: setMyCommands to populate the bot menu.

Deploy procedure

ssh vps cd /opt/telegram-mcp git pull npm ci npm run build npm run migrate sudo systemctl restart telegram-mcp sudo journalctl -u telegram-mcp -f

Rollback

  • Code: git checkout <prev-tag>; npm ci && npm run build && systemctl restart.
  • DB migrations: never auto-rollback; new migration that reverses the change if needed.

Tech Stack (Provisional)

LayerTechnology
RuntimeNode.js + TypeScript
HTTP frameworkFastify (provisional)
Telegram APItelegraf (provisional, see open question)
MCP SDK@modelcontextprotocol/sdk (streamable HTTP transport)
Queue / orchestrationInngest (Inngest Cloud) — no Redis required
DatabasePostgres
Migrationsnode-pg-migrate (provisional)
ValidationZod
Loggingpino (JSON) → journald
TestingVitest + MCP Inspector for manual E2E
Vector DBpgvector or Qdrant (post-Phase 4, if needed)
DeploymentUbuntu VPS + nginx (TLS via certbot/Let’s Encrypt) + systemd, on a dedicated subdomain

Open Questions

To be answered before Phase 1 begins (resolved questions kept here for traceability).

Resolved

  • Queue / orchestration — Inngest Cloud. Replaces BullMQ + Redis.
  • Notification strategy — MCP server pushes a notification to the agent on job.completed.
  • Per-user concurrency — Inngest concurrency key = userId (one job per user at a time).
  • Deployment target — Rented Ubuntu VPS with public IP, behind nginx.
  • Public HTTPS endpoint — Subdomain of an existing domain, TLS via certbot-managed Let’s Encrypt cert (renewed by certbot.timer).
  • Agent location — Separate machine; connects to this server remotely.
  • MCP transport — Streamable HTTP, single /mcp endpoint.
  • Runtime — Node.js + TypeScript.
  • MCP auth — Static bearer token in env (MCP_AUTH_TOKEN), checked on every /mcp request. TLS + bearer is the boundary.
  • Agent identity — Claude via the Anthropic SDK / Claude Agent SDK.
  • Existing agent project — None yet; built after this server is usable. Phase 6 uses MCP Inspector for validation until then.

Still open

  • Scale — Personal/private bot (few users) or potentially public? Drives Inngest Cloud tier sizing and Postgres choice.
  • Event schema ownership — Should Inngest event names/payloads be defined in a shared package the future agent project also consumes, or kept private to this repo and surfaced only via MCP?
  • Job state mirror — Do we mirror Inngest run state in Postgres for get_job_status, or query Inngest’s API on demand? (Mirror = faster, more code; API = simpler, rate-limited.) — currently planned as mirror; revisit at Phase 8.
  • Postgres location — On the same VPS (Phase 1, cheap) or managed (later, when uptime/backups matter more)?
  • Token rotation — Manual redeploy is fine for v1; revisit if multiple operators or a public agent ever needs access.
  • Inbound event → agent delivery — The agent connects over MCP; how does it learn about an incoming Telegram message? Options: (a) MCP server pushes a custom notifications/telegram_event and the agent reacts; (b) we expose a poll_inbound tool the agent calls; (c) the agent maintains a long-running tool call. Default leaning: (a), same mechanism as job-completion push.
  • MCP push style — Custom notifications/job_status method (simple, ad-hoc) vs modelling jobs as MCP resources and using resources/subscribe (more spec-aligned, more surface). Default leaning: custom notification for v1.
  • Image source variantssend_image supports {url} immediately. Add {fileId} (cheap) and {bytesBase64} (Phase 7). Any need for {s3Url} / signed-URL pattern? Depends on where the agent’s generated images actually live.
  • Telegram librarytelegraf chosen as default (modern, TS-native, middleware). Confirm before locking in; node-telegram-bot-api is the main alternative.
  • HTTP frameworkFastify chosen as default (faster, better TS types than Express). Confirm or swap.
  • Migration toolnode-pg-migrate chosen as default (plain SQL, no ORM lock-in). Alternative: Drizzle (more TS-integrated, brings an ORM).
  • Image generation backendimage.generate job needs a real provider eventually (OpenAI Images, Replicate, fal.ai, local SD). Phase 5 ships with a stub; pick before this becomes user-visible.