Skip to main content
Glama
hlucent

airkorea-realtime-mcp

by hlucent
README.md
> ⚠️ **서비스 종료 안내**
> 본 MCP 서버는 2026-08-23부로 fly.io 배포를 종료했습니다.
> 코드는 개발 참고용으로 저장소에 남겨둡니다.

# airkorea-realtime-mcp

한국환경공단 에어코리아 OpenAPI의 **실시간 대기오염정보 · 통합대기환경지수(CAI) ·
측정소정보**를 조회하는 MCP 서버입니다.

## 제공 도구 (5개)

| 도구 | 설명 |
|---|---|
| `get_station_realtime_air_quality` | 측정소명으로 해당 측정소의 실시간 대기오염 측정정보(SO2/CO/O3/NO2/PM10/PM2.5, 통합대기환경지수) 조회 |
| `get_sido_realtime_air_quality` | 시도명으로 해당 시도 전체 측정소의 실시간 측정정보 조회 |
| `get_station_cai` | 측정소명으로 실시간 통합대기환경지수(CAI) 조회 |
| `search_stations` | 주소 또는 측정소명으로 측정소 목록/좌표 검색 |
| `get_nearby_stations` | 좌표를 입력해 주변 측정소와 거리 조회 |

## 데이터 출처

- **제공기관**: 한국환경공단 기후대기본부 대기환경처 대기정책지원부
- **플랫폼**: [공공데이터포털](https://www.data.go.kr) (data.go.kr)
- **서비스 그룹**: 한국환경공단_에어코리아_대기오염정보, 한국환경공단_에어코리아_측정소정보,
  한국환경공단_에어코리아_통합대기환경지수(CAI) 조회 서비스 (서비스 그룹 코드 `B552584`)
- **이용허락범위**: 저작자표시-변경금지 (자료의 출처(환경부/한국환경공단) 표기 의무 준수)

## 측정 단위 및 등급 기준

| 항목 | SO2 | CO | O3 | NO2 | PM10 | PM2.5 |
|---|---|---|---|---|---|---|
| 단위 | ppm | ppm | ppm | ppm | ㎍/㎥ | ㎍/㎥ |

등급(Grade) 값: **1=좋음, 2=보통, 3=나쁨, 4=매우나쁨**

## 알려진 제약사항 (실측으로 확인된 사항)

- **측정소 목록(`search_stations`) 좌표축**: `ver=1.1`로 고정 호출 시 `dmX`=경도,
  `dmY`=위도로 정상 확인됨(서로 다른 측정소 5곳으로 교차검증 완료). WGS84 기준.
- **근접측정소 조회(`get_nearby_stations`) 좌표계**: WGS84 위경도를 그대로 넣으면
  완전히 엉뚱한 결과(예: 서울 강남구 좌표 입력 시 제주도 측정소 반환)가 나옴을 실측으로
  확인. **TM중부원점(EPSG:5181) 좌표 변환이 반드시 필요**하며, 본 서버는 pyproj로
  WGS84→EPSG:5181 자동 변환 후 API를 호출한다(사용자는 위경도만 입력하면 됨).
  강남구청 좌표(37.515336, 127.049357) 입력 시 강남구 측정소가 거리 0.4km로 최상위
  반환되어 변환 정확도를 검증함.
- **CAI 조회(`get_station_cai`) 응답 필드**: 명세서 표(`khaiValue`/`khaiGrade`/`khaiItem`)와
  달리 실제 필드명은 `caiValue`/`caiGrade`/`caiItem`이다. 전체 필드는
  `stationCode`, `stationName`, `mangName`, `dataTime`, `caiValue`, `caiGrade`, `caiItem`
  (개별 오염물질 필드 so2Value 등은 이 오퍼레이션 응답에는 포함되지 않음).
- **CAI 조회의 정상 resultCode**: 다른 4개 오퍼레이션은 정상 시 `resultCode="00"`이지만,
  `getMsrstnKhaiRltmDnsty`(CAI)만 정상 시 `resultCode="200"`, `resultMsg="NORMAL_CODE"`로
  응답함. 서버 코드는 `resultMsg`가 `"NORMAL_CODE"`/`"NORMAL SERVICE"`인 경우도 정상으로
  처리하도록 되어 있음.
- **items 응답 구조 차이**: `ArpltnInforInqireSvc`/`MsrstnInfoInqireSvc`는 `items`가 배열,
  `RltmKhaiInfoSvc`(CAI)는 `items.item`으로 한 단계 더 감싸져 있음. 서버는 이 차이를
  흡수해 항상 배열로 반환한다.
- **결측값 표현**: 이번 실측 범위(정상 대기질 데이터)에서는 `"-"` 결측 케이스가 나타나지
  않았으나, 명세서 예제에 근거해 `_safe_numeric`이 `"-"`/`None`/빈 문자열/실수(float) 모두
  `None`으로 안전 변환하도록 구현되어 있다(0으로 임의 대체하지 않음).
- **API 서버 안정성**: 실측 중 `SERVICETIMEOUT_ERROR`(에러코드 05, HTTP 504)가 빈번하게
  발생함을 확인. 서버는 코드 05에 한해 최대 3회까지 자동 재시도한다.
- **sidoName "전남광주"**: 정상 조회됨(2026년 전남광주특별시 출범 반영, totalCount 64건 확인).

## 환경변수

| 변수명 | 설명 |
|---|---|
| `AIRKOREA_SERVICE_KEY` | 공공데이터포털에서 발급받은 에어코리아 서비스키 (Decoding 키) |

## 설치 및 실행 (로컬)

```bash
pip install -r requirements.txt --break-system-packages
cp .env.example .env   # AIRKOREA_SERVICE_KEY 값 입력
python server.py
```

## 배포 (fly.io)

```powershell
fly launch --no-deploy
# fly.toml이 [http_service] 방식인지 확인 후
fly secrets set AIRKOREA_SERVICE_KEY=발급받은키
flyctl deploy
```

## Claude.ai 커넥터 연결

배포 완료 후 주소 뒤에 `/mcp`를 붙여서 연결합니다.

```
https://airkorea-realtime-mcp.fly.dev/mcp
```

## Rate Limit 정책

API 키 없이 URL만으로 연결 가능한 공개 서버이므로, IP 기준 3단계 rate limit이 적용됩니다.

- 분당 3회 초과 시 429 (멀티 머신 배포 시 머신 수에 비례해 실질 완화될 수 있음)
- 1시간 내 429를 5회 이상 받으면 24시간 차단
- 일일(rolling 24시간) 총 30회 초과 시 429

## 에러 코드

이 API는 공공데이터포털 표준 에러코드 체계를 사용합니다 (서울시 열린데이터광장의
INFO-000/ERROR-3xx 체계와 다름).

| 코드 | 의미 |
|---|---|
| 00 | 정상 |
| 03 | No Data (데이터 없음) |
| 10 | 잘못된 요청 파라미터 |
| 11 | 필수 파라미터 누락 |
| 20 | 서비스 접근 거부 (활용 미신청) |
| 22 | 일일 트래픽 제한 초과 |
| 30 | 등록하지 않은 서비스키 |
| 31 | 서비스키 사용 기간 만료 |

## 관련 프로젝트

에어코리아 OpenAPI는 규모가 커서 3개의 독립 MCP로 분리 개발됩니다:

| 단계 | 저장소 | 포함 범위 |
|---|---|---|
| 1단계 (이 프로젝트) | `airkorea-realtime-mcp` | 실시간 측정정보, CAI, 측정소정보 |
| 2단계 | `airkorea-forecast-alert-mcp` | 대기질 예보, 미세먼지 경보, 오존·황사 주의보 |
| 3단계 | `airkorea-statistics-mcp` | 시도·측정소 통계(일/월평균), CAI 나쁨이상 측정소 |

## 라이선스

MIT (코드) / 공공누리 제1유형(저작자표시) 준수 — 데이터 출처(환경부/한국환경공단) 표기