Skip to main content
Glama
mogoon

Yeosugaja(여수가자) Travel MCP

by mogoon
README.md
# Yeosugaja(여수가자) Travel MCP

A **read-only** [MCP](https://modelcontextprotocol.io) server that exposes verified
Yeosu travel itineraries, curated POIs, and transport schedules — sourced from the
daily R2 bundle cache the app already produces. Built to be registered on **Kakao
PlayMCP** and consumed by MCP clients (Claude, ChatGPT dev mode, OpenClaw, …).

It is a thin, stateless Cloudflare Worker: on each tool call it loads
`{region}/latest.json` from R2 (60s in-isolate memo) and answers from it. It never
touches the API or the database, so periodic bundle regeneration cannot affect it —
worst case is a few minutes of staleness.

## Architecture

```
API batch  ──►  R2  {region}/latest.json  ──►  this Worker (MCP)  ──►  MCP client
generate_daily_bundle    (atomic, gzipped)      read-only, stateless     (PlayMCP)
```

## Tools (7)

| tool | purpose |
|------|---------|
| `list_itineraries` | list verified itineraries, filter by mbti/transport/duration/party/season |
| `get_itinerary` | full time-ordered timeline of one itinerary |
| `recommend_itinerary` | top matches for an MBTI, ranked by confidence |
| `search_pois` | search POIs by text/tag/grade/indoor/signature |
| `get_poi` | full detail for one POI |
| `get_transport` | ferry / airport / train / bus schedules |
| `get_region_info` | weather, event banner, dining rules, bundle freshness |

All tools are `readOnlyHint: true`, non-destructive, idempotent, closed-world.

## 사용 예시 프롬프트 (Example prompts)

이 MCP를 연결한 에이전트(Claude·ChatGPT·OpenClaw 등)에 자연어로 말하면 아래처럼 도구가 불린다.

**단일 도구**
- "INFP한테 맞는 여수 하루 코스 추천해줘" → `recommend_itinerary(mbti=INFP)`
- "여수 당일치기 일정 목록 보여줘, 도보로 다니는 걸로" → `list_itineraries(duration_type=1day, transport=walk)`
- "그 코스(it1) 시간표 자세히 풀어줘" → `get_itinerary(id=it1)`
- "여수에서 실내 문화 명소 찾아줘" → `search_pois(tag=culture, indoor_outdoor=indoor)`
- "오동도 정보 알려줘" → `search_pois(query=오동도)` → `get_poi(id=…)`
- "여수 들어가는 배편 시간표 알려줘" → `get_transport(mode=ferry)`
- "지금 여수 날씨랑 진행 중인 이벤트 있어?" → `get_region_info()`

**여러 도구 조합 (이 MCP 안에서)**
- "ENFP 커플 여름 여수 코스 추천하고, 1등 코스 시간표까지 보여줘"
  → `recommend_itinerary(mbti=ENFP, party_type=couple, season=summer)` → `get_itinerary(id=…)`
- "비 오는 날 가기 좋은 여수 실내 코스랑 그 안 장소들 상세 정리해줘"
  → `get_region_info()` → `search_pois(indoor_outdoor=indoor)` → `get_poi(...)`
- "차 없이 뚜벅이로 갈 수 있는 시그니처 명소 위주 하루 코스 짜줘"
  → `list_itineraries(transport=walk)` + `search_pois(signature_only=true)` → `get_itinerary(...)`

**PlayMCP(카카오 도구함)와 함께 — 클라이언트 계층 합성**
> 이 MCP + 카카오 서버(맵/톡캘린더/카톡 나에게 보내기)를 같은 도구함에 함께 연결한 경우.
- "INTJ 여수 코스 추천받아서 **톡캘린더에 이번 주 토요일 일정으로** 넣어줘"
  → `recommend_itinerary(mbti=INTJ)` → (카카오) 톡캘린더 등록
- "추천 코스를 **카톡으로 나한테 보내줘**" → `get_itinerary(...)` → (카카오) 나에게 보내기
- "첫 장소를 **카카오맵으로 길찾기** 열어줘" → `get_poi(...)` → (카카오) 지도 길찾기

> 팁: MBTI·계절·이동수단·동행(solo/couple/family/group)을 함께 말하면 더 정확히 매칭된다.

## PlayMCP compliance (dev guide 2026-06-12)

| Requirement | This server |
|---|---|
| Streamable HTTP only | ✅ stateless Streamable HTTP at `POST /mcp` |
| Remote, public URL | ✅ Cloudflare Worker |
| Stateless (no session) | ✅ single request/response, no session id |
| MCP version 2025-03-26 … 2025-11-25 | ✅ `protocolVersion: 2025-03-26` |
| 3–10 tools (≤20) | ✅ 7 |
| `name`/`description`/`inputSchema`/`annotations` present | ✅ |
| all 5 annotation hints set | ✅ |
| English description incl. service name (EN/KO) | ✅ "…from Yeosugaja(여수가자)" |
| no "kakao" in server/tool names | ✅ |
| OAuth only if personal data | ✅ none needed (public, read-only) |
| minimal, cleaned (markdown) results | ✅ |
| p99 ≤ 3s, avg ≤ 100ms | ✅ R2 + memo cache |
| MCP Inspector pre-check | see below |

## Develop

```bash
cd apps/yeosugaja-mcp
npm install
npm run typecheck
npm run dev            # local Worker at http://localhost:8787/mcp
```

Local smoke test (JSON-RPC over Streamable HTTP):

```bash
curl -s http://localhost:8787/mcp \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'

curl -s http://localhost:8787/mcp -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"recommend_itinerary","arguments":{"mbti":"INFP"}}}'
```

## MCP Inspector (required before PlayMCP submission)

```bash
npx @modelcontextprotocol/inspector
# Transport: Streamable HTTP   URL: http://localhost:8787/mcp (or the deployed URL)
```
Verify `initialize`, `tools/list`, and each `tools/call` succeed.

## Deploy

```bash
npx wrangler login
npm run deploy         # → https://yeosugaja-mcp.<subdomain>.workers.dev/mcp
```

Optional, recommended:
- **R2 binding** — uncomment the `r2_buckets` block in `wrangler.jsonc` and set the
  real bucket name (same as the API's `R2_BUCKET_NAME`) for edge-local reads. Without
  it, the Worker falls back to the public `BUNDLE_PUBLIC_BASE` URL.
- **Custom domain** — uncomment `routes` for a stable registration URL.

## Register on PlayMCP

1. PlayMCP → 개발자 콘솔 → 새로운 MCP 서버 등록.
2. Server URL: the deployed `…/mcp` endpoint.
3. No OAuth (read-only public data) — leave auth unset.
4. Submit for review.

## Notes / limits

- **Read-only over the cache.** Only *verified* itineraries and the *yeosu* region are
  in the bundle. Live per-user generation would require calling the API engine and is
  intentionally out of scope here.
- **Schema contract.** If the bundle's field names change, update `src/bundle.ts` and
  `src/tools.ts`; handlers already guard missing fields defensively.
- **Kakao features** (map, calendar, KakaoTalk share) are composed at the *client*
  layer: the user connects this MCP and the relevant PlayMCP tool together and the
  agent chains them. This server does not call Kakao services directly.