Skip to content

Commit 8cc3835

Browse files
committed
Merge branch 'upstream/main' into feat/transport-selection-50 and remove CLAUDE.md
2 parents f8fc3cf + 9bbc98d commit 8cc3835

22 files changed

Lines changed: 2135 additions & 105 deletions

‎.env.example‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ TELEGRAM_API_HASH=0123456789abcdef0123456789abcdef
77
# TELEGRAM_EVENT_FEED_FILE=/path/to/incoming_feed.jsonl
88

99
# --- Single account (backward-compatible) ---
10-
# Option 1: File-based session (a .session file will be created)
10+
# Recommended for long-running servers: persists Telegram update state locally.
1111
TELEGRAM_SESSION_NAME=telegram_session
12-
# Option 2: String-based session
12+
# Portable setup/migration source: does not persist Telegram update state.
13+
# If both variables are set, TELEGRAM_SESSION_STRING takes precedence, so remove
14+
# or comment it out after running `uv run telegram-mcp-migrate-session`.
1315
# TELEGRAM_SESSION_STRING=1231231232erfdfdffd
1416

1517
# --- Multiple accounts ---

‎.github/workflows/docker-build.yml‎

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ on:
99

1010
jobs:
1111
docker_build:
12-
name: Build Docker Image
12+
name: Build Docker Image (Multistage)
1313
runs-on: ubuntu-latest
1414
steps:
1515
- name: Checkout code
@@ -21,8 +21,26 @@ jobs:
2121
- name: Set up Docker Buildx
2222
uses: docker/setup-buildx-action@v3
2323

24-
- name: Build Docker image
25-
run: docker build --file Dockerfile --tag telegram-mcp:ci .
24+
- name: Build production target
25+
uses: docker/build-push-action@v6
26+
with:
27+
context: .
28+
file: ./Dockerfile
29+
target: production
30+
tags: telegram-mcp:latest,telegram-mcp:ci
31+
load: true
32+
cache-from: type=gha
33+
cache-to: type=gha,mode=max
34+
35+
- name: Build and test development target
36+
uses: docker/build-push-action@v6
37+
with:
38+
context: .
39+
file: ./Dockerfile
40+
target: development
41+
tags: telegram-mcp:dev
42+
load: true
43+
cache-from: type=gha
2644

2745
docker_compose:
2846
name: Build & Validate Docker Compose
@@ -37,7 +55,6 @@ jobs:
3755
echo "TELEGRAM_API_ID=DUMMY_ID" > .env
3856
echo "TELEGRAM_API_HASH=DUMMY_HASH" >> .env
3957
echo "TELEGRAM_SESSION_STRING=DUMMY_SESSION" >> .env
40-
# Add other required variables with dummy values if needed
4158
4259
- name: Validate Compose file syntax
4360
run: docker compose config

‎CLAUDE.md‎

Lines changed: 0 additions & 52 deletions
This file was deleted.

‎Dockerfile‎

Lines changed: 51 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,65 @@
1-
# Use an official Python runtime as a parent image (Alpine-based for minimal vulnerabilities)
2-
FROM python:3.13-alpine
1+
# =============================================================================
2+
# Base stage: official minimal Alpine Python runtime
3+
# =============================================================================
4+
FROM python:3.13-alpine AS base
35

4-
# Set the working directory in the container
56
WORKDIR /app
67

7-
# Prevent Python from writing pyc files to disc
8-
ENV PYTHONDONTWRITEBYTECODE=1
9-
# Ensure Python output is sent straight to terminal (useful for logs)
10-
ENV PYTHONUNBUFFERED=1
8+
ENV PYTHONDONTWRITEBYTECODE=1 \
9+
PYTHONUNBUFFERED=1
1110

12-
# Install system dependencies if needed (e.g., for certain Python packages)
13-
# RUN apt-get update && apt-get install -y --no-install-recommends some-package && rm -rf /var/lib/apt/lists/*
11+
# =============================================================================
12+
# Builder stage: build wheels / dependencies
13+
# =============================================================================
14+
FROM base AS builder
1415

15-
# Copy dependency definition files
16-
# If using Poetry:
17-
# COPY pyproject.toml poetry.lock* ./
18-
# RUN pip install --no-cache-dir poetry
19-
# RUN poetry config virtualenvs.create false && poetry install --no-dev --no-interaction --no-ansi
20-
# If using pip with requirements.txt:
2116
COPY requirements.txt ./
22-
RUN pip install --no-cache-dir --upgrade pip
23-
RUN pip install --no-cache-dir -r requirements.txt
17+
RUN pip install --no-cache-dir --upgrade pip && \
18+
pip install --no-cache-dir --prefix=/install -r requirements.txt
2419

25-
# Copy the rest of the application code
20+
# =============================================================================
21+
# Development / Test stage: includes dev tooling from pyproject.toml
22+
# =============================================================================
23+
FROM base AS development
24+
25+
# Copy installed production packages
26+
COPY --from=builder /install /usr/local
27+
28+
# Copy dev dependencies and install test/lint tools
29+
COPY pyproject.toml ./
30+
RUN pip install --no-cache-dir --upgrade pip && \
31+
pip install --no-cache-dir black flake8 pytest pytest-asyncio pytest-cov
32+
33+
# Copy application source and tests
2634
COPY main.py sanitize.py ./
2735
COPY telegram_mcp ./telegram_mcp
28-
# COPY session_string_generator.py . # Optional: if needed within the container, otherwise can be run outside
36+
COPY tests ./tests
2937

30-
# Create a non-root user and switch to it
31-
RUN adduser --disabled-password --gecos "" appuser && chown -R appuser:appuser /app
32-
USER appuser
38+
CMD ["pytest", "--cov", "--cov-report=term-missing"]
39+
40+
# =============================================================================
41+
# Production stage: slim runtime container with non-root appuser
42+
# =============================================================================
43+
FROM base AS production
44+
45+
# Copy only installed dependencies from builder
46+
COPY --from=builder /install /usr/local
3347

34-
# Define environment variables needed by the application
35-
# These should be provided at runtime, not hardcoded (especially secrets)
36-
ENV TELEGRAM_API_ID=""
37-
ENV TELEGRAM_API_HASH=""
38-
# Specify one of the following at runtime:
39-
# Default session filename
40-
ENV TELEGRAM_SESSION_NAME="telegram_mcp_session"
41-
# Or provide the session string directly
42-
ENV TELEGRAM_SESSION_STRING=""
48+
# Copy application source code
49+
COPY main.py sanitize.py ./
50+
COPY telegram_mcp ./telegram_mcp
51+
52+
# Create non-root user and setup directories
53+
RUN adduser --disabled-password --gecos "" appuser && \
54+
mkdir -p /app/data/transcripts && \
55+
chown -R appuser:appuser /app
56+
57+
USER appuser
4358

44-
# Expose any ports if the application were a web server (not needed for stdio MCP)
45-
# EXPOSE 8000
59+
# Runtime environment variables (to be supplied at run/container start)
60+
ENV TELEGRAM_API_ID="" \
61+
TELEGRAM_API_HASH="" \
62+
TELEGRAM_SESSION_NAME="telegram_mcp_session" \
63+
TELEGRAM_SESSION_STRING=""
4664

47-
# Define the command to run the application
4865
CMD ["python", "main.py"]

‎README.md‎

Lines changed: 69 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Message sent successfully:
2525

2626
## Contents
2727

28+
- [Skills & Practical Workflows](skills/README.md)
2829
- [What It Can Do](#what-it-can-do)
2930
- [Requirements](#requirements)
3031
- [Quick Start](#quick-start)
@@ -40,6 +41,15 @@ Message sent successfully:
4041
- [Troubleshooting](#troubleshooting)
4142
- [License](#license)
4243

44+
## Skills & Workflows
45+
46+
Looking for ready-to-use workflows, prompt examples, or integration recipes?
47+
Explore the [**Skills Documentation**](skills/README.md) for step-by-step guides on:
48+
- [Summarizing unread messages safely](skills/examples/summarize-latest-unread.md)
49+
- [Drafting replies without sending](skills/examples/draft-replies-without-sending.md)
50+
- [Triaging action items & urgent requests](skills/examples/triage-and-action-items.md)
51+
- [Searching chat history and expanding context](skills/examples/search-chat-and-summarize-context.md)
52+
4353
## What It Can Do
4454

4555
The server currently includes 80+ MCP tools grouped into these areas:
@@ -159,7 +169,10 @@ uv sync
159169
uv run session_string_generator.py
160170
```
161171

162-
Follow the prompts. Save the generated session string securely.
172+
Follow the prompts. Save the generated session string securely. A session
173+
string is convenient for initial setup and portable deployments; for a
174+
long-running server, migrate it to a persistent file session after configuring
175+
your environment (see below).
163176

164177
For scripted setup or operational runbooks, choose the login method explicitly:
165178

@@ -189,6 +202,43 @@ TELEGRAM_API_HASH=your_api_hash_here
189202
TELEGRAM_SESSION_STRING=your_session_string_here
190203
```
191204

205+
### Recommended: Persistent Session for Long-Running Servers
206+
207+
`StringSession` stores the Telegram authorization key, but not Telethon's update
208+
state. After a restart, a busy account can therefore receive a large backlog of
209+
updates. A local SQLite session persists the update cursor and entity cache, so
210+
subsequent reconnects receive only the missed delta.
211+
212+
Stop all running telegram-mcp processes, then migrate the string configured in
213+
`.env`:
214+
215+
```bash
216+
uv run telegram-mcp-migrate-session
217+
```
218+
219+
The command verifies the new session with Telegram and prints the exact setting
220+
to use. Update `.env` as instructed, for example:
221+
222+
```env
223+
# Remove or comment this out; it takes precedence when both are present.
224+
# TELEGRAM_SESSION_STRING=your_session_string_here
225+
TELEGRAM_SESSION_NAME=/absolute/path/to/telegram_mcp_session
226+
```
227+
228+
The resulting `.session` file contains account credentials. Keep it private and
229+
do not commit it. The repository's `.gitignore` already excludes session files.
230+
231+
For a labeled account, use `--account` with the same label:
232+
233+
```bash
234+
uv run telegram-mcp-migrate-session --account work
235+
```
236+
237+
This reads `TELEGRAM_SESSION_STRING_WORK` and writes the corresponding
238+
`TELEGRAM_SESSION_NAME_WORK` instruction. Use `--target PATH` to choose another
239+
destination. Do not run the migration while another process is using the source
240+
session.
241+
192242
By default, all Telegram MCP tools are exposed. If you want to prevent MCP
193243
clients from sending messages or performing chat/account mutations, set
194244
`TELEGRAM_EXPOSED_TOOLS=read-only` to expose only tools annotated with
@@ -374,8 +424,10 @@ Telegram throttles and may flag accounts that open many parallel sessions.
374424

375425
Every tool call also has a server-side ceiling of 55 seconds, configured with
376426
`TELEGRAM_TOOL_TIMEOUT_SECONDS`. A timed-out Telegram request returns an explicit
377-
MCP error instead of leaving the client waiting indefinitely. Set the value to
378-
`0` only for a deliberately unbounded operator session.
427+
MCP error instead of leaving the client waiting indefinitely. The error says
428+
that completion is unknown: a write may already have succeeded, so check the
429+
destination before retrying. Set the value to `0` only for a deliberately
430+
unbounded operator session.
379431

380432
Register the shared server with clients:
381433

@@ -539,12 +591,13 @@ bypassing the proxy.
539591

540592
## File Path Security
541593

542-
File-path tools are disabled until allowed roots are configured. This affects tools such as `send_file`, `download_media`, `upload_file`, `send_voice`, `send_sticker`, `set_profile_photo`, and `edit_chat_photo`.
594+
File-path and media tools (`send_file`, `download_media`, `upload_file`, `send_voice`, `send_sticker`, `set_profile_photo`, `edit_chat_photo`, and `send_album`) are registered and supported, but remain strictly disabled by default until allowed roots are configured (safe-by-default to prevent unauthorized file access).
543595

544596
Allowed roots can come from:
545597

546-
- Server CLI arguments, used as a fallback.
547-
- MCP client Roots, when supported by the client.
598+
- **Environment Variable (`TELEGRAM_ALLOWED_ROOTS`):** Semicolon- (`;`) or comma- (`,`) separated paths (also supports colon `:` on POSIX systems). Ideal for Docker, headless daemon setups, and MCP clients that pass settings via environment.
599+
- **Server CLI arguments:** Positional folder paths passed to `main.py`, used as a server-side fallback.
600+
- **MCP client Roots:** Configured directly in supported MCP clients (`roots/list`).
548601

549602
Security behavior:
550603

@@ -567,13 +620,20 @@ Security behavior:
567620
and `upload_file` have no extension limit by default; see
568621
`TELEGRAM_FILE_EXTENSIONS` above to add one.
569622

570-
Run with allowed roots:
623+
Run with allowed roots via environment variable:
624+
625+
```bash
626+
# Semicolon- or comma-separated roots
627+
TELEGRAM_ALLOWED_ROOTS="/data/telegram,/tmp/telegram-mcp" uv run main.py
628+
```
629+
630+
Or run with allowed roots via CLI positional arguments:
571631

572632
```bash
573633
uv run main.py /data/telegram /tmp/telegram-mcp
574634
```
575635

576-
From an MCP client configuration, pass the same roots after `main.py`:
636+
From an MCP client configuration, you can pass roots via `env` or as arguments after `main.py`:
577637

578638
```json
579639
{
@@ -751,7 +811,7 @@ Telegram messages, display names, chat titles, and button labels are untrusted c
751811
Then set `TELEGRAM_SESSION_STRING` in `.env`. The MCP server does not perform
752812
interactive phone-code login over stdio.
753813
- **Invalid API credentials:** verify `TELEGRAM_API_ID` and `TELEGRAM_API_HASH` at [my.telegram.org/apps](https://my.telegram.org/apps).
754-
- **Database is locked:** prefer string sessions, or make sure no other process is using the same file session.
814+
- **Database is locked:** make sure no other process is using the same file session.
755815
- **`AuthKeyDuplicatedError` / "Another telegram-mcp process is already connected with this session":** two processes tried to connect the same Telegram session at once (e.g. an MCP client restarted the connector before the old process exited), which Telegram rejects and can invalidate the session for both. The server now takes an exclusive lock per session before connecting; a second concurrent launch waits briefly (default 20s, override with `TELEGRAM_LOCK_GRACE_SECONDS`) for the first to release it and otherwise exits without ever calling `connect()`, instead of racing into a duplicate connection. Retry once only one instance is running — the refusal names the PID holding the lock. If several instances on this host are meant to share one session (all reaching Telegram from the same IP), set `TELEGRAM_SESSION_LOCK=shared`; see [Sharing one session from one host](#sharing-one-session-from-one-host).
756816
- **File tools are disabled:** pass allowed roots or configure MCP Roots in your client.
757817
- **Path rejected:** ensure the path is inside an allowed root and does not use traversal or wildcard patterns.

‎docker-compose.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ services:
55
build:
66
context: .
77
dockerfile: Dockerfile
8+
target: production
89
container_name: telegram-mcp
910
# Load environment variables from a .env file in the same directory
1011
env_file:

0 commit comments

Comments
 (0)