Skip to main content
Glama

HRFCO Service

mcp.ts25.4 kB
import { Handler } from '@netlify/functions'; import { normalizeQuery, calculateSimilarity, fetchHRFCOData, Station, SearchResult } from './utils'; import { stationMapper } from './station-mapper'; import { getWaterDataByName, PipelineResult } from './water-data-pipeline'; import { smartStationMapper, SearchResult as SmartSearchResult } from './smart-station-mapper'; // StationMapper 초기화 상태 let isMapperInitialized = false; // MCP Tools 정의 (get_water_info를 최상단으로 이동) const tools = [ { name: "get_water_info", description: "관측소 검색 및 실시간 수위 데이터 통합 조회 (ChatGPT 무한 반복 방지용) - 권장 도구", inputSchema: { type: "object", properties: { query: { type: "string", description: "검색어 (관측소명, 하천명, 위치)" } }, required: ["query"] } }, { name: "search_water_station_by_name", description: "지역명이나 강 이름으로 관측소 검색 (실제 코드 포함)", inputSchema: { type: "object", properties: { location_name: { type: "string", description: "검색할 지역명 또는 강 이름 (예: '한강', '서울', '부산')" }, data_type: { type: "string", description: "데이터 타입", enum: ["waterlevel", "rainfall", "dam"], default: "waterlevel" }, limit: { type: "number", description: "반환할 최대 결과 수", default: 5, maximum: 10 } }, required: ["location_name"] } }, { name: "get_water_info_by_location", description: "자연어 수문 정보 조회 (실제 코드 포함)", inputSchema: { type: "object", properties: { query: { type: "string", description: "자연어 쿼리 (예: '한강 수위', '서울 강우량')" }, limit: { type: "number", description: "반환할 최대 결과 수", default: 5, maximum: 10 } }, required: ["query"] } }, { name: "recommend_nearby_stations", description: "주변 관측소 추천 (실제 코드 포함)", inputSchema: { type: "object", properties: { location: { type: "string", description: "기준 위치 (지역명)" }, radius: { type: "number", description: "검색 반경 (km)", default: 20 }, priority: { type: "string", description: "우선순위 기준", enum: ["distance", "relevance"], default: "distance" } }, required: ["location"] } } ]; // MCP 메서드 핸들러들 async function mcpInitialize() { return { jsonrpc: "2.0", result: { protocolVersion: "2024-11-05", capabilities: { tools: {} }, serverInfo: { name: "K-Water 수문정보 MCP 서버", version: "1.0.0" } } }; } async function mcpToolsList() { return { jsonrpc: "2.0", result: { tools } }; } async function mcpToolsCall(toolName: string, args: any) { try { let result; switch (toolName) { case "search_water_station_by_name": result = await searchWaterStationByName(args); break; case "get_water_info_by_location": result = await getWaterInfoByLocation(args); break; case "get_water_info": result = await getWaterInfoIntegrated(args); break; case "recommend_nearby_stations": result = await recommendNearbyStations(args); break; default: throw new Error(`Unknown tool: ${toolName}`); } // 통합 검색인 경우 특별한 형태로 반환 if (toolName === 'get_water_info' && (result as any).status) { return { jsonrpc: "2.0", result: { content: [ { type: "text", text: formatIntegratedResponse(result) } ] } }; } return { jsonrpc: "2.0", result: { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] } }; } catch (error: any) { return { jsonrpc: "2.0", error: { code: -32603, message: error.message || 'Internal error' } }; } } function mcpError(id: any, message: string) { return { jsonrpc: "2.0", id, error: { code: -32601, message } }; } // 기존 함수 로직들 (그대로 유지) async function searchWaterStationByName(params: any) { const locationName = params.location_name; const dataType = params.data_type || 'waterlevel'; const limit = Math.min(parseInt(params.limit || '5'), 10); if (!locationName) { throw new Error('location_name parameter required'); } try { // SmartStationMapper를 사용하여 지능형 검색 const searchResults = smartStationMapper.searchByName(locationName, dataType as 'dam' | 'waterlevel' | 'rainfall'); // 결과를 기존 형식으로 변환 const stations = searchResults.slice(0, limit).map(result => ({ code: result.code, name: result.name, address: result.region, agency: result.agency, real_code: result.code // 실제 HRFCO 코드 })); const result: SearchResult = { query: locationName, data_type: dataType, found_stations: stations.length, total_available: smartStationMapper.getStats().total, stations: stations }; return result; } catch (error: any) { return { query: locationName, data_type: dataType, error: `검색 중 오류 발생: ${error.message}`, found_stations: 0, total_available: 0, stations: [], note: "SmartStationMapper 오류로 인해 검색에 실패했습니다." }; } } async function getWaterInfoByLocation(params: any) { const query = params.query; const limit = Math.min(parseInt(params.limit || '5'), 10); if (!query) { throw new Error('query parameter required'); } const queryInfo = normalizeQuery(query); try { const data = await fetchHRFCOData(`${queryInfo.dataType}/info.json`); const stations: Station[] = data.content || []; if (stations.length === 0) { return { status: 'no_match', message: `'${query}'에 대한 관측소를 찾을 수 없습니다`, suggestions: [] }; } const scoredStations = stations .filter(station => station && station.obsnm) // null 체크 추가 .map(station => ({ station, score: calculateSimilarity(station, queryInfo) })) .filter(item => item.score > 0.1) .sort((a, b) => b.score - a.score) .slice(0, limit); if (scoredStations.length === 0) { const suggestions = stations .filter(s => s && s.obsnm && s.addr) // null 체크 추가 .slice(0, 5) .map(s => `${s.obsnm} (${s.addr})`) .filter(s => s.includes(query.slice(0, 1))); return { status: 'no_match', message: `'${query}'에 대한 관측소를 찾을 수 없습니다`, suggestions: suggestions.slice(0, 3) }; } return { status: 'success', summary: `${query} 관련 ${scoredStations.length}개 관측소 발견`, data: { query, data_type: queryInfo.dataType, found_stations: scoredStations.length, total_available: stations.length, stations: scoredStations.map(({ station }) => { // 실제 코드 매핑을 통해 코드 개선 const stationName = station.obsnm || ''; const mappedCode = findStationCode(stationName) || station.wlobscd || station.rfobscd || station.damcd || ''; return { code: mappedCode, name: stationName, address: station.addr || '', agency: station.agcnm || '', real_code: mappedCode !== '' ? '실제 HRFCO 코드' : 'API 코드' }; }) } }; } catch (error: any) { // API 오류 시 대체 응답 return { status: 'error', message: `${query} 조회 중 오류가 발생했습니다: ${error.message}`, suggestion: 'API 키를 확인하거나 다른 검색어를 시도해보세요.' }; } } async function recommendNearbyStations(params: any) { const location = params.location; const radius = parseInt(params.radius || '20'); const priority = params.priority || 'distance'; if (!location) { throw new Error('location parameter required'); } const queryInfo = normalizeQuery(location); const data = await fetchHRFCOData('waterlevel/info.json'); const stations: Station[] = data.content || []; const scoredStations = stations .map(station => ({ station, score: calculateSimilarity(station, queryInfo) })) .filter(item => item.score > 0.05) .sort((a, b) => b.score - a.score) .slice(0, 10); const recommendations = scoredStations .slice(0, 5) .map(({ station }) => { // 실제 코드 매핑을 통해 코드 개선 const stationName = station.obsnm || ''; const mappedCode = findStationCode(stationName) || station.wlobscd || ''; return { code: mappedCode, name: stationName, address: station.addr || '', agency: station.agcnm || '', real_code: mappedCode !== '' ? '실제 HRFCO 코드' : 'API 코드' }; }); return { location, radius_km: radius, priority, recommendations }; } // 통합 검색 및 데이터 조회 (ChatGPT 무한 반복 방지용) async function getWaterInfoIntegrated(params: any) { const query = params.query; if (!query) { throw new Error('query parameter required'); } try { // 새로운 파이프라인으로 데이터 조회 const result: PipelineResult = await getWaterDataByName(query); if (result.found_stations === 0) { return createErrorResponse(`'${query}' 관측소를 찾을 수 없습니다.`); } // 파이프라인 결과를 통합 응답 형식으로 변환 const responseText = formatPipelineResponse(result); console.log('🔍 getWaterInfoIntegrated response:', { responseText, hasDirectAnswer: !!result.direct_answer, directAnswer: result.direct_answer }); // ChatGPT가 추가 질문하지 않도록 완전한 응답 구조로 반환 return { status: 'success', summary: result.summary || `${result.found_stations}개 관측소 발견`, direct_answer: responseText, detailed_data: { primary_station: result.stations[0] || {}, related_stations: result.stations.slice(1, 4) || [] }, timestamp: result.timestamp, no_additional_query_needed: true }; } catch (error: any) { return createErrorResponse(`데이터 조회 중 오류가 발생했습니다: ${error.message}`); } } function formatPipelineResponse(result: PipelineResult): string { if (!result || result.found_stations === 0 || !result.stations || result.stations.length === 0) { return result.direct_answer || '관측소 정보를 찾을 수 없습니다.'; } const primaryStation = result.stations[0]; const stationData = primaryStation.current_data || {}; const dataType = result.query_analysis?.dataType || 'waterlevel'; // 데이터 타입에 따라 동적 제목 생성 let title: string; if (dataType === 'rainfall') { title = `🌧️ ${primaryStation.name} 실시간 강수량 정보`; } else if (dataType === 'dam') { title = `댐 ${primaryStation.name} 실시간 댐 정보`; } else { title = `🌊 ${primaryStation.name} 실시간 수위 정보`; } let directAnswer = result.direct_answer || '데이터를 요약할 수 없습니다.'; // 상세 정보 포맷팅 let details = '📈 **상세 정보**:\n'; if (dataType === 'rainfall') { details += `• 강수량: ${stationData.rainfall || 'N/A'}\n`; } else if (dataType === 'waterlevel') { details += `• 수위: ${stationData.water_level || 'N/A'}\n`; } else if (dataType === 'dam') { details += `• 수위: ${stationData.water_level || 'N/A'}\n`; details += `• 저수율: ${stationData.storage_rate || 'N/A'}\n`; details += `• 유입량: ${stationData.inflow || 'N/A'}\n`; details += `• 방류량: ${stationData.outflow || 'N/A'}\n`; } details += `• 상태: ${stationData.status || '정상'}\n`; if (stationData.trend) { details += `• 추세: ${stationData.trend}\n`; } details += `• 최종 업데이트: ${stationData.last_updated ? new Date(stationData.last_updated).toLocaleString('ko-KR') : 'N/A'}\n`; // 관련 관측소 포맷팅 let related = ''; if (result.stations.length > 1) { related = '\n🔗 **관련 관측소**:\n'; result.stations.slice(1, 4).forEach(st => { related += `• ${st.name} (코드: ${st.code})\n`; }); } const timestamp = `\n⏰ 조회 시간: ${new Date(result.timestamp).toLocaleString('ko-KR')}`; return `${title}\n\n📊 **현재 상태**: ${directAnswer}\n\n${details}${related}${timestamp}`; } // StationMapper 초기화 함수 async function initializeStationMapper(): Promise<void> { if (!isMapperInitialized) { console.log('🔄 StationMapper 초기화 시작...'); await stationMapper.initializeMapping(); isMapperInitialized = true; console.log('✅ StationMapper 초기화 완료'); } } function findStationCode(query: string): string | null { // StationMapper를 사용하여 관측소 코드 찾기 const result = stationMapper.findStationCode(query); return result ? result.code : null; } function createIntegratedResponse(stationName: string, stationCode: string, data: any) { const currentLevel = `${data.water_level.toFixed(1)}m`; const storageRate = calculateStorageRate(data.water_level); const status = determineStatus(data.water_level); const trend = determineTrend(data.water_level); const lastUpdated = new Date(data.obs_time).toLocaleString('ko-KR'); return { status: 'success', summary: `${stationName} 현재 수위는 ${currentLevel}입니다 (저수율 ${storageRate})`, direct_answer: `${stationName}의 현재 수위는 ${currentLevel}이며, 저수율 ${storageRate}로 ${status} 상태입니다.`, detailed_data: { primary_station: { name: stationName, code: stationCode, current_level: currentLevel, storage_rate: storageRate, status: status, trend: trend, last_updated: lastUpdated }, related_stations: getRelatedStations(stationName) }, timestamp: new Date().toISOString() }; } function createErrorResponse(message: string) { return { status: 'error', summary: message, direct_answer: message, detailed_data: { primary_station: { name: '', code: '' } }, timestamp: new Date().toISOString() }; } function calculateStorageRate(waterLevel: number): string { // 간단한 저수율 계산 (실제로는 더 복잡한 공식 필요) const baseLevel = 100; // 기준 수위 const maxLevel = 150; // 최대 수위 const rate = Math.min(100, Math.max(0, ((waterLevel - baseLevel) / (maxLevel - baseLevel)) * 100)); return `${rate.toFixed(1)}%`; } function determineStatus(waterLevel: number): string { if (waterLevel < 110) return '낮음'; if (waterLevel > 140) return '높음'; return '정상'; } function determineTrend(waterLevel: number): string { // 실제로는 이전 데이터와 비교해야 함 const random = Math.random(); if (random < 0.3) return '상승'; if (random < 0.6) return '하강'; return '안정'; } function getRelatedStations(stationName: string): Array<{name: string, code: string, current_level?: string, status?: string}> { // 관련 관측소 반환 (간단한 예시) const related = []; if (stationName.includes('댐')) { related.push({ name: '소양댐', code: '1018681' }); related.push({ name: '충주댐', code: '1018682' }); } else if (stationName.includes('대교')) { related.push({ name: '한강대교', code: '1018690' }); related.push({ name: '잠실대교', code: '1018691' }); } return related; } function getDemoWaterLevelData(obsCode: string): any[] { // 관측소별 현실적인 수위 데이터 (댐, 수위관측소, 우량관측소 포함) const stationData: Record<string, number> = { // 주요 댐들 '1018680': 120.5, // 대청댐 '1018681': 115.2, // 소양댐 '1018682': 118.8, // 충주댐 '1018683': 125.3, // 안동댐 '1018684': 122.1, // 임하댐 '1018685': 128.7, // 합천댐 '1018686': 116.9, // 영주댐 '1018687': 119.4, // 보령댐 '1018688': 124.6, // 대암댐 '1018689': 117.8, // 춘천댐 '1018690': 123.2, // 팔당댐 '1018691': 115.7, // 의암댐 '1018692': 121.3, // 청평댐 '1018693': 118.9, // 화천댐 // 한강 수위관측소들 '1018700': 8.5, // 한강대교 '1018701': 7.2, // 잠실대교 '1018702': 6.8, // 성산대교 '1018703': 7.5, // 반포대교 '1018704': 6.9, // 동작대교 '1018705': 7.1, // 한남대교 '1018706': 6.7, // 청담대교 '1018707': 7.3, // 영동대교 '1018708': 8.1, // 구리대교 '1018709': 7.8, // 팔당대교 '1018710': 8.3, // 양평대교 '1018711': 7.9, // 여주대교 '1018712': 8.0, // 이천대교 '1018713': 7.6, // 안성대교 '1018714': 7.4, // 평택대교 '1018715': 7.7, // 아산대교 '1018716': 7.8, // 천안대교 '1018717': 8.2, // 공주대교 '1018718': 7.9, // 부여대교 '1018719': 8.1, // 논산대교 '1018720': 7.5, // 익산대교 '1018721': 7.3, // 전주대교 '1018722': 7.1, // 군산대교 '1018723': 7.4, // 김제대교 '1018724': 7.6, // 정읍대교 '1018725': 7.8, // 순창대교 '1018726': 7.2, // 남원대교 '1018727': 7.0, // 구례대교 '1018728': 6.9, // 곡성대교 '1018729': 6.8, // 순천대교 '1018730': 6.7, // 여수대교 '1018731': 6.6, // 광양대교 // 낙동강 수위관측소들 '1018761': 5.2, // 낙동강 '1018762': 5.1, // 낙동강대교 '1018763': 5.0, // 구포대교 '1018764': 4.9, // 사상대교 '1018765': 4.8, // 금정대교 '1018766': 4.7, // 동래대교 '1018767': 4.6, // 해운대대교 '1018768': 4.5, // 기장대교 '1018769': 4.4, // 울산대교 '1018770': 4.3, // 양산대교 '1018771': 4.2, // 밀양대교 '1018772': 4.1, // 창녕대교 '1018773': 4.0, // 의령대교 '1018774': 3.9, // 합천대교 '1018775': 3.8, // 거창대교 '1018776': 3.7, // 함양대교 '1018777': 3.6, // 산청대교 '1018778': 3.5, // 하동대교 '1018779': 3.4, // 남해대교 '1018780': 3.3, // 통영대교 '1018781': 3.2, // 거제대교 '1018782': 3.1, // 고성대교 // 금강 수위관측소들 '1018783': 6.5, // 금강 '1018784': 6.4, // 금강대교 '1018785': 6.3, // 공주대교 '1018786': 6.2, // 부여대교 '1018787': 6.1, // 논산대교 '1018788': 6.0, // 익산대교 '1018789': 5.9, // 전주대교 '1018790': 5.8, // 군산대교 '1018791': 5.7, // 김제대교 '1018792': 5.6, // 정읍대교 '1018793': 5.5, // 순창대교 '1018794': 5.4, // 남원대교 '1018795': 5.3, // 구례대교 '1018796': 5.2, // 곡성대교 '1018797': 5.1, // 순천대교 '1018798': 5.0, // 여수대교 '1018799': 4.9, // 광양대교 // 영산강 수위관측소들 '1018800': 4.5, // 영산강 '1018801': 4.4, // 영산강대교 '1018802': 4.3, // 나주대교 '1018803': 4.2, // 함평대교 '1018804': 4.1, // 영광대교 '1018805': 4.0, // 장성대교 '1018806': 3.9, // 담양대교 '1018807': 3.8, // 곡성대교 '1018808': 3.7, // 순천대교 '1018809': 3.6, // 여수대교 '1018810': 3.5, // 광양대교 // 섬진강 수위관측소들 '1018811': 3.2, // 섬진강 '1018812': 3.1, // 섬진강대교 '1018813': 3.0, // 구례대교 '1018814': 2.9, // 곡성대교 '1018815': 2.8, // 순천대교 '1018816': 2.7, // 여수대교 '1018817': 2.6, // 광양대교 // 임진강 수위관측소들 '1018818': 2.5, // 임진강 '1018819': 2.4, // 임진강대교 '1018820': 2.3, // 파주대교 '1018821': 2.2, // 연천대교 '1018822': 2.1, // 철원대교 '1018823': 2.0, // 화천대교 '1018824': 1.9, // 춘천대교 // 우량관측소들 (강우량 데이터) '1018825': 0.0, // 서울우량관측소 '1018826': 0.0, // 부산우량관측소 '1018827': 0.0, // 대구우량관측소 '1018828': 0.0, // 인천우량관측소 '1018829': 0.0, // 광주우량관측소 '1018830': 0.0, // 대전우량관측소 '1018831': 0.0, // 울산우량관측소 '1018832': 0.0, // 경기우량관측소 '1018833': 0.0, // 강원우량관측소 '1018834': 0.0, // 충북우량관측소 '1018835': 0.0, // 충남우량관측소 '1018836': 0.0, // 전북우량관측소 '1018837': 0.0, // 전남우량관측소 '1018838': 0.0, // 경북우량관측소 '1018839': 0.0, // 경남우량관측소 '1018840': 0.0, // 제주우량관측소 // 지역별 수위관측소들 '1018841': 8.5, // 서울수위관측소 '1018842': 5.2, // 부산수위관측소 '1018843': 4.8, // 대구수위관측소 '1018844': 7.2, // 인천수위관측소 '1018845': 4.5, // 광주수위관측소 '1018846': 6.8, // 대전수위관측소 '1018847': 4.4, // 울산수위관측소 '1018848': 7.8, // 경기수위관측소 '1018849': 6.2, // 강원수위관측소 '1018850': 5.9, // 충북수위관측소 '1018851': 6.1, // 충남수위관측소 '1018852': 5.7, // 전북수위관측소 '1018853': 4.3, // 전남수위관측소 '1018854': 4.6, // 경북수위관측소 '1018855': 4.2, // 경남수위관측소 '1018856': 2.8, // 제주수위관측소 }; const waterLevel = stationData[obsCode] || (Math.random() * 10 + 5); return [ { obs_code: obsCode, obs_time: new Date().toISOString(), water_level: waterLevel, unit: 'm', }, ]; } function formatIntegratedResponse(response: any): string { if (response.status === 'error') { return `❌ ${response.direct_answer}`; } const { primary_station, related_stations } = response.detailed_data; let formatted = `🌊 **${primary_station.name} 실시간 수위 정보**\n\n`; formatted += `📊 **현재 상태**: ${response.direct_answer}\n\n`; formatted += `📈 **상세 정보**:\n`; formatted += `• 수위: ${primary_station.current_level}\n`; formatted += `• 저수율: ${primary_station.storage_rate}\n`; formatted += `• 상태: ${primary_station.status}\n`; formatted += `• 추세: ${primary_station.trend}\n`; formatted += `• 최종 업데이트: ${primary_station.last_updated}\n`; if (related_stations && related_stations.length > 0) { formatted += `\n🔗 **관련 관측소**:\n`; related_stations.forEach((station: any) => { formatted += `• ${station.name} (코드: ${station.code})\n`; }); } formatted += `\n⏰ 조회 시간: ${new Date(response.timestamp).toLocaleString('ko-KR')}`; return formatted; } // 메인 핸들러 export const handler: Handler = async (event) => { const headers = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type', 'Content-Type': 'application/json' }; if (event.httpMethod === 'OPTIONS') { return { statusCode: 200, headers, body: '' }; } try { const body = JSON.parse(event.body || '{}'); const { method, params, id } = body; let result; switch (method) { case 'initialize': result = await mcpInitialize(); break; case 'tools/list': result = await mcpToolsList(); break; case 'tools/call': result = await mcpToolsCall(params.name, params.arguments); break; default: result = mcpError(id, `Unknown method: ${method}`); } // ID가 있으면 추가 if (id !== undefined) { (result as any).id = id; } return { statusCode: 200, headers, body: JSON.stringify(result) }; } catch (error: any) { return { statusCode: 500, headers, body: JSON.stringify({ jsonrpc: "2.0", error: { code: -32700, message: error.message || 'Parse error' } }) }; } };

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