https://www.gpters.org/nocode/post/claude-code-telegram-jadong-alrim-siseutem-gucuggi-majimag-gwayeon-iye3YWTeNJoYxhz
위 글이 최종본입니다. 아래는 실패(?) 사례입니다. 참고만하세요
아래 사례글의 주요 내용은 체험을 바탕으로 claude code가 작성해 주었습니다.
따라하기 보다는 하나의 아이디어로 인식하시고, 여러분의 claude와 대화를 통해 구현해 보세요.
소개
시도하고자 했던 것과 그 이유
디지털노마드를 표방하며 작업환경을 만들고 있는데. Claude Code에 장시간 작업을 맡기고 외출하는 경우가 많은데, 기존 방식에는 두 가지 큰 불편함이 있었습니다.
불편함 1: 원격 접속의 번거로움
Claude Code가 질문을 하거나 작업이 완료/실패하면, 확인하고 응답하기 위해 구글 원격 데스크톱으로 내 컴퓨터에 접속해서 터미널에 직접 명령어를 입력해야 했습니다.
불편함 2: 여러 터미널에서 claude code, glm 이용 시
Claude Code를 여러 개 띄워서 동시에 작업을 시킬 때가 많습니다. 기존에 Telegram 알림을 설정해뒀는데, 문제는 메시지가 오면 어느 터미널에서 보낸 건지 확인해서 일일히 찾아들어가 작업지시를 내리는게 불편했습니다.
[기존 상황]
터미널 1 (feedback 프로젝트) ──┐
터미널 2 (hotel 프로젝트) ────┼──► 📱 Telegram 1개
터미널 3 (auth 모듈) ─────────┘
"어떤 방식으로 진행할까요?"
→ 이게 어느 터미널이지...?
Telegram에서 답장을 보내도 어느 터미널로 전달해야 하는지 알 수 없어서, 결국 원격 데스크톱을 열어 직접 확인해야 했습니다.
진행 방법
위 문제들을 개선하기 위해서, 특히, 텔레그램의 reply 메시지를 이용하면 메시지를 보낼때 구분자를 보내주고,고, 응답 시 해당 구분자를 잉요하면 각각의 claude code 터미널에서 응답을 확인하고, 작업을 이어갈 수 있을 것 같았어요.
그래서, cluade code에게 개선안을 계획해 보라고 했더니, 텔레그램용 MCP 서버를 만들면 된다고 해서 시도해보았습니다.
MCP(Model Context Protocol)란?
MCP(Model Context Protocol)는 AI 애플리케이션이 외부 도구와 데이터 소스에 연결할 수 있게 해주는 표준 프로토콜입니다. Anthropic에서 만들었으며, Claude Code, Cursor 등 다양한 AI 클라이언트에서 사용할 수 있습니다.
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ AI 클라이언트 │◄─MCP──►│ MCP 서버 │◄──────►│ 외부 서비스 │
│ (Claude Code, │ │ (도구 제공) │ │ (Telegram, DB, │
│ Cursor 등) │ │ │ │ 파일시스템 등) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
핵심 특징:
표준화된 인터페이스: 다양한 AI 클라이언트에서 동일한 MCP 서버 사용 가능
도구(Tools) 제공: AI가 호출할 수 있는 함수들을 정의
stdio 기반 통신: AI 클라이언트와 MCP 서버는 표준 입출력으로 통신
관련 링크:
기존 Telegram MCP 서버들과의 차이점
GitHub에는 이미 여러 Telegram MCP 서버가 있습니다:
프로젝트
주요 기능
용도
MTProto 기반, 읽기 전용
Telegram 데이터 조회
Telegraf 기반, 채널 관리
봇으로 채널 운영
Telethon 기반, 메시지/그룹 관리
Telegram 자동화
알림 전송 전용
AI → 사용자 단방향 알림
이 MCP들의 공통점: 모두 "AI가 Telegram을 조작하는" 단방향 도구입니다.
왜 기존 MCP로는 안 되는가?
기존 MCP들은 단방향(AI → Telegram)입니다. 메시지를 보낼 수는 있지만, 사용자의 답장을 받아서 AI에게 전달하는 기능이 없습니다.
[기존 MCP 사용 시]
Claude Code: telegram_send("어떤 방식으로 할까요? A or B")
→ 메시지 전송 완료
→ 끝. 답변을 받을 방법이 없음.
개발자: (Telegram에서 "A" 입력)
→ 이 메시지는 어디로도 전달되지 않음
→ Claude Code는 영원히 대기
우리 MCP의 핵심: Reply 기반 양방향 통신
Claude Code에게 "여러 터미널에서 보낸 메시지를 구분하고, Telegram에서 바로 답장할 수 있는 방법이 없을까?"라고 물어봤습니다. Claude Code가 제안한 해결책은 Telegram의 Reply 기능을 활용하는 것이었습니다.
구분
기존 Telegram MCP
우리 Telegram MCP
목적
AI가 Telegram을 조작
AI ↔ 개발자 양방향 대화
방향
단방향 (AI → Telegram)
양방향 (AI ↔ 개발자)
Reply 활용
없음
핵심 기능
세션 관리
없음
터미널별 세션 ID
한 줄 요약: 기존 MCP는 "Telegram 자동화 도구", 우리 MCP는 "원격 협업 채널"입니다.
아키텍처: 왜 2-Layer 구조인가?
MCP 서버만으로는 Telegram Reply를 받을 수 없습니다.
MCP의 한계
MCP 서버는 stdio(표준 입출력) 기반으로 동작합니다. Claude Code가 도구를 호출할 때만 실행되고, 호출이 끝나면 대기 상태가 됩니다. 상시 HTTP 연결을 유지할 수 없어서 Telegram Reply를 받을 방법이 없습니다.
해결책: Bridge Daemon 분리
Bridge Daemon을 별도 프로세스로 분리하여 상시 실행합니다.
역할
MCP Server
Bridge Daemon
실행 방식
도구 호출 시만
상시 백그라운드
통신 방식
stdio (Claude ↔ MCP)
HTTP (MCP ↔ Bridge)
Telegram
메시지 전 송 요청
전송 + Reply 수신
세션 관리
불가능
가능 (메모리 유지)
환경설정 : 사용할 프로젝트 .claude/settings.local.json에 CLAUDE_PROJECT_DIR 추가.
"env": {
"CLAUDE_PROJECT_DIR": "/Users/eyeson/workspaces/itdalife-new"
},멀티 터미널 세션 라우팅
터미널 1 (abc) 터미널 2 (def) 터미널 3 (ghi)
│ │ │
│ telegram_send │ telegram_send │ telegram_send
│ "테스트 완료" │ "DB 어떻게?" │ "에러 발생"
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ Bridge Daemon - questionId 생성 │
│ │
│ Q-abc-1706012340 Q-def-1706012345 Q-ghi-1706012350 │
└─────────────────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────────────────┐
│ 📱 Telegram - 3개 메시지 수신 │
│ │
│ ✅ 테스트 완료 [Q-abc-1706012340] 오후 3:40 │
│ ❓ DB 스키마를 어떻게 할까요? [Q-def-1706012345] 오후 3:42 │
│ 🛑 마이그레이션 에러 발생 [Q-ghi-1706012350] 오후 3:45 │
│ │
│ 개발자가 두 번째 메시지에 Reply: "A" │
└─────────────────────────────────────────────────────────────────────────────────┘
│
│ Reply에서 questionId 추출
│ Q-def-1706012345 → session: def
▼
터미널 1 (abc) 터미널 2 (def) 터미널 3 (ghi)
│ │ │
│ (대기 중) │ 응답 수신: "A" │ (대기 중)
│ │ → 작업 계속 │
▼ ▼ ▼버전별 진화 과정
처음부터 완벽한 시스템을 만든 것이 아닙니다. 문제를 발견하고 해결하는 과정을 거쳐 v1.0에서 v2.8까지 발전했습니다.
v1.0 → v2.0: 도구 통합 (5개 → 3개)
문제: 처음에는 5개의 독립 도구가 있어서 Claude Code가 혼란스러워했습니다.
해결: telegram_send 하나로 통합하고, type 파라미터로 구분합니다.
telegram_send({ type: 'complete', message: '작업 완료' }) // 완료 알림
telegram_send({ type: 'blocked', message: '에러 발생' }) // 중단 알림
telegram_send({ type: 'question', message: '어떻게 할까요?' }) // 질문
v2.0 → v2.2: 양방향 응답
문제: Telegram에서만 응답 가능해서, 모니터 앞에 있어도 폰을 꺼내야 했습니다.
해결: Telegram Reply 또는 터미널 직접 입력, 둘 다 가능하게 개선했습니다.
v2.2 → v2.3: 모든 메시지 Reply 지원
문제: question 타입만 Reply 가능했습니다.
해결: 모든 메시지에 [Q-xxx-xxx] 태그를 추가하여 complete/blocked 타입도 Reply 가능하게 했습니다.
v2.3 → v2.6: Hook 강제화
문제: Claude Code가 telegram_send를 호출하지 않는 경우가 있었습니다.
해결: Claude Code Hooks로 시스템 레벨에서 강제합니다.
v2.6 → v2.8: 자동 전송
문제: Hook이 block만 하고 끝나면 Claude가 재시도해야 해서 비효율적이었습니다.
해결: Hook이 telegram_send 누락을 감지하면 자동으로 대신 전송합니다.
v2.6 방식: "안 했잖아! 다시 해"
Claude: (작업 완료) "완료했습니다~" (텍스트만 출력, telegram_send 깜빡함)
│
▼
Hook: "잠깐! telegram_send 안 했잖아!"
│
▼
Hook → Claude에게 block 반환: "telegram_send 먼저 호출하세요"
│
▼
Claude: "아 맞다!" → telegram_send 호출 → 메시지 전송
│
▼
Claude: "완료했습니다~" (다시 출력)
문제: Claude가 한 번 더 시도해야 해서 왕복 1회 낭비
v2.8 방식: "안 했네? 내가 대신 보내줄게"
Claude: (작업 완료) "완료했습니다~" (텍스트만 출력, telegram_send 깜빡함)
│
▼
Hook: "telegram_send 안 했네? 내가 직접 보내줄게!"
│
▼
Hook → Bridge API 직접 호출 → 📱 Telegram 메시지 전송
│
▼
완료! (Claude 재시도 필요 없음)
핵심 코드
telegram_send 도구 스키마
// mcp-servers/telegram/src/mcp/tools/send.ts
const SendInputSchema = z.object({
type: z.enum(['complete', 'blocked', 'question']),
message: z.string().min(1),
options: z.array(z.object({
key: z.string(),
label: z.string(),
recommended: z.boolean().optional(),
})).optional(),
result: z.record(z.string()).optional(),
blocker: z.object({
description: z.string(),
suggestion: z.string().optional(),
}).optional(),
waitForResponse: z.boolean().optional().default(true),
});
Bridge Daemon 시작 스크립트
# scripts/telegram-bridge.sh
#!/bin/bash
case "$1" in
start)
nohup node /path/to/bridge/index.js > /tmp/telegram-bridge.log 2>&1 &
echo $! > /tmp/telegram-bridge.pid
echo "Bridge started"
;;
stop)
kill $(cat /tmp/telegram-bridge.pid) 2>/dev/null
echo "Bridge stopped"
;;
status)
curl -s http://127.0.0.1:9876/health && echo "Running" || echo "Not running"
;;
esac
Claude Code Hook (알림 강제화)
# ~/.claude/hooks/check-completion-notification.py
import json
import urllib.request
def main():
transcript = read_transcript()
if not has_telegram_send(transcript):
# Claude가 빼먹으면 자동으로 대신 전송
send_auto_notification("Claude 턴 종료 - 대기 중입니다.")
def send_auto_notification(message):
payload = {
"sessionId": get_session_id(),
"formattedMessage": f"🤖 [자동 알림]\n\n{message}\n\n💬 Reply로 응답 가능합니다",
}
req = urllib.request.Request(
"http://127.0.0.1:9876/send",
data=json.dumps(payload).encode('utf-8'),
headers={"Content-Type": "application/json"},
)
urllib.request.urlopen(req)
settings.json (Hook 등록)
{
"hooks": {
"PreToolUse": [{
"matcher": "AskUserQuestion",
"hooks": [{
"type": "command",
"command": "python3 ~/.claude/hooks/validate-telegram-before-ask.py"
}]
}],
"Stop": [{
"hooks": [{
"type": "command",
"command": "python3 ~/.claude/hooks/check-completion-notification.py"
}]
}]
}
}
결과와 배운 점
배운 점과 꿀팁
MCP가 생각보다 어렵지 않다
MCP(Model Context Protocol)라고 하면 막연히 어렵게 느껴질 수 있 는데, 실제로 해보니 그렇지 않았습니다. Claude Code에게 "MCP 서버 만들어줘"라고 하면 기본 구조를 잡아주고, 거기서 조금씩 수정해 나가면 됩니다.
Hook은 강력한 안전장치
Claude가 규칙을 100% 따르지 않을 때가 있습니다. CLAUDE.md에 "항상 telegram_send를 호출하라"고 적어도 가끔 빼먹습니다. Hook을 사용하면 시스템 레벨에서 강제할 수 있어서 훨씬 안정적입니다.
시행착오
Hook vs Claude Code 역할 구분의 혼란
처음에는 "메시지를 Hook이 보내는 건지, Claude Code가 보내는 건지" 구분이 안 됐습니다.
정리하면:
Claude Code:
telegram_send도구를 호출해서 메시지 전송Hook: Claude가
telegram_send를 빼먹었는지 감시하고, 빼먹으면 대신 전송
스팸처럼 느껴지는 알림
Hook으로 알림을 강제하니까, 모니터 앞에 앉아서 작업할 때도 모든 턴마다 Telegram 알림이 왔습니다. 외출 중에는 유용하지만, 작업 중에는 스팸처럼 느껴졌습니다.
그래서 ON/OFF 토글 기능을 슬래시커맨드로 추가했습니다:
외출할 때:
/telegram on