yeoamlog/slack-mcp-server
If you are the rightful owner of slack-mcp-server and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to henry@mcphub.com.
This project is a complete implementation of a Slack MCP Server using FastMCP v2, integrating Slack API with essential, optional, and bonus features.
Slack MCP Server - Complete Implementation
π νλ‘μ νΈ κ°μ
FastMCP v2λ₯Ό μ¬μ©νμ¬ κ΅¬νν μμ ν Slack API μ°λ MCP μλ²μ λλ€. κ³Όμ κ°μ΄λλΌμΈμ λ°λΌ νμ κΈ°λ₯ 4κ°, μ ν κΈ°λ₯ 4κ°, κ·Έλ¦¬κ³ λ³΄λμ€ λ½λͺ¨λλ‘ νμ΄λ¨Έ κΈ°λ₯ 4κ°λ₯Ό λͺ¨λ ꡬννμ΅λλ€.
π― μ£Όμ νΉμ§
- β μμ ν UTF-8 νκΈ μ§μ - λͺ¨λ λ©μμ§μμ νκΈ μλ²½ μ§μ
- β μ΄μ€ ν ν° μμ€ν - Bot Token + User TokenμΌλ‘ λͺ¨λ κΈ°λ₯ μ§μ
- β μ€λ§νΈ νμΌ μ λ‘λ - ν¬κΈ°λ³ μ΅μ μ λ‘λ λ°©μ μλ μ ν
- β λ½λͺ¨λλ‘ νμ΄λ¨Έ - μλ μλ¦Ό κΈ°λ₯μ΄ ν¬ν¨λ μκ° κ΄λ¦¬ λꡬ
- β λΉλκΈ° μ²λ¦¬ - κ³ μ±λ₯ asyncio κΈ°λ° κ΅¬ν
- β μμΈν μλ¬ νΈλ€λ§ - λͺ¨λ API νΈμΆμ λν μ μ ν μμΈ μ²λ¦¬
π κΈ°λ₯ λͺ©λ‘
π΄ νμ κΈ°λ₯ (Required Features - 4κ°)
-
send_slack_message
- λ©μμ§ μ μ‘- μ±λ λλ DMμ λ©μμ§ μ μ‘
- μ€λ λ λ΅κΈ μ§μ
- μμ ν UTF-8 νκΈ μ§μ
-
get_slack_channels
- μ±λ λͺ©λ‘ μ‘°ν- 곡κ°/λΉκ³΅κ° μ±λ ꡬλΆ
- λ©€λ²μ μν νμΈ
- 보κ΄λ μ±λ νν°λ§
-
get_slack_channel_history
- λ©μμ§ νμ€ν 리 μ‘°ν- μ΅μ λ©μμ§λΆν° μ‘°ν
- μκ° λ²μ μ§μ κ°λ₯
- λ©μμ§ λ©νλ°μ΄ν° ν¬ν¨
-
send_slack_direct_message
- DM μ μ‘- νΉμ μ¬μ©μμκ² 1:1 λ©μμ§ μ μ‘
- μλ DM μ±λ μμ±
- λ΄ μ¬μ©μ νν°λ§
π‘ μ ν κΈ°λ₯ (Optional Features - 4κ°)
-
get_slack_users
- μ¬μ©μ λͺ©λ‘ μ‘°ν- μ¬μ©μ νμ λ³ λΆλ₯ (κ΄λ¦¬μ, λ©€λ², κ²μ€νΈ, λ΄)
- DM κ°λ₯ μ¬μ©μ νν°λ§
- μμΈν νλ‘ν μ 보
-
search_slack_messages
- λ©μμ§ κ²μ (User Token νμ)- ν€μλ κΈ°λ° μ 체 μν¬μ€νμ΄μ€ κ²μ
- μ λ ¬ λ° νν°λ§ μ΅μ
- κ²μ κ²°κ³Ό λ©νλ°μ΄ν°
-
upload_file_to_slack
- μ€λ§νΈ νμΌ μ λ‘λ- νμΌ ν¬κΈ°λ³ μ΅μ μ λ‘λ λ°©μ
- λ€μν νμΌ νμ μ§μ
- μλ 미리보기 λ° μ½λ νμ΄λΌμ΄ν
-
add_slack_reaction
- λ©μμ§ λ°μ μΆκ°- μ΄λͺ¨μ§ λ°μ μΆκ°
- λ€μν μ΄λͺ¨μ§ νμ μ§μ
π’ 보λμ€ κΈ°λ₯ (Bonus Features - 4κ°)
-
start_pomodoro_timer
- λ½λͺ¨λλ‘ νμ΄λ¨Έ μμ- 5κ°μ§ νμ΄λ¨Έ νμ (study, work, break, meeting, custom)
- μλ μμ/μ’ λ£ μλ¦Ό
- μ¬μ©μ μ μ μκ° λ° λ©μμ§
-
cancel_pomodoro_timer
- νμ΄λ¨Έ μ·¨μ- μ€ν μ€μΈ νμ΄λ¨Έ μ¦μ μ€λ¨
- μ·¨μ μλ¦Ό μ μ‘
-
list_active_timers
- νμ± νμ΄λ¨Έ λͺ©λ‘- νμ¬ μ€ν μ€μΈ λͺ¨λ νμ΄λ¨Έ
- μ§νλ₯ λ° λ¨μ μκ° νμ
-
get_timer_status
- νμ΄λ¨Έ μν μ‘°ν- νΉμ νμ΄λ¨Έμ μμΈ μν
- μ€μκ° μ§ν μν©
π οΈ μ νΈλ¦¬ν° κΈ°λ₯
test_slack_connection
- μ°κ²° ν μ€νΈget_workspace_info
- μν¬μ€νμ΄μ€ μ 보get_file_preview
- νμΌ λ―Έλ¦¬λ³΄κΈ°verify_or_create_file
- νμΌ νμΈ/μμ±
π¦ μ€μΉ λ° μ€ν λ°©λ²
1. νλ‘μ νΈ μ΄κΈ°ν
# νλ‘μ νΈ ν΄λ‘ λλ λ€μ΄λ‘λ ν
cd slack-mcp
# uv ν¨ν€μ§ λ§€λμ μ€μΉ (μλ κ²½μ°)
curl -LsSf https://astral.sh/uv/install.sh | sh
# κ°μνκ²½ μμ± λ° νμ±ν
uv venv
source .venv/bin/activate # Linux/macOS
# .venv\Scripts\activate # Windows
# μμ‘΄μ± μ€μΉ
uv sync
# λλ
uv pip install -r requirements.txt
2. νκ²½ λ³μ μ€μ
.env
νμΌμ μμ±νκ³ λ€μκ³Ό κ°μ΄ μ€μ :
# νμ: Slack Bot Token (xoxb-λ‘ μμ)
SLACK_BOT_TOKEN=xoxb-your-bot-token-here
# μ ν: Slack User Token (xoxp-λ‘ μμ) - κ²μ, νμΌμ
λ‘λμ©
SLACK_USER_TOKEN=xoxp-your-user-token-here
# μ ν: κΈ°λ³Έ μ±λ ID (ν
μ€νΈμ©)
SLACK_TEST_CHANNEL_ID=C08UZKK9Q4R
# μ ν: λ‘κ·Έ λ 벨
LOG_LEVEL=INFO
3. Slack App μ€μ
νμν Bot Token Scopes:
channels:read # μ±λ λͺ©λ‘ μ‘°ν
channels:history # μ±λ λ©μμ§ νμ€ν 리 μ‘°ν
chat:write # λ©μμ§ μ μ‘
im:read # DM μ±λ μ½κΈ°
im:write # DM λ©μμ§ μ μ‘
im:history # DM νμ€ν 리 μ‘°ν
users:read # μ¬μ©μ μ 보 μ‘°ν
reactions:write # λ°μ μΆκ°
μΆκ° User Token Scopes (μ ν κΈ°λ₯μ©):
search:read # λ©μμ§ κ²μ
files:write # νμΌ μ
λ‘λ
4. μλ² μ€ν
# MCP μλ² μ€ν
python slack_mcp_server.py
# λλ μ§μ μ€ν
uv run slack_mcp_server.py
π‘ μ¬μ©λ² λ° μμ
κΈ°λ³Έ λ©μμ§ μ μ‘
# Claude/LLMμ΄ λꡬ νΈμΆ μ
send_slack_message(
channel="C08UZKK9Q4R",
text="μλ
νμΈμ! MCPμμ 보λ΄λ λ©μμ§μ
λλ€! π"
)
νμΌ μ λ‘λ (μ€λ§νΈ μ²λ¦¬)
# λ€μν ν¬κΈ°μ νμΌμ μλμΌλ‘ μ΅μ λ°©μμΌλ‘ μ
λ‘λ
upload_file_to_slack(
file_path="./report.pdf",
channels="C08UZKK9Q4R",
title="λΆμ λ³΄κ³ μ",
comment="μκ° λ°μ΄ν° λΆμ κ²°κ³Όμ
λλ€."
)
λ½λͺ¨λλ‘ νμ΄λ¨Έ μ¬μ©
# μμ
νμ΄λ¨Έ μμ (50λΆ)
start_pomodoro_timer(
timer_type="study",
channel_id="C08UZKK9Q4R",
duration_minutes=50,
custom_name="νμ΄μ¬ κ³ κΈ λ¬Έλ² νμ΅"
)
# νμ± νμ΄λ¨Έ νμΈ
list_active_timers()
# νμ΄λ¨Έ μ·¨μ
cancel_pomodoro_timer("study_20250602_143022_123456")
λ©μμ§ κ²μ (User Token νμ)
# μν¬μ€νμ΄μ€ μ 체μμ λ©μμ§ κ²μ
search_slack_messages(
query="MCP μλ²",
count=10,
sort="timestamp"
)
ποΈ νλ‘μ νΈ κ΅¬μ‘°
slack-mcp/
βββ .env # νκ²½ λ³μ (Git μ μΈ)
βββ .env.example # νκ²½ λ³μ ν
νλ¦Ώ
βββ .gitignore # Git 무μ νμΌ
βββ README.md # μ΄ νμΌ
βββ requirements.txt # μμ‘΄μ± λͺ©λ‘
βββ pyproject.toml # νλ‘μ νΈ μ€μ
βββ slack_api_client.py # Slack API ν΄λΌμ΄μΈνΈ (ν΅μ¬ λͺ¨λ)
βββ pomodoro_timer.py # λ½λͺ¨λλ‘ νμ΄λ¨Έ λͺ¨λ
βββ slack_mcp_server.py # FastMCP μλ² λ©μΈ
π§ κΈ°μ μ ꡬν μΈλΆμ¬ν
μ΄μ€ ν ν° μμ€ν
- Bot Token (xoxb-): μΌλ°μ μΈ λ΄ κΈ°λ₯ (λ©μμ§ μ μ‘, μ±λ μ‘°ν λ±)
- User Token (xoxp-): μ¬μ©μ κΆν νμν κΈ°λ₯ (κ²μ, λμ©λ νμΌ μ λ‘λ)
μ€λ§νΈ νμΌ μ λ‘λ μ λ΅
- μμ ν μ€νΈ νμΌ (< 50KB): λ©μμ§ λ΄μ©μΌλ‘ μ§μ μ μ‘
- μ€κ° νμΌ (50KB - 1MB): μ½λ μ€λν«μΌλ‘ μ λ‘λ
- μΌλ° νμΌ (1MB - 100MB): νμ€ νμΌ μ λ‘λ
- λμ©λ νμΌ (100MB - 1GB): User TokenμΌλ‘ μ λ‘λ
- μ΄λμ©λ νμΌ (> 1GB): νμΌ μ λ³΄λ§ κ³΅μ
λΉλκΈ° μ²λ¦¬ λ° λμμ±
asyncio
κΈ°λ° μμ λΉλκΈ° ꡬν- λ½λͺ¨λλ‘ νμ΄λ¨Έμ λ³λ ¬ μ€ν μ§μ
- λ½(Lock)μ ν΅ν ν΄λΌμ΄μΈνΈ μ΄κΈ°ν μμ μ± λ³΄μ₯
UTF-8 νκΈ μ§μ
headers = {
'Content-Type': 'application/json; charset=utf-8'
}
π κ°λ° κ³Όμ μμ κ²ͺμ μ΄λ €μκ³Ό ν΄κ²° λ°©λ²
1. ν¨μλ€ κ°μ Input λ³μλͺ ν΅μΌ μ΄λ €μ
λ¬Έμ : API ν¨μλ§λ€ λ§€κ°λ³μ μ΄λ¦μ΄ λ¬λΌμ μΌκ΄μ± λΆμ‘±
channel
vschannel_id
vschannels
text
vsmessage
vscontent
ν΄κ²°μ± :
- Slack API 곡μ λ¬Έμ κΈ°μ€μΌλ‘ λ³μλͺ ν΅μΌ(ν₯ν μ λ°μ΄νΈ μμ )
- λ΄λΆ ν¨μμμλ μΌκ΄λ λ€μ΄λ° 컨벀μ μ μ©
- docstringμ λͺ νν λ§€κ°λ³μ μ€λͺ μΆκ°
async def send_message(
self,
channel: str, # κ°λ₯ν λΆλΆλ€μ channel λ‘ ν΅μΌ μμ
text: str, # ν΅μΌ: text
thread_ts: Optional[str] = None
) -> Dict[str, Any]:
2. Input-Output λ³μλͺ μ°ΎκΈ° μ΄λ €μ
λ¬Έμ : Slack API μλ΅μ 볡μ‘ν μ€μ²© κ΅¬μ‘°λ‘ νμν λ°μ΄ν° μΆμΆ μ΄λ €μ
ν΄κ²°μ± :
- API μλ΅μ λ‘κ·Έλ‘ μΆλ ₯νμ¬ κ΅¬μ‘° νμ
- κ³΅ν΅ λ°μ΄ν° μΆμΆ ν¨μ μμ±
- μλ΅ λ°μ΄ν° μ κ·ν λ° ν¬λ§·ν
# μλ΅ λ°μ΄ν° μ κ·ν μμ
formatted_messages.append({
'text': msg.get('text', ''),
'user': msg.get('user', 'Unknown'),
'timestamp': readable_time,
'ts': msg.get('ts', ''),
# ... νμν νλλ§ μΆμΆ
})
3. Outdated file_upload ν¨μλ₯Ό μ΅μ λ²μ μΌλ‘ μ¬μ©νκΈ°κΉμ§ λ§μ μνμ°©μ€
λ¬Έμ : Slackμ νμΌ μ λ‘λ APIκ° μ¬λ¬ λ² λ³κ²½λμ΄ κΈ°μ‘΄ λ°©μ deprecated
ν΄κ²°μ± :
files.upload
(deprecated) βfiles.getUploadURLExternal
+files.completeUploadExternal
- Slack SDKμ κΈ°μ‘΄ REST APIμ νμ΄λΈλ¦¬λ μ κ·Όλ²
- νμΌ ν¬κΈ°λ³ λ€λ₯Έ μ λ‘λ μ λ΅ κ΅¬ν
# μλ‘μ΄ νμΌ μ
λ‘λ νλ‘μ°
# 1. μ
λ‘λ URL μμ²
upload_response = await self._make_request('files.getUploadURLExternal', 'POST', upload_data)
# 2. μΈλΆ URLλ‘ νμΌ μ
λ‘λ
async with self._session.put(upload_url, data=file_content) as response:
# νμΌ μ
λ‘λ
# 3. μ
λ‘λ μλ£ μ²λ¦¬
complete_response = await self._make_request('files.completeUploadExternal', 'POST', complete_data)
4. User Tokenμ΄ νμν κ²½μ° vs Bot Tokenλ§μΌλ‘ ν΄κ²°λλ κ²½μ°
λ¬Έμ : μ΄λ€ κΈ°λ₯μ μ΄λ€ ν ν°μ΄ νμνμ§ νμ νκΈ° μ΄λ €μ
ν΄κ²°μ± :
- κΈ°λ₯λ³ ν ν° μꡬμ¬ν λͺ νν λΆλ₯
- μ΄μ€ ν ν° μμ€ν μΌλ‘ μλ μ ν
- ν ν° λΆμ‘± μ λͺ νν μλ΄ λ©μμ§
# Bot TokenμΌλ‘ κ°λ₯ν κΈ°λ₯
- λ©μμ§ μ μ‘ (chat.postMessage)
- μ±λ λͺ©λ‘ (conversations.list)
- μ¬μ©μ λͺ©λ‘ (users.list)
- νμΌ μ
λ‘λ (< 100MB)
# User Tokenμ΄ νμν κΈ°λ₯
- λ©μμ§ κ²μ (search.messages)
- λμ©λ νμΌ μ
λ‘λ (> 100MB)
5. GET vs POSTμ μ°¨μ΄
λ¬Έμ : μΈμ GETμ μ°κ³ μΈμ POSTλ₯Ό μ¨μΌ νλμ§ νΌλ
ν΄κ²°μ± :
- Slack API λ¬Έμμ HTTP λ©μλ μ νν νμΈ
- λ°μ΄ν° μ‘°νλ GET, λ°μ΄ν° μμ±/μμ μ POST
- ν΅μΌλ
_make_request
ν¨μλ‘ μ²λ¦¬
# GET: λ°μ΄ν° μ‘°ν
await self._make_request('conversations.list', 'GET', params)
await self._make_request('users.list', 'GET', params)
# POST: λ°μ΄ν° μμ±/μμ
await self._make_request('chat.postMessage', 'POST', data)
await self._make_request('files.getUploadURLExternal', 'POST', data)
6. κΈ°ν ν΄κ²°ν μ΄μλ€
- Rate Limiting: μ§μ λ°±μ€νμ μ¬μλ λ‘μ§
- Error Handling: κ° μλ¬ μ½λλ³ λ§μΆ€ν ν΄κ²° μ μ
- UTF-8 Encoding: νκΈ λ©μμ§ μλ²½ μ§μ
- Async Safety: λ½λͺ¨λλ‘ νμ΄λ¨Έμ λμ μ€ν μ²λ¦¬
π μ±λ₯ λ° μ νμ¬ν
νμΌ μ λ‘λ μ ν
- λ¬΄λ£ νλ: μ΅λ 5GB μν¬μ€νμ΄μ€ μ€ν 리μ§
- κ°λ³ νμΌ: μ΅λ 1GB (μ λ£ νλμμ)
- Bot Token: μ΅λ 100MB νμΌ μ λ‘λ
- User Token: μ΅λ 1GB νμΌ μ λ‘λ
API Rate Limits
- Tier 1 λ©μλ: 1+ per minute
- Tier 2 λ©μλ: 20+ per minute
- Tier 3 λ©μλ: 50+ per minute
- Tier 4 λ©μλ: 100+ per minute
π ν μ€νΈ λ°©λ²
1. μ°κ²° ν μ€νΈ
# μλ² μ€ν ν Claudeμμ ν
μ€νΈ
test_slack_connection()
2. κΈ°λ³Έ κΈ°λ₯ ν μ€νΈ
# μ±λ λͺ©λ‘ μ‘°ν
get_slack_channels()
# λ©μμ§ μ μ‘ ν
μ€νΈ
send_slack_message("C08UZKK9Q4R", "ν
μ€νΈ λ©μμ§μ
λλ€! π")
3. κ³ κΈ κΈ°λ₯ ν μ€νΈ
# νμΌ μ
λ‘λ ν
μ€νΈ
upload_file_to_slack("./test.txt", title="ν
μ€νΈ νμΌ")
# λ½λͺ¨λλ‘ νμ΄λ¨Έ ν
μ€νΈ
start_pomodoro_timer("work", duration_minutes=25, custom_name="ν
μ€νΈ μμ
")
π€ κΈ°μ¬ λ°©λ²
- μ΄μ μ 보: GitHub Issues μ¬μ©
- κΈ°λ₯ μ μ: Feature Request ν νλ¦Ώ μ¬μ©
- μ½λ κΈ°μ¬: Pull Request νμ
- λ¬Έμ κ°μ : README λ° docstring κ°μ
π λΌμ΄μ μ€
μ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€λ₯Ό λ°λ¦ λλ€.
πββοΈ λ¬Έμ λ° μ§μ
- κ°λ°μ: JunHyuck Kwon
- λ²μ : 8.5.2 (Complete Implementation)
- μ΅μ’ μ λ°μ΄νΈ: 2025-06-04
κ³Όμ μμ±λ: β νμ 4κ° + β μ ν 4κ° + β 보λμ€ 4κ° + β μ νΈλ¦¬ν° 4κ° = μ΄ 16κ° κΈ°λ₯ μμ ꡬν
μ΄ νλ‘μ νΈλ₯Ό ν΅ν΄ μ€μ μ 무μμ νμ©ν μ μλ κ³ νμ§ Slack μ°λ λꡬλ₯Ό ꡬμΆν μ μμ΅λλ€! π