@rokrokss/claude-slack-channel
v0.3.3
Published
Two-way Slack channel for Claude Code — Slack DM과 채널 멘션으로 Claude Code 세션과 대화
Downloads
310
Readme
Claude Slack Channel
Claude Code 세션과 Slack을 양방향으로 연결하는 MCP 채널 서버입니다. Slack DM이나 채널 멘션으로 Claude Code와 대화할 수 있습니다.
설치
프로젝트 .mcp.json 또는 ~/.claude.json에 추가합니다:
{
"mcpServers": {
"slack-channel": {
"command": "bunx",
"args": ["@rokrokss/claude-slack-channel"],
"env": {
"SLACK_BOT_TOKEN": "xoxb-...",
"SLACK_APP_TOKEN": "xapp-...",
"SLACK_ALLOW_FROM": "U123,U456",
"SLACK_WORKSPACE": "your-workspace",
"SLACK_ACK_REACTION": "eyes"
}
}
}
}| 환경변수 | 필수 | 설명 |
|---|---|---|
| SLACK_BOT_TOKEN | O | Bot User OAuth Token (xoxb-...) |
| SLACK_APP_TOKEN | O | App-Level Token (xapp-...) |
| SLACK_ALLOW_FROM | O | 허용할 Slack 사용자 ID (쉼표 구분) |
| SLACK_WORKSPACE | O | Slack 워크스페이스 서브도메인 (예: msuniverse). permalink 생성에 사용 |
| SLACK_BOT_OWNER | - | 봇 소유자 사용자 ID. allowlist 없이도 항상 허용 |
| SLACK_ACK_REACTION | - | 수신 확인 이모지 (예: eyes). 답장 후 자동 제거 |
| SLACK_DEFAULT_COLOR | - | 메시지 사이드바 색상 hex (기본: #e5da9a) |
| SLACK_SHOW_FOOTER | - | 메시지 하단 footer 표시 여부 (기본: true). false로 설정 시 숨김 |
| SLACK_FORCE_SOCKET_MODE | - | 1로 설정 시 플래그 감지를 건너뛰고 Socket Mode 강제 연결 |
실행
claude --dangerously-load-development-channels server:slack-channel허용된 사용자는 DM이든 채널이든 어디서든 봇과 대화할 수 있습니다.
아키텍처
┌─────────────────────────────────────────────────────────────────┐
│ Claude Code (Host) │
│ │
│ Claude LLM <──── <channel> notification <──── stdout │
│ │ ^ │
│ │ tool call (reply/react/...) │ │
│ v │ │
│ stdin ────> server.ts (MCP Server) ────> mcp.notification() │
│ │ ^ │
│ │ │ │
│ v │ │
│ Slack Web API Socket Mode (WebSocket) │
│ │ │ │
└────────────────────┼───────────┼────────────────────────────────┘
│ │
v │
┌──────────────────┴──────┐
│ Slack Platform │
│ (채널, DM, 스레드) │
└─────────────────────────┘- 전송 프로토콜: MCP (Model Context Protocol) over stdio
- 인바운드: Slack → WebSocket(Socket Mode) →
server.ts→mcp.notification()→ stdout → Claude - 아웃바운드: Claude → tool call → stdin →
server.ts→ Slack Web API → Slack console.error만 디버그 로그에 사용 (stdout은 MCP 전용)
시작 흐름
main()
│
├── hasChannelsFlag()
│ └── 부모 프로세스(Claude Code)의 command line에서
│ --dangerously-load-development-channels 또는 --channels 확인
│
├── [플래그 있음] startSocketMode()
│ ├── killPreviousInstance() → 기존 인스턴스 종료 (PID 파일)
│ ├── writePidFile() → 자신의 PID 기록
│ ├── web.auth.test() → botUserId 확인
│ │ └── 실패 → process.exit(1)
│ └── socket.start() → Slack 이벤트 수신 시작
│
├── [플래그 없음] Socket Mode 스킵 (Tools-only mode)
│
└── mcp.connect(transport) → MCP stdio 연결Socket Mode는 부모 프로세스(Claude Code)에 channels 플래그가 있을 때만 연결됩니다. 플래그 없이 실행된 세션은 도구만 제공하며 Socket Mode를 건드리지 않으므로, 기존 channels 세션의 연결이 보호됩니다.
Channels 플래그 감지 (hasChannelsFlag)
Claude Code는 --dangerously-load-development-channels 또는 --channels 플래그와 함께 실행될 때만 채널 기능을 사용합니다. 이 MCP 서버는 부모 프로세스의 커맨드라인을 검사하여 플래그 존재 여부를 판단합니다.
hasChannelsFlag()
│
├── SLACK_FORCE_SOCKET_MODE=1 ? ──── true (환경변수 오버라이드)
│
└── findAncestorClaudeCommand()
│
│ process.ppid부터 시작, 최대 5단계 탐색
│
v
┌──────────────────────────────────────────────────────┐
│ PID 1234 (bun server.ts) │
│ └── ppid -> PID 1230 │
│ │
│ PID 1230 (node ...) │
│ └── ppid -> PID 1225 │
│ │
│ PID 1225 (claude --channels server:slack) <── 발견! │
│ └── 커맨드라인에 "channels" 포함 -> true │
└──────────────────────────────────────────────────────┘프로세스 정보 조회 방법:
| 플랫폼 | 명령 |
|---|---|
| macOS / Linux | ps -o ppid=,command= -p <pid> |
| Windows | wmic process where ProcessId=<pid> get ParentProcessId,CommandLine /format:csv |
각 단계에서 부모 PID와 커맨드라인을 함께 가져와, claude라는 이름의 프로세스를 찾으면 그 커맨드라인에서 플래그를 확인합니다. 5단계 안에 찾지 못하면 false를 반환하며, Socket Mode를 시작하지 않습니다.
왜 이것이 중요한가:
Claude Code는 하나의 앱에 대해 여러 세션을 열 수 있습니다. 플래그 없이 실행된 세션이 Socket Mode에 연결하면, 이미 연결된 channels 세션의 WebSocket을 끊어버립니다. 플래그 검사를 통해 일반 세션은 tools-only 모드로 동작하여 기존 channels 세션을 보호합니다.
세션 A: claude --channels server:slack <- Socket Mode 연결 O
세션 B: claude <- Socket Mode 스킵 (tools-only)
세션 C: claude <- Socket Mode 스킵 (tools-only)
세션 A의 연결은 안전하게 유지됨Socket Preemption (PID 파일 기반 단일 인스턴스 보장)
Slack Socket Mode는 앱당 하나의 WebSocket 연결만 허용합니다. 두 번째 연결이 생기면 첫 번째가 끊기며 이벤트가 유실될 수 있습니다. PID 파일(~/.claude/channels/slack/socket.pid)을 사용하여 항상 최신 인스턴스만 Socket Mode를 유지합니다.
인스턴스 A 시작 인스턴스 B 시작
│ │
v │
PID 파일 없음 │
│ │
v │
socket.pid <- A.pid 기록 │
│ │
v │
Socket Mode 연결 O │
이벤트 수신 중... │
│ v
│ socket.pid 읽기 -> A.pid
│ │
│ v
│ kill(A.pid, 0) -> 생존 확인
│ │
│<──── SIGTERM ─────────────── kill(A.pid, SIGTERM)
│ │
v v
cleanupPidFile() socket.pid <- B.pid 기록
PID 파일 정리 (자기 PID일 때만) │
│ v
v Socket Mode 연결 O
프로세스 종료 이벤트 수신 중...PID 파일 안전 장치:
- 생존 확인:
process.kill(oldPid, 0)— signal 0은 프로세스를 죽이지 않고 존재 여부만 확인합니다. 이미 종료된 프로세스에 SIGTERM을 보내는 것을 방지합니다. - 소유권 확인:
cleanupPidFile()은 PID 파일의 내용이 자신의 PID일 때만 삭제합니다. 새 인스턴스가 이미 PID를 덮어썼다면 건드리지 않습니다. - 종료 핸들링:
exit,SIGTERM,SIGINT세 가지 시그널 모두에서 PID 파일을 정리합니다.
인바운드 메시지 파이프라인
Slack에서 메시지가 들어오면 5단계 파이프라인을 거칩니다. 각 단계에서 조건을 만족하지 않으면 메시지를 drop합니다.
Slack Event (message / app_mention)
│
v
┌─────────────┐
│ 1. Dedup │──── 이미 처리한 이벤트? ──── drop
└──────┬──────┘
│ 새 이벤트
v
┌─────────────┐
│ 2. Stale │──── 10분 이상 된 이벤트? ──── drop
└──────┬──────┘
│ 최근 이벤트
v
┌─────────────┐
│ 3. Empty │──── 텍스트/파일/블록 없음? ──── drop
└──────┬──────┘
│ 내용 있음
v
┌─────────────┐
│ 4. Gate │──── 봇 자신? subtype 불허? ──── drop
│ (접근제어) │──── allowlist에 없음? ──────── drop
└──────┬──────┘
│ 허용된 사용자
v
┌─────────────┐
│ 5. Deliver │──── permalink 생성
│ │──── ack reaction 추가
│ │──── mcp.notification() -> Claude
└─────────────┘아웃바운드 도구
Claude가 사용할 수 있는 MCP 도구:
| 도구 | 기능 | Slack API |
|---|---|---|
| reply | 메시지 전송 (mrkdwn 포맷 변환, ack reaction 자동 제거) | chat.postMessage |
| react | 이모지 리액션 추가 | reactions.add |
| delete_bot_message | 봇 자신의 메시지 삭제 | chat.delete |
| fetch_dm_thread | DM 스레드 내용 읽기 (is_dm=true일 때만 사용) | conversations.replies |
모든 아웃바운드 호출은 audit log에 기록되며, outbound gate를 통과해야 합니다 (인바운드를 받은 채널에만 응답 가능).
보안
┌───────────────────────────────────────────────────────┐
│ Inbound Security │
│ │
│ Allowlist Gate ── SLACK_ALLOW_FROM에 등록된 │
│ 사용자만 Claude에 메시지 전달 │
│ │
│ Bot Owner ────── SLACK_BOT_OWNER는 항상 허용 │
│ │
│ Self-loop ────── 봇 자신의 메시지 자동 drop │
│ │
│ Dedup ────────── 중복 이벤트 필터링 (TTL 10분) │
│ │
├───────────────────────────────────────────────────────┤
│ Outbound Security │
│ │
│ Outbound Gate ── 인바운드 수신 이력이 있는 │
│ 채널에만 응답 허용 │
│ │
│ Prompt Hardening ── 시스템 프롬프트에서 │
│ 설정 변경 요청 거부 │
└───────────────────────────────────────────────────────┘데이터 흐름 상세
Permission Request
도구 실행에 권한 확인이 필요할 때 Slack으로 알림을 보냅니다:
Claude Code ──> permission_request notification
│
v
server.ts 수신
│
v
마지막 inbound 채널/스레드로
Slack 알림 전송 (경고색 #f0ad4e)
│
v
사용자가 Slack에서 확인
(승인/거부는 터미널에서)Ack Reaction
메시지 수신 확인과 답장 완료를 이모지로 표시합니다:
메시지 수신 ──> ack reaction 추가 (예: eyes)
│
v
Claude가 처리 중...
│
v
reply 도구 호출 시
ack reaction 자동 제거Slack App 설정
이미 Slack App이 있다면 (예: slack MCP 서버용), 같은 앱에 아래 설정을 추가합니다.
1. Socket Mode 활성화
- api.slack.com/apps → 앱 선택
- Settings → Socket Mode → Enable
- App-Level Token 생성 →
connections:write스코프 →xapp-...토큰 복사
2. Event Subscriptions 활성화
Event Subscriptions → Enable → Bot Events 구독:
message.im— DMmessage.channels— 공개 채널message.groups— 비공개 채널app_mention— @ 멘션
3. Bot Token Scopes 추가
OAuth & Permissions → Bot Token Scopes에 추가:
chat:write— 메시지 전송channels:history— 공개 채널 읽기groups:history— 비공개 채널 읽기im:history— DM 읽기reactions:write— 리액션 추가/제거users:read— 사용자 이름 확인
4. 워크스페이스에 재설치
OAuth & Permissions → Reinstall to Workspace → Bot Token (xoxb-...) 복사
프로젝트 구조
server.ts MCP 서버, Slack 클라이언트, 이벤트 핸들링
tools.ts MCP 도구 등록 (reply, react, delete_bot_message, fetch_dm_thread)
lib/
gate.ts 접근 제어 (Access, GateOptions, gate)
process.ts 부모 프로세스 channels 플래그 감지 (hasChannelsFlag)
security.ts 아웃바운드 게이트 (assertOutboundAllowed)
formatting.ts Slack mrkdwn 변환, 메시지 텍스트 추출
audit.ts 감사 로그 (AuditEntry, auditLog)
event.ts DM 판별, 스레드 해석, stale/empty 필터링
resilience.ts 이벤트 중복 제거 (EventDeduplicator)
permalink.ts Slack 퍼마링크 빌더
index.ts barrel re-export
server.test.ts lib/ 테스트 (bun:test)개발
bun test # 테스트 실행
bun run typecheck # 타입 체크라이선스
MIT
