seoul-opendata-mcp
# seoul-opendata-mcp
[](https://github.com/youjin8812-hub/seoul-opendata-mcp/actions/workflows/test.yml)
서울 열린데이터광장(data.seoul.go.kr) 공공데이터 **8,266건**을 자연어 한 문장으로 찾아주는 MCP(Model Context Protocol) 서버입니다.
"생활인구 데이터 있어?" 대신 **"우리 동네 유동인구로 창업 입지 분석하는 앱 만들고 싶어"** 라고만 말하면, 딱 맞는 API를 추천해줍니다.

---
## 🚀 지금 바로 써보기 (설치·인증키 발급 불필요)
이미 원격 서버가 떠 있어서, **내 컴퓨터에 아무것도 설치하지 않고 URL만 연결하면 바로 씁니다.** 서울시 인증키를 각자 발급받을 필요도, 별도 토큰을 받을 필요도 없습니다. 아래 URL 하나만 등록하면 끝입니다.
```
https://seoul-opendata-mcp.fly.dev/mcp
```
<table>
<tr><td><b>Claude.ai (웹)</b></td><td>
설정 → Connectors → **Add custom connector**
- URL: `https://seoul-opendata-mcp.fly.dev/mcp`
- 인증 설정 없음
</td></tr>
<tr><td><b>Claude Desktop / Cursor</b></td><td>
`mcp.json`에 추가:
```json
{
"mcpServers": {
"seoul-opendata": {
"url": "https://seoul-opendata-mcp.fly.dev/mcp"
}
}
}
```
</td></tr>
<tr><td><b>Claude Code (터미널)</b></td><td>
```bash
claude mcp add --transport http seoul-opendata \
https://seoul-opendata-mcp.fly.dev/mcp
```
등록 후 새 세션을 열면 바로 연결됩니다 (`claude mcp list`로 확인).
</td></tr>
</table>
등록이 끝나면 별도 명령어 없이 **대화창에 그냥 물어보면** 됩니다.
> **호출 제한 안내** — 공용 서버라 IP당 **분당 20회 · 시간당 200회** 제한이 걸려 있습니다. 서울시 인증키 한도 때문이 아니라(카탈로그 API는 호출 횟수 제한이 없습니다) 서버 자원 보호와 남용 방지 목적입니다. 평소 대화에서는 걸릴 일이 거의 없고, 한도를 넘으면 잠시 후 다시 시도하면 됩니다. 다만 교육장처럼 여러 명이 같은 네트워크에서 접속하면 하나의 IP로 묶이니 주의하세요.
## 💬 이렇게 물어보세요
- "생활인구 250m 격자 데이터로 유동인구 분석하는 앱 만들고 싶어"
- "한강공원 주차장 실시간 잔여 자리 알려주는 앱 만들려는데 쓸 만한 데이터 있어?"
- "최근에 새로 갱신된 교통 관련 API만 보여줘"
- "서울시 인허가 데이터로 만들 수 있는 서비스 아이디어 줘봐"
---
## ✨ 이 앱을 쓰면 뭐가 좋을까요
**창업 전, 상권 리스크를 30초 만에**
"이 상가 자리, 예전에 어떤 가게가 있었고 몇 번이나 망했을까?" — 이렇게만 물어보면 자치구별로 흩어진 업종별 인허가 데이터 중 관련 후보를 바로 찾아줍니다. 원래는 3,000건 넘는 인허가 데이터셋을 구별·업종별로 하나하나 뒤져야 하는 일입니다.
**등하굣길 안전지도, 반나절이 아니라 몇 분**
"등하굣길에 있는 비상벨 위치를 지도에 보여주고 싶어" 한 문장이면 관련 데이터 후보가 바로 나옵니다. 데이터명을 몰라 포털을 뒤지고, 파일인지 API인지 상세페이지마다 들어가 확인하던 시간이 사라집니다.
**이미 죽은 API 붙잡고 삽질하지 않기**
서울시 공공데이터는 매년 수백 건씩 서비스가 종료됩니다(2025년만 173건). "최근에 갱신된 교통 API만 보여줘"라고 물으면, 목록엔 남아 있지만 실제로는 운영이 끊긴 데이터를 걸러내고 지금도 살아있는 것만 추천합니다.
**여러 사람이 인증키 하나로**
서울시 인증키는 하루 호출 한도가 있어 여러 명이 나눠 쓰기 번거롭습니다. 팀원이나 스터디원이 각자 발급받을 필요 없이, 이미 떠 있는 원격 서버 하나에 다같이 연결해서 씁니다.
---
## 🧠 왜 이 프로젝트를 만들었나요?
<details>
<summary>서울 열린데이터광장 실태 분석 펼쳐보기 (숫자로 보는 문제 상황)</summary>
### 서울 열린데이터광장 현황
출처: 서울시 「열린데이터광장 공공데이터 현황」 참고자료 (**2026. 7. 31. 기준**)
**개방 현황 — 공공데이터 8,266건**
- 타 지자체 비교: 부산 12,411 · 인천 4,379 · 충남 4,167 · 경남 3,447
- 해외 주요도시 비교: 뉴욕 3,014 · 런던 1,301
**제공 주체별 분포 — 카탈로그 전수 집계 8,255건**
| 중분류 값 | 건수 | 비중 |
|---|---|---|
| 서울시(본청) | 5,274 | 63.9% |
| 자치구 및 자치구산하 | 2,136 | 25.9% |
| 공공기관(외부) | 467 | 5.7% |
| 서울시(산하기관) | 364 | 4.4% |
| 서울시(사업소) | 12 | 0.1% |
| 민간(기업) | 2 | 0.0% |
| **합계** | **8,255** | **100%** |
- 본청이 약 3분의 2, 자치구가 약 4분의 1을 차지합니다. 25개 자치구가 각자 등록한 2,136건을 구 단위로 골라내려면 `division`·`orgName` 필터가 필요합니다.
- 제공기관 상위: 서울특별시 3,499 · 양천구 236 · 강서구 200 · 강북구 196 · 서울교통공사 179
**서비스 유형별 — 데이터셋 8,266건이 서비스 16,577개로 제공**
| 유형 | OpenAPI | SHEET | CHART | FILE | LINK | MAP | LOD |
|---|---|---|---|---|---|---|---|
| 건수 | 5,630 | 7,331 | 1,883 | 1,184 | 334 | 124 | 91 |
| 비율 | 34% | 44% | 11% | 7% | 2% | 1% | 1% |
**OpenAPI는 전체 서비스의 34%뿐입니다.** "검색된 이 데이터를 API로 쓸 수 있는가"가 매번 확인해야 할 질문이 되는 이유이며, 이 MCP가 `SRV_TYPE` 필드로 형식을 확정 판별하는 근거입니다.
**분야별 데이터 보유 현황 (12개 정책분야)**
| 분야 | 건수 | 분야 | 건수 | 분야 | 건수 |
|---|---|---|---|---|---|
| 보건 | 1,800 | 환경 | 605 | 안전 | 244 |
| 문화/관광 | 1,633 | 교통 | 569 | 도시관리 | 233 |
| 산업/경제 | 936 | 일반행정 | 550 | 주택/건설 | 156 |
| 복지 | 648 | 인구/가구 | 256 | 교육 | 625 |
### 카탈로그 전수 분석 (8,255건 × 15필드)
`SearchCatalogService`가 반환하는 카탈로그 전체를 내려받아 직접 집계한 결과입니다. 이 MCP의 분류·필터·정렬 로직은 모두 이 필드값에 근거합니다.
| 필드 | MCP에서의 역할 |
|---|---|
| `소분류` | 12개 정책분야 분류 — `brm` 판정 근거 |
| `중분류` | 본청·자치구·산하기관 구분 — `division` 필터 |
| `제공형식` | Api·Sheet·File 판별 — `apiOnly` 확정 근거 |
| `최종갱신일자` | 신선도 정렬 — `list_seoul_recent_updates` |
| `서비스 ID` | 중복제거 기준키 · 단건 조회 입력 |
| `서비스명` | 키워드·동의어 매칭 대상 |
| `제공기관` | `orgName` 필터 |
| `제공부서명`, `담당자연락처` | 문의처 반환 (활용도 점수) |
| `갱신주기` | 실시간성 판정 (수시·일간 우선) |
| `서비스URL`, `제공사이트` | 공식 상세페이지 링크 |
**데이터 신선도 — 최종갱신일자 기준 (2026. 8. 31.)**
| 구간 | 건수 | 비중 |
|---|---|---|
| 1개월 이내 | 5,115 | 62.0% |
| 1~3개월 | 292 | 3.5% |
| 3개월~1년 | 900 | 10.9% |
| 1~2년 | 644 | 7.8% |
| 2~5년 | 1,135 | 13.7% |
| 5년 초과 | 165 | 2.0% |
**1년 이상 갱신되지 않은 데이터가 1,944건(23.5%)**, 그중 165건은 5년을 넘겼습니다. 목록에는 그대로 남아 있어 검색 결과만으로는 운영 중인 데이터와 구분되지 않습니다. `list_seoul_recent_updates`가 부가 기능이 아니라 필수 기능인 이유입니다.
### 이용 현황 — 이미 313억 건이 쓰였다
**분야별 누적 이용현황** (페이지뷰 + 다운로드 + 서비스 호출, 단위: 백만 건)
| 교통 | 환경 | 문화/관광 | 일반행정 | 주택/건설 | 보건 | 안전 | 산업/경제 | 도시관리 | 인구/가구 | 복지 | 교육 | **합계** |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 21,594 | 8,247 | 809 | 405 | 69 | 65 | 46 | 42 | 41 | 20 | 12 | 9 | **31,359** |
**교통 한 분야가 전체 이용의 68.9%**, 교통+환경이 95.2%를 차지합니다. 공급은 8,266건인데 수요는 극소수 데이터에 쏠려 있습니다. 이 격차의 상당 부분은 "데이터가 없어서"가 아니라 **"있는 줄 몰라서 / 못 찾아서"**에 가깝습니다 — 이 프로젝트가 해결하려는 지점입니다.
### 카탈로그는 고정된 목록이 아니다
**공공데이터 개방·종료 현황** (단위: 건)
| 구분 | 2023 | 2024 | 2025 | 2026. 7. 31. |
|---|---|---|---|---|
| 개방 | 525 | 373 | 277 | 174 |
| 종료 | 110 | 64 | 173 | 121 |
| **순증** | **+415** | **+309** | **+104** | **+53** |
| 누적 | 7,800 | 8,109 | 8,213 | 8,266 |
2025년에는 개방 277건에 종료 173건 — 순증이 104건까지 떨어졌습니다. 목록에 있다고 살아있는 데이터가 아닙니다. 오래된 문서·블로그가 안내하는 API는 이미 종료됐을 수 있습니다.
### API 이용의 공식 제약
- 오픈API는 **1회 호출당 최대 1,000건**까지 요청 가능 (초과 시 횟수를 나누어 호출). **호출 횟수 제한은 없음**
- 단, **실시간 지하철 오픈API는 인증키 1개당 1일 1,000회**로 제한
- 실시간 지하철 오픈API는 활용사례(갤러리)에 인증키와 함께 서비스를 등록하고 승인받으면 호출 제한 없이 사용 가능
> 이 MCP는 1회 요청 상한(1,000건)을 초과하는 요청을 자동으로 잘라 처리하고, 조건에 맞는 전체 건수가 상한을 넘으면 "표본 내 정렬"이라는 한계를 응답의 `note`에 명시합니다.
>
> 이 서버가 호출하는 것은 카탈로그 검색(`SearchCatalogService`) 하나뿐입니다. 호출 횟수 제한이 없는 API이고, 1일 1,000회 제한이 걸리는 실시간 지하철 API는 호출하지 않습니다. **따라서 인증키의 일일 한도가 소진될 일은 없습니다.**
### 기존 검색 방식의 한계 → 이 도구가 바꾸는 것
| | 기존 | Seoul OpenData MCP |
|---|---|---|
| 시작 | 맞는 검색어를 추측해 입력 | 만들고 싶은 것을 한 문장으로 서술 |
| 형식 판별 | 목록에서 눈으로 구분 | `SRV_TYPE` 기반 확정 판별 |
| 최신성 | 개별 확인 | 최종갱신일 기준 정렬 조회 |
| 담당부서 | 상세페이지 개별 진입 | 결과에 함께 반환 |
| 근거 | 없음 | 배점 근거를 문장으로 반환 |
| 소요 | 여러 화면·반복 이동 | 추천 질의 1회 실측 91ms |
### 도입 배경 (검증 과정)
- 처음엔 공공데이터포털(data.go.kr) 통합 검색으로 서울시 데이터만 걸러내는 방식을 시도했으나, "서울 생활인구 250m 격자 API 있어?" 같은 질의에 답을 주지 못함
- 원인 확인: data.go.kr 색인은 갱신 지연이 있고, 실제로는 존재하는 "생활인구 250m" API가 색인 누락으로 "없다"고 판단됨
- 결론: 서울시 전용 도구는 서울시가 직접 운영하는 카탈로그(`SearchCatalogService`)를 원천으로 삼아야 한다고 판단, 데이터 소스를 전면 교체
</details>
---
## 🛠️ 도구 목록 (개발자용 상세 스펙)
<details>
<summary>5개 도구의 파라미터·동작 방식 펼쳐보기</summary>
### `recommend_seoul_apis_for_idea`
아이디어 텍스트 → 키워드 추출(유사어 확장 포함) → 카탈로그 병렬 검색 → 점수화 → 상위 N개 반환
도메인 적합도가 40점이라 **유사어 확장 품질이 추천 결과를 좌우한다.** 관련도 게이트가 있어 유사어가 부실하면 후보가 아예 0건이 되므로, 세 경로로 유사어를 모은다.
| 출처 | 방식 | 강점 |
|---|---|---|
| `core` | 원문에서 조사·어미와 요청 표현("만들게", "추천해줘") 제거 후 추출 | 사용자 의도 그대로 |
| `catalog` | **카탈로그 실제 등재명에서 자동 생성한 색인**으로 확장 | 검색이 반드시 걸림 · 유지보수 불필요 |
| `client` | 호출하는 AI 어시스턴트가 `synonyms`로 전달 | 세상 지식 · 신조어/정책용어에 강함 |
| `dictionary` | 서버 내장 사전(`DOMAIN_EXPANSIONS`) 확장 | 오프라인 폴백 |
검색 우선순위는 `core → catalog → client → dictionary` 순이다. 카탈로그 등재명을 앞에 두는 이유는 정의상 검색이 반드시 걸리기 때문이다. 총 최대 20개 키워드를 모아 상위 8개로 카탈로그를 병렬 검색하고, 출처별 내역은 응답의 `keywordSources`로 확인할 수 있다.
**예: "그늘맵 앱 만들게 데이터 추천해줘"**
```
core (원문) 그늘맵
catalog (등재명) 그늘목 · 가로수 · 도시숲
client (Claude) 그늘막 · 무더위쉼터 · 폭염저감시설 · 쿨링포그 · 폭염취약지역
dictionary (사전) 그늘 · 폭염 · 녹지
```
개선 전에는 `그늘맵 · 만들게 · 추천해줘` 3개뿐이었고 그중 2개가 검색 노이즈였다.
### 카탈로그 어휘 색인 (`src/vocab/catalogVocabulary.ts`)
손으로 쓴 사전에는 두 가지 한계가 있다 — 신조어가 나올 때마다 사람이 추가해야 하고, 사전 어휘가 카탈로그 실제 등재명과 다를 수 있다. 그래서 카탈로그 전체(8천여 건)의 서비스명을 한 번 읽어 색인해 둔다 (24시간 캐시, 실패 시 사전으로 폴백).
- **n-gram 역색인** — "그늘맵"의 2-gram `그늘`로 등재명 `그늘막`·`그늘목`을 찾는다
- **분류 공출현** — 매칭된 표제어와 같은 정책분야(`MAP_CATE_NM`)의 빈출어를 제안한다. 표기가 전혀 겹치지 않는 `쿨링포그` 같은 용어를 잡는 경로다
- 범용어(`현황`, `설치`, `운영`…)와 여러 분류에 걸친 표제어는 변별력이 없어 제외한다
| 파라미터 | 타입 | 설명 |
|---|---|---|
| `ideaText` | string (필수) | 만들고 싶은 서비스 설명 |
| `apiOnly` | boolean | `SRV_TYPE`에 Api가 포함된 것만 반환 |
| `realtimePreferred` | boolean | 실시간/고빈도 갱신 데이터 우선 정렬 |
| `domainHint` | string | 도메인 힌트 (예: "교통", "따릉이") |
| `orgName` | string | 제공기관명으로 범위 축소 (예: "강남구") |
| `division` | string | "본청"/"산하기관"/"자치구" 포함 매칭 필터 |
| `limit` | number | 최대 추천 수 (기본 5, 최대 10) |
| `synonyms` | string[] | 호출하는 AI 어시스턴트가 넘기는 동의어·유의어 (최대 10개 반영) |
- **점수 배점 (95점 만점)**: 도메인 적합도 40 · 데이터 형태(`SRV_TYPE` 기준) 20 · 갱신주기 10 · 최신성 10 · 지역성 10 · 설명 품질 5
- **도메인 적합도 계산**: 매칭 위치별 가산(제목 > 태그 > 본문) 후 40점 상한. 원문 키워드(제목 **20** · 태그 5 · 본문 3)가 확장 유사어(7 · 3 · 2)보다 높은 가중을 받는다. 매칭 비율 방식이 아니라 가산 방식이므로 **유사어를 늘려도 점수가 희석되지 않는다**
- 원문 키워드 제목 매칭에 20점을 주는 이유는, 이 값이 낮으면 도메인 40점을 채우기 어려워 관련도와 무관하게 붙는 형태(20)+갱신주기(10)+최신성(10)이 순위를 뒤집기 때문이다. "그늘막"으로 검색했는데 그늘막 데이터가 3등으로 밀리던 문제가 그 경우였다
- **관련도 게이트**: 키워드가 주어진 질의에서 도메인 적합도가 0점인 데이터셋은 후보에서 제외된다. 이 가드가 없으면 질문과 무관한 데이터가 형태(20)+갱신주기(10)+최신성(10)+지역성(5)만으로 45점을 받아 상위에 올라온다
- 검색 결과는 있으나 게이트를 통과한 데이터가 없으면 억지 추천 대신 `warning`으로 안내
- 각 추천 결과에 `scoreBreakdown`(관련도·활용도 분리), `brm`(정책분야 분류), `organization`(제공기관 유형 분류)이 함께 반환됨
- **응답은 마크다운 표로 정리되어 나온다** — 데이터명(상세페이지 링크) · 제공형식 · 갱신주기 · 최종갱신 · 제공기관 · 담당부서 · 점수. 원시 JSON만 돌려주면 클라이언트마다 요약이 달라져 항목이 누락되므로, 표를 고정하고 구조화된 JSON을 `content`의 두 번째 블록으로 함께 보낸다 (`refine_seoul_recommendations` 입력용). `structuredContent` 필드는 일부러 쓰지 않는다 — 실제 MCP 클라이언트가 그 필드가 있으면 `content`(마크다운 표)를 무시하고 `structuredContent`만 보여주는 것을 확인했기 때문이다
- 여러 키워드 검색에서 동일 데이터가 중복 반환될 때 서비스 ID 기준으로 중복 제거
### `search_seoul_datasets`
서비스명 키워드 직접 검색, 원시 결과 반환. `orgName`·`division` 필터 지원.
### `list_seoul_recent_updates`
키워드/기관/제공주체로 범위를 좁혀 최종갱신일 내림차순 조회. 운영이 활발한 API를 우선 파악할 때 사용.
### `get_seoul_dataset_detail`
서비스 ID(예: `OA-22784`) 또는 상세 URL로 단건 조회. 제공기관·담당부서·갱신주기·최종갱신일·`SRV_TYPE` 반환. 개별 API의 요청 URL·파라미터 명세는 반환된 상세페이지 링크의 "Open API" 탭에서 확인 필요.
### `refine_seoul_recommendations`
이전 추천 결과를 API 재호출 없이 재필터링·재정렬 (호출 비용 절감).
</details>
<details>
<summary>정책분야·기관유형 분류 로직, 점수 산정 근거 펼쳐보기</summary>
### 정책분야(BRM) 분류
카탈로그 전체 8,255건 실측 결과 `MAP_CATE_NM`(소분류) 필드가 12개 정책분야와 전 건 1:1로 일치함을 확인했습니다. 근거가 없으면 추측하지 않고 미분류로 남깁니다.
| 분야 | 건수 | 분야 | 건수 |
|---|---|---|---|
| 보건 | 1,800 | 안전 | 244 |
| 문화/관광 | 1,634 | 인구/가구 | 256 |
| 산업/경제 | 936 | 도시관리 | 233 |
| 복지 | 647 | 주택/건설 | 157 |
| 교육 | 625 | 일반행정 | 549 |
| 환경 | 605 | 교통 | 569 |
### 제공기관 유형 분류
`DITC_NM`(제공 주체 구분) 실측 6개 값을 직접 매핑합니다.
| DITC_NM 원본값 | 건수 | 매핑 유형 |
|---|---|---|
| 서울시(본청) | 5,276 | headquarters — 서울시 본청 |
| 자치구 및 자치구산하 | 2,134 | district — 자치구 |
| 서울시(사업소) | 12 | business_office — 사업소 |
| 서울시(산하기관) | 364 | invested_funded — 투자·출연기관 |
| 공공기관(외부) | 467 | other — 기타 기관 |
| 민간(기업) | 2 | other — 기타 기관 |
### 관련도·활용도 분리 점수
기존 95점 스코어(정렬·필터링용)는 그대로 두고, `recommend_seoul_apis_for_idea` 결과마다 `scoreBreakdown`을 추가로 반환합니다.
- **질문 관련도** (배점 합 80): 데이터명·키워드·동의어 일치 40 · 정책분야 일치 15 · 지역조건 일치 10 · 실시간성 요구 일치 10 · 제공기관 조건 일치 5
- **데이터 활용도** (배점 합 65): 최신성 10 · 갱신주기 10 · 제공형식 존재 15 · 제공기관·부서 존재 10 · 문의처 존재 5 · 공식 상세페이지 존재 5 · 메타정보 충실도 10
`relevanceReasons`/`qualityReasons`에 각 배점이 부여된 근거를 사람이 읽을 수 있는 문장으로 함께 반환합니다.
</details>
## 🧭 시스템 구성
```
AI 어시스턴트 (Claude / Cursor)
│ MCP — stdio(로컬) 또는 Streamable HTTP(원격)
▼
seoul-opendata-mcp
├─ recommend_seoul_apis_for_idea 아이디어 → 키워드 → 검색 → 점수화 → 추천
├─ search_seoul_datasets 키워드 직접 검색
├─ list_seoul_recent_updates 최근 갱신일 기준 조회
├─ get_seoul_dataset_detail 서비스 ID 단건 조회
└─ refine_seoul_recommendations 이전 결과 재필터링 (API 재호출 없음)
│ HTTP GET
▼
openapi.seoul.go.kr:8088/{키}/json/SearchCatalogService/{시작}/{종료}/{ID}/{서비스명}/{기관명}/
```
**성능**: 추천 질의 1회(키워드 5개 병렬 검색) 기준 약 90ms대, 동일 조건 재질의는 캐시 히트로 외부 API 재호출 없이 즉시 응답. 단위 테스트 47개 전체 통과.
캐시 정책: 실시간성 키워드 질의 1분 · 일반 검색/추천 5분 · 상세 조회 30분 (in-memory TTL). 네트워크 오류·5xx 응답은 지수 백오프로 최대 3회 재시도합니다.
---
## 🖥️ 내 컴퓨터에 직접 설치하고 싶다면
공용 원격 서버 대신, **내 서울시 인증키로 직접 로컬에 설치**하고 싶은 경우입니다. (일반 사용자는 이 단계 필요 없음 — 위 "지금 바로 써보기"로 충분합니다.)
```bash
pnpm install
pnpm build
cp .env.example .env # SEOUL_OPEN_DATA_API_KEY 입력
```
- 인증키 발급: [data.seoul.go.kr](https://data.seoul.go.kr) 마이페이지 → 인증키 신청 (발급 즉시가 아니라 실제 반영까지 다소 시간이 걸릴 수 있음)
**Claude Code에 등록**
```bash
claude mcp add seoul-opendata-mcp -s user \
-e SEOUL_OPEN_DATA_API_KEY="발급받은_인증키" \
-- node "/절대경로/seoul-opendata-mcp/dist/server.js"
```
**Cursor / Claude Desktop** (`mcp.json` / `claude_desktop_config.json`)
```json
{
"mcpServers": {
"seoul-opendata-mcp": {
"command": "node",
"args": ["/절대경로/seoul-opendata-mcp/dist/server.js"],
"env": { "SEOUL_OPEN_DATA_API_KEY": "발급받은_인증키" }
}
}
}
```
## ☁️ 내 서버로 직접 배포하고 싶다면
내 계정으로 이 서버를 통째로 새로 띄우고 싶을 때입니다 (Fly.io / Render / Railway / Cloud Run 등, 저장소에 `Dockerfile`이 포함돼 있어 어디든 동일하게 올라갑니다).
```
POST /mcp MCP JSON-RPC (Streamable HTTP, 무상태 모드)
GET /healthz 헬스체크
```
**Fly.io 예시**
```bash
fly launch --no-deploy --copy-config
# 인증 없이 공개할 경우 (현재 공용 서버 운영 방식)
fly secrets set SEOUL_OPEN_DATA_API_KEY=발급받은키
# 특정 사용자에게만 열려면 토큰을 추가로 설정
# fly secrets set MCP_AUTH_TOKEN=$(openssl rand -hex 32)
fly deploy
curl -s https://<app>.fly.dev/healthz
```
**클라이언트 등록**
```bash
claude mcp add --transport http seoul-opendata https://<app>.fly.dev/mcp
# MCP_AUTH_TOKEN을 설정했다면 헤더를 함께 넘긴다
claude mcp add --transport http seoul-opendata https://<app>.fly.dev/mcp \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"
```
- 무상태 모드라 머신을 늘려도 세션이 꼬이지 않고, 트래픽이 없으면 머신이 자동으로 잠들어 비용이 거의 들지 않습니다.
- **인증 없이 공개한다면 호출 제한을 켜 두세요.** 서울시 카탈로그 API는 호출 횟수 제한이 없어 인증키 한도가 소진될 걱정은 없지만, 누구나 호출할 수 있는 상태에서는 서버 자원이 그대로 노출됩니다. IP당 호출 제한이 기본값(분당 20 · 시간당 200)으로 켜져 있어 `MCP_RATE_LIMIT_*`를 `0`으로 끄지 않는 한 보호됩니다.
- 한도를 넘으면 `429`와 함께 `Retry-After` 헤더가 반환됩니다. 정상 사용에서는 걸릴 일이 거의 없지만, 실습 인원이 같은 네트워크(공유 공인 IP)에서 접속한다면 한도를 넉넉히 올려 주세요.
자세한 환경변수·플랫폼별 절차·운영 주의점은 **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)** 참고.
---
## 🧪 테스트 · 빌드
```bash
pnpm test # vitest — 47개 테스트
pnpm build # TypeScript 컴파일
pnpm dev # stdio 서버 (로컬 개발)
pnpm dev:http # HTTP 서버 (기본 http://localhost:8080/mcp)
```
## 🔧 사용 기술
TypeScript · Node.js 20+ · `@modelcontextprotocol/sdk` · zod · vitest · pnpm · GitHub Actions
## 📄 라이선스
MIT
TDQS
Scored across 5 tools
Each tool has a distinct role: recommendation, direct search, detail lookup, refinement, and recent updates. The main overlap is between recommend_seoul_apis_for_idea and search_seoul_datasets, but their input types and purposes are clearly differentiated.
All tool names follow a consistent snake_case verb_noun pattern (recommend_, search_, get_, refine_, list_). The objects vary slightly (apis, datasets, recommendations, recent_updates), but the structure is predictable and readable.
Five tools is a lean but reasonable scope for a catalog discovery server. Each tool serves a distinct facet of finding and exploring Seoul open data, though coverage could be expanded without feeling bloated.
The core discovery lifecycle is covered: search, detail, recommendations, and recent updates. However, a significant gap exists—get_seoul_dataset_detail explicitly cannot return API request URLs or parameter specs, requiring a browser workaround, which limits the set's usefulness for actually consuming APIs.