Skip to main content
Glama
README.md
# πŸš‡ μ§€ν•˜μ²  ν˜Όμž‘λ„ μ•Œλ¦¬λ―Έ MCP μ„œλ²„

> μ„œμšΈ μ§€ν•˜μ²  1~8ν˜Έμ„ μ˜ ν˜Όμž‘λ„ 정보λ₯Ό μ œκ³΅ν•˜λŠ” Model Context Protocol (MCP) μ„œλ²„

μΆœν‡΄κ·Ό μ‹œκ°„λŒ€μ— μ§€ν•˜μ² μ΄ μ–Όλ§ˆλ‚˜ ν˜Όμž‘ν•œμ§€ 미리 μ•Œκ³ , κ°€μž₯ μΎŒμ ν•œ μ‹œκ°„μ— 이동할 수 μžˆλ„λ‘ λ„μ™€μ£ΌλŠ” λ˜‘λ˜‘ν•œ λΉ„μ„œμž…λ‹ˆλ‹€.

---

## πŸ“‹ λͺ©μ°¨

- [μ£Όμš” κΈ°λŠ₯](#-μ£Όμš”-κΈ°λŠ₯)
- [μ™œ 이 ν”„λ‘œμ νŠΈμΈκ°€?](#-μ™œ-이-ν”„λ‘œμ νŠΈμΈκ°€)
- [μ‹œμž‘ν•˜κΈ°](#-μ‹œμž‘ν•˜κΈ°)
- [μ‚¬μš© 방법](#-μ‚¬μš©-방법)
- [폴더 ꡬ쑰](#-폴더-ꡬ쑰)
- [개발 κ°€μ΄λ“œ](#-개발-κ°€μ΄λ“œ)
- [문제 ν•΄κ²°](#-문제-ν•΄κ²°)

---

## ✨ μ£Όμš” κΈ°λŠ₯

### 1. πŸ” 경둜 검색
```
"κ°•λ‚¨μ—­μ—μ„œ μ—­μ‚Όμ—­ κ°€λ €λŠ”λ°"
β†’ 졜적 경둜 + ν˜Όμž‘λ„ 정보 + λŒ€μ•ˆ 경둜 2개 μ œμ‹œ
```

### 2. πŸ“Š μ—­ ν˜Όμž‘λ„ 쑰회
```
"강남역 μ§€κΈˆ ν˜Όμž‘ν•΄?"
β†’ μ‹€μ‹œκ°„ ν˜Όμž‘λ„ + λ‹€μŒ μ—΄μ°¨ 도착 μ‹œκ°„
```

### 3. ⏰ 도착 μ‹œκ°„ 계산
```
"κ°•λ‚¨μ—μ„œ μ—­μ‚ΌκΉŒμ§€ 9μ‹œκΉŒμ§€ 도착해야 ν•΄"
β†’ ν•„μš”ν•œ 좜발 μ‹œκ°„ + κ·Έ μ‹œκ°„λŒ€ ν˜Όμž‘λ„
```

### 4. πŸš† μ—΄μ°¨ 도착 정보
```
"강남역 μ—΄μ°¨ μ–Έμ œ 와?"
β†’ λ‹€μŒ 3개 μ—΄μ°¨ 도착 μ‹œκ°„ + 각 ν˜Όμž‘λ„
```

### 5. πŸ”” μ§€ν•˜μ²  이슈 검색
```
"2ν˜Έμ„  μ§€κΈˆ 지연돼?"
β†’ μ΅œμ‹  μš΄ν–‰ 이슈, κ³ μž₯, μ‹œμœ„ 정보
```

---

## 🎯 μ™œ 이 ν”„λ‘œμ νŠΈμΈκ°€?

### κΈ°μ‘΄ μ•±μ˜ ν•œκ³„
- 넀이버/카카였맡: ν˜Όμž‘λ„ 정보 μ—†μŒ
- μ„œμšΈκ΅ν†΅κ³΅μ‚¬ μ•±: μ‹€μ‹œκ°„ μ •λ³΄λ§Œ 제곡, 예츑 λΆˆκ°€

### 우리의 차별점
βœ… **톡계 기반 예츑**: κ³Όκ±° λ°μ΄ν„°λ‘œ μ‹œκ°„λŒ€λ³„ ν˜Όμž‘λ„ 예츑  
βœ… **μ˜μ‚¬κ²°μ • 지원**: "μ§€κΈˆ vs 30λΆ„ ν›„" 비ꡐ 제곡  
βœ… **λŒ€ν™”ν˜• μΈν„°νŽ˜μ΄μŠ€**: Claude와 μžμ—°μ–΄λ‘œ μ†Œν†΅  
βœ… **λŒ€μ•ˆ μ œμ‹œ**: λ‹¨μˆœ 정보 β†’ 졜적 선택 μΆ”μ²œ

---

## πŸš€ μ‹œμž‘ν•˜κΈ°

### ν•„μˆ˜ μš”κ΅¬μ‚¬ν•­

- **Python** 3.10 이상
- **pip** λ˜λŠ” **uv** (ꢌμž₯)
- **Claude Desktop** (MCP ν΄λΌμ΄μ–ΈνŠΈ)
- API ν‚€ 2개 (무료, ν•„μˆ˜):
  - ODsay API Key
  - μ„œμšΈμ‹œ Open API Key
  - (선택) Kakao REST API Key

---

### μ„€μΉ˜ 방법

#### 1단계: μ €μž₯μ†Œ 볡제
```bash
git clone https://github.com/yourusername/subway-congestion-mcp.git
cd subway-congestion-mcp
```

#### 2단계: κ°€μƒν™˜κ²½ 생성 (ꢌμž₯)
```bash
# uv μ‚¬μš© (ꢌμž₯ - 빠름)
uv venv
source .venv/bin/activate  # Mac/Linux
# λ˜λŠ”
.venv\Scripts\activate  # Windows

# λ˜λŠ” 일반 venv
python -m venv .venv
source .venv/bin/activate
```

#### 3단계: μ˜μ‘΄μ„± μ„€μΉ˜
```bash
# uv μ‚¬μš©
uv pip install -e .

# λ˜λŠ” pip μ‚¬μš©
pip install -e .
```

#### 4단계: ν™˜κ²½ μ„€μ •
```bash
cp .env.example .env
# .env νŒŒμΌμ„ μ—΄μ–΄μ„œ API ν‚€ μž…λ ₯
```

#### 5단계: 데이터 λ‹€μš΄λ‘œλ“œ
```bash
python scripts/download_data.py
```

#### 6단계: ν…ŒμŠ€νŠΈ
```bash
pytest
```
 
#### 7단계: μ‹€ν–‰
```bash
# κ°€μƒν™˜κ²½ ν™œμ„±ν™” μƒνƒœμ—μ„œ μ‹€ν–‰
python -m subway_congestion_mcp
```

---

### API ν‚€ λ°œκΈ‰

#### 1. ODsay API (λŒ€μ€‘κ΅ν†΅ 경둜 검색)

**λ°œκΈ‰ μ‚¬μ΄νŠΈ**: https://lab.odsay.com

1. νšŒμ›κ°€μž… (무료)
2. "API μ‚¬μš© μ‹ μ²­" 클릭
3. μ•± 정보 μž…λ ₯:
   - μ•± 이름: `μ§€ν•˜μ²  ν˜Όμž‘λ„ μ•Œλ¦¬λ―Έ`
   - μ‚¬μš© λͺ©μ : `개인 ν”„λ‘œμ νŠΈ`
   - ν”Œλž«νΌ: `Web/Mobile`
4. API ν‚€ 볡사 β†’ `.env` 파일의 `ODSAY_API_KEY`에 λΆ™μ—¬λ„£κΈ°

**μ œν•œ**: ν•˜λ£¨ 1,000건 (무료)

---

#### 2. μ„œμšΈμ‹œ Open API (μ‹€μ‹œκ°„ 도착 정보)

**λ°œκΈ‰ μ‚¬μ΄νŠΈ**: https://data.seoul.go.kr

1. νšŒμ›κ°€μž… (무료)
2. 상단 메뉴 "인증킀 μ‹ μ²­" 클릭
3. "ν™œμš© μ‹ μ²­" λ²„νŠΌ 클릭
4. 인증킀 볡사 β†’ `.env` 파일의 `SEOUL_API_KEY`에 λΆ™μ—¬λ„£κΈ°

**μ œν•œ**: ν•˜λ£¨ 1,000건 (무료)

---

### μ„€μ •

#### `.env` 파일 μ˜ˆμ‹œ
```bash
# ODsay API (ν•„μˆ˜)
ODSAY_API_KEY=your_odsay_api_key_here

# μ„œμšΈμ‹œ Open API (ν•„μˆ˜)
SEOUL_API_KEY=your_seoul_api_key_here

# 둜그 레벨 (DEBUG, INFO, WARNING, ERROR)
LOG_LEVEL=INFO

# μΊμ‹œ TTL (초)
CACHE_TTL=300
```

#### Claude Desktop μ„€μ •

Mac: `~/Library/Application Support/Claude/claude_desktop_config.json`  
Windows: `%APPDATA%/Claude/claude_desktop_config.json`

```json
{
  "mcpServers": {
    "subway-congestion": {
      "command": "uv",
      "args": [
        "--directory",
        "/μ ˆλŒ€κ²½λ‘œ/subway-congestion-mcp",
        "run",
        "subway-congestion-mcp"
      ]
    }
  }
}
```

**λ˜λŠ” 일반 Python μ‚¬μš© μ‹œ:**
```json
{
  "mcpServers": {
    "subway-congestion": {
      "command": "python",
      "args": [
        "-m",
        "subway_congestion_mcp"
      ],
      "env": {
        "PYTHONPATH": "/μ ˆλŒ€κ²½λ‘œ/subway-congestion-mcp/src"
      }
    }
  }
}
```

μ„€μ • ν›„ Claude Desktop을 μž¬μ‹œμž‘ν•˜μ„Έμš”.

---

## πŸ’¬ μ‚¬μš© 방법

### 예제 1: 경둜 검색
```
μ‚¬μš©μž: "κ°•λ‚¨μ—­μ—μ„œ μ—­μ‚Όμ—­ κ°€λ €λŠ”λ°"

Claude: 
πŸš‡ 졜적 경둜: 2ν˜Έμ„  상선 직톡 (1μ—­, 2λΆ„)
 β€’ 강남역: ν˜Όμž‘λ„ 152% (맀우혼작) β›”
 β€’ μ—­μ‚Όμ—­: ν˜Όμž‘λ„ 148% (혼작) πŸ”΄

πŸ“Œ λŒ€μ•ˆ 경둜:
 1. 9ν˜Έμ„  ν™˜μŠΉ (12λΆ„) - ν˜Όμž‘λ„ 정보 μ—†μŒ
 2. 146번 λ²„μŠ€ (15λΆ„) - ν˜Όμž‘λ„ 정보 μ—†μŒ

πŸ’‘ 2ν˜Έμ„ μ΄ κ°€μž₯ λΉ λ₯΄μ§€λ§Œ ν˜„μž¬ 맀우 ν˜Όμž‘ν•©λ‹ˆλ‹€.
```

---

## πŸ“ 폴더 ꡬ쑰

```
subway-congestion-mcp/
β”œβ”€β”€ README.md                    # πŸ‘ˆ μ§€κΈˆ 보고 μžˆλŠ” 파일
β”œβ”€β”€ pyproject.toml               # ν”„λ‘œμ νŠΈ 메타데이터 및 μ˜μ‘΄μ„±
β”œβ”€β”€ .env.example                 # ν™˜κ²½ λ³€μˆ˜ ν…œν”Œλ¦Ώ
β”œβ”€β”€ .gitignore                   # Git μ œμ™Έ 파일
β”‚
β”œβ”€β”€ src/
β”‚   └── subway_congestion_mcp/   # 메인 νŒ¨ν‚€μ§€
β”‚       β”œβ”€β”€ __init__.py
β”‚       β”œβ”€β”€ __main__.py          # MCP μ„œλ²„ μ§„μž…μ 
β”‚       β”‚
β”‚       β”œβ”€β”€ server.py            # MCP μ„œλ²„ μ„€μ •
β”‚       β”‚
β”‚       β”œβ”€β”€ tools/               # MCP 도ꡬ μ •μ˜
β”‚       β”‚   β”œβ”€β”€ __init__.py
β”‚       β”‚   β”œβ”€β”€ search_route.py          # 경둜 검색
β”‚       β”‚   β”œβ”€β”€ get_station_congestion.py  # μ—­ ν˜Όμž‘λ„ 쑰회
β”‚       β”‚   β”œβ”€β”€ get_route_congestion.py    # 경둜 ν˜Όμž‘λ„ + μ‹œκ°„ 계산
β”‚       β”‚   β”œβ”€β”€ get_train_arrival.py       # μ—΄μ°¨ 도착 정보
β”‚       β”‚   └── search_issues.py           # μ§€ν•˜μ²  이슈 검색
β”‚       β”‚
β”‚       β”œβ”€β”€ services/            # λΉ„μ¦ˆλ‹ˆμŠ€ 둜직
β”‚       β”‚   β”œβ”€β”€ __init__.py
β”‚       β”‚   β”œβ”€β”€ odsay_service.py     # ODsay API 호좜
β”‚       β”‚   β”œβ”€β”€ seoul_metro_service.py # μ„œμšΈμ‹œ API 호좜
β”‚       β”‚   β”œβ”€β”€ congestion_service.py  # ν˜Όμž‘λ„ 계산 둜직
β”‚       β”‚   └── cache_service.py       # 캐싱 λ ˆμ΄μ–΄
β”‚       β”‚
β”‚       β”œβ”€β”€ data/                # 데이터 처리
β”‚       β”‚   β”œβ”€β”€ __init__.py
β”‚       β”‚   β”œβ”€β”€ loader.py        # CSV 데이터 λ‘œλ”
β”‚       β”‚   β”œβ”€β”€ parser.py        # 데이터 νŒŒμ‹±
β”‚       β”‚   └── validator.py     # 데이터 검증
β”‚       β”‚
β”‚       β”œβ”€β”€ utils/               # μœ ν‹Έλ¦¬ν‹°
β”‚       β”‚   β”œβ”€β”€ __init__.py
β”‚       β”‚   β”œβ”€β”€ logger.py        # λ‘œκΉ… μ„€μ •
β”‚       β”‚   β”œβ”€β”€ time_utils.py    # μ‹œκ°„ λ³€ν™˜
β”‚       β”‚   β”œβ”€β”€ error_handler.py # μ—λŸ¬ 핸듀링
β”‚       β”‚   └── constants.py     # μƒμˆ˜ μ •μ˜
β”‚       β”‚
β”‚       └── models/              # 데이터 λͺ¨λΈ (Pydantic)
β”‚           β”œβ”€β”€ __init__.py
β”‚           β”œβ”€β”€ route.py         # 경둜 κ΄€λ ¨ λͺ¨λΈ
β”‚           β”œβ”€β”€ station.py       # μ—­ κ΄€λ ¨ λͺ¨λΈ
β”‚           └── congestion.py    # ν˜Όμž‘λ„ κ΄€λ ¨ λͺ¨λΈ
β”‚
β”œβ”€β”€ data/                        # 정적 데이터
β”‚   β”œβ”€β”€ congestion/              # ν˜Όμž‘λ„ CSV 파일
β”‚   β”‚   β”œβ”€β”€ line1.csv
β”‚   β”‚   β”œβ”€β”€ line2.csv
β”‚   β”‚   └── ...
β”‚   └── stations.json            # μ—­ 정보 (μ’Œν‘œ, λ…Έμ„  λ“±)
β”‚
β”œβ”€β”€ tests/                       # ν…ŒμŠ€νŠΈ μ½”λ“œ
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ test_services/           # μ„œλΉ„μŠ€ ν…ŒμŠ€νŠΈ
β”‚   β”‚   β”œβ”€β”€ test_odsay_service.py
β”‚   β”‚   └── test_congestion_service.py
β”‚   └── test_tools/              # 도ꡬ ν…ŒμŠ€νŠΈ
β”‚       β”œβ”€β”€ test_search_route.py
β”‚       └── test_get_station_congestion.py
β”‚
β”œβ”€β”€ scripts/                     # μœ ν‹Έλ¦¬ν‹° 슀크립트
β”‚   β”œβ”€β”€ download_data.py         # CSV 데이터 λ‹€μš΄λ‘œλ“œ
β”‚   └── validate_env.py          # ν™˜κ²½ λ³€μˆ˜ 검증
β”‚
└── logs/                        # 둜그 파일 (gitignore)
```

---

## πŸ›  기술 μŠ€νƒ

### 핡심 기술
- **Python 3.10+** - λŸ°νƒ€μž„
- **MCP SDK** - Model Context Protocol
- **Pydantic** - 데이터 검증
- **pandas** - CSV 데이터 처리

### μ£Όμš” 라이브러리
```toml
[project]
dependencies = [
    "mcp>=0.9.0",              # MCP SDK
    "pydantic>=2.0.0",         # 데이터 검증
    "pandas>=2.0.0",           # CSV 처리
    "requests>=2.31.0",        # HTTP ν΄λΌμ΄μ–ΈνŠΈ
    "python-dateutil>=2.8.2",  # λ‚ μ§œ/μ‹œκ°„ 처리
    "python-dotenv>=1.0.0",    # ν™˜κ²½ λ³€μˆ˜
]

[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",           # ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬
    "pytest-asyncio>=0.21.0",  # 비동기 ν…ŒμŠ€νŠΈ
    "black>=23.0.0",           # μ½”λ“œ ν¬λ§·νŒ…
    "ruff>=0.1.0",             # λ¦°νŒ…
    "mypy>=1.5.0",             # νƒ€μž… 체킹
]
```

---

## πŸ‘¨β€πŸ’» 개발 κ°€μ΄λ“œ

### 개발 μ„œλ²„ μ‹€ν–‰
```bash
# MCP Inspector둜 ν…ŒμŠ€νŠΈ
npx @modelcontextprotocol/inspector uv --directory . run subway-congestion-mcp

# λ˜λŠ” 직접 μ‹€ν–‰
python -m subway_congestion_mcp
```

### ν…ŒμŠ€νŠΈ
```bash
# 전체 ν…ŒμŠ€νŠΈ
pytest

# νŠΉμ • 파일 ν…ŒμŠ€νŠΈ
pytest tests/test_services/test_congestion_service.py

# 컀버리지 확인
pytest --cov=subway_congestion_mcp --cov-report=html
```

### μ½”λ“œ ν’ˆμ§ˆ
```bash
# ν¬λ§·νŒ…
black src/

# λ¦°νŒ…
ruff check src/

# νƒ€μž… 체킹
mypy src/
```

### 둜그 확인
```bash
# μ‹€μ‹œκ°„ 둜그
tail -f logs/app.log

# μ—λŸ¬ 둜그만
tail -f logs/error.log
```

---

## πŸ› 문제 ν•΄κ²°

### Q1. MCP μ„œλ²„κ°€ Claude Desktop에 μ—°κ²°λ˜μ§€ μ•Šμ•„μš”

**확인 사항**:
1. `claude_desktop_config.json` κ²½λ‘œκ°€ μ •ν™•ν•œκ°€?
2. μ ˆλŒ€ 경둜λ₯Ό μ‚¬μš©ν–ˆλŠ”κ°€?
3. κ°€μƒν™˜κ²½μ΄ ν™œμ„±ν™”λ˜μ–΄ μžˆλŠ”κ°€?
4. Claude Desktop을 μž¬μ‹œμž‘ν–ˆλŠ”κ°€?

**디버깅**:
```bash
# 직접 싀행해보기
cd /μ ˆλŒ€κ²½λ‘œ/subway-congestion-mcp
source .venv/bin/activate
python -m subway_congestion_mcp

# 둜그 확인
cat ~/Library/Logs/Claude/mcp*.log
```

---

### Q2. API 호좜이 μ‹€νŒ¨ν•΄μš”

**확인 사항**:
1. `.env` νŒŒμΌμ— API ν‚€κ°€ μ •ν™•νžˆ μž…λ ₯λ˜μ—ˆλŠ”κ°€?
2. API ν‚€κ°€ μœ νš¨ν•œκ°€?
3. 인터넷 연결이 정상인가?

**디버깅**:
```python
# 슀크립트둜 API ν‚€ ν…ŒμŠ€νŠΈ
python scripts/test_api_keys.py
```

---

### Q3. ν˜Όμž‘λ„ 데이터가 μ—†λ‹€κ³  λ‚˜μ™€μš”

**확인 사항**:
1. CSV 데이터λ₯Ό λ‹€μš΄λ‘œλ“œν–ˆλŠ”κ°€?
   ```bash
   python scripts/download_data.py
   ```
2. `data/congestion/` 폴더에 파일이 μžˆλŠ”κ°€?
3. 1~8ν˜Έμ„  역인가? (9ν˜Έμ„  미지원)

---

## πŸ“Š μ„±λŠ₯ μ΅œμ ν™”

### 캐싱 μ „λž΅
- **ν˜Όμž‘λ„ 데이터**: λ©”λͺ¨λ¦¬ μΊμ‹œ (pandas DataFrame)
- **μ‹€μ‹œκ°„ 도착 정보**: 30초 TTL
- **경둜 검색 κ²°κ³Ό**: 10λΆ„ TTL

### μ—λŸ¬ 핸듀링
```python
# Retry λ°μ½”λ ˆμ΄ν„°
@retry(max_attempts=3, backoff=exponential)
async def call_api():
    ...

# Fallback μ „λž΅
try:
    return await get_realtime_data()
except APIError:
    return get_statistical_data()  # 톡계 λ°μ΄ν„°λ‘œ λŒ€μ²΄
```

---

## πŸ”’ λ³΄μ•ˆ

- βœ… API ν‚€λŠ” `.env` νŒŒμΌμ—λ§Œ μ €μž₯ν•˜κ³  `.gitignore`둜 컀밋 μ œμ™Έ
- βœ… ν‚€ 유좜 μ‹œ μ¦‰μ‹œ μž¬λ°œκΈ‰ 및 폐기
- βœ… μƒ˜ν”Œ 섀정은 `.env.example`만 곡유
- βœ… λ―Όκ°ν•œ μ •λ³΄λŠ” λ‘œκ·Έμ— 남기지 μ•ŠμŒ
- βœ… μ˜μ‘΄μ„±μ€ `pyproject.toml`μ—μ„œ 관리

---

## πŸ“ˆ λ‘œλ“œλ§΅

### v1.0 (ν˜„μž¬) - MVP
- βœ… κΈ°λ³Έ 5κ°€μ§€ 도ꡬ κ΅¬ν˜„
- βœ… 1~8ν˜Έμ„  지원
- βœ… 톡계 기반 ν˜Όμž‘λ„

### v1.1 (μ˜ˆμ •)
- ⬜ 9ν˜Έμ„ , μ‹ λΆ„λ‹Ήμ„  지원
- ⬜ λŒ€μ•ˆ μ—­ μΆ”μ²œ
- ⬜ μ›Ή μΈν„°νŽ˜μ΄μŠ€

### v2.0 (κ³„νš)
- ⬜ κ°œμΈν™” κΈ°λŠ₯
- ⬜ μ•Œλ¦Ό μ„€μ •
- ⬜ 톡계 λŒ€μ‹œλ³΄λ“œ

---

## 🀝 κΈ°μ—¬ν•˜κΈ°

1. Fork the repository
2. Create your feature branch (`git checkout -b feature/AmazingFeature`)
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)
4. Push to the branch (`git push origin feature/AmazingFeature`)
5. Open a Pull Request

---

## πŸ“ λΌμ΄μ„ΌμŠ€

MIT License - 자유둭게 μ‚¬μš©ν•˜μ„Έμš”!

---

## πŸ’¬ μ—°λ½μ²˜

λ¬Έμ˜μ‚¬ν•­μ΄λ‚˜ μ œμ•ˆμ‚¬ν•­μ΄ μžˆμœΌμ‹œλ©΄:
- GitHub Issues
- Email: kimjinjuind@gmail.com

---

**Made with ❀️ for Seoul Subway Commuters**