Command Palette

Search for a command to run...

UnylyUnyly
Browse all

Tg Guarded

FreeNot checked

Dual-plane Telegram MCP server providing read-only analytics and guarded write actions with built-in rate limiting, anti-spam protection, and session management

GitHubEmbed

About

Dual-plane Telegram MCP server providing read-only analytics and guarded write actions with built-in rate limiting, anti-spam protection, and session management.

README

MCP server + Python library for Telegram API with built-in rate limiting, anti-spam protection, and session management.

Dual-plane Telegram MCP: read-only analytics + guarded write actions with anti-spam and block-risk controls.

What is this?

tg-mcp provides:

  • Two MCP serversRead and Actions profiles with different risk levels
  • Rate limiting — Token bucket (4 RPS), daily quotas (20 DM/day, 20 joins/day)
  • Multi-process protection — optional shared RPS limiter across projects/processes
  • Anti-spam — FLOOD_WAIT retry with exponential backoff
  • Circuit breaker — auto cooldown after long FLOOD_WAIT events
  • Session security — chmod 700/600 hardening for session files
  • Data exporters — participants, messages, groups, dialogs

Positioning

tg-mcp-guarded emphasizes what makes this project different from generic Telegram MCP integrations:

  • Risk split by design — separate tgmcp-read and tgmcp-actions servers
  • Guarded write path — write operations only via Action MCP policy gates
  • Block-risk awareness — anti-flood, anti-spam, retry/backoff, and circuit-breaker defaults

Quick Start

# Install dependencies
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt

# Configure
cp .env.sample .env
# Edit .env with your TG_API_ID, TG_API_HASH

# Run tests
PYTHONPATH=tganalytics:. python3 -m pytest tests/ -q

Developer Setup Shortcut

For local development, you can bootstrap everything (env sync, dependencies, and required data directories) with one command:

make dev-setup

Before opening a PR, run the standard repository checks:

make dev-check

Session Bootstrap (Auth)

read profile remains write-safe, but initial Telegram login requires auth requests. Use dedicated auth bootstrap helpers:

# Interactive code login (enables auth-only requests internally)
PYTHONPATH=tganalytics:. python3 tganalytics/examples/create_telegram_session.py \
  --session-name my_read

# QR login fallback (more reliable when code is delayed)
PYTHONPATH=tganalytics:. python3 scripts/create_session_qr.py \
  --session-name my_read

Where to find login code:

  • Telegram app in-app message (most common, SentCodeTypeApp)
  • Archived chats folder
  • Another active device/session notifications

Secrets Without .env (Keychain / Secret Provider)

TG_API_ID and TG_API_HASH can be loaded from secrets providers:

  • TG_SECRET_PROVIDER=env (default): read plain env values
  • TG_SECRET_PROVIDER=keychain: load from macOS Keychain
  • TG_SECRET_PROVIDER=command: load from command outputs

macOS Keychain example

security add-generic-password -a TG_API_ID -s tg-mcp -w "12345678" -U
security add-generic-password -a TG_API_HASH -s tg-mcp -w "your_api_hash" -U

Set env:

TG_SECRET_PROVIDER=keychain
TG_KEYCHAIN_SERVICE=tg-mcp
TG_KEYCHAIN_ACCOUNT_API_ID=TG_API_ID
TG_KEYCHAIN_ACCOUNT_API_HASH=TG_API_HASH

Generic command provider example

TG_SECRET_PROVIDER=command
TG_SECRET_CMD_API_ID="pass tg/api_id"
TG_SECRET_CMD_API_HASH="pass tg/api_hash"

Agent Onboarding

Start here if this repository will be used by other AI agents/operators:

Installation Profiles

Use one repository, choose profile by risk level:

  • read profile: installs only tgmcp-read (recommended default for new users)
  • full profile: installs tgmcp-read + tgmcp-actions

Generate config:

# prereq check
bash scripts/check_tg_mcp.sh /absolute/path/to/tg-mcp

# read-only config (safe default)
python3 scripts/render_mcp_config.py \
  --repo /absolute/path/to/tg-mcp \
  --profile read \
  --read-session-name my_account_ro

# full config (read + actions)
python3 scripts/render_mcp_config.py \
  --repo /absolute/path/to/tg-mcp \
  --profile full \
  --read-session-name my_account_ro \
  --actions-session-name my_account

# preflight: fail fast on same-session misconfig
python3 scripts/check_session_paths.py \
  --read-session-path /absolute/path/to/tg-mcp/data/sessions/my_account_ro.session \
  --actions-session-path /absolute/path/to/tg-mcp/data/sessions/my_account.session

Global MCP Setup (All Projects in Codex)

For one global setup across all working directories, add servers via codex mcp add. Recommended default: separate session files for read and actions. Routine concurrent read+actions traffic on one Telethon sqlite session can trigger database is locked.

REPO="/absolute/path/to/tg-mcp"

codex mcp add tgmcp-read \
  --env PYTHONPATH="$REPO/tganalytics:$REPO" \
  --env TG_SESSIONS_DIR="$REPO/data/sessions" \
  --env TG_SESSION_PATH="$REPO/data/sessions/my_account_ro.session" \
  --env TG_READ_SESSION_PATH="$REPO/data/sessions/my_account_ro.session" \
  --env TG_ACTIONS_SESSION_PATH="$REPO/data/sessions/my_account.session" \
  --env TG_SESSION_PATH_CONFLICT_MODE=warn \
  --env TG_SESSION_CONFLICT_REGISTRY_FILE="$REPO/data/anti_spam/session_registry.json" \
  --env TG_EXPECTED_USERNAME="my_main_account" \
  --env TG_ALLOW_SESSION_SWITCH=0 \
  --env TG_BLOCK_DIRECT_TELETHON_WRITE=1 \
  --env TG_ALLOW_DIRECT_TELETHON_WRITE=0 \
  --env TG_ENFORCE_ACTION_PROCESS=1 \
  --env TG_DIRECT_TELETHON_WRITE_ALLOWED_CONTEXTS=actions_mcp \
  --env TG_WRITE_CONTEXT=read_mcp \
  --env TG_ACTION_PROCESS=0 \
  --env TG_SESSION_RUNTIME_MODE=copy \
  --env TG_RECEIVE_UPDATES=0 \
  --env TG_SESSION_LOCK_MODE=shared \
  --env TG_GLOBAL_RPS_MODE=shared \
  -- "$REPO/venv/bin/python3" "$REPO/tganalytics/mcp_server_read.py"

Optional actions server:

REPO="/absolute/path/to/tg-mcp"

codex mcp add tgmcp-actions \
  --env PYTHONPATH="$REPO/tganalytics:$REPO" \
  --env TG_SESSIONS_DIR="$REPO/data/sessions" \
  --env TG_SESSION_PATH="$REPO/data/sessions/my_account.session" \
  --env TG_READ_SESSION_PATH="$REPO/data/sessions/my_account_ro.session" \
  --env TG_ACTIONS_SESSION_PATH="$REPO/data/sessions/my_account.session" \
  --env TG_SESSION_PATH_CONFLICT_MODE=warn \
  --env TG_SESSION_CONFLICT_REGISTRY_FILE="$REPO/data/anti_spam/session_registry.json" \
  --env TG_EXPECTED_USERNAME="my_main_account" \
  --env TG_ALLOW_SESSION_SWITCH=0 \
  --env TG_ACTIONS_ENABLED=1 \
  --env TG_ACTIONS_REQUIRE_ALLOWLIST=1 \
  --env TG_ACTIONS_ALLOWED_GROUPS="-1001234567890" \
  --env TG_ACTIONS_REQUIRE_CONFIRMATION_TEXT=1 \
  --env TG_ACTIONS_REQUIRE_APPROVAL_CODE=1 \
  --env TG_ACTIONS_IDEMPOTENCY_ENABLED=1 \
  --env TG_BLOCK_DIRECT_TELETHON_WRITE=1 \
  --env TG_ALLOW_DIRECT_TELETHON_WRITE=0 \
  --env TG_ENFORCE_ACTION_PROCESS=1 \
  --env TG_DIRECT_TELETHON_WRITE_ALLOWED_CONTEXTS=actions_mcp \
  --env TG_WRITE_CONTEXT=actions_mcp \
  --env TG_ACTION_PROCESS=1 \
  --env TG_RECEIVE_UPDATES=0 \
  --env TG_SESSION_LOCK_MODE=shared \
  --env TG_GLOBAL_RPS_MODE=shared \
  -- "$REPO/venv/bin/python3" "$REPO/tganalytics/mcp_server_actions.py"

MCP Servers

Add to your project's .mcp.json:

{
  "mcpServers": {
    "tgmcp-read": {
      "command": "path/to/tg-mcp/venv/bin/python3",
      "args": ["path/to/tg-mcp/tganalytics/mcp_server_read.py"],
      "env": {
        "PYTHONPATH": "path/to/tg-mcp/tganalytics:path/to/tg-mcp",
        "TG_SESSIONS_DIR": "path/to/tg-mcp/data/sessions",
        "TG_SESSION_PATH": "path/to/tg-mcp/data/sessions/my_account_ro.session",
        "TG_READ_SESSION_PATH": "path/to/tg-mcp/data/sessions/my_account_ro.session",
        "TG_ACTIONS_SESSION_PATH": "path/to/tg-mcp/data/sessions/my_account.session",
        "TG_SESSION_PATH_CONFLICT_MODE": "warn",
        "TG_SESSION_CONFLICT_REGISTRY_FILE": "path/to/tg-mcp/data/anti_spam/session_registry.json",
        "TG_EXPECTED_USERNAME": "my_main_account",
        "TG_ALLOW_SESSION_SWITCH": "0",
        "TG_BLOCK_DIRECT_TELETHON_WRITE": "1",
        "TG_ALLOW_DIRECT_TELETHON_WRITE": "0",
        "TG_ENFORCE_ACTION_PROCESS": "1",
        "TG_DIRECT_TELETHON_WRITE_ALLOWED_CONTEXTS": "actions_mcp",
        "TG_WRITE_CONTEXT": "read_mcp",
        "TG_ACTION_PROCESS": "0",
        "TG_SESSION_RUNTIME_MODE": "copy",
        "TG_RECEIVE_UPDATES": "0",
        "TG_SESSION_LOCK_MODE": "shared",
        "TG_GLOBAL_RPS_MODE": "shared"
      }
    },
    "tgmcp-actions": {
      "command": "path/to/tg-mcp/venv/bin/python3",
      "args": ["path/to/tg-mcp/tganalytics/mcp_server_actions.py"],
      "env": {
        "PYTHONPATH": "path/to/tg-mcp/tganalytics:path/to/tg-mcp",
        "TG_SESSIONS_DIR": "path/to/tg-mcp/data/sessions",
        "TG_SESSION_PATH": "path/to/tg-mcp/data/sessions/my_account.session",
        "TG_READ_SESSION_PATH": "path/to/tg-mcp/data/sessions/my_account_ro.session",
        "TG_ACTIONS_SESSION_PATH": "path/to/tg-mcp/data/sessions/my_account.session",
        "TG_SESSION_PATH_CONFLICT_MODE": "warn",
        "TG_SESSION_CONFLICT_REGISTRY_FILE": "path/to/tg-mcp/data/anti_spam/session_registry.json",
        "TG_EXPECTED_USERNAME": "my_main_account",
        "TG_ALLOW_SESSION_SWITCH": "0",
        "TG_ACTIONS_ENABLED": "1",
        "TG_ACTIONS_REQUIRE_ALLOWLIST": "1",
        "TG_ACTIONS_ALLOWED_GROUPS": "my_safe_group,-1001234567890",
        "TG_ACTIONS_MAX_MESSAGE_LEN": "2000",
        "TG_ACTIONS_MAX_FILE_MB": "20",
        "TG_ACTIONS_REQUIRE_CONFIRMATION_TEXT": "1",
        "TG_ACTIONS_CONFIRMATION_PHRASE": "",
        "TG_ACTIONS_MIN_CONFIRM_TEXT_LEN": "6",
        "TG_ACTIONS_REQUIRE_APPROVAL_CODE": "1",
        "TG_ACTIONS_APPROVAL_TTL_SEC": "1800",
        "TG_ACTIONS_APPROVAL_MIN_AGE_SEC": "30",
        "TG_ACTIONS_APPROVAL_FILE": "data/anti_spam/action_approvals.json",
        "TG_ACTIONS_IDEMPOTENCY_ENABLED": "1",
        "TG_ACTIONS_IDEMPOTENCY_WINDOW_SEC": "86400",
        "TG_ACTIONS_BATCH_FILE": "data/anti_spam/action_batches.json",
        "TG_ACTIONS_BATCH_TTL_HOURS": "168",
        "TG_ACTIONS_BATCH_APPROVAL_LEASE_SEC": "86400",
        "TG_ACTIONS_BATCH_RUN_LEASE_SEC": "1800",
        "TG_ACTIONS_UNSAFE_OVERRIDE": "0",
        "TG_BLOCK_DIRECT_TELETHON_WRITE": "1",
        "TG_ALLOW_DIRECT_TELETHON_WRITE": "0",
        "TG_ENFORCE_ACTION_PROCESS": "1",
        "TG_DIRECT_TELETHON_WRITE_ALLOWED_CONTEXTS": "actions_mcp",
        "TG_WRITE_CONTEXT": "actions_mcp",
        "TG_ACTION_PROCESS": "1",
        "TG_RECEIVE_UPDATES": "0",
        "TG_SESSION_LOCK_MODE": "shared",
        "TG_GLOBAL_RPS_MODE": "shared",
        "TG_FLOOD_CIRCUIT_THRESHOLD_SEC": "300",
        "TG_FLOOD_CIRCUIT_COOLDOWN_SEC": "900",
        "MAX_GROUP_MSGS_PER_DAY": "30"
      }
    }
  }
}

mcp_server.py is kept as backward-compatible alias to mcp_server_read.py.

Read Tools

Tool Description
tg_list_sessions List available Telegram sessions
tg_use_session Switch active session
tg_get_group_info Get group/channel/direct dialog info
tg_get_participants Export group members
tg_search_participants Search members by query
tg_get_messages Export messages from a dialog target
tg_get_messages_since Export messages newest-to-oldest until a cutoff date
tg_get_message_count Get dialog message count
tg_get_group_creation_date Get dialog first-message date
tg_get_my_dialogs List account dialogs
tg_resolve_username Resolve username to entity
tg_get_user_by_id Get user by numeric ID
tg_download_media Download media from message
tg_get_stats Anti-spam system stats
tg_auth_status Check current session authorization status

Actions Tools

Tool Description
tg_list_sessions List available Telegram sessions
tg_use_session Switch active session (if enabled)
tg_get_group_info Validate target group
tg_get_my_dialogs Browse possible targets
tg_resolve_username Resolve target username
tg_send_message Send message with anti-spam + policy gates (confirm=true + exact confirmation_text + one-time approval_code)
tg_send_file Send local file with anti-spam + policy gates (confirm=true + exact confirmation_text + one-time approval_code)
tg_delete_messages Delete specific messages in a dialog/group (dry_run by default; same confirm + approval gates on execution)
tg_clear_history Clear dialog history with DeleteHistoryRequest (dry_run by default; same confirm + approval gates on execution)
tg_leave_dialog Leave an allowlisted channel/group (dry_run by default; same confirm + approval gates on execution)
tg_edit_message Edit a message in a dialog/group (dry_run by default; same confirm + approval gates on execution)
tg_forward_messages Forward messages between allowlisted dialogs (dry_run by default; same confirm + approval gates on execution)
tg_add_member_to_group Add user to group/channel (dry_run by default; same confirm + approval gates on execution)
tg_remove_member_from_group Remove user from group/channel (dry_run by default; same confirm + approval gates on execution)
tg_migrate_member Add new user + remove old user in one safe flow (same confirm + approval gates on execution)
tg_create_add_member_batch Build one add-member batch for many groups (single approval for whole task)
tg_create_add_member_batch_from_report Build batch from failed groups in JSON report
tg_create_delete_messages_batch_from_manifest Build delete batch from a reviewed privacy scrubber manifest
tg_create_leave_dialog_batch_from_candidates Build leave batch from reviewed channel candidates
tg_approve_batch One-time approve a batch
tg_get_batch_status Check batch progress and pending groups
tg_run_add_member_batch Run approved batch in chunks (no per-group approvals)
tg_run_delete_messages_batch Run approved delete batch in chunks
tg_run_leave_dialog_batch Run approved leave-dialog batch in chunks
tg_get_actions_policy Show active action restrictions
tg_get_stats Anti-spam system stats
tg_auth_status Check current session authorization status

Session Concurrency

  • Default mode is TG_SESSION_LOCK_MODE=shared: multiple MCP servers/projects can use one .session.
  • tgmcp-read now defaults to TG_SESSION_RUNTIME_MODE=copy: each MCP process reads from its own runtime shadow of the configured session file, which avoids sqlite lock collisions across multiple Codex clients.
  • Optional strict mode: TG_SESSION_LOCK_MODE=exclusive blocks concurrent use of the same session.
  • Shared session is still allowed, but NOT recommended for routine concurrent tgmcp-read + tgmcp-actions.
  • Recommended default:
  • tgmcp-read -> dedicated *_ro.session
  • tgmcp-actions -> dedicated write session (for example account.session)
  • If both profiles point to one sqlite session file, Telethon can fail with database is locked.
  • Set TG_READ_SESSION_PATH + TG_ACTIONS_SESSION_PATH in both server envs so runtime can warn/fail on same-session misconfig.
  • TG_SESSION_PATH_CONFLICT_MODE=warn prints startup/runtime warning; set fail to block server start on same-session conflict.
  • Run python3 scripts/check_session_paths.py --config /path/to/.mcp.json before enabling both servers.
  • If a Codex thread did not expose native mcp__tgmcp_actions__* tools, use python3 scripts/tg_action_bridge.py ... as a shell fallback. The bridge still talks to mcp_server_actions.py over MCP/JSON-RPC, defaults to TG_SESSION_RUNTIME_MODE=copy, and preserves the same allowlist/approval/confirm policy.
  • TG_EXPECTED_USERNAME enables fail-fast on session/account mismatch (@expected vs actual account in session).
  • TG_RECEIVE_UPDATES=0 (default) disables Telethon updates loop to reduce sqlite session lock contention.
  • TG_GLOBAL_RPS_MODE=shared applies one shared RPS budget across all processes using the same data/anti_spam.
  • TG_FLOOD_CIRCUIT_THRESHOLD_SEC + TG_FLOOD_CIRCUIT_COOLDOWN_SEC pause all calls after critical FLOOD_WAIT.
  • Non-dry-run write actions require confirm=true and exact confirmation_text (TG_ACTIONS_CONFIRMATION_PHRASE).
  • Non-dry-run write actions also require one-time approval_code from the matching dry_run preview (TG_ACTIONS_REQUIRE_APPROVAL_CODE=1).
  • approval_code has minimum age (TG_ACTIONS_APPROVAL_MIN_AGE_SEC, default 30s): immediate execute right after dry_run is blocked.
  • Action MCP blocks duplicate sends/actions by payload hash for 24h (TG_ACTIONS_IDEMPOTENCY_*), unless force_resend=true.
  • Action state files (approval/idempotency/batch) now use file locks + atomic write, so parallel ActionMCP processes do not corrupt JSON state.
  • Batch execution uses a per-batch run lease lock (TG_ACTIONS_BATCH_RUN_LEASE_SEC) to avoid duplicate processing of the same batch by two workers.
  • For long tasks, batch mode supports scoped approval: tg_create_add_member_batch -> tg_approve_batch -> repeat tg_run_add_member_batch until complete.
  • Batch run permission is time-limited (TG_ACTIONS_BATCH_APPROVAL_LEASE_SEC, default 24h). After lease expiry, re-approve the same batch.
  • ActionMCP is fail-closed by default: weakening core safe flags auto-disables actions unless TG_ACTIONS_UNSAFE_OVERRIDE=1.
  • If allowlist is required, ActionMCP also fails closed when TG_ACTIONS_ALLOWED_GROUPS is empty.
  • Direct TelegramClient.send_* writes and raw MTProto write requests (client(Request)) are blocked by default.
  • Use tgmcp-actions tools for any write operation.
  • In strict mode (TG_ENFORCE_ACTION_PROCESS=1), write is allowed only when process entrypoint is mcp_server_actions.py.
  • For hard session isolation, run MCP under a dedicated OS user and keep data/sessions owned by that user only.

Fallback bridge examples:

# list tools from ~/.codex/config.toml server tgmcp_actions
python3 scripts/tg_action_bridge.py tools

# raw call
python3 scripts/tg_action_bridge.py call tg_get_actions_policy --args-json '{}'

# canonical write flow: dry_run -> approval_code -> confirm
python3 scripts/tg_action_bridge.py write-call tg_send_message \
  --args-json '{"group":"@mybot","message_text":"hello"}'

Structure

tganalytics/
├── tganalytics/        # Core package
│   ├── infra/          # Clients, rate limiting, metrics
│   ├── domain/         # GroupManager, participants
│   └── config/         # Configuration
├── mcp_server.py       # Backward-compatible read alias
├── mcp_server_read.py  # Read-focused MCP server
├── mcp_server_actions.py # Actions-focused MCP server
├── mcp_server_common.py # Shared state/session helpers
├── mcp_actions_policy.py # Policy helpers (allowlist/hash/confirmation/safe defaults)
├── mcp_actions_state.py  # Locked JSON state helpers (approval/idempotency/batch)
├── mcp_actions_batch.py  # Batch record + summary helpers
└── examples/           # Usage examples

Architecture

All Telegram API calls go through a 5-layer protection chain:

_safe_api_call → safe_call → TokenBucket → Telegram API
                    ↓            ↓              ↓
              DM/join quotas   4 RPS    FLOOD_WAIT retry + backoff

See docs/ANTISPAM_SECURITY.md for details.

License

MIT. See LICENSE.

Governance

This is an open-source project with maintainer-led merge policy:

  • external contributors are welcome via issues and pull requests
  • commits to main are performed by maintainer only

See CONTRIBUTING.md.

from github.com/matskevich/tg-mcp-guarded

Installing Tg Guarded

This server has no published package — it is built from source. Open the repository and follow its README.

▸ github.com/matskevich/tg-mcp-guarded

FAQ

Is Tg Guarded MCP free?

Yes, Tg Guarded MCP is free — one-click install via Unyly at no cost.

Does Tg Guarded need an API key?

No, Tg Guarded runs without API keys or environment variables.

Is Tg Guarded hosted or self-hosted?

Self-hosted: the server runs locally on your machine via the install command above.

How do I install Tg Guarded in Claude Desktop, Claude Code or Cursor?

Open Tg Guarded on unyly.org, pick your client tab (Claude Desktop, Claude Code, Cursor) and press Install — the config is generated automatically, no JSON editing.

Related MCPs

Compare Tg Guarded with

Not sure what to pick?

Find your stack in 60 seconds

Author?

Embed badge for your README

Browse similar

All communication MCPs