claude code + telegram MCP + telegram(reply) 처리 - 디지털노마드 되기 3탄

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 서버가 있습니다:

프로젝트

주요 기능

용도

sparfenyuk/mcp-telegram

MTProto 기반, 읽기 전용

Telegram 데이터 조회

IQAIcom/mcp-telegram

Telegraf 기반, 채널 관리

봇으로 채널 운영

chigwell/telegram-mcp

Telethon 기반, 메시지/그룹 관리

Telegram 자동화

harnyk/mcp-telegram-notifier

알림 전송 전용

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

  • 모니터 앞에서 작업할 때: /telegram off

마지막, 시행착오 : Claude Code를 모든 터미널에서 재시작 해야 정상 동작함.

앞으로의 계획

  1. 터미널 이름 지정: 현재는 세션 ID로 구분하는데, "feedback-터미널", "hotel-터미널"처럼 내가 이름을 지정할 수 있으면 좋겠습니다.

  2. 메시지 포맷 개선: 현재 메시지 형식이 조금 장황한데, 더 간결하고 보기 좋게 다듬고 싶습니다.

  3. Inline Keyboard 버튼: Reply로 "A", "B" 입력하는 대신, 버튼을 눌러서 선택할 수 있으면 더 편할 것 같습니다.


도움 받은 글

  • 개발자F님 팁: Claude Code Hooks 관련 공식 매뉴얼 문서를 참조하라는 조언이 도움이 되었습니다.

  • Claude Code Hooks 문서


claude code와 부대끼다 보면, 뭐든 쉽게 만들 수 있을것 같아요~

3
10개의 답글

뉴스레터 무료 구독

👉 이 게시글도 읽어보세요