PluginWorld
Af

afterhours-telegram-mcp-public

MCP

Enables a single owner to run an authenticated Streamable HTTP MCP endpoint backed by their own BotFather token, with tools to check bot status, poll allowlisted updates, inspect and acknowledge pending messages, and optionally send replies when explicitly enabled. It supports safe disconnected startup and avoids private-account sessions, multi-user services, schedulers, or automatic retries.

@deploy103 · MIT License · updated today

SECURITY

B

SCORE

60

STARS

▲ 0

PLUG IN

git clone https://github.com/deploy103/afterhours-telegram-mcp-public.git

See the README to configure this MCP server

README

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는 프로세스 잠금으로 단일 실행을 강제한다.

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.

SIMILAR PLUGINS