Skip to main content
Glama
README.md
# telegram-call-mcp

An [MCP](https://modelcontextprotocol.io) server that lets AI agents **place
real Telegram voice calls** and speak a message aloud. The agent calls one
tool — `make_call` — with the text and a repeat count; the server synthesizes
speech (any TTS model on [OpenRouter](https://openrouter.ai), default
[`x-ai/grok-voice-tts-1.0`](https://openrouter.ai/x-ai/grok-voice-tts-1.0)
with the `rex` voice), texts the fuller `details` version to the owner's chat,
rings their Telegram, waits for them to pick up, plays the message, and hangs
up. A second tool — `send_message` — texts the owner without ringing, for
updates that can wait.

Built for "wake me up if production is on fire" scenarios: text notifications
are easy to sleep through, a ringing phone is not.

```
Agent ──make_call("DB is down", details="…")──▶ telegram-call-mcp
                                                   │  1. TTS via OpenRouter (mp3)
                                                   │  2. ffmpeg → WAV 48kHz mono
                                                   │  3. 💬 text message with the details
                                                   │  4. 📞 Telegram P2P call (py-tgcalls)
                                                   ▼
                                           owner's phone rings,
                                           message plays twice
```

## The tools

### `make_call`

| Argument  | Type   | Required | Description |
|-----------|--------|----------|-------------|
| `message` | string | yes      | Text to speak. Markdown, URLs, emojis and long IDs are stripped; if the result still exceeds `MAX_MESSAGE_LENGTH` (default 500 chars), the call is rejected with an error asking the agent to shorten it. |
| `repeat`  | int 1–10 | no (default 2) | How many times to play the message, with a 2s pause between repeats. |
| `details` | string ≤4000 | no | Fuller version of the alert, sent as a plain Telegram **text message** right before the call rings — links, IDs, error output and next steps belong here, not in the voice. When omitted, the spoken text is sent instead, so every call is preceded by a text. |

Returns a structured result:

```json
{ "status": "answered", "detail": "message played in full", "message_spoken": "DB is down", "text_sent": true }
```

`status` is one of `answered`, `no_answer`, `busy`, `declined`, `error`;
`text_sent` reports whether the pre-call text message reached the chat. An
unanswered call means the spoken message was **not** heard — the text usually
still lands, but for anything critical the agent should retry the call later.

### `send_message`

| Argument  | Type   | Required | Description |
|-----------|--------|----------|-------------|
| `message` | string ≤4000 | yes | Text delivered verbatim to the owner's Telegram chat. |

Returns `{ "status": "sent", "detail": "text message sent (57 chars)" }`.

No ringing — for "deploy finished, all green" class updates that the owner
reads whenever they next pick up the phone. The tool descriptions steer the
agent to reserve `make_call` for things that cannot wait.

## Requirements

- Python 3.10+
- **ffmpeg** (with ffprobe) in `PATH` — `brew install ffmpeg` / `apt install ffmpeg`
- A **dedicated Telegram account** for the server (bots can't make calls, so
  it signs in as a real user account; see [Security notes](#security-notes))
- An [OpenRouter](https://openrouter.ai) API key for TTS

## Setup

### 1. Install

```bash
pip install telegram-call-mcp        # or: pipx install / uv tool install
```

Or from source:

```bash
git clone https://github.com/maloleg/telegram-call-mcp
cd telegram-call-mcp
pip install .
```

### 2. Get Telegram API credentials

1. Sign in at [my.telegram.org](https://my.telegram.org) **with the account
   that will be making the calls** (a spare/dedicated account, not your own).
2. Open *API development tools*, create an application (any name, URL can be
   left empty).
3. Note the `api_id` and `api_hash`.

### 3. Sign in once

```bash
export TELEGRAM_API_ID=123456
export TELEGRAM_API_HASH=abcdef...
export CALL_TARGET_USER_ID=111111111   # your own numeric Telegram user id

telegram-call-mcp login
```

(The OpenRouter key is not needed here — `login` only talks to Telegram.)

The login prompts for the phone number, the code Telegram sends, and the 2FA
password if enabled, then saves a session file to
`~/.telegram-call-mcp/telegram.session`.

Not sure about your numeric user id? Ask
[@userinfobot](https://t.me/userinfobot) on Telegram.

> **The caller and the callee must be different accounts.** Telegram cannot
> call itself. Sign the server in with a dedicated account and point
> `CALL_TARGET_USER_ID` at your personal one.

On the receiving account, allow calls from non-contacts (*Settings → Privacy →
Calls → Everybody*) or add the server's account to your contacts — otherwise
Telegram rejects the call before it ever rings.

### 4. Verify the setup

```bash
export OPENROUTER_API_KEY=sk-or-...   # the remaining variable, for TTS

telegram-call-mcp check
```

`check` (alias: `doctor`) verifies everything a real call needs — config,
ffmpeg, the Telegram session, that the target user is reachable, and the TTS
key — without ringing anyone:

```
OK   config   all required variables set
OK   ffmpeg   ffmpeg and ffprobe found in PATH
OK   session  signed in as Alert Bot (id=8012345678)
OK   target   can call Oleg (id=111111111)
OK   tts      x-ai/grok-voice-tts-1.0 synthesized 38400 bytes of audio

All checks passed - ready to place calls.
```

Run it after any config change: the server connects lazily, so a broken
setup would otherwise surface only on the first real call — usually at the
worst possible moment.

### 5. Add to your MCP client

**Claude Code** — a personal alert tool belongs in **user scope** (`-s user`,
available in all your projects); the default scope registers the server only
in the current project. Reference the variables exported above instead of
pasting literal values, so the secrets don't end up in your shell history:

```bash
claude mcp add -s user telegram-call \
  --env TELEGRAM_API_ID="$TELEGRAM_API_ID" \
  --env TELEGRAM_API_HASH="$TELEGRAM_API_HASH" \
  --env CALL_TARGET_USER_ID="$CALL_TARGET_USER_ID" \
  --env OPENROUTER_API_KEY="$OPENROUTER_API_KEY" \
  -- telegram-call-mcp
```

(If you keep the variables in a `.env` file, load them first with
`set -a; source .env; set +a`.)

**Claude Desktop / any JSON-config client** (`claude_desktop_config.json`):

```json
{
  "mcpServers": {
    "telegram-call": {
      "command": "telegram-call-mcp",
      "env": {
        "TELEGRAM_API_ID": "123456",
        "TELEGRAM_API_HASH": "abcdef...",
        "CALL_TARGET_USER_ID": "111111111",
        "OPENROUTER_API_KEY": "sk-or-..."
      }
    }
  }
}
```

That's it. Ask the agent to "call me and say the deploy finished" to test.

## Configuration reference

| Variable | Required | Default | Description |
|---|---|---|---|
| `TELEGRAM_API_ID` | ✅ | — | From my.telegram.org |
| `TELEGRAM_API_HASH` | ✅ | — | From my.telegram.org |
| `CALL_TARGET_USER_ID` | ✅ | — | Numeric Telegram user id to call |
| `OPENROUTER_API_KEY` | ✅ | — | OpenRouter API key for TTS (not needed for `login`) |
| `TTS_MODEL` | | `x-ai/grok-voice-tts-1.0` | Any TTS model on OpenRouter's `/audio/speech` endpoint |
| `TTS_VOICE` | | `rex` | Voice name. The default applies only with the default model (voice names are model-specific); set it explicitly for other models, or set it empty to send no voice |
| `TTS_BASE_URL` | | `https://openrouter.ai/api/v1` | Any OpenAI-compatible audio API works |
| `TELEGRAM_PROXY` | | — | `socks5://host:port` or `http://host:port` for MTProto (Telethon ignores `HTTP_PROXY`) |
| `TELEGRAM_SESSION_PATH` | | `~/.telegram-call-mcp/telegram` | Session file location (without `.session`) |
| `CALL_ANSWER_TIMEOUT` | | `45` | Seconds to wait for the callee to answer |
| `CALL_REPEAT_PAUSE` | | `2` | Pause between repeats, seconds |
| `MAX_MESSAGE_LENGTH` | | `500` | Max message length after sanitization; longer messages are rejected, not truncated |
| `LOG_LEVEL` | | `INFO` | stderr logging verbosity |

## Security notes

- **The session file is full access to the Telegram account.** It is created
  with your user permissions in `~/.telegram-call-mcp/`; treat it like a
  private key. Use a dedicated account so a leak never exposes your personal
  chats.
- The tool can only reach the **one user id fixed in the server config** — the
  agent cannot choose an arbitrary callee or text recipient, dial numbers, or
  message anyone else. The pre-call text goes to that same user.
- The message text passes through OpenRouter for synthesis; don't have agents
  speak secrets aloud.
- Automating a user account is subject to Telegram's terms; a dedicated
  account keeps any risk away from your personal one.

## Troubleshooting

Start with `telegram-call-mcp check` — it validates the config, ffmpeg, the
session, target reachability and the TTS key in one pass, without placing a
call.

| Symptom | Cause / fix |
|---|---|
| Tool error: *session not authorized* | Run `telegram-call-mcp login` with the same env vars the MCP client uses. |
| `Connection to Telegram failed` at startup | Direct MTProto is blocked in your network. Set `TELEGRAM_PROXY` to a local SOCKS/HTTP proxy. |
| Call connects but there is silence / `error (TelegramServerError)` after ~10s of "REFLECTOR" log lines | The voice media stream (UDP to Telegram relays) is blocked. A SOCKS proxy is not enough — voice traffic bypasses it. Run your VPN in **TUN / system-tunnel mode** so UDP is routed too. |
| `declined` immediately, phone never rang | The callee's privacy settings reject calls from non-contacts, or the two accounts are the same. |
| *ffmpeg not found* | Install ffmpeg and make sure `ffmpeg`/`ffprobe` are in the `PATH` visible to your MCP client. |
| TTS HTTP 401/402 | Bad or out-of-credit `OPENROUTER_API_KEY`. |

## Development

```bash
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/mypy src
LOG_LEVEL=DEBUG .venv/bin/telegram-call-mcp   # run on stdio
```

The call machinery lives in `caller.py` (Telethon + py-tgcalls, pinned to
2.3.3 — private-call APIs are version-sensitive), TTS and audio prep in
`tts.py`, tool definition in `server.py`.

## License

MIT

TDQS

A4.6/5.0

Scored across 1 tool

Disambiguation5/5

There is only one tool in this server, so there is no possibility of overlap or confusion between tools. The tool's purpose is clearly distinct because it is the sole capability provided.

Naming Consistency5/5

The single tool uses a clear verb_noun convention (`make_call`), which is consistent and intuitive. With only one tool, there are no naming conflicts or style inconsistencies to evaluate.

Tool Count3/5

The server has only one tool, which is at the lower boundary of the typical range. While the narrow purpose of making Telegram calls could justify a single tool, the count feels thin compared to more comprehensive servers, making it borderline.

Completeness5/5

The tool fully covers the stated domain: it places a call, delivers a spoken message, and reports the call outcome. There are no obvious missing operations for this specific use case, as calls are ephemeral and require no update or delete actions.

Maintenance

ActivitySlowing
ResponsivenessNo issues