Skip to Content
Telegram MCPPlan & Phases2 — Mcp Tool Server

Phase 2 — MCP Tool Server

Goal

An external agent (or MCP Inspector) can connect over HTTPS, authenticate with a bearer token, and call basic messaging tools that actually hit Telegram. This is the phase where the system stops being “a webhook” and starts being “an MCP server.”

Scope / Deliverables

  • Mount @modelcontextprotocol/sdk streamable HTTP transport at /mcp
  • Bearer-token middleware (MCP_AUTH_TOKEN, constant-time compare)
  • mcp/server.ts — register tools; emit tools/list + tools/call correctly
  • Tool: send_message (full Tool Contract including reply markup)
  • Tool: send_image (start with {url} source only; defer base64 to Phase 7)
  • Tool: edit_message
  • Tool: send_buttons
  • Zod input parsing for every tool; consistent error mapping into MCP JSON-RPC errors
  • Telegram error mapper (telegram/errors.ts) — typed mapping from telegraf errors to tool error codes
  • Outbound logging: every send → chat_messages row (direction='outbound')

Tool contracts to implement match data-and-contracts.md.

Automated Unit Testing

  • auth.bearer.test.ts
    • missing Authorization header → 401
    • header present but token mismatched → 401 (constant-time path exercised)
    • matching token → middleware passes the request through
    • header with leading/trailing whitespace handled correctly
  • tools/messaging.test.ts — for each of send_message, send_image, edit_message, send_buttons:
    • Zod schema rejects malformed input with JSON-RPC error code INVALID_INPUT
    • happy-path call invokes the outbound stub with normalized args
    • send_image rejects every shape that isn’t {url: string} in this phase (base64/fileId rejected with INVALID_INPUT)
    • send_buttons accepts both {label, callbackData} and {label, url} button variants; rejects mixed/invalid rows
  • telegram/errors.test.ts — telegraf error → tool error code mapping:
    • 400 “chat not found” → NOT_FOUND, not retried
    • 403 “bot was blocked by the user” → USER_BLOCKED_BOT and the user row is marked status='blocked'
    • 429 “Too Many Requests” → respects retry_after once, then surfaces RATE_LIMITED
    • 5xx → retried once then surfaces UPSTREAM_FAILURE
  • outbound.archive.test.ts
    • successful send_message writes a chat_messages row with direction='outbound', kind='text', populated message_id, chat_id, text
    • failed send (mapped error) does not write an archive row
  • mcp/server.test.tstools/list returns the four registered tools with correct input schemas; tools/call for an unknown tool name returns the standard JSON-RPC method-not-found error.

Integration scope (deferred): MCP Inspector connects over HTTPS to a real local server with bearer auth — verified manually in the Definition of Done.

Definition of Done

  • MCP Inspector connects to https://bot.<domain>/mcp with bearer token.
  • Calling send_message from Inspector lands the message in Telegram within 2s.
  • Calling send_message to an unknown chat returns NOT_FOUND JSON-RPC error.
  • Missing bearer token → 401.