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."
완성본 데모 · 표준 프로토콜의 등장 · Tool / Resource / Prompt · 클라이언트-서버 프로세스 관계.
이 세션이 끝나면 참여자 노트북에서 다음 세 가지가 한꺼번에 돌아간다.
파이썬으로 작성한 MCP 서버가 로컬 프로세스로 뜬다.
mcp dev server.py 한 줄이면 인스펙터가 함께 뜬다.
Claude Code 가 이 서버를 자기 툴 목록에 잡는다.
claude mcp list 에 slack-summary 가 뜬다.
"지난 24시간 #eng 채널 요약해줘" 라는 자연어 요청이 실제 슬랙 히스토리를 읽고 요약본으로 돌아온다.
📌 만드는 코드는 100줄 남짓. 이 100줄이 강력한 이유는 재사용성이다 — Codex · Cursor · Windsurf 같은 다른 클라이언트에서도 같은 서버를 그대로 붙여 쓴다.
LLM 이 나온 뒤 2년 동안 사람들은 같은 문제를 반복해 풀었다. "이 챗봇이 내 슬랙·노션·지라·GitHub 를 다 알면 좋겠다." 그런데 챗봇마다 통합 방식이 다 달랐다.
같은 슬랙 통합을 클라이언트 수만큼 다시 짜야 했다.
Anthropic 이 Model Context Protocol 발표.
골자. 툴 제공자(서버) 와 툴 소비자(클라이언트) 사이에 표준 프로토콜을 두자.
슬랙 통합 서버를 한 번 짜면 Claude · Cursor · Codex · Windsurf 가 모두 그것을 그대로 붙여 쓴다.
📖 출처 · Wikipedia "Model Context Protocol" (en.wikipedia.org/wiki/Model_Context_Protocol) · Anthropic News "Introducing the Model Context Protocol" (anthropic.com/news/model-context-protocol)
MCP 서버가 클라이언트에 노출하는 것은 정확히 세 가지다. 성격 차이를 처음부터 잡아 두면 서버 설계가 명료해진다.
클라이언트가 호출하는 함수. 파라미터·리턴값 정의. 부작용 있는 액션 (읽기·쓰기·외부 API 호출).
→ summarize_channel · send_message · create_issue
클라이언트가 참조하는 읽기 전용 데이터. URI 로 식별. 컨텍스트 주입용.
→ file://path/to/note.md · db://users/42
재사용 가능한 프롬프트 템플릿. 슬래시 명령으로 호출.
→ /review-pr · /summarize-thread

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 · Cursor · Windsurf. 서버 — 참여자가 이 세션에서 만드는 것.
① 서버는 상태가 없어야 자연스럽다. 매 호출이 독립적으로 완결되는 편이 유지보수·재사용에 유리하다.
② 인증 정보는 서버가 갖고 있다. 슬랙 봇 토큰은 서버 프로세스의 환경 변수로만 존재하고, 클라이언트(LLM) 는 절대 보지 못한다.
⚠️ 참여자가 자주 오해한다. "Claude 가 내 슬랙 토큰을 알게 되는 건가요?" 아니다. LLM 은 툴 이름과 파라미터 스키마만 본다. 실제 토큰은 서버 프로세스 안에만 있다. 이 대목을 반드시 짚어 참여자를 안심시킨다.
pip install "mcp[cli]"· FastMCP 뼈대 · MCP Inspector ·claude mcp add· 첫 자연어 호출.
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 트랜스포트로 대기. 클라이언트가 이 스크립트를 자식 프로세스로 띄우면 즉시 대화가 시작.
mcp dev server.pyClaude Code 에 붙이기 전에, 서버가 제대로 뜨는지 확인하는 게 먼저다. SDK 가 붙여 주는 MCP Inspector 를 쓴다.
mcp dev server.py
이 명령은 서버를 띄우고 브라우저에서 인스펙터 UI 를 연다. 왼쪽 패널에서 등록된 Tool 목록이 보이고, 오른쪽에서 파라미터를 넣어 호출해 결과를 확인할 수 있다.
hello 표시.name 파라미터 타입 string.name: "홍길동" 호출 결과 "안녕하세요, 홍길동님.".세 가지가 다 맞으면 SDK 기본 동작은 검증된 것이다.
클라이언트를 붙이기 전 서버가 살아 있음을 확인하는 단계.
claude mcp addClaude Code 에 MCP 서버를 붙이는 명령은 하나다.
claude mcp add slack-summary -- python /absolute/path/to/server.py
slack-summary — 클라이언트에서 이 서버를 부를 때 쓰는 이름. 툴 이름 앞에 mcp__slack-summary__ 접두어가 붙는다.-- — 여기부터 뒤는 서버를 띄우는 명령. 절대 경로를 쓰는 게 사고를 막는다.--transport 를 안 붙였다.claude mcp list
여기에 slack-summary 가 뜨면 등록이 끝난 것이다.
Claude Code 를 새로 띄우면 /mcp 슬래시 명령으로 상태를 볼 수 있다.
📌 --scope user 를 붙이면 모든 프로젝트에서, --scope project 를 붙이면 팀과 공유할 수 있다. 이 세션에서는 기본 (로컬) 로 두고 넘어간다. 배포 이슈는 마지막 정리에서 짚는다.
Claude Code 를 새로 띄운다. /mcp 로 서버가 잡혔는지 확인. 그리고 자연어로 물어본다.
"hello 툴로 '홍길동' 이라는 이름 넣어서 불러줘"
Claude 는 이 문장을 보고 mcp__slack-summary__hello({name: "홍길동"}) 로 변환해 호출한다. 결과가 대화창에 뜬다. "안녕하세요, 홍길동님."
툴 이름 규칙. mcp__ 접두어 + 서버명 + __ + 툴명. 이 세션에서는 서버명이 slack-summary, 툴명이 hello 라 mcp__slack-summary__hello 가 된다.
📌 첫 순환이 완성됐다.
서버가 자기 능력을 광고 → LLM 이 자연어를 툴 호출로 매핑 → 서버가 실행 → 결과가 대화로 돌아옴.
나머지 코드는 이 순환의 안쪽 함수 하나를 채워 넣는 일이다.
이 지점에서 5분 정도 참여자가 각자 노트북에서 여기까지 오는 걸 확인한다.
여기서 막힌 채 넘어가면 남은 부분이 다 무너진다.
파이썬 경로에 주의.
which python 결과의 절대 경로를 그대로 붙여 넣어야 한다.
상대 경로·심볼릭 링크는 사고의 원천.
슬랙 앱 만들기 6단계 ·
conversations.history· 채널명 → ID 매퍼 · 메시지 → 대화 텍스트.
슬랙 워크스페이스에 API 로 접근하려면 먼저 봇 앱 을 만들고 토큰을 받아야 한다.
https://api.slack.com/apps 접속 · Create New App · From scratch 선택.mcp-summary-bot, 워크스페이스 선택.xoxb- 로 시작) 을 복사.channels:history — 공개 채널 메시지 읽기channels:read — 채널 목록 조회users:read — 메시지 작성자 이름 매핑chat:write — (선택) 결과를 채널에 다시 게시두 값을 환경 변수로 저장.
export SLACK_BOT_TOKEN="xoxb-..."
export SLACK_SIGNING_SECRET="..."
⚠️ 라이브에서 6단계를 다 밟으면 15분이 훌쩍 간다. 강사는 사전에 개인 워크스페이스에서 한 번 완주해 두고, 스크린샷을 슬라이드에 준비한다. 세션 도중에는 강사 화면 공유로 요약해 보여주고, 참여자는 세션 뒤 실습 시간에 각자 완주한다.
conversations.history파이썬으로 슬랙 API 를 다루는 공식 SDK 는 slack-bolt. Anthropic 이 아니라 슬랙 공식 프로젝트.
pip install slack-bolt
slack-bolt 의 App 은 웹서버 스타일(이벤트 수신) 로도 쓰지만, 이 세션에서는 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() 결과를 그대로 문자열 캐스팅.
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 로 감싸는 걸 숙제로 남긴다. 매 호출마다 전체 채널 목록을 훑는 건 낭비.
format_historyconversations.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 이 대화 흐름을 잡기 위해 슬랙의 최신순을 뒤집는다.
요약 프롬프트 카드 · Anthropic API 호출 ·
summarize_channelTool 정의 · 완성본 서버 실행.
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} = 변수 치환, {{ = 리터럴 {, }} = 리터럴 }.
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 로 낮춰도 된다. 이 세션에서는 품질 우선.
여기서는 raise 시켜서 툴 호출자(Claude Code) 가 다시 요청하게 한다.
서비스 안에서 재시도 루프를 감추지 않는다. MCP 는 실패도 프로토콜의 일부.
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)

세 조각(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."
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개 + 자연어 인터페이스가 다 들어 있다.
환경 변수 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 — 미해결 질문 배열이 순서로 체크하면 대부분 잡힌다.
⚠️ 여기서 3분 정도 참여자가 각자 자기 슬랙 워크스페이스로 이 호출을 완주하는 시간을 준다. 완주 못한 참여자가 남아 있으면 PART 5 가 무너진다.
--env로 3키 주입 · 실전 시나리오 3종 · 자주 걸리는 오류 4종 · stderr 로그 규약.
인스펙터에서 검증이 끝났다면 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 조합을 권장한다. 이 세션에서는 시연 편의로 인라인.
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 은 프리젠터. 서버에 "여러 채널을 한 번에" 라는 기능을 넣지 않아도 되고, 서버 코드가 얇으니 유지보수가 쉽다.
현장에서 실제로 걸리는 함정들을 미리 짚어 둔다.
| 증상 | 원인 | 해결 |
|---|---|---|
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 을 먼저 실행.
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)
...
stdout 은 JSON-RPC 전용.
한 줄이라도 stdout 에 흘리면 클라이언트가 파싱 에러를 뱉고 서버가 죽는다. Claude Code 창에 Unexpected token in JSON 같은 오류가 뜨면 대개 이 경우다.
FastMCP 가 자동으로 클라이언트에게 에러 응답을 되돌린다.
try/except 로 삼키지 말 것. 에러가 감춰지면 사용자에게는 원인을 알 수 없는 실패만 남는다.
⚠️ 프린트 디버깅 습관이 몸에 밴 참여자가 이 지점에서 자주 실수한다. print("debug: ...") 한 줄이 서버 전체를 죽인다.
100줄로 얻은 세 자산 · 재사용 지점 · 같은 뼈대로 만들 수 있는 서버 5종.
만든 것은 server.py 한 파일이다. 실제 코드 라인은 100줄 남짓. 그 100줄로 다음 세 가지를 한꺼번에 얻는다.
Claude Code 에서 만든 서버가 Codex · Cursor · Windsurf 에 그대로 붙는다.
통합 코드를 클라이언트별로 다시 짜지 않는다.
--scope project 로 등록하고 리포에 커밋하면 팀원 전원이 같은 툴을 쓴다.
각자 다시 셋업할 필요가 없다.
사용자는 채널 ID · 파라미터를 몰라도 된다.
"지난 12시간 요약해줘" 로 충분하다. LLM 이 매핑을 맡는다.
| 자산 | 어디에 다시 쓰나 |
|---|---|
server.py · MCP 서버 뼈대 |
다른 툴 (send_message · pin_message 등) |
| Slack Bolt 통합 패턴 | 회사 워크스페이스 자동화 봇 |
| Anthropic 요약 프롬프트 카드 | 회의록 · PR 코멘트 · 지라 티켓 요약 |
claude mcp add 등록 절차 |
Codex · Cursor 등 다른 클라이언트에 동일 서버 붙이기 |
같은 뼈대에서 방향만 바꾸면 만들 수 있는 서버 목록이다.
discord.py 로 채널 히스토리를 긁는다.
스코프 개념·프롬프트 카드는 이 세션과 동일.
Notion API 로 페이지·데이터베이스를 가져와 요약.
Resource 로 노출하면 컨텍스트 주입도 가능.
Linear GraphQL API 로 오픈 이슈를 훑고 우선순위 태깅.
스프린트 종료 시점에 완료·미완료·차단 이슈를 자동 정리.
오늘·내일 미팅을 훑고 준비사항을 리마인드.
@mcp.tool() 로 노출📌 뼈대가 같기 때문에 두 번째 서버부터는 20~30분이면 만든다. 한 번 완주한 절차가 그대로 재사용된다.
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."