AI 바이브/페어 개발 — Claude Code/Codex 과정
Session 6 · Slack 요약봇 MCP 서버 · 실습 ①
AI 바이브/페어 개발 — Claude Code/Codex 과정

Slack 요약봇 MCP 서버
100줄로 만드는 재사용 툴

Session 6 · 재사용 가능한 Slack 요약봇 MCP 서버

FastMCP · Inspector · Slack Bolt · Anthropic Python SDK · claude mcp add

강사 박수현 · 🚀 젠아이랩스(GenAI Labs)

🎨 이미지 프롬프트: "Editorial illustration on dark navy #0f1722 background showing a terminal window on the left labeled 'Claude Code' and a Slack channel UI on the right labeled 'Slack'. Between them sits a small server box with a Python snake emblem labeled 'MCP Server'. Two-way arrows flow between the terminal and the server, and between the server and the Slack UI. Clean editorial illustration style, warm orange gradient accents with teal #00b894 and purple #6c5ce7 highlights, 16:9. CRITICAL: All visible text MUST be in English ONLY (no Korean characters). Each label appears EXACTLY ONCE — no duplicates. No mirrored or reversed text."

바이브코딩 2일 집중 · 프롬프트·컨텍스트·하네스 엔지니어링

PART 1

MCP 개념 — 왜 필요한가 · 3요소 · 관계

완성본 데모 · 표준 프로토콜의 등장 · Tool / Resource / Prompt · 클라이언트-서버 프로세스 관계.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

세션 목표 — 세 가지가 한꺼번에 돌아간다

이 세션이 끝나면 참여자 노트북에서 다음 세 가지가 한꺼번에 돌아간다.

① 서버가 뜬다

파이썬으로 작성한 MCP 서버가 로컬 프로세스로 뜬다.

mcp dev server.py 한 줄이면 인스펙터가 함께 뜬다.

② 클라이언트가 잡는다

Claude Code 가 이 서버를 자기 툴 목록에 잡는다.

claude mcp listslack-summary 가 뜬다.

③ 자연어가 요약이 된다

"지난 24시간 #eng 채널 요약해줘" 라는 자연어 요청이 실제 슬랙 히스토리를 읽고 요약본으로 돌아온다.

📌 만드는 코드는 100줄 남짓. 이 100줄이 강력한 이유는 재사용성이다 — Codex · Cursor · Windsurf 같은 다른 클라이언트에서도 같은 서버를 그대로 붙여 쓴다.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

MCP 가 왜 필요한가 — 같은 통합을 N번 짜던 시대의 종결

LLM 이 나온 뒤 2년 동안 사람들은 같은 문제를 반복해 풀었다. "이 챗봇이 내 슬랙·노션·지라·GitHub 를 다 알면 좋겠다." 그런데 챗봇마다 통합 방식이 다 달랐다.

😩 2022 ~ 2024 — 파편화

  • ChatGPT 플러그인
  • OpenAI Functions
  • LangChain 툴
  • 각 IDE 확장

같은 슬랙 통합을 클라이언트 수만큼 다시 짜야 했다.

✨ 2024-11 — MCP 등장

Anthropic 이 Model Context Protocol 발표.

골자. 툴 제공자(서버) 와 툴 소비자(클라이언트) 사이에 표준 프로토콜을 두자.

슬랙 통합 서버를 한 번 짜면 Claude · Cursor · Codex · Windsurf 가 모두 그것을 그대로 붙여 쓴다.

📌 세 가지 관찰

  • HTTP + JSON-RPC 2.0 기반 — 새 프로토콜을 익힐 필요가 없다. 요청·응답 스키마가 명확하다.
  • 오픈 스펙·오픈 SDK — 파이썬·타입스크립트 SDK 가 공식으로 나와 있다. 라이선스 MIT.
  • Linux Foundation 산하로 이관 (2025) — 특정 벤더의 폐쇄 프로토콜이 아니다. 월 다운로드 9,700만 회 (Anthropic 공식 발표 기준).

📖 출처 · Wikipedia "Model Context Protocol" (en.wikipedia.org/wiki/Model_Context_Protocol) · Anthropic News "Introducing the Model Context Protocol" (anthropic.com/news/model-context-protocol)

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

MCP 3요소 — Tool · Resource · Prompt

MCP 서버가 클라이언트에 노출하는 것은 정확히 세 가지다. 성격 차이를 처음부터 잡아 두면 서버 설계가 명료해진다.

🔧 Tool

클라이언트가 호출하는 함수. 파라미터·리턴값 정의. 부작용 있는 액션 (읽기·쓰기·외부 API 호출). → summarize_channel · send_message · create_issue

📄 Resource

클라이언트가 참조하는 읽기 전용 데이터. URI 로 식별. 컨텍스트 주입용. → file://path/to/note.md · db://users/42

💬 Prompt

재사용 가능한 프롬프트 템플릿. 슬래시 명령으로 호출. → /review-pr · /summarize-thread

🎨 Editorial illustration on dark navy #0f1722 ba…

Tool 만 warm orange 로 강조 — 이 세션에서 다룰 유일한 요소

📌 이 세션에서는 Tool 하나만 다룬다. 채널 히스토리를 가져와 요약해 반환하는 summarize_channel 이다. Resource 와 Prompt 는 부작용이 없는 참조·템플릿용이라 별도 설계 판단이 필요하고, 서버 규모가 커진 뒤 붙이는 게 정석이다.

🎨 이미지 프롬프트: "Editorial illustration on dark navy #0f1722 background showing a server box in the center with three glowing outlets labeled 'Tool', 'Resource', 'Prompt'. From each outlet a thin luminous line flows out to a client silhouette on the right. The 'Tool' outlet is emphasized with a warm orange highlight, the others are dimmer. Clean editorial illustration style, teal #00b894 and purple #6c5ce7 accents, 16:9. CRITICAL: All visible text MUST be in English ONLY (no Korean characters). Each label appears EXACTLY ONCE — no duplicates. No mirrored or reversed text."

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

클라이언트 ↔ 서버 관계 — 프로세스 · 프로토콜 · 인증

클라이언트 — 사용자가 대화하는 AI 도구. Claude Code · Codex · Cursor · Windsurf. 서버 — 참여자가 이 세션에서 만드는 것.

🧵 프로세스 관계

  • 클라이언트가 서버를 자기 자식 프로세스로 띄운다. stdio (표준 입출력) 로 대화한다.
  • JSON-RPC 2.0 메시지가 오간다. 요청·응답·알림 세 종류.
  • 초기 핸드셰이크에서 서버가 자기 능력을 광고한다. "나는 이런 Tool 을 갖고 있다" 를 스키마와 함께 보낸다.
  • 사용자 요청이 있을 때만 클라이언트가 Tool 을 호출한다. LLM 이 "이 자연어에는 이 Tool 이 맞겠다" 고 판단하는 순간.

🎯 두 가지 결과

① 서버는 상태가 없어야 자연스럽다. 매 호출이 독립적으로 완결되는 편이 유지보수·재사용에 유리하다.

② 인증 정보는 서버가 갖고 있다. 슬랙 봇 토큰은 서버 프로세스의 환경 변수로만 존재하고, 클라이언트(LLM) 는 절대 보지 못한다.

⚠️ 참여자가 자주 오해한다. "Claude 가 내 슬랙 토큰을 알게 되는 건가요?" 아니다. LLM 은 툴 이름과 파라미터 스키마만 본다. 실제 토큰은 서버 프로세스 안에만 있다. 이 대목을 반드시 짚어 참여자를 안심시킨다.

PART 2

mcp SDK 기초 — 설치·스캐폴드·Inspector·등록

pip install "mcp[cli]" · FastMCP 뼈대 · MCP Inspector · claude mcp add · 첫 자연어 호출.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

설치 · 첫 서버 스캐폴드 — mcp[cli] + FastMCP

파이썬 SDK 는 mcp 라는 이름으로 PyPI 에 올라와 있다. 설치는 한 줄이다.

pip install "mcp[cli]"

[cli] extra 를 붙이면 로컬에서 서버를 띄워 테스트할 수 있는 mcp 커맨드가 함께 깔린다. 강의에서 이 커맨드를 몇 번 쓴다.

뼈대 파일 server.py.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("slack-summary")

@mcp.tool()
def hello(name: str) -> str:
    """이름을 받아 인사말을 돌려준다."""
    return f"안녕하세요, {name}님."

if __name__ == "__main__":
    mcp.run()

📌 세 가지 관찰. - FastMCP 는 SDK 가 제공하는 고수준 헬퍼. 저수준 mcp.server.Server 도 있지만 스키마 등록·라우팅을 다 수동으로 짜야 한다. 실무는 대부분 FastMCP 로 시작. - @mcp.tool() 데코레이터가 파이썬 함수를 Tool 로 등록. 타입 힌트와 docstring 만 있으면 JSON Schema 가 자동 생성. - mcp.run() 은 stdio 트랜스포트로 대기. 클라이언트가 이 스크립트를 자식 프로세스로 띄우면 즉시 대화가 시작.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

로컬 인스펙터로 확인 — mcp dev server.py

Claude Code 에 붙이기 전에, 서버가 제대로 뜨는지 확인하는 게 먼저다. SDK 가 붙여 주는 MCP Inspector 를 쓴다.

mcp dev server.py

이 명령은 서버를 띄우고 브라우저에서 인스펙터 UI 를 연다. 왼쪽 패널에서 등록된 Tool 목록이 보이고, 오른쪽에서 파라미터를 넣어 호출해 결과를 확인할 수 있다.

✅ 확인 순서 3단계

  1. Tool 목록에 hello 표시.
  2. name 파라미터 타입 string.
  3. name: "홍길동" 호출 결과 "안녕하세요, 홍길동님.".

세 가지가 다 맞으면 SDK 기본 동작은 검증된 것이다.

🎯 인스펙터의 역할

클라이언트를 붙이기 전 서버가 살아 있음을 확인하는 단계.

  • Claude Code · Codex 에 붙였을 때 안 되면 원인이 서버인지 클라이언트인지 헷갈린다.
  • 인스펙터로 서버 단독 정상을 확인해 두면 나머지 트러블은 등록·인증 문제로 좁혀진다.
AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

Claude Code 에 등록 — claude mcp add

Claude Code 에 MCP 서버를 붙이는 명령은 하나다.

claude mcp add slack-summary -- python /absolute/path/to/server.py

🔍 형식 뜯어보기

  • slack-summary — 클라이언트에서 이 서버를 부를 때 쓰는 이름. 툴 이름 앞에 mcp__slack-summary__ 접두어가 붙는다.
  • -- — 여기부터 뒤는 서버를 띄우는 명령. 절대 경로를 쓰는 게 사고를 막는다.
  • 트랜스포트 기본값은 stdio. 그래서 --transport 를 안 붙였다.

✅ 등록 확인

claude mcp list

여기에 slack-summary 가 뜨면 등록이 끝난 것이다.

Claude Code 를 새로 띄우면 /mcp 슬래시 명령으로 상태를 볼 수 있다.

📌 --scope user 를 붙이면 모든 프로젝트에서, --scope project 를 붙이면 팀과 공유할 수 있다. 이 세션에서는 기본 (로컬) 로 두고 넘어간다. 배포 이슈는 마지막 정리에서 짚는다.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

첫 호출 — Claude Code 에서 hello

Claude Code 를 새로 띄운다. /mcp 로 서버가 잡혔는지 확인. 그리고 자연어로 물어본다.

"hello 툴로 '홍길동' 이라는 이름 넣어서 불러줘"

Claude 는 이 문장을 보고 mcp__slack-summary__hello({name: "홍길동"}) 로 변환해 호출한다. 결과가 대화창에 뜬다. "안녕하세요, 홍길동님."

툴 이름 규칙. mcp__ 접두어 + 서버명 + __ + 툴명. 이 세션에서는 서버명이 slack-summary, 툴명이 hellomcp__slack-summary__hello 가 된다.

📌 첫 순환이 완성됐다.

서버가 자기 능력을 광고 → LLM 이 자연어를 툴 호출로 매핑 → 서버가 실행 → 결과가 대화로 돌아옴.

나머지 코드는 이 순환의 안쪽 함수 하나를 채워 넣는 일이다.

✅ 참여자 체크포인트

이 지점에서 5분 정도 참여자가 각자 노트북에서 여기까지 오는 걸 확인한다.

여기서 막힌 채 넘어가면 남은 부분이 다 무너진다.

🖥️ WSL 사용자 주의

파이썬 경로에 주의.

which python 결과의 절대 경로를 그대로 붙여 넣어야 한다.

상대 경로·심볼릭 링크는 사고의 원천.

PART 3

Slack Bolt 통합 — 앱·히스토리·매퍼·정규화

슬랙 앱 만들기 6단계 · conversations.history · 채널명 → ID 매퍼 · 메시지 → 대화 텍스트.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

슬랙 앱 만들기 — 6단계 절차

슬랙 워크스페이스에 API 로 접근하려면 먼저 봇 앱 을 만들고 토큰을 받아야 한다.

📝 절차 6단계

  1. https://api.slack.com/apps 접속 · Create New App · From scratch 선택.
  2. App Namemcp-summary-bot, 워크스페이스 선택.
  3. 좌측 OAuth & PermissionsBot Token Scopes 에 스코프 4개 추가.
  4. 상단 Install to Workspace 클릭 · 권한 승인.
  5. 승인 후 Bot User OAuth Token (xoxb- 로 시작) 을 복사.
  6. Basic Information 페이지에서 Signing Secret 도 복사.

🔑 필요한 봇 스코프 4개

  • channels:history — 공개 채널 메시지 읽기
  • channels:read — 채널 목록 조회
  • users:read — 메시지 작성자 이름 매핑
  • chat:write — (선택) 결과를 채널에 다시 게시

두 값을 환경 변수로 저장.

export SLACK_BOT_TOKEN="xoxb-..."
export SLACK_SIGNING_SECRET="..."

⚠️ 라이브에서 6단계를 다 밟으면 15분이 훌쩍 간다. 강사는 사전에 개인 워크스페이스에서 한 번 완주해 두고, 스크린샷을 슬라이드에 준비한다. 세션 도중에는 강사 화면 공유로 요약해 보여주고, 참여자는 세션 뒤 실습 시간에 각자 완주한다.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

slack-bolt · conversations.history

파이썬으로 슬랙 API 를 다루는 공식 SDK 는 slack-bolt. Anthropic 이 아니라 슬랙 공식 프로젝트.

pip install slack-bolt

slack-boltApp 은 웹서버 스타일(이벤트 수신) 로도 쓰지만, 이 세션에서는 app.client 만 쓴다. 슬랙 REST API 를 감싼 클라이언트.

핵심 메서드 conversations.history.

from slack_bolt import App
import os

app = App(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"],
)

result = app.client.conversations_history(
    channel="C0123456789",
    limit=200,
    oldest="1735689600",   # UNIX epoch (2025-01-01 UTC). 이 시각 이후 메시지만.
)

for msg in result["messages"]:
    print(msg["user"], msg["text"])

📌 세 가지 관찰. - channel 은 채널 ID (C 로 시작하는 문자열) 다. 채널 이름을 받아 ID 로 바꾸는 매퍼가 필요하다. - limit 은 최대 1,000, 권장 200. 200 을 넘기면 페이지네이션을 돌아야 한다. - oldest 는 UNIX epoch (문자열). 파이썬 datetime.timestamp() 결과를 그대로 문자열 캐스팅.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

채널명 → ID 매퍼 — resolve_channel_id

사용자는 #eng 라고 자연어로 부르지 채널 ID 를 외우지 않는다. 이름을 ID 로 바꾸는 헬퍼를 하나 만든다.

def resolve_channel_id(app: App, channel_name: str) -> str:
    """채널 이름(#eng 또는 eng) 을 채널 ID (C0123...) 로 변환."""
    name = channel_name.lstrip("#")
    cursor = None
    while True:
        resp = app.client.conversations_list(
            types="public_channel",
            limit=200,
            cursor=cursor,
        )
        for ch in resp["channels"]:
            if ch["name"] == name:
                return ch["id"]
        cursor = resp.get("response_metadata", {}).get("next_cursor")
        if not cursor:
            break
    raise ValueError(f"채널 '{channel_name}' 을 찾지 못했다.")

🔄 커서 페이지네이션

채널 수가 200 을 넘으면 필수. 커서는 이번 요청의 마지막 지점, 다음 요청은 여기부터 이어 받는다. next_cursor 가 빈 문자열이면 끝.

🔒 public_channel 로 한정

프라이빗 채널까지 다루려면 groups:history · groups:read 스코프가 추가로 붙는다. 이 세션에서는 공개 채널만.

📌 이 함수는 캐시 대상 — functools.lru_cache 로 감싸는 걸 숙제로 남긴다. 매 호출마다 전체 채널 목록을 훑는 건 낭비.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

메시지 → 대화 텍스트 — format_history

conversations.history 가 돌려주는 messages 는 슬랙 형식 그대로다. user 필드는 사용자 ID (U0...) 이고, text 는 멘션이 <@U0...> 형태로 남아 있다. LLM 에 넣기 전에 사람이 읽는 형태로 다듬는다.

from datetime import datetime

def format_history(app: App, messages: list) -> str:
    """슬랙 메시지 리스트를 요약용 텍스트로 변환."""
    user_cache = {}
    lines = []
    for msg in reversed(messages):  # 슬랙은 최신순 → 시간순으로 뒤집기
        if msg.get("subtype") == "bot_message":
            continue  # 봇 메시지는 요약에서 제외
        user_id = msg.get("user")
        if not user_id:
            continue
        if user_id not in user_cache:
            info = app.client.users_info(user=user_id)
            user_cache[user_id] = info["user"]["real_name"] or info["user"]["name"]
        name = user_cache[user_id]
        ts = datetime.fromtimestamp(float(msg["ts"]))
        text = msg.get("text", "").strip()
        lines.append(f"[{ts:%H:%M}] {name}: {text}")
    return "\n".join(lines)

📌 세 가지 결정. - 봇 메시지는 배제 — 알림봇이 채널을 도배한 경우 요약 품질을 망친다. - 사용자 이름 캐시 — 같은 호출 안에서 users_info 를 반복 호출하지 않는다. - 시간순 정렬 — LLM 이 대화 흐름을 잡기 위해 슬랙의 최신순을 뒤집는다.

PART 4

채널 요약 툴 조립 — 프롬프트·Anthropic·Tool 정의

요약 프롬프트 카드 · Anthropic API 호출 · summarize_channel Tool 정의 · 완성본 서버 실행.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

요약 프롬프트 카드 — SUMMARIZE_PROMPT

format_history 결과를 Anthropic API 로 넘겨 요약을 받는다. 프롬프트가 이 툴의 품질을 결정한다.

SUMMARIZE_PROMPT = """다음은 슬랙 채널 '{channel}' 의 지난 {hours} 시간 대화다.

--- 대화 시작 ---
{conversation}
--- 대화 끝 ---

다음 4가지를 JSON 으로 반환한다. 코드블록 없이 JSON 만.

{{
  "summary": "3~5문장 요약",
  "participants": ["대화 참여자 이름 배열"],
  "decisions": ["핵심 결정 사항 배열. 없으면 빈 배열"],
  "open_questions": ["미해결 질문 배열. 없으면 빈 배열"]
}}

한국어로 답한다. 대화가 너무 짧아 요약할 게 없으면 summary 필드에 '요약할 대화 부족' 이라고만 쓴다."""

📌 세 가지 원칙. - 구조화 리턴 — JSON 스키마를 명시하면 후속 파싱이 안전. - 범위 명시 — 채널 이름·시간 범위를 프롬프트 안에 다시 밝힌다. LLM 이 어느 구간을 요약해야 하는지 혼동하지 않는다. - 빈 결과 폴백 — 대화가 부족한 경우의 응답 형태를 미리 정한다. 크래시가 아니라 명시적 실패.

⚠️ {{ }} 이중 중괄호 주의. Python f-string/.format() 에서 JSON 예시의 중괄호를 리터럴로 남기려면 {{ }} 로 이스케이프해야 한다. 이 부분이 참여자가 가장 자주 실수하는 지점.

규칙 한 줄로. 단일 {name} = 변수 치환, {{ = 리터럴 {, }} = 리터럴 }.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

Anthropic API 호출 — summarize 함수

Anthropic Python SDK 로 요약을 뽑는다.

import os, json
from anthropic import Anthropic

anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])

def summarize(channel: str, hours: int, conversation: str) -> dict:
    """대화 텍스트를 받아 요약 JSON 을 반환."""
    if not conversation.strip():
        return {"summary": "요약할 대화 부족",
                "participants": [], "decisions": [], "open_questions": []}
    prompt = SUMMARIZE_PROMPT.format(
        channel=channel, hours=hours, conversation=conversation)
    resp = anthropic.messages.create(
        model="claude-opus-4-8",
        max_tokens=2000,
        messages=[{"role": "user", "content": prompt}],
    )
    return json.loads(resp.content[0].text.strip())

🧠 모델 선택

기본claude-opus-4-8.

짧은 요약이면 claude-haiku-4-5 로 낮춰도 된다. 이 세션에서는 품질 우선.

🔁 JSON 파싱 실패

여기서는 raise 시켜서 툴 호출자(Claude Code) 가 다시 요청하게 한다.

서비스 안에서 재시도 루프를 감추지 않는다. MCP 는 실패도 프로토콜의 일부.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

summarize_channel 툴 정의 — 세 조각을 한 툴로

앞의 세 조각 — 채널 ID 매퍼 · 히스토리 포매터 · 요약기 — 을 한 툴로 묶는다.

from datetime import datetime, timedelta, timezone

@mcp.tool()
def summarize_channel(
    channel_name: str,
    lookback_hours: int = 24,
) -> dict:
    """슬랙 채널 지난 N 시간 대화 요약.
    Returns summary·participants·
    decisions·open_questions dict."""
    channel_id = resolve_channel_id(
        app, channel_name)
    oldest = (datetime.now(timezone.utc)
        - timedelta(hours=lookback_hours)
    ).timestamp()
    resp = app.client.conversations_history(
        channel=channel_id, limit=200,
        oldest=str(oldest))
    conversation = format_history(
        app, resp["messages"])
    return summarize(channel_name,
        lookback_hours, conversation)

🎨 Editorial illustration on dark navy #0f1722 ba…

세 조각(resolve_channel_id · format_history · summarize)이 한 툴로 합쳐져 MCP Tool 로 노출된다

📌 세 가지 관찰. - 파라미터 default 값 (lookback_hours=24) 을 두면 사용자가 "요약해줘" 라고만 해도 24시간이 잡힌다. - docstring 이 곧 LLM 에게 보이는 설명Args: · Returns: 를 명시하면 Claude 가 파라미터 의도를 정확히 잡는다. 리턴은 dict — MCP SDK 가 자동으로 JSON 직렬화한다.

🎨 이미지 프롬프트: "Editorial illustration on dark navy #0f1722 background showing three small function blocks stacked vertically on the left, labeled 'resolve_channel_id', 'format_history', 'summarize'. A thick teal arrow flows from all three into a single big block on the right labeled 'summarize_channel'. From the big block a warm orange arrow flows out labeled 'MCP Tool'. Clean editorial illustration style, teal #00b894 and purple #6c5ce7 accents, 16:9. CRITICAL: All visible text MUST be in English ONLY (no Korean characters). Each label appears EXACTLY ONCE — no duplicates. No mirrored or reversed text."

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

완성본 · server.py 조립도

전체 server.py 는 다음처럼 정리된다. 이전 슬라이드의 조각들이 순서대로 들어간다.

"""Slack 채널 요약 MCP 서버."""
import os, json
from datetime import datetime, timedelta, timezone
from mcp.server.fastmcp import FastMCP
from slack_bolt import App
from anthropic import Anthropic

mcp = FastMCP("slack-summary")
app = App(
    token=os.environ["SLACK_BOT_TOKEN"],
    signing_secret=os.environ["SLACK_SIGNING_SECRET"],
)
anthropic = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])
SUMMARIZE_PROMPT = """..."""  # 프롬프트 카드 원문 그대로

def resolve_channel_id(app, channel_name): ...   # 채널명 → ID
def format_history(app, messages): ...           # 메시지 → 대화 텍스트
def summarize(channel, hours, conversation): ... # Anthropic 호출

@mcp.tool()
def summarize_channel(channel_name: str, lookback_hours: int = 24) -> dict:
    """..."""  # 앞 슬라이드 본체 그대로

if __name__ == "__main__":
    mcp.run()

📌 실제 라인 수는 100줄 남짓. 이 안에 표준 프로토콜 + 외부 API 3개 + 자연어 인터페이스가 다 들어 있다.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

실행 시퀀스 — 환경변수 → 인스펙터 → 실 채널

환경 변수 3개 + mcp dev 한 줄로 서버가 뜬다.

export SLACK_BOT_TOKEN="xoxb-..."
export SLACK_SIGNING_SECRET="..."
export ANTHROPIC_API_KEY="sk-ant-..."

mcp dev server.py    # 인스펙터로 먼저 확인

🧪 인스펙터에서 실 호출

summarize_channel(channel_name="general", lookback_hours=6) 을 넣어 호출.

결과 JSON 이 뜨면 서버가 완성이다.

  • summary — 3~5문장
  • participants — 이름 배열
  • decisions — 결정 배열
  • open_questions — 미해결 질문 배열

🔧 에러가 나면 순서대로

  1. 스코프 4개 다 붙어 있는가
  2. 채널이 실제 존재하는가
  3. 봇이 채널에 초대됐는가

이 순서로 체크하면 대부분 잡힌다.

⚠️ 여기서 3분 정도 참여자가 각자 자기 슬랙 워크스페이스로 이 호출을 완주하는 시간을 준다. 완주 못한 참여자가 남아 있으면 PART 5 가 무너진다.

PART 5

Claude Code 실사용 — 등록·시나리오·트러블·로그

--env 로 3키 주입 · 실전 시나리오 3종 · 자주 걸리는 오류 4종 · stderr 로그 규약.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

등록 · 재시작 — 환경 변수 3개를 함께 담는다

인스펙터에서 검증이 끝났다면 Claude Code 에 붙일 차례다. 환경 변수 3개를 등록 명령에 함께 담는다.

claude mcp add slack-summary \
  --env SLACK_BOT_TOKEN=xoxb-... \
  --env SLACK_SIGNING_SECRET=... \
  --env ANTHROPIC_API_KEY=sk-ant-... \
  -- python /absolute/path/to/server.py

등록 확인.

claude mcp list
# slack-summary  running  1 tool

Claude Code 를 새 창으로 띄우고 /mcp 를 친다. slack-summary 서버가 connected 상태로 뜨고, 그 아래 summarize_channel 툴이 있으면 준비 완료.

⚠️ --env 는 값이 쉘 히스토리에 남는다. 실무에서는 .env 파일과 direnv 조합을 권장한다. 이 세션에서는 시연 편의로 인라인.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

실전 시나리오 3종 — 기본 요약 · 시간 범위 · 병렬 호출

Claude Code 대화창에서 실제로 써 본다.

① 기본 요약

지난 24시간 #general 요약해줘

Claude 가 summarize_channel(channel_name="general", lookback_hours=24) 로 매핑해 호출하고, 결과 JSON 을 한국어로 재정리해 보여준다.

  • "지난 24시간"lookback_hours=24
  • "#general"channel_name="general"

docstring 의 Args: 가 이 매핑의 근거.

② 시간 범위 지정

#eng 지난 6시간 논의 정리하고
오픈 이슈만 뽑아줘

lookback_hours=6 으로 호출된 뒤, Claude 가 리턴 dict 의 open_questions 필드만 골라 재구성한다.

툴이 리턴한 dict 를 LLM 이 후처리 하는 이 흐름이 MCP 의 강점이다.

③ 여러 채널 병렬

#eng #product #design 세 채널
지난 12시간 요약해서 부서별로 정리해줘

Claude 는 툴을 3번 병렬 호출 하고, 결과를 표로 묶어 낸다.

서버 코드는 단일 채널만 알지만, 조합의 지능은 LLM 몫이다.

📌 핵심 원칙. 툴은 구조화 dict 만 돌려준다. 최종 표현·조합은 LLM 이 맡는다. 서버는 원자적 데이터 소스, LLM 은 프리젠터. 서버에 "여러 채널을 한 번에" 라는 기능을 넣지 않아도 되고, 서버 코드가 얇으니 유지보수가 쉽다.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

트러블슈팅 — 자주 걸리는 오류 4종

현장에서 실제로 걸리는 함정들을 미리 짚어 둔다.

증상 원인 해결
not_authed · invalid_auth 봇 토큰 오타 · 환경 변수 미주입 env 로 값 확인 · claude mcp remove 후 재등록
missing_scope 봇 스코프 부족 Slack 앱 설정에서 스코프 추가 → Reinstall to Workspace
not_in_channel 봇이 채널에 초대되지 않음 슬랙 채널에서 /invite @mcp-summary-bot
ratelimited 60초 안에 API 호출 과다 Retry-After 헤더 확인 후 대기 · slack-bolt 는 자동 재시도

⚠️ not_in_channel 이 가장 자주 걸리는 함정. 스코프 4개가 다 붙어 있어도 봇이 채널에 초대되지 않으면 conversations.history 가 열리지 않는다. 슬랙 채널에서 /invite @mcp-summary-bot 을 먼저 실행.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

로그·디버깅 — stdout 은 JSON-RPC 전용

MCP 서버는 stdio 로 클라이언트와 대화하므로, print() 로 로그를 남기면 프로토콜을 오염시킨다. stderr 나 파일로 뽑아야 한다.

import logging, sys
logging.basicConfig(
    level=logging.INFO, stream=sys.stderr,
    format="%(asctime)s [%(levelname)s] %(message)s",
)
log = logging.getLogger("slack-summary")

@mcp.tool()
def summarize_channel(channel_name: str, lookback_hours: int = 24) -> dict:
    log.info("called: %s / %dh", channel_name, lookback_hours)
    ...

📝 규칙 ① — 로그는 stderr 로만

stdout 은 JSON-RPC 전용.

한 줄이라도 stdout 에 흘리면 클라이언트가 파싱 에러를 뱉고 서버가 죽는다. Claude Code 창에 Unexpected token in JSON 같은 오류가 뜨면 대개 이 경우다.

🚨 규칙 ② — 에러는 raise 로

FastMCP 가 자동으로 클라이언트에게 에러 응답을 되돌린다.

try/except 로 삼키지 말 것. 에러가 감춰지면 사용자에게는 원인을 알 수 없는 실패만 남는다.

⚠️ 프린트 디버깅 습관이 몸에 밴 참여자가 이 지점에서 자주 실수한다. print("debug: ...") 한 줄이 서버 전체를 죽인다.

PART 6

정리 · 확장 — 만든 것 · 재사용 · 확장 5선

100줄로 얻은 세 자산 · 재사용 지점 · 같은 뼈대로 만들 수 있는 서버 5종.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

만든 것 · 얻은 것 — 100줄이 준 세 자산

만든 것은 server.py 한 파일이다. 실제 코드 라인은 100줄 남짓. 그 100줄로 다음 세 가지를 한꺼번에 얻는다.

🔄 재사용성

Claude Code 에서 만든 서버가 Codex · Cursor · Windsurf 에 그대로 붙는다.

통합 코드를 클라이언트별로 다시 짜지 않는다.

👥 팀 공유

--scope project 로 등록하고 리포에 커밋하면 팀원 전원이 같은 툴을 쓴다.

각자 다시 셋업할 필요가 없다.

🗣️ 자연어 인터페이스

사용자는 채널 ID · 파라미터를 몰라도 된다.

"지난 12시간 요약해줘" 로 충분하다. LLM 이 매핑을 맡는다.

📌 재사용 지점 4가지

자산 어디에 다시 쓰나
server.py · MCP 서버 뼈대 다른 툴 (send_message · pin_message 등)
Slack Bolt 통합 패턴 회사 워크스페이스 자동화 봇
Anthropic 요약 프롬프트 카드 회의록 · PR 코멘트 · 지라 티켓 요약
claude mcp add 등록 절차 Codex · Cursor 등 다른 클라이언트에 동일 서버 붙이기
AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

확장 아이디어 5선 — 같은 뼈대에서 방향만

같은 뼈대에서 방향만 바꾸면 만들 수 있는 서버 목록이다.

💬 Discord 요약 봇

discord.py 로 채널 히스토리를 긁는다.

스코프 개념·프롬프트 카드는 이 세션과 동일.

📓 Notion 페이지 요약

Notion API 로 페이지·데이터베이스를 가져와 요약.

Resource 로 노출하면 컨텍스트 주입도 가능.

📋 Linear 이슈 트리아지

Linear GraphQL API 로 오픈 이슈를 훑고 우선순위 태깅.

🎯 Jira 스프린트 리포트

스프린트 종료 시점에 완료·미완료·차단 이슈를 자동 정리.

📅 Google Calendar 회의 요약

오늘·내일 미팅을 훑고 준비사항을 리마인드.

🧩 공통 패턴 5단계

  1. 외부 서비스 SDK 를 import
  2. 인증 토큰을 환경 변수로
  3. 데이터를 사람이 읽기 좋게 정규화
  4. Anthropic API 로 구조화 리턴
  5. @mcp.tool() 로 노출

📌 뼈대가 같기 때문에 두 번째 서버부터는 20~30분이면 만든다. 한 번 완주한 절차가 그대로 재사용된다.

AI 바이브/페어 개발 — Claude Code/Codex 과정·Session 6 · Slack 요약봇 MCP 서버 · 실습 ①출처: MCP SDKSlack BoltAnthropic Docs

마무리 — 표준의 힘은 협업 규약에서

🎯 오늘의 결론

MCP 는 도구가 아니라 표준이다. 그 힘은 협업 규약에서 나온다.

팀에서 슬랙 요약봇 서버 하나를 정리해 두면, 팀원 누구든 Claude Code · Codex 어느 도구를 쓰든 그 서버를 붙여 같은 요약 품질을 얻는다. 도구 선택이 팀 표준을 흔들지 않는다.

팀 리포에 mcp-servers/ 폴더를 두고 각 서버를 데코레이터 함수 단위로 추가해 나가면, 한 사람이 만든 툴이 팀 전체 워크플로에 즉시 반영된다.

🎨 이미지 프롬프트: "Editorial illustration on dark navy #0f1722 background centered on a stylized MCP hub emblem. Around the hub, four client icons on the top labeled 'Claude Code', 'Codex', 'Cursor', 'Windsurf', and five service icons on the bottom labeled 'Slack', 'Discord', 'Notion', 'Linear', 'Jira'. Arrows flow from services into the hub and from the hub out to the clients. Clean editorial illustration style, warm orange gradient background accents with teal #00b894 and purple #6c5ce7 highlights, 16:9. CRITICAL: All visible text MUST be in English ONLY (no Korean characters). Each label appears EXACTLY ONCE — no duplicates. No mirrored or reversed text."

1 / 31

목차 — Session 6

키보드 단축키 (뷰어)

네비게이션

다음 슬라이드 Space
이전 슬라이드 PgUp
처음 / 끝Home End
목차M

화면

전체화면F
테마 전환D
글자 크게 / 작게+ -
원래 크기 (100%)=
도움말 (열기/닫기)H ?
닫기 (모든 오버레이)Esc
강의 종료 (메인으로)Q

키보드 단축키 (강사)

네비게이션

다음 슬라이드 Space
이전 슬라이드 PgUp
처음 / 끝Home End
목차 (Menu)M
전체화면F

화면

테마 전환D
도움말 (열기/닫기)H ?
닫기 (모든 오버레이)Esc
툴바 펼치기 / 접기[ ]
강의 종료 (메인으로)Q

화면 확대

글자 크게 / 작게+ -
원래 크기 (100%)=
돋보기 (마우스 따라 2× 확대)V

발표자 도구 (PPT 호환)

화이트 화면W ,
블랙 화면B .
레이저 포인터L
펜 모드 ON/OFFP
펜 색상1 2 3 4
펜 자국 지우기 (현재 슬라이드)E
펜 — 마지막 스트로크 취소 (Undo)Z
펜 — 다시 (Redo)R
⏰ 쉬는시간 타이머T
📱 노트뷰 QR (휴대폰 동기화)N

PDF / 종료

PDF 저장 (슬라이드)Ctrl+P
강의 종료 (메인으로)Q X