{"id":50050483,"url":"https://github.com/mctlhq/mctl-telegram","last_synced_at":"2026-05-24T00:02:29.492Z","repository":{"id":357480343,"uuid":"1237147130","full_name":"mctlhq/mctl-telegram","owner":"mctlhq","description":"Go remote MCP server for Telegram user-account access (gotd/td MTProto)","archived":false,"fork":false,"pushed_at":"2026-05-21T08:33:50.000Z","size":709,"stargazers_count":0,"open_issues_count":13,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-21T14:11:42.406Z","etag":null,"topics":["claude","mcp","telegram"],"latest_commit_sha":null,"homepage":"https://tg.mctl.ai","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mctlhq.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-12T23:24:24.000Z","updated_at":"2026-05-21T08:20:53.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mctlhq/mctl-telegram","commit_stats":null,"previous_names":["mctlhq/mctl-telegram"],"tags_count":38,"template":false,"template_full_name":null,"purl":"pkg:github/mctlhq/mctl-telegram","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mctlhq%2Fmctl-telegram","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mctlhq%2Fmctl-telegram/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mctlhq%2Fmctl-telegram/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mctlhq%2Fmctl-telegram/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mctlhq","download_url":"https://codeload.github.com/mctlhq/mctl-telegram/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mctlhq%2Fmctl-telegram/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33416315,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"ssl_error","status_checked_at":"2026-05-23T22:14:43.778Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["claude","mcp","telegram"],"created_at":"2026-05-21T09:04:36.427Z","updated_at":"2026-05-24T00:02:29.483Z","avatar_url":"https://github.com/mctlhq.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mctl-telegram\n\nGo remote MCP server exposing Telegram user-account access (via `gotd/td` MTProto) as MCP tools — `list_dialogs`, `get_unread_messages`, `get_messages`, `send_message`, `pin_message`, and account controls — for Claude.ai and any MCP-compatible client.\n\nStatus: **Apps SDK readiness track** (v0.x). Seven tools, OAuth-protected, draft-by-default send gate, and production-facing docs/metadata intended for ChatGPT Apps review. Telegram session is per-operator and persisted encrypted. APIs and tool schemas may change before v1.0.\n\n## Security and privacy model\n\nmctl-telegram holds a **server-side** Telegram MTProto session on your behalf. This means the server can technically read your Telegram data while processing your requests — it is the one making MTProto calls to Telegram. We minimize what is stored (encrypted session blob only) and what is logged (no message text, no phone numbers), but you are trusting both the operator of this deployment and the integrity of this code.\n\nSee [SECURITY.md](SECURITY.md) for the full threat model, cryptographic invariants, send-gate design, and reporting channel.\n\n**Do not expose this server publicly without OAuth, HTTPS, and a trusted deployment boundary.**\n\n## Endpoints\n\n| Path                                       | Purpose                                                                                              |\n|--------------------------------------------|------------------------------------------------------------------------------------------------------|\n| `/healthz`, `/readyz`                      | Probes — `ok` 200.                                                                                   |\n| `/.well-known/oauth-protected-resource`    | RFC 9728 metadata declaring the authorization server for this deployment.                            |\n| `/mcp`                                     | MCP Streamable HTTP endpoint. Auth: `Authorization: Bearer \u003cJWT\u003e`.                                   |\n\n## MCP tools\n\n| Tool                          | Annotations       | Notes |\n|-------------------------------|-------------------|-------|\n| `list_dialogs`                | `readOnly`        | Inputs: `limit` (≤200, default 50), optional `query`. Peer id format: `user:\u003cid\u003e` / `chat:\u003cid\u003e` / `channel:\u003cid\u003e`. |\n| `get_unread_messages`         | `readOnly`        | Inputs: optional `peer`, `limit` (≤200). Returns only unread messages. |\n| `get_messages`                | `readOnly`        | Full message history for a specific peer, not just unread. |\n| `send_message`                | `destructive`     | Inputs: `peer`, `text`. Draft-by-default: sends for real only when the gate is fully open (server `ALLOW_SEND=true`, identity has `telegram:messages:send` scope, per-account `send_enabled=true`). Otherwise returns a dry-run preview (`sent=false`) with `dry_reason` — nothing is sent. The result's `sent` field indicates which happened. |\n| `pin_message`                 | `destructive`     | Inputs: `peer`, `message_id`, `unpin` (bool). |\n| `disconnect_telegram_account` | `destructive`     | Soft-revokes your session — marks it revoked and tears down the in-memory MTProto client. |\n| `delete_telegram_account`     | `destructive`     | Hard-deletes the encrypted session blob and all per-account metadata from the server. |\n\n## Quick start (local dev)\n\n```bash\n# 1. Build \u0026 run the server (auth bypassed for local dev)\nADDR=:8080 \\\nAUTH_MODE=local-dev AUTH_REQUIRED=false \\\nOPERATOR_GITHUB_LOGIN=your-github-handle \\\nDATABASE_URL='file:./mctl-telegram.db?_pragma=journal_mode(WAL)' \\\ngo run ./cmd/server\n\n# 2. First-time login (register an app at https://my.telegram.org first)\nTG_API_ID=12345 TG_API_HASH=hexhexhex... \\\nDATABASE_URL='file:./mctl-telegram.db?_pragma=journal_mode(WAL)' \\\nOPERATOR_GITHUB_LOGIN=your-github-handle \\\ngo run ./cmd/login --phone +1...\n\n# 3. Smoke test via MCP inspector or curl\ncurl -s -X POST localhost:8080/mcp \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Accept: application/json, text/event-stream\" \\\n  -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{\"protocolVersion\":\"2025-03-26\",\"capabilities\":{},\"clientInfo\":{\"name\":\"local\",\"version\":\"0\"}}}'\n```\n\n## Self-hosted deployment\n\n### Prerequisites\n\n- Telegram API credentials: register an app at \u003chttps://my.telegram.org\u003e to get `TG_API_ID` and `TG_API_HASH`.\n- A Telegram bot for the OIDC login flow: create one via BotFather and note its numeric id and token.\n- A Postgres database (or SQLite for single-node deployments).\n- An HTTPS endpoint reachable by your MCP clients.\n\n### Configuration\n\nCopy `.env.example` to `.env` and fill in the values:\n\n```bash\ncp .env.example .env\n$EDITOR .env\n```\n\nKey variables:\n\n| Variable                      | Description                                                                 |\n|-------------------------------|-----------------------------------------------------------------------------|\n| `AUTH_MODE`                   | `local-jwt` — server signs its own tokens (recommended for self-hosting)    |\n| `AUTH_REQUIRED`               | `true` in production; `false` only for local dev                            |\n| `OAUTH_JWT_SIGNING_KEY`       | HS256 signing key for access tokens; see below                              |\n| `TELEGRAM_OIDC_CLIENT_ID`     | Login bot's numeric Telegram id (from BotFather; not secret)                |\n| `TELEGRAM_OIDC_CLIENT_SECRET` | OIDC client secret for the login bot                                        |\n| `TELEGRAM_LOGIN_BOT_TOKEN`    | Bot token — used only for the new-client welcome digest                     |\n| `TG_API_ID`                   | Telegram API id from my.telegram.org                                        |\n| `TG_API_HASH`                 | Telegram API hash from my.telegram.org                                      |\n| `ENCRYPTION_KEY`              | 32-byte hex key for encrypting session blobs at rest                        |\n| `DATABASE_URL`                | `postgres://...` or `file:./mctl-telegram.db?_pragma=journal_mode(WAL)`     |\n| `ALLOW_SEND`                  | `false` by default; set `true` only after validating the send gate          |\n| `PUBLIC_BASE_URL`             | External HTTPS base URL, e.g. `https://tg.example.com`                     |\n| `OAUTH_ACCESS_TOKEN_TTL`      | optional, default `1h`                                                      |\n| `OAUTH_REFRESH_TOKEN_TTL`     | optional, default `720h` (30 days)                                          |\n\n\u003e `OAUTH_JWT_SECRET` is a deprecated alias of `OAUTH_JWT_SIGNING_KEY`. It is\n\u003e still accepted as a fallback but logs a warning at startup. Use\n\u003e `OAUTH_JWT_SIGNING_KEY` for new deployments.\n\n### JWT signing key\n\nIn `local-jwt` mode mctl-telegram signs its own access tokens (HS256). The signing key **must persist across restarts** and **must be dedicated to this service** — if it changes, every previously issued token fails verification.\n\nGenerate a key:\n\n```bash\n# 64 random bytes, base64-encoded — store this in a secret manager or .env\nopenssl rand -base64 64\n```\n\nSet it as `OAUTH_JWT_SIGNING_KEY` in your environment. In local development any non-empty string works.\n\n### Token lifetimes and refresh\n\nAccess tokens are intentionally short-lived (`OAUTH_ACCESS_TOKEN_TTL`, default 1h). Clients renew them silently with the OAuth 2.1 `refresh_token` grant: the `/oauth/token` endpoint accepts `grant_type=refresh_token` and returns a new access token plus a rotated refresh token, with no Telegram sign-in interaction. Refresh tokens are opaque, stored SHA-256-hashed, and rotated on every use; replaying an already-rotated token revokes the whole token family.\n\n### Docker\n\n```bash\n# Build\ndocker build -t mctl-telegram .\n\n# Run (pass env from a file)\ndocker run --rm -p 8080:8080 --env-file .env mctl-telegram\n```\n\nImage published to `ghcr.io/mctlhq/mctl-telegram:\u003csemver\u003e` (no `v` prefix).\n\n### Docker Compose\n\nA `docker-compose.yml` is included for a self-contained local deployment with Postgres:\n\n```bash\ncp .env.example .env\n$EDITOR .env          # fill in TG_API_ID, TG_API_HASH, keys\ndocker compose up -d\n```\n\nServices started: `app` (mctl-telegram on port 8080) and `db` (Postgres 16).\n\nFor Beta-tier service-level objectives, error-budget policy, and burn-rate alert definitions, see [docs/slo.md](docs/slo.md).\n\n## Connecting to Claude.ai\n\n1. Start mctl-telegram and confirm the well-known is reachable:\n   ```bash\n   curl https://\u003cyour-host\u003e/.well-known/oauth-protected-resource\n   ```\n2. In Claude.ai → Settings → Connectors → Add custom connector:\n   * Remote MCP URL: `https://\u003cyour-host\u003e/mcp`\n   * Authentication: OAuth (the connector discovers the authorization server from the well-known metadata).\n3. Complete the Telegram login flow in the browser; the issued access token is used automatically on every MCP request.\n\n## Connecting to ChatGPT Apps (Draft → review-ready)\n\n\u003e Note: mctl-telegram does **not** require an `OPENAI_API_KEY` server-side.\n\u003e ChatGPT connects to your MCP endpoint using OAuth bearer tokens issued by\n\u003e this service.\n\n1. In ChatGPT, open **Settings → Apps** and select your draft app (enable Developer Mode first if required).\n2. Set the MCP server URL to the public endpoint: `https://\u003cyour-host\u003e/mcp` (OAuth auth).\n3. Ensure these public pages are reachable over HTTPS:\n   - Landing: `https://\u003cyour-host\u003e/`\n   - Docs: `https://\u003cyour-host\u003e/docs`\n   - Security: `https://\u003cyour-host\u003e/security`\n   - Privacy: `https://\u003cyour-host\u003e/privacy`\n4. Verify OAuth discovery:\n   - Fetch `https://\u003cyour-host\u003e/.well-known/oauth-protected-resource` (always served on your host).\n   - Follow the `authorization_servers` URL it advertises and confirm that server's `/.well-known/oauth-authorization-server` resolves. In the default `local-jwt` mode this is your own host; in `shared-hmac`/`shared-hmac-legacy` mode discovery points at `https://api.mctl.ai`, so do **not** expect that document on `\u003cyour-host\u003e`.\n5. Run a live handshake (`initialize`) and at least one read-only tool call with MCP Inspector or ChatGPT Developer Mode before submitting.\n\n### Quick connect to the hosted endpoint (`tg.mctl.ai`)\n\nIf you are using the shared hosted deployment, configure:\n\n- Landing page: `https://tg.mctl.ai/`\n- MCP connector URL: `https://tg.mctl.ai/mcp`\n\nSubmission notes:\n- Keep real sends gated (`ALLOW_SEND=false` on tg.mctl.ai blocks all real sends until opt-in gates are enabled).\n- If you enable real sends later, keep the per-account `send_enabled` gate and confirmation flow documented in `/security`.\n- Prepare the dashboard submission package with the privacy policy URL, MCP/tool information, screenshots, and test prompts/responses.\n\n## Operations: Canary account\n\nThe synthetic canary probe (`cmd/canary`) verifies the live service end-to-end. It requires a dedicated Telegram test account — **not the operator's personal account** — to avoid false-positive FLOOD_WAIT interference.\n\n### Setting up the canary account\n\n1. Create a fresh Telegram account for the canary. Note its numeric user id.\n\n2. Complete the browser-based setup flow by visiting `GET /telegram/connect` while signed in as the canary. This links the session to an authenticated identity in the database.\n\n3. Issue a read-only bearer token with the `set_telegram_access` admin MCP tool:\n\n   ```\n   set_telegram_access(tg_user_id=\"\u003ccanary-account-id\u003e\", scopes=\"telegram:dialogs:read,telegram:messages:read\")\n   ```\n\n   The token must carry exactly `telegram:dialogs:read,telegram:messages:read` and must **not** include `telegram:messages:send`.\n\n4. Pass `tg_user_id` and `bearer_token` to the canary probe via environment variables or a Kubernetes Secret.\n\n5. Schedule the canary to run every two minutes against your deployment.\n\nThe canary pushes three Prometheus metric families to a Pushgateway:\n\n- `mctl_telegram_canary_success` — 1 if all probes passed, 0 if any failed.\n- `mctl_telegram_canary_duration_seconds` — wall-clock time of the run.\n- `mctl_telegram_canary_step_failure_total{step=}` — per-step failure counters.\n\nAn alert rule template is provided in `deploy/alerts/canary.rules.yaml`.\n\n## Contributing\n\nContributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, code style, and the PR process.\n\n## Security\n\nSee [SECURITY.md](SECURITY.md) — covers the send-gate invariants, session encryption, and the reporting channel.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmctlhq%2Fmctl-telegram","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmctlhq%2Fmctl-telegram","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmctlhq%2Fmctl-telegram/lists"}