Skip to main content
Glama
openhee

Jeju Humanities Tour MCP Server

by openhee
README.md
# 제주 인문학 투어 추천 MCP 서버 (학습용)

자연어 질문 → Claude Code가 MCP 툴 발견·호출 → SQLite에서 여러 테이블을
JOIN해 조합 → 정리된 결과 반환, 이 왕복(round-trip)을 손에 익히기 위한
연습 프로젝트.

## 프로젝트 구조

- `server.py` : MCP 서버 + 툴 정의 (FastMCP, stdio)
- `seed.py`   : SQLite 스키마 생성 + 시드 데이터 삽입
- `tour.db`   : `seed.py` 실행으로 생성되는 SQLite 파일 (git에 커밋하지 않음)
- `.mcp.json` : VS Code의 Claude Code가 이 서버에 연결하는 설정
- `pyproject.toml` / `uv.lock` : uv 의존성 관리

## 1. 실행 준비 (최초 1회)

```bash
# 의존성 설치 (uv가 .venv를 알아서 만들고 관리)
uv sync

# DB 생성 + 시드 데이터 삽입 (다시 실행하면 tour.db를 지우고 새로 만듦)
uv run seed.py
```

서버 단독 실행 확인 (선택, 정상이면 아무 출력 없이 대기 상태가 됨 — Ctrl+C로 종료):

```bash
uv run server.py
```

## 2. VS Code(Claude Code)에 연결하기

이 폴더에 있는 `.mcp.json`이 프로젝트 범위 MCP 서버 설정이다.

1. VS Code로 이 프로젝트 폴더(`jeju-tour-mcp`)를 연다.
2. Claude Code가 `.mcp.json`을 감지하면 "이 프로젝트의 MCP 서버를 신뢰하고
   연결할지" 승인을 요청한다 → 승인.
3. 대화창에서 `/mcp` 명령으로 서버 연결 상태와 등록된 툴 목록을 확인할 수
   있다. `jeju-tour` 서버가 연결됨(connected) 상태여야 한다.
4. 만약 자동으로 뜨지 않으면, Claude Code 설정에서 프로젝트 MCP 서버를
   수동으로 승인/새로고침 할 수 있다 (`/mcp` 안에서 재연결 시도 가능).

> `.mcp.json`은 `uv run --directory <프로젝트경로> server.py`로 서버를
> 띄우도록 되어 있어서, VS Code의 현재 작업 디렉터리와 무관하게 항상
> 같은 프로젝트의 `.venv`/`tour.db`를 사용한다.

## 3. 단계별 진행 상황 & 테스트 질문

### 1단계 — `list_regions()` ✅

- 인자 없이 지역 목록만 반환. 연결·툴 발견이 되는지 확인하는 용도.
- 테스트 질문 예시:
  - "제주에서 투어 가능한 지역이 어디 어디 있어?"
  - "지역 목록 좀 알려줘."
- 체크포인트: Claude가 `list_regions` 툴을 스스로 호출해서
  `["서귀포", "성산", "안덕", "애월", "제주시"]` 형태의 응답을 가져오면 성공.
  (도구 호출 없이 모델이 그냥 아는 척 답하면 실패 — `/mcp`에서 연결 상태부터
  다시 확인.)

### 2단계 — `get_spot(name)` ✅

- 스팟 이름(정확한 이름 또는 일부)으로 상세 정보 1건을 조회.
  자연어에서 인자(스팟 이름)를 뽑아내는 걸 확인하는 용도.
- 정확히 일치하는 이름이 없으면 부분 일치(`LIKE %name%`)를 시도하고,
  일치가 1건이면 그걸 반환, 여러 건이면 에러 메시지로 후보 목록을 알려주고,
  없으면 "찾을 수 없다"는 에러를 낸다.
- 테스트 질문 예시:
  - "성산일출봉 몇 시에 열어?"
  - "이중섭거리는 실내야 실외야?"
  - "일출봉 정보 알려줘" (부분 일치 테스트)
  - "제주 알려줘" (의도적으로 모호한 이름 → 여러 후보 안내 확인)
- 체크포인트: Claude가 `get_spot`을 호출해서 `open_t`/`close_t`,
  `is_indoor` 등 구조화된 필드를 근거로 답하면 성공. 모호한 이름을 던졌을 때
  후보 목록을 보여주며 되묻는지도 확인.

### 3단계 — `recommend_course(region, theme, max_hours)` ✅ (핵심 JOIN)

- `courses ⋈ course_spots ⋈ spots`를 조인해 지역·테마·소요시간 조건에
  맞는 코스 1개를 찾고, `course_spots.seq` 순서로 정렬된 스팟 목록을
  구성해서 반환한다. 조건에 맞는 `total_hours` 중 `max_hours`를 넘지
  않는 가장 긴(=꽉 채운) 코스를 고른다.
- 반환 형태: `{ title, region, theme, total_hours, stops: [{ seq, name,
  walk_min, why }, ...] }`. 원본 row가 아니라 서버에서 이미 조합·정렬된
  구조만 LLM에 전달한다.
- 조건에 맞는 코스가 없으면, 현재 DB에 있는 (지역/테마/소요시간) 조합
  목록을 담은 에러 메시지를 반환해 대안을 제시할 수 있게 한다.
- 테스트 질문 예시:
  - "성산에서 신화 테마로 4시간 이내 코스 추천해줘"
  - "애월에서 성찰 테마로 3시간 반짜리 코스 있어?"
  - "서귀포 문학 코스 5시간 안에 뭐가 있어?"
  - "제주시에서 문학 테마 코스 추천해줘" (일부러 없는 조합 → 대안 안내 확인)
- 체크포인트: Claude가 `stops`를 seq 순서 그대로 나열하며 각 스팟의
  `why`(코스에 포함된 이유)까지 자연스럽게 설명하면 성공. 조건 불일치
  질문에서는 에러 메시지 속 대안 조합을 근거로 되묻거나 대안을 제안하는지
  확인.

### 4단계 — `get_weather(region)` (예정, mock)

각 단계 구현이 끝나면 이 섹션에 실제 내용과 테스트 질문을 채워 넣는다.

## DB 스키마 메모

```
spots(id, name, region, theme, is_indoor, walk_min, open_t, close_t)
courses(id, title, region, theme, total_hours)
course_spots(course_id, spot_id, seq, why)  -- 코스↔스팟 다대다 조인 테이블
```

`course_spots.why`는 원래 스펙에는 없었지만, `recommend_course`가 각
스팟이 "왜 이 코스에 포함되었는지"를 반환해야 해서 코스-스팟 조합에
귀속되는 정보로 추가했다 (스팟 자체 속성이 아니라 코스 문맥에 따라
달라지는 값이라 `spots` 테이블이 아니라 조인 테이블에 둠).

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool serves a distinct purpose: listing regions, getting spot details, and recommending courses. No overlap exists, so an agent can clearly select the correct tool.

Naming Consistency5/5

All tools follow a consistent verb_noun snake_case pattern: list_regions, get_spot, recommend_course. No deviations or mixed conventions.

Tool Count5/5

Three tools is well-scoped for a humanities tour server: regions, spots, and courses. Each tool earns its place without redundancy or excessive minimality.

Completeness3/5

Core operations are covered, but missing a tool to list spots within a region or list all courses. Agents may struggle to discover spots without a listing tool, creating a minor gap.

Maintenance

ActivityMaintained
ResponsivenessSyncing