README.mdβ’12.7 kB
# Slack MCP Server - Complete Implementation
## π νλ‘μ νΈ κ°μ
FastMCP v2λ₯Ό μ¬μ©νμ¬ κ΅¬νν μμ ν Slack API μ°λ MCP μλ²μ
λλ€. κ³Όμ κ°μ΄λλΌμΈμ λ°λΌ **νμ κΈ°λ₯ 4κ°**, **μ ν κΈ°λ₯ 4κ°**, κ·Έλ¦¬κ³ **보λμ€ λ½λͺ¨λλ‘ νμ΄λ¨Έ κΈ°λ₯ 4κ°**λ₯Ό λͺ¨λ ꡬννμ΅λλ€.
### π― μ£Όμ νΉμ§
- β
**μμ ν UTF-8 νκΈ μ§μ** - λͺ¨λ λ©μμ§μμ νκΈ μλ²½ μ§μ
- β
**μ΄μ€ ν ν° μμ€ν
** - Bot Token + User TokenμΌλ‘ λͺ¨λ κΈ°λ₯ μ§μ
- β
**μ€λ§νΈ νμΌ μ
λ‘λ** - ν¬κΈ°λ³ μ΅μ μ
λ‘λ λ°©μ μλ μ ν
- β
**λ½λͺ¨λλ‘ νμ΄λ¨Έ** - μλ μλ¦Ό κΈ°λ₯μ΄ ν¬ν¨λ μκ° κ΄λ¦¬ λꡬ
- β
**λΉλκΈ° μ²λ¦¬** - κ³ μ±λ₯ asyncio κΈ°λ° κ΅¬ν
- β
**μμΈν μλ¬ νΈλ€λ§** - λͺ¨λ API νΈμΆμ λν μ μ ν μμΈ μ²λ¦¬
## π κΈ°λ₯ λͺ©λ‘
### π΄ νμ κΈ°λ₯ (Required Features - 4κ°)
1. **`send_slack_message`** - λ©μμ§ μ μ‘
- μ±λ λλ DMμ λ©μμ§ μ μ‘
- μ€λ λ λ΅κΈ μ§μ
- μμ ν UTF-8 νκΈ μ§μ
2. **`get_slack_channels`** - μ±λ λͺ©λ‘ μ‘°ν
- 곡κ°/λΉκ³΅κ° μ±λ ꡬλΆ
- λ©€λ²μ μν νμΈ
- 보κ΄λ μ±λ νν°λ§
3. **`get_slack_channel_history`** - λ©μμ§ νμ€ν 리 μ‘°ν
- μ΅μ λ©μμ§λΆν° μ‘°ν
- μκ° λ²μ μ§μ κ°λ₯
- λ©μμ§ λ©νλ°μ΄ν° ν¬ν¨
4. **`send_slack_direct_message`** - DM μ μ‘
- νΉμ μ¬μ©μμκ² 1:1 λ©μμ§ μ μ‘
- μλ DM μ±λ μμ±
- λ΄ μ¬μ©μ νν°λ§
### π‘ μ ν κΈ°λ₯ (Optional Features - 4κ°)
5. **`get_slack_users`** - μ¬μ©μ λͺ©λ‘ μ‘°ν
- μ¬μ©μ νμ
λ³ λΆλ₯ (κ΄λ¦¬μ, λ©€λ², κ²μ€νΈ, λ΄)
- DM κ°λ₯ μ¬μ©μ νν°λ§
- μμΈν νλ‘ν μ 보
6. **`search_slack_messages`** - λ©μμ§ κ²μ (User Token νμ)
- ν€μλ κΈ°λ° μ 체 μν¬μ€νμ΄μ€ κ²μ
- μ λ ¬ λ° νν°λ§ μ΅μ
- κ²μ κ²°κ³Ό λ©νλ°μ΄ν°
7. **`upload_file_to_slack`** - μ€λ§νΈ νμΌ μ
λ‘λ
- νμΌ ν¬κΈ°λ³ μ΅μ μ
λ‘λ λ°©μ
- λ€μν νμΌ νμ μ§μ
- μλ 미리보기 λ° μ½λ νμ΄λΌμ΄ν
8. **`add_slack_reaction`** - λ©μμ§ λ°μ μΆκ°
- μ΄λͺ¨μ§ λ°μ μΆκ°
- λ€μν μ΄λͺ¨μ§ νμ μ§μ
### π’ 보λμ€ κΈ°λ₯ (Bonus Features - 4κ°)
9. **`start_pomodoro_timer`** - λ½λͺ¨λλ‘ νμ΄λ¨Έ μμ
- 5κ°μ§ νμ΄λ¨Έ νμ
(study, work, break, meeting, custom)
- μλ μμ/μ’
λ£ μλ¦Ό
- μ¬μ©μ μ μ μκ° λ° λ©μμ§
10. **`cancel_pomodoro_timer`** - νμ΄λ¨Έ μ·¨μ
- μ€ν μ€μΈ νμ΄λ¨Έ μ¦μ μ€λ¨
- μ·¨μ μλ¦Ό μ μ‘
11. **`list_active_timers`** - νμ± νμ΄λ¨Έ λͺ©λ‘
- νμ¬ μ€ν μ€μΈ λͺ¨λ νμ΄λ¨Έ
- μ§νλ₯ λ° λ¨μ μκ° νμ
12. **`get_timer_status`** - νμ΄λ¨Έ μν μ‘°ν
- νΉμ νμ΄λ¨Έμ μμΈ μν
- μ€μκ° μ§ν μν©
### π οΈ μ νΈλ¦¬ν° κΈ°λ₯
13. **`test_slack_connection`** - μ°κ²° ν
μ€νΈ
14. **`get_workspace_info`** - μν¬μ€νμ΄μ€ μ 보
15. **`get_file_preview`** - νμΌ λ―Έλ¦¬λ³΄κΈ°
16. **`verify_or_create_file`** - νμΌ νμΈ/μμ±
## π¦ μ€μΉ λ° μ€ν λ°©λ²
### 1. νλ‘μ νΈ μ΄κΈ°ν
```bash
# νλ‘μ νΈ ν΄λ‘ λλ λ€μ΄λ‘λ ν
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` νμΌμ μμ±νκ³ λ€μκ³Ό κ°μ΄ μ€μ :
```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. μλ² μ€ν
```bash
# MCP μλ² μ€ν
python slack_mcp_server.py
# λλ μ§μ μ€ν
uv run slack_mcp_server.py
```
## π‘ μ¬μ©λ² λ° μμ
### κΈ°λ³Έ λ©μμ§ μ μ‘
```python
# Claude/LLMμ΄ λꡬ νΈμΆ μ
send_slack_message(
channel="C08UZKK9Q4R",
text="μλ
νμΈμ! MCPμμ 보λ΄λ λ©μμ§μ
λλ€! π"
)
```
### νμΌ μ
λ‘λ (μ€λ§νΈ μ²λ¦¬)
```python
# λ€μν ν¬κΈ°μ νμΌμ μλμΌλ‘ μ΅μ λ°©μμΌλ‘ μ
λ‘λ
upload_file_to_slack(
file_path="./report.pdf",
channels="C08UZKK9Q4R",
title="λΆμ λ³΄κ³ μ",
comment="μκ° λ°μ΄ν° λΆμ κ²°κ³Όμ
λλ€."
)
```
### λ½λͺ¨λλ‘ νμ΄λ¨Έ μ¬μ©
```python
# μμ
νμ΄λ¨Έ μμ (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 νμ)
```python
# μν¬μ€νμ΄μ€ μ 체μμ λ©μμ§ κ²μ
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-)**: μ¬μ©μ κΆν νμν κΈ°λ₯ (κ²μ, λμ©λ νμΌ μ
λ‘λ)
### μ€λ§νΈ νμΌ μ
λ‘λ μ λ΅
1. **μμ ν
μ€νΈ νμΌ (< 50KB)**: λ©μμ§ λ΄μ©μΌλ‘ μ§μ μ μ‘
2. **μ€κ° νμΌ (50KB - 1MB)**: μ½λ μ€λν«μΌλ‘ μ
λ‘λ
3. **μΌλ° νμΌ (1MB - 100MB)**: νμ€ νμΌ μ
λ‘λ
4. **λμ©λ νμΌ (100MB - 1GB)**: User TokenμΌλ‘ μ
λ‘λ
5. **μ΄λμ©λ νμΌ (> 1GB)**: νμΌ μ λ³΄λ§ κ³΅μ
### λΉλκΈ° μ²λ¦¬ λ° λμμ±
- `asyncio` κΈ°λ° μμ λΉλκΈ° ꡬν
- λ½λͺ¨λλ‘ νμ΄λ¨Έμ λ³λ ¬ μ€ν μ§μ
- λ½(Lock)μ ν΅ν ν΄λΌμ΄μΈνΈ μ΄κΈ°ν μμ μ± λ³΄μ₯
### UTF-8 νκΈ μ§μ
```python
headers = {
'Content-Type': 'application/json; charset=utf-8'
}
```
## π κ°λ° κ³Όμ μμ κ²ͺμ μ΄λ €μκ³Ό ν΄κ²° λ°©λ²
### 1. ν¨μλ€ κ°μ Input λ³μλͺ
ν΅μΌ μ΄λ €μ
**λ¬Έμ **: API ν¨μλ§λ€ λ§€κ°λ³μ μ΄λ¦μ΄ λ¬λΌμ μΌκ΄μ± λΆμ‘±
- `channel` vs `channel_id` vs `channels`
- `text` vs `message` vs `content`
**ν΄κ²°μ±
**:
- Slack API 곡μ λ¬Έμ κΈ°μ€μΌλ‘ λ³μλͺ
ν΅μΌ(ν₯ν μ
λ°μ΄νΈ μμ )
- λ΄λΆ ν¨μμμλ μΌκ΄λ λ€μ΄λ° 컨벀μ
μ μ©
- docstringμ λͺ
νν λ§€κ°λ³μ μ€λͺ
μΆκ°
```python
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 μλ΅μ λ‘κ·Έλ‘ μΆλ ₯νμ¬ κ΅¬μ‘° νμ
- κ³΅ν΅ λ°μ΄ν° μΆμΆ ν¨μ μμ±
- μλ΅ λ°μ΄ν° μ κ·ν λ° ν¬λ§·ν
```python
# μλ΅ λ°μ΄ν° μ κ·ν μμ
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μ νμ΄λΈλ¦¬λ μ κ·Όλ²
- νμΌ ν¬κΈ°λ³ λ€λ₯Έ μ
λ‘λ μ λ΅ κ΅¬ν
```python
# μλ‘μ΄ νμΌ μ
λ‘λ νλ‘μ°
# 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λ§μΌλ‘ ν΄κ²°λλ κ²½μ°
**λ¬Έμ **: μ΄λ€ κΈ°λ₯μ μ΄λ€ ν ν°μ΄ νμνμ§ νμ
νκΈ° μ΄λ €μ
**ν΄κ²°μ±
**:
- κΈ°λ₯λ³ ν ν° μꡬμ¬ν λͺ
νν λΆλ₯
- μ΄μ€ ν ν° μμ€ν
μΌλ‘ μλ μ ν
- ν ν° λΆμ‘± μ λͺ
νν μλ΄ λ©μμ§
```python
# 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` ν¨μλ‘ μ²λ¦¬
```python
# 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. μ°κ²° ν
μ€νΈ
```bash
# μλ² μ€ν ν Claudeμμ ν
μ€νΈ
test_slack_connection()
```
### 2. κΈ°λ³Έ κΈ°λ₯ ν
μ€νΈ
```bash
# μ±λ λͺ©λ‘ μ‘°ν
get_slack_channels()
# λ©μμ§ μ μ‘ ν
μ€νΈ
send_slack_message("C08UZKK9Q4R", "ν
μ€νΈ λ©μμ§μ
λλ€! π")
```
### 3. κ³ κΈ κΈ°λ₯ ν
μ€νΈ
```bash
# νμΌ μ
λ‘λ ν
μ€νΈ
upload_file_to_slack("./test.txt", title="ν
μ€νΈ νμΌ")
# λ½λͺ¨λλ‘ νμ΄λ¨Έ ν
μ€νΈ
start_pomodoro_timer("work", duration_minutes=25, custom_name="ν
μ€νΈ μμ
")
```
## π€ κΈ°μ¬ λ°©λ²
1. μ΄μ μ 보: GitHub Issues μ¬μ©
2. κΈ°λ₯ μ μ: Feature Request ν
νλ¦Ώ μ¬μ©
3. μ½λ κΈ°μ¬: Pull Request νμ
4. λ¬Έμ κ°μ : README λ° docstring κ°μ
## π λΌμ΄μ μ€
μ΄ νλ‘μ νΈλ MIT λΌμ΄μ μ€λ₯Ό λ°λ¦
λλ€.
## πββοΈ λ¬Έμ λ° μ§μ
- **κ°λ°μ**: JunHyuck Kwon
- **λ²μ **: 8.5.2 (Complete Implementation)
- **μ΅μ’
μ
λ°μ΄νΈ**: 2025-06-04
---
**κ³Όμ μμ±λ**: β
νμ 4κ° + β
μ ν 4κ° + β
보λμ€ 4κ° + β
μ νΈλ¦¬ν° 4κ° = **μ΄ 16κ° κΈ°λ₯ μμ ꡬν**
μ΄ νλ‘μ νΈλ₯Ό ν΅ν΄ μ€μ μ
무μμ νμ©ν μ μλ κ³ νμ§ Slack μ°λ λꡬλ₯Ό ꡬμΆν μ μμ΅λλ€! π