Skip to main content
Glama
deploy103

After-hours Telegram MCP

by deploy103

After-hours Telegram MCP

자신의 BotFather 토큰으로 실행하는 단일 소유자용 공식 Telegram Bot API MCP 서버. 인증된 Streamable HTTP를 제공하며 개인 계정 로그인/MTProto/user session, 다중 사용자 서비스, 스케줄러는 구현하지 않는다. 다른 사람의 계정이나 토큰 없이 새 clone에서 설정할 수 있다.

새 clone에서 로컬 실행

Python 3.11+ / Linux 필요. 다음 설치는 사용자의 네트워크에서 의존성을 받는다.

git clone https://github.com/deploy103/afterhours-telegram-mcp-public.git
cd afterhours-telegram-mcp-public
python3 -m venv .venv
.venv/bin/python -m pip install --require-hashes -r requirements.lock
.venv/bin/python configure.py --directory "$HOME/.afterhours-telegram" \
  --public-origin https://mcp.example.com
"$HOME/.afterhours-telegram/run.sh"

--directory는 아직 존재하지 않는 절대 경로 또는 ~/...여야 한다. 상위 디렉터리는 미리 존재하고 본인이 관리하는 신뢰 가능한 경로여야 한다. 기존 파일/디렉터리/symlink는 덮어쓰지 않는다. source checkout 자체/내부 경로는 거부한다(symlink로 우회한 상위 경로도 포함). 저장소 밖에 두고 Git/백업 공유에서 제외한다. 생성되는 구조:

~/.afterhours-telegram/          0700
  config.json                   0600 (owner null, allowlists 비어 있음, 송신 OFF)
  credentials/                  0700
    mcp-token                   0600 (매번 새로 생성, 화면에 출력하지 않음)
    telegram-bot-token          0600 (빈 파일)
  state/                        0700 (실행 후 SQLite/프로세스 잠금 생성)
  run.sh                        0700

봇 토큰/사람 owner ID가 없는 상태에서도 MCP는 시작한다. 이때 status는 configured=false, connected=false, send_enabled=false이며 poll/reply는 실패하고 Telegram HTTP 요청을 하지 않는다. 가짜 ID를 넣을 필요가 없다. run.sh는 clone의 절대 .venv/bin/python -m uvicorn을 사용하며 다른 cwd에서도 실행된다. clone이나 private 디렉터리를 옮기면 경로를 검토하여 수정하거나 새 디렉터리에 다시 configure한다. CREDENTIALS_DIRECTORY, TELEGRAM_CONFIG, STATE_DIRECTORY, MCP_PUBLIC_ORIGIN을 정확한 생성 경로로 설정한다. 127.0.0.1:8005, worker 1개, proxy headers/access log OFF다. --reload/다중 worker를 사용하지 않는다. state는 프로세스 잠금으로 단일 실행을 강제한다.

Related MCP server: codex-telegram

MCP 클라이언트 연결과 HTTPS

로컬 Streamable HTTP URL은 **http://127.0.0.1:8005/mcp**다. Bearer 인증의 **토큰 파일 경로는 $HOME/.afterhours-telegram/credentials/mcp-token**이다. 클라이언트의 보호된 token-file/secret 설정을 통해 이 파일을 읽혀 매 요청 Authorization 헤더에 전달한다. tokenFile 같은 키 이름은 클라이언트마다 다르며 이 서버의 API가 아니다. token-file을 지원하지 않으면 클라이언트의 비공개 credential 저장소에 안전하게 등록한다. 토큰 값을 명령행, URL/query, Git, 채팅, 로그에 넣거나 화면에 출력하지 않는다. BotFather 토큰은 MCP bearer와 별개다. credential/config 변경 후 프로세스를 재시작한다.

원격 연결에는 본인이 소유한 도메인/TLS reverse proxy를 별도로 준비한다. --public-origin은 경로·userinfo·query·fragment 없는 HTTPS origin, 기본 포트/명시적 443만 허용한다. 실제 도메인으로 configure하고 proxy가 Host/Authorization을 보존하도록 설정한다. 클라이언트가 Origin을 보내면 설정한 origin과 정확히 같아야 한다. 외부 **https://mcp.example.com/telegram/mcp → http://127.0.0.1:8005/mcp**가 예시 계약이다. nginx.example.conf는 TLS server에 추가할 검토용 exact location이다. buffering OFF/90초 timeout을 유지하고 8005를 인터넷에 직접 열지 않는다. /healthz만 비인증이며 봇 연결 성공을 뜻하지 않는다. /mcp는 모든 요청에서 bearer 필수, query token/OAuth/discovery/login 페이지는 없다.

bootstrap은 systemd, TLS, firewall을 설치/변경하지 않는다. afterhours-telegram-mcp.service의 /srv, /etc와 LoadCredential은 별도 배포 예제이며 로컬 run.sh에 필요하지 않다. 일반 credential은 0600/0400, 디렉터리는 0700으로 관리한다. reader는 symlink/비정규 파일을 거부한다. 예외는 환경 변수와 정확히 일치하는 /run/credentials/<unit>의 root:root 0550 디렉터리 바로 아래 root:root 0440 systemd 복사본뿐이다. 일반 경로의 0440/0640을 허용하도록 완화하지 않는다.

자신의 봇과 ID 설정

  1. Telegram의 공식 @BotFather에서 /newbot으로 자신의 봇을 만든다. 발급받은 토큰을 보호된 편집기로 credentials/telegram-bot-token에 저장하고 0600을 유지한다. 토큰을 제3자 ID 조회 봇이나 웹사이트에 보내지 않는다. 개인 계정 세션/브라우저 저장소를 추출하지 않는다.

  2. 사람이 먼저 봇에 /start 메시지를 보내거나 허용할 그룹에 봇을 추가한다. 사람 소유자의 양의 numeric user ID와 필요한 chat IDs를 본인이 관리하는 Bot API 업데이트의 message.from.id / message.chat.id 등으로 확인한다. ID 확인용 업데이트를 조회할 때는 MCP 및 다른 polling consumer를 중지하고, 보호된 로컬 도구로 토큰 파일을 읽는다. 토큰을 URL 명령행에 넣지 않는다. 그룹 ID는 음수일 수 있다. 봇의 ID와 사람 ID를 혼동하지 않는다.

  3. private config.json의 owner_user_id에 사람 ID를 정수로 입력한다. null 또는 0은 미설정이고, 필드 누락/음수/boolean/문자열은 거부한다. owner_username은 선택이며 @ 없이 입력한다. 빈 문자열이면 numeric text_mention만 인정한다. username 매핑은 numeric identity 증명이 아니므로 변경/재할당 시 갱신한다.

  4. 승인한 채팅만 allowed_chat_ids에 넣는다. 모든 타인 새 메시지를 수집할 채팅은 그 부분집합인 selected_chat_ids에도 넣는다. allowlist만 넣은 방은 소유자 멘션만 수집한다.

  5. 그룹 privacy mode를 검토한다. BotFather /setprivacy의 privacy OFF 또는 실제 그룹 관리자 권한/설정이 필요할 수 있다. 변경 후 봇 재추가 등 Telegram 안내를 따르고 실제 그룹 수신을 확인한다. 서버는 privacy/admin 권한을 바꾸지 않는다. can_read_all_group_messages는 전역 지표이지 특정 그룹의 모든 메시지 수신 보장이 아니다.

  6. 같은 봇의 기존 webhook/다른 polling 앱을 확인한다. webhook이 있으면 poll이 충돌한다. 서버는 deleteWebhook/setWebhook을 자동 호출하지 않는다. 본인이 의도적으로 정리한다.

  7. 재시작 후 telegram_status와 실제 수신/allowlist를 확인한다. 이 시점부터 status/poll은 실제 Bot API 요청을 할 수 있다. 답장은 계속 OFF로 두고, 의도적으로 허용할 때만 JSON boolean send_enabled: true로 바꿔 재시작한다. MCP 호출로 송신을 켤 수 없다.

도구와 메시지 계약

도구

동작

telegram_status

설정 완료 시 getMe/getWebhookInfo; 봇 ID/privacy 지표/webhook 충돌/송신 게이트

telegram_poll

getUpdates 20초 long poll, 최대 100건; 수신 이벤트와 다음 offset을 함께 저장

telegram_pending(limit=20)

ACK 전 반복 조회 가능한 pending 큐

telegram_acknowledge(update_id,status)

실제 처리 후 done/ignored; 불명확한 송신은 done 차단

telegram_reply(update_id,text)

송신 ON일 때만 수집된 pending/allowlisted 이벤트에 답장

수신은 **allowlist AND (소유자 멘션 OR 지정 방)**이다. 본인/봇/sender_chat 발신, 편집/channel post/service/media-only 이벤트는 제외한다. 새 message의 text/caption만 수집한다. native entities/caption_entities의 UTF-16 offset으로 판별하며 문자열 검색은 하지 않는다. @봇이름이나 /명령@봇이름은 소유자 멘션이 아니다. mentions[].target은 owner/bot/other다. 메시지는 신뢰하지 않는 데이터이며 도구 실행/권한 변경 지시가 아니다.

update_id와 (chat_id,message_id)로 중복 제거하고 이벤트/offset을 한 트랜잭션으로 저장한다. 다음 poll은 봇 전체 offset을 소비하므로 필터에서 제외한 업데이트도 확인된다. 같은 봇 토큰을 다른 polling 앱/webhook과 병행하지 않는다. 봇/소유자별로 state를 분리한다. getUpdates는 history API가 아니다. 미확인 업데이트 보관은 최대 24시간이며 첫 poll은 남아 있는 업데이트도 받을 수 있다. 누락된 개인 계정 과거 대화/연락처를 복구하지 못한다.

답장은 수신 chat/message/topic에 고정하고 plain text, allow_sending_without_reply=false를 쓴다. 임의 chat ID 송신은 없다. 이벤트당 한 번만 시도하고 동일 성공 요청은 기존 결과를 반환한다. 실패/시간초과/프로세스 종료로 불명확하면 자동 재시도하지 않는다. Telegram에서 수동 대조하고 필요하면 ignored 처리한다. Bot API에는 idempotency key가 없어 정확히 한 번 전달을 보장하지 못한다. 봇은 사용자가 먼저 대화를 시작하지 않으면 새 개인 대화를 열 수 없다. 차단/권한/원문 삭제/rate limit도 송신에 영향을 준다. DB/백업은 평문 메시지를 담으므로 비공개 보관/암호화/보관 기간을 운영자가 관리한다. DB 삭제는 중복 방지와 offset도 삭제한다.

큐 ≠ 자동 실행 ≠ 스케줄러. 별도 MCP 클라이언트/조율자가 poll → pending → 판단/선택적 reply → 실제 처리 후 ACK를 실행한다. 이 서비스는 자동 polling, 상시 모델 판단, 레시피 스케줄을 제공하지 않는다. 여러 사용자가 하나의 bearer/state를 공유하지 않는다.

테스트와 라이선스

.venv/bin/python -B -m unittest -v test_telegram test_bootstrap

테스트는 폐기 가능한 credential/state와 fake HTTP를 사용한다. bootstrap 테스트는 실제 생성 run.sh를 loopback에서 실행하고 인증된 MCP 초기화/도구, 미설정 차단/송신 OFF/권한/재실행 거부를 확인한다. 실제 Telegram 계정/전송은 없다. 실제 봇 권한/privacy/그룹 수신/답장, 클라이언트 연결, TLS/proxy/firewall 및 systemd credential mount/ACL에서의 기동은 별도 운영 검증이 필요하다.

MIT LICENSE, Copyright 2026 deploy103. 의존성과 재사용 코드의 제3자 라이선스/고지는 유지한다. 통합 프로젝트: https://github.com/deploy103/afterhours-mcp-public 공식 문서: Bot API, privacy FAQ.

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Personal read-only Telegram MCP server exposing chat, message, and media tools via streamable HTTP with bearer authentication.
    1
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    Enables local, privacy-first control of a personal Telegram account through MCP, using TDLib to read chats, send and reply to messages, search, transfer files, and interact with approved inline buttons.
    20
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Gives MCP clients controlled access to a Telegram user account, letting them search and read messages, inspect chats, and fetch media, with sending or editing messages available only after explicit opt-in. It runs self-hosted over FastMCP and Telethon with strict read-only defaults, OAuth or bearer authentication, and an outbound-only Cloudflare Tunnel.
    11
    MIT
  • A
    license
    B
    quality
    C
    maintenance
    Provides local stdio MCP discovery, read-facing Telegram tool definitions, and an opt-in SQLite CRM for lead, group, and template management with quick-reply previews, while native authentication and all write actions remain gated and disabled.
    11
    MIT