UNPKG

@minjunkwon/korea-welfare-mcp-server

Version:

MCP server for Korean Central Government Welfare Services - provides intelligent search and information retrieval for Korean social welfare programs

357 lines (352 loc) 18 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode } from "@modelcontextprotocol/sdk/types.js"; import { WelfareAPI } from './api.js'; const server = new Server({ name: "korea-welfare-mcp-server", version: "1.0.0" }, { capabilities: { tools: {}, }, }); const welfareAPI = new WelfareAPI(); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search_korea_welfare_details", description: `복지서비스 목록 조회 후 각 서비스의 상세 정보를 배치로 처리하는 통합 도구입니다. 🚀 주요 기능: • 조건에 맞는 복지서비스 목록을 먼저 조회 • 찾은 모든 서비스의 상세 정보를 병렬로 조회 • 목록 정보와 상세 정보를 통합하여 완전한 결과 제공 • 대량 데이터 처리에 최적화된 배치 처리 🎯 효과적인 검색 전략 (중앙정부 복지서비스 특성): • ❌ 지역명(강남구, 서울 등) → 중앙정부 서비스는 전국 단위! • ✅ 생애주기, 관심주제, 가구유형이 핵심 검색 조건! • "청년 일자리" → lifeArray: "004" + intrsThemaArray: "050""저소득층 주거" → trgterIndvdlArray: "050" + intrsThemaArray: "040""장애인 지원" → trgterIndvdlArray: "040""임신·출산" → lifeArray: "007" 💡 올바른 사용 시나리오: • "25세 청년의 주거 지원 서비스" → lifeArray: "004", intrsThemaArray: "040""한부모가족 대상 모든 지원" → trgterIndvdlArray: "060""창업 관련 서비스" → searchWrd: "창업", intrsThemaArray: "050""장애인 일자리 지원" → trgterIndvdlArray: "040", intrsThemaArray: "050" ⚡ 성능 특징: • 단일 요청으로 목록+상세 정보 일괄 처리 • 병렬 처리로 빠른 응답 시간 • 실패한 상세 조회도 부분 결과 제공`, inputSchema: { type: "object", required: ["callTp", "pageNo", "numOfRows", "srchKeyCode"], properties: { serviceKey: { type: "string", description: "공공데이터포털 서비스 인증키 (선택). 생략 시 내장된 기본 키를 사용합니다." }, callTp: { type: "string", enum: ["L"], default: "L", description: "호출 타입 (필수). L: 목록 조회 (고정값)" }, searchWrd: { type: "string", description: "복지서비스 제목/내용에서 검색할 키워드. 예: '산모', '일자리', '교육', '주택', '장학금', '돌봄' 등" }, srchKeyCode: { type: "string", enum: ["001", "002", "003"], default: "003", description: "검색 범위: 001=제목만, 002=내용만, 003=제목+내용 (추천). searchWrd와 함께 사용" }, lifeArray: { type: "string", enum: ["001", "002", "003", "004", "005", "006", "007"], description: "연령별 필터링. 001:영유아(0-5세), 002:아동(6-12세), 003:청소년(13-18세), 004:청년(19-34세), 005:중장년(35-64세), 006:노년(65세+), 007:임신·출산" }, trgterIndvdlArray: { type: "string", enum: ["010", "020", "030", "040", "050", "060"], description: "가구 특성별 필터링. 010:다문화·탈북민, 020:다자녀 가정, 030:보훈대상자, 040:장애인, 050:저소득층, 060:한부모·조손 가정" }, intrsThemaArray: { type: "string", enum: ["010", "020", "030", "040", "050", "060", "070", "080", "090", "100", "110", "120", "130", "140", "160"], description: "분야별 필터링. 010:신체건강, 020:정신건강, 030:생활지원, 040:주거, 050:일자리, 060:문화·여가, 070:안전·위기, 080:임신·출산, 090:보육, 100:교육, 110:입양·위탁, 120:보호·돌봄, 130:서민금융, 140:법률, 160:에너지" }, age: { type: "integer", minimum: 0, maximum: 150, description: "특정 나이로 필터링 (0-150세). 예: 25세 청년 → age: 25" }, onapPsbltYn: { type: "string", enum: ["Y", "N"], description: "온라인 신청 가능한 서비스만 찾기. Y:온라인 신청 가능, N:방문 신청만 가능" }, orderBy: { type: "string", enum: ["date", "popular"], description: "결과 정렬 기준. date:최신순, popular:인기순(많이 조회된 순)" }, pageNo: { type: "integer", minimum: 1, maximum: 1000, default: 1, description: "조회할 페이지 번호 (필수, 기본값: 1). 더 많은 결과를 보려면 2, 3 등으로 설정" }, numOfRows: { type: "integer", minimum: 1, maximum: 500, default: 10, description: "한 번에 가져올 결과 개수 (필수, 기본값: 10). 많은 결과가 필요하면 20, 50 등으로 설정" }, maxDetailItems: { type: "integer", minimum: 1, maximum: 100, default: 50, description: "상세 조회할 최대 서비스 개수 (기본값: 50). 성능을 위해 제한됩니다." } } } } // { // name: "get_code_definitions", // description: `복지서비스 검색 코드 정의를 조회합니다. // 📚 제공 정보: // • 생애주기 코드 (lifeCycle): 연령대별 분류 // • 가구유형 코드 (householdType): 가구 특성별 분류 // • 관심주제 코드 (interestTheme): 서비스 분야별 분류 // 💡 사용법: search_welfare_services 사용 전 코드를 확인하거나, 전체 코드 체계를 파악할 때 사용하세요.`, // inputSchema: { // type: "object", // properties: { // codeType: { // type: "string", // enum: ["lifeCycle", "householdType", "interestTheme", "all"], // description: "조회할 코드 분류. lifeCycle:연령대별 코드, householdType:가구특성별 코드, interestTheme:분야별 코드, all:모든 코드 (기본값: all)" // } // } // } // } ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; if (!args) { throw new McpError(ErrorCode.InvalidParams, "No arguments provided"); } switch (name) { case "search_korea_welfare_details": { const searchParams = { callTp: args.callTp || 'L', pageNo: args.pageNo || 1, numOfRows: args.numOfRows || 10, srchKeyCode: args.srchKeyCode || '003' }; // 선택적 매개변수들을 조건부로 추가 (기존 코드와 동일) if (args.searchWrd && args.searchWrd.trim()) { searchParams.searchWrd = args.searchWrd; } if (args.lifeArray && args.lifeArray.trim()) { searchParams.lifeArray = args.lifeArray; } if (args.trgterIndvdlArray && args.trgterIndvdlArray.trim()) { searchParams.trgterIndvdlArray = args.trgterIndvdlArray; } if (args.intrsThemaArray && args.intrsThemaArray.trim()) { searchParams.intrsThemaArray = args.intrsThemaArray; } if (args.age && Number.isInteger(Number(args.age))) { searchParams.age = Number(args.age); } if (args.onapPsbltYn && ['Y', 'N'].includes(args.onapPsbltYn)) { searchParams.onapPsbltYn = args.onapPsbltYn; } if (args.orderBy && ['date', 'popular'].includes(args.orderBy)) { searchParams.orderBy = args.orderBy; } // 검색 조건이 하나도 없으면 기본 검색어 추가 const hasAnyFilter = searchParams.searchWrd || searchParams.lifeArray || searchParams.trgterIndvdlArray || searchParams.intrsThemaArray || searchParams.age || searchParams.onapPsbltYn; if (!hasAnyFilter) { searchParams.searchWrd = '지원'; } const maxDetailItems = args.maxDetailItems || 50; try { const apiInstance = args.serviceKey ? new WelfareAPI(args.serviceKey) : welfareAPI; // 1단계: 목록 조회 let listResult; try { listResult = await apiInstance.getWelfareList(searchParams); } catch (listError) { // 목록 조회 자체에서 오류 발생 (예: NO DATA FOUND) return { content: [ { type: "text", text: JSON.stringify({ status: "error", message: "목록 조회 중 오류가 발생했습니다.", error: listError instanceof Error ? listError.message : String(listError), searchParams, detailResults: [] }, null, 2) } ] }; } if (!listResult || !listResult.wantedList?.servList || listResult.wantedList.servList.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ status: "success", message: "검색 조건에 맞는 서비스를 찾을 수 없습니다.", searchParams, listResult: listResult || null, detailResults: [] }, null, 2) } ] }; } // 2단계: 상세 조회할 서비스 목록 결정 (최대 개수 제한) const servicesToDetail = listResult.wantedList.servList.slice(0, maxDetailItems); // 3단계: 모든 서비스의 상세 정보를 병렬로 조회 const detailPromises = servicesToDetail.map(async (service) => { try { const detailResult = await apiInstance.getWelfareDetail({ callTp: 'D', servId: service.servId }); return { serviceId: service.servId, serviceName: service.servNm, status: "success", listInfo: service, detailInfo: detailResult.wantedDtl }; } catch (error) { return { serviceId: service.servId, serviceName: service.servNm, status: "failed", error: error instanceof Error ? error.message : String(error), listInfo: service, detailInfo: null }; } }); // 모든 상세 조회 완료 대기 const detailResults = await Promise.all(detailPromises); // 성공/실패 통계 const successCount = detailResults.filter(r => r.status === "success").length; const failCount = detailResults.filter(r => r.status === "failed").length; return { content: [ { type: "text", text: JSON.stringify({ status: "completed", summary: { totalFound: listResult.wantedList.totalCount, listRetrieved: listResult.wantedList.servList.length, detailRequested: servicesToDetail.length, detailSuccess: successCount, detailFailed: failCount }, searchParams, listSummary: { pageNo: listResult.wantedList.pageNo, numOfRows: listResult.wantedList.numOfRows, totalCount: listResult.wantedList.totalCount }, services: detailResults }, null, 2) } ] }; } catch (error) { return { content: [ { type: "text", text: `배치 처리 실패: 오류: ${error instanceof Error ? error.message : String(error)} 사용된 매개변수: ${JSON.stringify(searchParams, null, 2)}` } ] }; } } // case "get_code_definitions": { // const codeType = (args as any).codeType || "all"; // let codes: any = {}; // switch (codeType) { // case "lifeCycle": // codes = { lifeCycle: LifeCycleCode }; // break; // case "householdType": // codes = { householdType: HouseholdTypeCode }; // break; // case "interestTheme": // codes = { interestTheme: InterestThemeCode }; // break; // case "all": // default: // codes = { // lifeCycle: LifeCycleCode, // householdType: HouseholdTypeCode, // interestTheme: InterestThemeCode // }; // break; // } // return { // content: [ // { // type: "text", // text: JSON.stringify(codes, null, 2) // } // ] // }; // } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${error instanceof Error ? error.message : String(error)}`); } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Korea Welfare MCP Server running on stdio"); } main().catch((error) => { console.error("Server failed to start:", error); process.exit(1); });