knu-mcp
# 강원대 MCP 서버 (비공식)
강원대학교 춘천캠퍼스의 **공개 정보**를 조회하는 MCP(Model Context Protocol) 서버입니다.
Claude Desktop, Cursor, Cline 등 MCP를 지원하는 클라이언트에서 자연어로 학사일정·학식·공지·버스 정보를 조회할 수 있습니다. 여러 tool을 조합해 "이번주 학식이랑 수강신청 공지 정리해줘" 같은 통합 질문에 한 번에 답하는 것을 목표로 합니다.
강원대는 공식 API가 없어, 이 서버가 사실상 유일한 프로그래매틱 접근점입니다.
> ⚠️ **비공식 프로젝트입니다.** 강원대학교와 무관한 학생 제작 도구이며, 공개 웹페이지 및 공공데이터포털 API에서 정보를 수집합니다. 학교 사이트 구조가 바뀌면 일부 기능이 동작하지 않을 수 있습니다. 로그인이 필요한 정보(수강신청·성적·도서관 좌석 등)는 제공하지 않습니다.
## 설치
```bash
uv sync
```
## 로컬 클라이언트 등록 (Claude Desktop 예시)
`claude_desktop_config.json`에 추가:
```json
{
"mcpServers": {
"kangwon": {
"command": "uv",
"args": ["run", "--directory", "/절대경로/knu-mcp", "knu-mcp"],
"env": { "KNU_BUS_API_KEY": "발급받은_공공데이터포털_인증키" }
}
}
}
```
`KNU_BUS_API_KEY`는 버스 도착 정보 tool에만 필요합니다(아래 참조). 없으면 버스 tool만 에러를 반환하고 나머지는 정상 동작합니다.
## 제공 도구
| tool | 설명 |
|------|------|
| `get_academic_calendar(year=올해)` | 학사일정(해당 연도 전체) |
| `get_cafeteria_menu(restaurant="백록관", week=0)` | 학식 식단. restaurant: 백록관/천지관/두리, week: 0=이번주,1=다음주 |
| `get_all_cafeteria_menus(week=0)` | 3식당 학식 일괄 |
| `search_notices(query, limit=10)` | 학사공지(메인 홈페이지) 최근분에서 제목 검색 |
| `get_shuttle_schedule()` | 교내 순환버스(두리버스) 시간표. 정규학기만 운영 |
| `get_next_shuttle()` | 두리버스 현재 시각 기준 다음 출발 |
| `get_bus_arrival(stop="강원대정문", route="300")` | 시내버스 실시간 도착. stop: 강원대정문/후문/중앙도서관/백록관/학병원/남춘천역/춘천역/시외버스터미널 |
## 버스 API 키 (get_bus_arrival 전용)
시내버스 실시간 도착은 [공공데이터포털](https://www.data.go.kr) TAGO 버스도착정보 API를 사용합니다.
1. data.go.kr 로그인 → "국토교통부_(TAGO)_버스도착정보" 활용신청(개발계정, 즉시 승인)
2. 발급된 **일반 인증키(Decoding)** 를 환경변수 `KNU_BUS_API_KEY`로 설정
키는 코드에 하드코딩하지 말고 환경변수로만 전달합니다.
## 전송 모드
기본은 로컬 클라이언트용 **stdio**. 원격(클라우드/PlayMCP) 배포 시 **streamable-http**:
```bash
KNU_MCP_TRANSPORT=streamable-http PORT=8000 uv run knu-mcp
# 엔드포인트: http://<host>:<PORT>/mcp
```
환경변수: `KNU_MCP_TRANSPORT`(stdio|sse|streamable-http, 기본 stdio), `PORT`(http 포트, 기본 8000).
## Docker로 배포
원격 배포는 컨테이너 이미지로 하는 것이 간단합니다. streamable-http 전송이 기본으로 설정되어 있습니다.
```bash
docker build -t knu-mcp .
docker run -d --restart unless-stopped -p 8000:8000 \
-e KNU_BUS_API_KEY=<공공데이터포털_인증키> knu-mcp
# 엔드포인트: http://<host>:8000/mcp
```
- `PORT` 환경변수를 주입하면 그 포트로 뜹니다(클라우드 플랫폼이 자동 주입하는 경우 그대로 동작).
- 이미지에 `HEALTHCHECK`가 포함되어 있어 `/mcp` 핸드셰이크로 상태를 확인합니다.
- API 키는 이미지에 넣지 말고 런타임에 `-e`로만 주입하세요.
## 프로젝트 구조
```
knu_mcp/
server.py # FastMCP 서버, tool 7개 등록
http.py # 공용 HTTP(get/post) + 10분 TTL 캐시
academic.py # 학사일정 (scheduleData 파싱)
cafeteria.py # 학식 (POST + 표 파싱)
notices.py # 공지 검색 (목록 파싱)
shuttle.py # 두리버스 시간표 / 다음 차
bus.py # 시내버스 실시간 도착 (TAGO API)
tests/ # 파서 단위 테스트 (fixture 기반, 네트워크 안 탐)
scripts/e2e_smoke.py # 실 네트워크 E2E 스모크
```
각 소스 모듈은 하나의 데이터 소스만 담당하며 서로 의존하지 않습니다.
## 데이터 출처
- 학사일정·학식·공지·두리버스: `www.kangwon.ac.kr` 공개 페이지 (스크래핑)
- 시내버스 실시간 도착: [공공데이터포털](https://www.data.go.kr) 국토교통부 TAGO 버스도착정보 API
## 개발
```bash
uv run pytest # 단위 테스트 (네트워크 안 탐, fixture 기반)
KNU_BUS_API_KEY=<키> uv run python scripts/e2e_smoke.py # E2E 스모크 (실 네트워크 왕복)
```
새 기능은 파서 단위 테스트를 먼저 작성(TDD)하고, 외부 사이트/API 실패 시 예외 대신 `{"error": ...}`를 반환하는 규칙을 따릅니다.
## 라이선스
MIT
TDQS
Scored across 7 tools
Most tools target distinct resources: notices, bus arrivals, academic calendar, etc. The main overlap is between get_cafeteria_menu and get_all_cafeteria_menus (single vs. all restaurants), and between get_next_shuttle and get_shuttle_schedule (next departure vs. full timetable), but the descriptions make these differences clear.
The naming pattern is predominantly get_<noun>, with one search_notices exception. All names use lower_snake_case and are readable, but the mix of 'get' and 'search' plus the get_ vs get_all_ prefix variation creates minor inconsistency.
Seven tools is well within the ideal 3-15 range for a university information server. Each tool addresses a distinct user need (meals, notices, shuttles, city buses, calendar) without redundancy or bloat.
The tool surface covers the main information needs for the university domain: cafeteria, shuttle, bus, notices, and academic calendar. Minor gaps exist, such as no direct route info for shuttles or detailed notice content retrieval, but these are workable limitations rather than critical dead ends.