From eb39633150574c248696c4495999882da28ff227 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=D0=B8=D1=82=D0=B0=20=D0=9F=D0=B5=D1=80?= =?UTF-8?q?=D0=BC=D1=8F=D0=BA=D0=BE=D0=B2?= Date: Wed, 15 Jul 2026 13:37:25 +0100 Subject: [PATCH] feat: session pool + AuthKeyDuplicatedError retry for concurrent clients Running two MCP clients against the same Telegram account (e.g. the desktop app and a terminal CLI) can trip AuthKeyDuplicatedError: Telegram forbids one session (auth key) being used from two IPs at once, and on a VPN/dual-stack host two local clients egress via different source IPs. Add an opt-in pool of interchangeable sessions via TELEGRAM_SESSION_STRINGS (whitespace/comma/semicolon separated). Each process claims the first session not already held by a live client via an advisory flock, so clients deterministically pick distinct slots; the OS frees the lock if a process dies. The pool takes precedence over TELEGRAM_SESSION_STRING for the default account and is a no-op unless configured. Falls back to the first session (with a warning) when the pool is exhausted, and to the first session on platforms without fcntl (e.g. Windows). As a safety net, a transient AuthKeyDuplicatedError at connect time is retried with exponential backoff before the server gives up. Documented in README and .env.example; covered by tests/test_session_pool.py. Co-Authored-By: Claude Opus 4.8 (1M context) --- .env.example | 11 +++ README.md | 20 ++++++ telegram_mcp/runner.py | 31 +++++++- telegram_mcp/runtime.py | 102 ++++++++++++++++++++++++-- tests/test_session_pool.py | 144 +++++++++++++++++++++++++++++++++++++ 5 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 tests/test_session_pool.py 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_