Skip to main content
Glama

HRFCO Service

http_mcp_server.py15.4 kB
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ HTTP MCP Server for ChatGPT text/event-stream 헤더 요구 없이 일반 HTTP로 MCP 프로토콜 지원 """ import os import sys import asyncio import json from datetime import datetime, timedelta from typing import Dict, Any # FastAPI 및 관련 라이브러리 try: from fastapi import FastAPI, HTTPException, Response, Body from fastapi.responses import JSONResponse from fastapi.middleware.cors import CORSMiddleware import httpx import uvicorn except ImportError as e: print(f"필수 패키지 설치가 필요합니다: {e}") print("다음 명령을 실행하세요:") print('pip install fastapi httpx uvicorn') sys.exit(1) # 환경변수 설정 HRFCO_API_KEY = os.getenv('HRFCO_API_KEY', '') WEATHER_API_KEY = os.getenv('WEATHER_API_KEY', '') WAMIS_API_KEY = os.getenv('WAMIS_API_KEY', '') # FastAPI 앱 생성 app = FastAPI(title="HRFCO HTTP MCP Server", version="1.1.0") # CORS 허용 (ChatGPT 등 외부에서 사전요청/검증 가능하도록) app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"] ) class HRFCOClient: """홍수통제소 API 클라이언트""" def __init__(self, api_key: str): self.api_key = api_key self.base_url = "http://api.hrfco.go.kr" async def get_observatories(self, hydro_type: str = "waterlevel") -> Dict[str, Any]: """관측소 정보 조회""" if not self.api_key: raise ValueError("API 키가 필요합니다. HRFCO_API_KEY 환경변수를 설정해주세요.") try: url = f"http://api.hrfco.go.kr/{self.api_key}/{hydro_type}/info.json" async with httpx.AsyncClient() as client: response = await client.get(url, timeout=30.0) response.raise_for_status() return response.json() except Exception as e: raise Exception(f"홍수통제소 API 호출 실패: {str(e)}") async def get_waterlevel_data(self, obs_code: str, time_type: str = "1H") -> Dict[str, Any]: """수위 데이터 조회""" if not self.api_key: raise ValueError("API 키가 필요합니다. HRFCO_API_KEY 환경변수를 설정해주세요.") try: url = f"http://api.hrfco.go.kr/{self.api_key}/waterlevel/data.json" params = { "obs_code": obs_code, "time_type": time_type } async with httpx.AsyncClient() as client: response = await client.get(url, params=params, timeout=30.0) response.raise_for_status() return response.json() except Exception as e: raise Exception(f"수위 데이터 조회 실패: {str(e)}") class WeatherClient: """기상청 API 클라이언트""" def __init__(self, api_key: str): self.api_key = api_key self.base_url = "http://apis.data.go.kr/1360000/VilageFcstInfoService_2.0" async def get_weather_data(self, nx: int, ny: int) -> Dict[str, Any]: """날씨 데이터 조회""" if not self.api_key: raise ValueError("API 키가 필요합니다. WEATHER_API_KEY 환경변수를 설정해주세요.") try: url = f"{self.base_url}/getVilageFcst" params = { "serviceKey": self.api_key, "numOfRows": 10, "pageNo": 1, "dataType": "JSON", "base_date": datetime.now().strftime("%Y%m%d"), "base_time": "0500", "nx": nx, "ny": ny } async with httpx.AsyncClient() as client: response = await client.get(url, params=params, timeout=30.0) response.raise_for_status() return response.json() except Exception as e: raise Exception(f"기상청 API 호출 실패: {str(e)}") # 클라이언트 인스턴스 생성 hrfco_client = HRFCOClient(HRFCO_API_KEY) weather_client = WeatherClient(WEATHER_API_KEY) @app.get("/") async def root(): """루트 엔드포인트""" return { "message": "HRFCO HTTP MCP Server", "version": "1.1.0", "endpoints": { "mcp": "/mcp", "health": "/health", "tools": "/tools" } } @app.get("/health") async def health(): """헬스 체크""" return { "status": "healthy", "timestamp": datetime.now().isoformat(), "api_keys": { "hrfco": bool(HRFCO_API_KEY), "weather": bool(WEATHER_API_KEY), "wamis": bool(WAMIS_API_KEY) } } @app.get("/.well-known/mcp") async def mcp_well_known(): """간단한 MCP 메타데이터(선택적)""" return { "protocol": "mcp", "version": "1.0.0", "capabilities": { "tools": True, "resources": False, "prompts": False } } @app.get("/tools") async def list_tools(): """사용 가능한 도구 목록""" return { "tools": [ { "name": "get_observatories", "description": "홍수통제소 관측소 정보 조회", "inputSchema": { "type": "object", "properties": { "hydro_type": { "type": "string", "description": "수문 유형 (waterlevel, flow 등)", "default": "waterlevel" } } } }, { "name": "get_waterlevel_data", "description": "수위 데이터 조회", "inputSchema": { "type": "object", "properties": { "obs_code": { "type": "string", "description": "관측소 코드" }, "time_type": { "type": "string", "description": "시간 유형 (1H, 1D 등)", "default": "1H" } }, "required": ["obs_code"] } }, { "name": "get_weather_data", "description": "날씨 데이터 조회", "inputSchema": { "type": "object", "properties": { "nx": { "type": "integer", "description": "격자 X 좌표" }, "ny": { "type": "integer", "description": "격자 Y 좌표" } }, "required": ["nx", "ny"] } } ] } @app.get("/mcp") async def mcp_probe(): """사전 점검용 엔드포인트 (GET)""" return {"status": "ok", "message": "MCP endpoint. Use POST JSON-RPC."} @app.head("/mcp") async def mcp_head(): """사전 점검용 엔드포인트 (HEAD)""" return Response(status_code=200) @app.options("/mcp") async def mcp_options(): return Response(status_code=204, headers={"Allow": "POST, GET, HEAD, OPTIONS"}) @app.post("/mcp") async def mcp_endpoint(payload: Dict[str, Any] = Body(...)): """MCP 프로토콜 엔드포인트""" try: # JSON-RPC 요청 처리 method = payload.get("method") params = payload.get("params", {}) request_id = payload.get("id") # MCP initialize 핸드셰이크 지원 (OpenAI Platform 호환) if method == "initialize": result = { "protocolVersion": "2024-11-05", "capabilities": { "tools": { # 서버가 도중에 도구 목록 변경 알림을 보낼 수 있는지 여부 "listChanged": False }, # 일부 클라이언트가 키 존재를 기대할 수 있으므로 명시 "resources": {}, "prompts": {} }, "serverInfo": { "name": "hrfco-http-mcp", "version": "1.1.0" } } return {"jsonrpc": "2.0", "id": request_id, "result": result} # 무시 가능한 알림/핑 처리 if method in ("notifications/initialized", "ping"): return {"jsonrpc": "2.0", "id": request_id, "result": {"ok": True}} if method == "tools/list": return { "jsonrpc": "2.0", "id": request_id, "result": { "tools": [ { "name": "get_observatories", "description": "홍수통제소 관측소 정보 조회", "inputSchema": { "type": "object", "properties": { "hydro_type": { "type": "string", "description": "수문 유형 (waterlevel, flow 등)", "default": "waterlevel" } }, "additionalProperties": False } }, { "name": "get_waterlevel_data", "description": "수위 데이터 조회", "inputSchema": { "type": "object", "properties": { "obs_code": { "type": "string", "description": "관측소 코드" }, "time_type": { "type": "string", "description": "시간 유형 (1H, 1D 등)", "default": "1H" } }, "additionalProperties": False, "required": ["obs_code"] } }, { "name": "get_weather_data", "description": "날씨 데이터 조회", "inputSchema": { "type": "object", "properties": { "nx": { "type": "integer", "description": "격자 X 좌표" }, "ny": { "type": "integer", "description": "격자 Y 좌표" } }, "additionalProperties": False, "required": ["nx", "ny"] } } ] } } elif method == "tools/call": tool_name = params.get("name") arguments = params.get("arguments", {}) if tool_name == "get_observatories": result = await hrfco_client.get_observatories( hydro_type=arguments.get("hydro_type", "waterlevel") ) return { "jsonrpc": "2.0", "id": request_id, "result": { "content": [ { "type": "text", "text": json.dumps(result, ensure_ascii=False, indent=2) } ] } } elif tool_name == "get_waterlevel_data": result = await hrfco_client.get_waterlevel_data( obs_code=arguments.get("obs_code"), time_type=arguments.get("time_type", "1H") ) return { "jsonrpc": "2.0", "id": request_id, "result": { "content": [ { "type": "text", "text": json.dumps(result, ensure_ascii=False, indent=2) } ] } } elif tool_name == "get_weather_data": result = await weather_client.get_weather_data( nx=arguments.get("nx"), ny=arguments.get("ny") ) return { "jsonrpc": "2.0", "id": request_id, "result": { "content": [ { "type": "text", "text": json.dumps(result, ensure_ascii=False, indent=2) } ] } } else: return { "jsonrpc": "2.0", "id": request_id, "error": { "code": -32601, "message": f"Unknown tool: {tool_name}" } } else: return { "jsonrpc": "2.0", "id": request_id, "error": { "code": -32601, "message": f"Unknown method: {method}" } } except Exception as e: return { "jsonrpc": "2.0", "id": payload.get("id"), "error": { "code": -32603, "message": f"Internal error: {str(e)}" } } if __name__ == "__main__": print("🌐 HTTP MCP 서버를 시작합니다...") print("📡 URL: http://0.0.0.0:8000") print("🔗 MCP 엔드포인트: http://0.0.0.0:8000/mcp") print(f"API 키 상태:") print(f" HRFCO: {'✅' if HRFCO_API_KEY else '❌'}") print(f" WEATHER: {'✅' if WEATHER_API_KEY else '❌'}") print(f" WAMIS: {'✅' if WAMIS_API_KEY else '❌'}") uvicorn.run( app, host="0.0.0.0", port=8000, log_level="info" )

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kwenhwang/hrfco-service'

If you have feedback or need assistance with the MCP directory API, please join our Discord server