diff --git a/.env.example b/.env.example index 31540c5e..029ab26c 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,17 @@ TELEGRAM_SESSION_NAME=telegram_session # TELEGRAM_SESSION_STRING_WORK= # TELEGRAM_SESSION_STRING_PERSONAL= +# --- Session pool (same account, concurrent clients) --- +# Run several MCP clients (e.g. the desktop app AND a terminal CLI) against ONE +# Telegram account without hitting AuthKeyDuplicatedError. Telegram forbids a +# single session (auth key) being used from two IPs at once; on a VPN/dual-stack +# host two local clients can collide. List several interchangeable session +# strings (whitespace, comma or semicolon separated) and each process claims a +# free one via an advisory file lock. Generate extra sessions with +# `uv run session_string_generator.py`. Takes precedence over +# TELEGRAM_SESSION_STRING for the default account. +# TELEGRAM_SESSION_STRINGS= + # --- Device identity (optional) --- # Controls how this client appears in Telegram > Settings > Devices. If unset, # Telethon falls back to the host platform (e.g. "arm64"). Set these so the diff --git a/README.md b/README.md index 3a258e22..898e7ddb 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,26 @@ Example prompts: - "List my accounts" - "Show unread messages from all accounts" + +### Session pool (one account, several concurrent clients) + +To run several MCP clients against the **same** Telegram account at once (for +example the desktop app *and* a terminal CLI), give each client its own +authorized session. Telegram forbids one session (auth key) being used from two +IPs simultaneously, so on a VPN or dual-stack host two local clients can collide +with `AuthKeyDuplicatedError`. List several interchangeable session strings in +`TELEGRAM_SESSION_STRINGS` (separated by whitespace, comma or semicolon); each +process claims a free one via an advisory file lock, so clients deterministically +pick distinct sessions: + +```env +TELEGRAM_SESSION_STRINGS= +``` + +Generate extra sessions with `uv run session_string_generator.py`. The pool +takes precedence over `TELEGRAM_SESSION_STRING` for the default account. As an +extra safety net, a transient `AuthKeyDuplicatedError` at connect time (e.g. +during a VPN reconnect) is retried with backoff before the server gives up. - "Send this from my work account to @example" ## Device Identity diff --git a/telegram_mcp/runner.py b/telegram_mcp/runner.py index 5c1157c6..156be021 100644 --- a/telegram_mcp/runner.py +++ b/telegram_mcp/runner.py @@ -7,13 +7,42 @@ except UnsafeInstallationError as exc: raise SystemExit(str(exc)) from None +from telethon.errors import AuthKeyDuplicatedError + from telegram_mcp import runtime as _runtime from telegram_mcp.runtime import * import telegram_mcp.tools # noqa: F401 - registers MCP tools via decorators async def _connect_authorized_client(label, client) -> None: - await client.connect() + # Tolerate a transient AuthKeyDuplicatedError (the same session briefly seen + # from two IPs, e.g. during a VPN reconnect) with a bounded retry so a blip + # does not take the whole server down. Give each concurrent client its own + # session (TELEGRAM_SESSION_STRINGS pool or TELEGRAM_SESSION_STRING_