Skip to main content
Glama
sandoche

Telegram Relay Rich Markdown MCP

by sandoche

Telegram Relay Rich Markdown MCP

A small, private Model Context Protocol server that lets ChatGPT or another MCP client send Telegram Rich Markdown and photos to one fixed Telegram destination.

The server is intentionally narrow: it exposes two write tools and one read-only connection check, cannot choose another chat, and does not provide Telegram administration capabilities.

Features

  • Sends Telegram Rich Markdown through sendRichMessage.

  • Checks the authenticated MCP connection without sending messages or contacting Telegram.

  • Sends photos through sendPhoto: public HTTPS URLs, existing Telegram file_id values, or PNG/JPEG byte uploads.

  • Supports headings, lists, task lists, tables, links, quotations, details, spoilers, footnotes, code, and LaTeX formulas.

  • Uses one destination configured by TELEGRAM_CHANNEL_ID.

  • Protects the MCP endpoint with a high-entropy capability URL.

  • Keeps remote media embeds blocked in Rich Markdown; images use a separate, validated photo tool.

  • Suppresses accidental duplicate messages.

  • Applies a small best-effort per-instance rate limit.

  • Avoids logging message text, bot tokens, destination IDs, or token-bearing Telegram URLs.

  • Deploys on Vercel.

Related MCP server: Telegram Bot MCP Server

Security model

The MCP endpoint is:

https://YOUR-DOMAIN/<MCP_ACCESS_TOKEN>/mcp

Treat the complete URL as a password. This capability-URL approach is suitable for a private, single-user deployment. For a public or multi-user integration, replace it with a standards-based OAuth 2.1 authorization flow.

No tool accepts a chat_id; the destination only comes from the server environment. The server also does not implement message editing, deletion, member management, webhooks, invite links, paid broadcasts, or other Telegram administration methods.

Remote media blocks inside Rich Markdown are rejected intentionally. The photo tool sends only to the same fixed destination and shares the existing rate budget and duplicate guard. Image URLs are passed to Telegram, never fetched or DNS-resolved by this server. HTTPS is required; credentials, IP literals, local hostnames, custom ports, and fragments are rejected. This is input screening, not a guarantee about DNS or redirects: Telegram handles remote retrieval. Image data and upstream error descriptions are not logged or echoed in photo errors.

Required environment variables

Variable

Required

Description

TELEGRAM_BOT_TOKEN

Yes

Bot token issued by @BotFather.

TELEGRAM_CHANNEL_ID

Yes

Fixed Telegram destination ID or public channel username.

MCP_ACCESS_TOKEN

Yes

URL-safe random secret with at least 32 characters.

TELEGRAM_PROTECT_CONTENT

No

Defaults to true. Prevents forwarding/saving where Telegram supports it.

TELEGRAM_SKIP_ENTITY_DETECTION

No

Defaults to false.

MCP_MAX_MESSAGES_PER_MINUTE

No

Defaults to 5; accepted range is 1–30.

MCP_DUPLICATE_WINDOW_SECONDS

No

Defaults to 120; accepted range is 0–600.

Generate the MCP access token with:

openssl rand -hex 32

Telegram destination IDs

Use the ID exactly as returned by Telegram:

Destination

Example

Private user chat

123456789

Basic group

-123456789

Supergroup or channel

-1001234567890

Public channel

@examplechannel

For a private bot conversation, first open the bot and send /start. Then retrieve the chat ID:

$updates = Invoke-RestMethod "https://api.telegram.org/bot$token/getUpdates"

$updates.result | ForEach-Object {
  if ($_.message) {
    $_.message.chat
  }
}

Use the returned message.chat.id without modifying it.

Local development

npm install
cp .env.example .env.local
npm run dev

The local endpoint is:

http://localhost:3000/<MCP_ACCESS_TOKEN>/mcp

A non-secret health endpoint is available at:

http://localhost:3000/health

It reports only whether each required setting is present, never its value.

Deploy to Vercel

  1. Import this repository into Vercel.

  2. Add the required environment variables for Production and Preview.

  3. Deploy.

  4. Connect your MCP client using:

https://YOUR-PROJECT.vercel.app/<MCP_ACCESS_TOKEN>/mcp

Once the GitHub repository is connected, pushes to main create production deployments and pull requests create preview deployments.

Read-only connection check

Call get_telegram_relay_status with {} before creating a scheduled task that needs this connector. It uses the same authenticated MCP endpoint as the sending tools; the public /health endpoint does not verify the connector connection.

The tool makes no external requests, sends no Telegram messages, does not log configuration, and neither consumes nor changes the sending rate/duplicate guards. Its annotations are readOnlyHint: true, destructiveHint: false, idempotentHint: true, and openWorldHint: false. It accepts no arguments.

Result field

Meaning

ok

Always true on a successful tool response: the authenticated MCP call worked.

status_version

Status response contract version, currently 1.

telegram_configured

Both local Telegram settings are nonblank; not proof of valid credentials or delivery.

telegram_api_checked

Always false; Telegram was not contacted.

message

Fixed explanation of the result, containing no tokens or destination identifiers.

A false telegram_configured still confirms the MCP connection, but the server needs configuration before scheduling sends. A true value does not validate the bot token, destination, permissions, Telegram availability, or future delivery. Do not send a test message as a substitute for this check.

Deploy and refresh the existing ChatGPT connection

  1. Merge the status-tool PR into main after tests pass. No new environment variables, token rotation, destination changes, or endpoint changes are needed.

  2. In the existing Vercel project, verify a Production / Ready deployment contains that merge commit. A previous Ready deployment is insufficient. If Git integration does not start a deployment, check the project's Git connection and production branch (main), then deploy the merged source. Redeploying an old deployment can redeploy old code. Do not expose the capability URL in logs or PRs.

  3. Open the Telegram Elara MCP connection in ChatGPT Plugins and select Refresh. Confirm get_telegram_relay_status appears alongside both existing sending tools. This refresh is a user UI action; deployment alone does not prove ChatGPT has updated its cached tool metadata.

  4. Start a new conversation with Telegram Elara enabled and ask:

    Call Telegram Elara's get_telegram_relay_status with {}. Report the actual result. Do not send any Telegram messages or create any scheduled tasks.

  5. Record the visible tool name and successful call result. Expect ok: true, status_version: 1, telegram_configured: true, and telegram_api_checked: false. If the tool is absent, refresh the connection and retry in a new conversation; if it errors, resolve that error before scheduling. Never claim this ChatGPT verification based only on local tests or a Vercel build.

See OpenAI's connection and metadata-refresh guide. The Refresh flow applies to developer-mode MCP connections; published plugins require updating their reviewed metadata snapshot.

Photo tool

send_photo_to_telegram_channel sends one photo. Call it only after the user explicitly asks to publish that image.

Argument

Description

photo

Direct public HTTPS image URL, or a photo file_id already available to this bot.

image_data_url

Upload PNG/JPEG bytes as data:image/png;base64,... or data:image/jpeg;base64,.... Maximum decoded size: 2 MiB.

caption

Optional plain text, at most 1024 Unicode characters. No Markdown/HTML parsing.

silent

Optional; defaults to false.

has_spoiler

Optional; defaults to false.

Provide exactly one of photo and image_data_url. The result contains message_id, date, and a reusable file_id when returned by Telegram. TELEGRAM_PROTECT_CONTENT applies to photos too. No new environment variables are required.

Example tool arguments for an image URL (replace the example URL with a real image):

{
  "photo": "https://example.com/chart.png",
  "caption": "Weekly job-search progress",
  "silent": true
}

For a local PNG, an MCP client with file-system access can construct the arguments without publishing the image on a public host:

import { readFile } from "node:fs/promises";

const bytes = await readFile("chart.png");
if (bytes.length > 2 * 1024 * 1024) throw new Error("Image exceeds 2 MiB");
const args = {
  image_data_url: `data:image/png;base64,${bytes.toString("base64")}`,
  caption: "Weekly job-search progress"
};
// Pass args to your MCP client's callTool for send_photo_to_telegram_channel.

Use image/jpeg for JPEG files. The server validates canonical base64, the decoded size, and the PNG/JPEG file signature, then uploads using multipart/form-data. Telegram performs actual image decoding and dimension checks; a valid signature alone does not prove an image is decodable.

The 2 MiB upload cap leaves room for base64 and JSON overhead below transport/hosting limits, including Vercel's 4.5 MB function payload limit. Clients can have smaller argument limits; prefer a URL or file_id for large images. Telegram currently documents a 5 MB photo limit for URL retrieval, a 10 MB photo upload limit, width + height at most 10000 pixels, and an aspect ratio at most 20; see sendPhoto and Sending files. The relay's own byte-upload cap is stricter.

Local filenames and ChatGPT sandbox: links are not remotely accessible. This tool does not automatically transfer chat attachments. The client must supply accessible image bytes or an HTTPS URL. Do not expose confidential images on a public host merely to obtain a URL; use byte upload where supported. URL mode shares the full URL with Telegram, so avoid secret-bearing links.

Photos and Rich Markdown remain separate Telegram messages. For a long formatted report plus a chart, explicitly send the report using the existing tool and the chart using the photo tool. Albums, videos, arbitrary documents, and inline images within a rich message are not added by this feature.

On a timeout/network error, delivery can be unknown. The tool deliberately does not retry automatically; check Telegram before retrying to avoid duplicate posts. Successful sends share the existing best-effort duplicate/rate controls; these do not guarantee exactly-once delivery under concurrency or across server instances.

Upgrade an existing deployment

Merge the photo-support PR and redeploy the existing MCP server. Keep the same bot, destination, access token, and MCP URL. Refresh/reconnect your MCP client so tools/list exposes both send_rich_markdown_to_telegram_channel and send_photo_to_telegram_channel, and approve the new write tool as required by the client. An already-open chat may retain the old tool list.

Tests

npm install
npm test
npm run check
npm run build

The tests compile the server-side helpers and route and use Node's test runner with mocked Telegram responses. They exercise validation, JSON and multipart requests, safe errors/timeouts, duplicate/rate guards, and existing Rich Markdown behavior. Status tests use the real MCP SDK client, HTTP transport, route authentication, and handler with in-process HTTP delivery. They verify discovery, schemas, calls, missing configuration, repeatability, unchanged sending guards, and zero external fetches. They never publish a real Telegram message. The status tool can be verified live without authorizing a Telegram post.

Example Rich Markdown

# Release update

**Version 2.4 is live.**

- Faster imports
- Better error messages
- New analytics table

| Metric | Result |
| --- | ---: |
| Build time | 42 s |
| Tests | 318 passed |

> Deployment completed successfully.

<details>
<summary>Technical notes</summary>

The complexity is $O(n \log n)$.

</details>

Limitations

  • The in-memory rate limit and duplicate cache are best-effort because Vercel instances do not share memory.

  • Embedded images in Rich Markdown remain blocked; use the photo tool. The photo tool supports one image per call and plain-text captions only.

  • Telegram bots cannot initiate a private conversation until the user has contacted the bot.

  • Rich Markdown support depends on the Telegram Bot API version available to the bot.

License

MIT

Related MCP Connectors

Related MCP Servers