UNPKG

@minjunkwon/localgovernment-welfare-mcp-server

Version:

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

332 lines (325 loc) β€’ 15.7 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 { LocalWelfareAPI } from './api.js'; const server = new Server({ name: "localgovernment-welfare-mcp-server", version: "2.0.0" }, { capabilities: { tools: {}, }, }); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search_local_welfare_services", description: `πŸ” Search local government welfare services with automatic detailed information retrieval. ⚑ WHAT THIS TOOL DOES: 1. Searches welfare services based on your criteria 2. Automatically fetches detailed information for ALL found services 3. Returns both search results AND complete details in one response 🎯 WHEN TO USE: When user asks for welfare services information πŸ“‹ PARAMETER INFERENCE GUIDE: 🏒 Region (ctpvNm/sggNm): - "μ„œμšΈ 은평ꡬ" β†’ ctpvNm: "μ„œμšΈνŠΉλ³„μ‹œ", sggNm: "은평ꡬ" - "경기도 μ„±λ‚¨μ‹œ" β†’ ctpvNm: "경기도", sggNm: "μ„±λ‚¨μ‹œ" - "λΆ€μ‚°" β†’ ctpvNm: "λΆ€μ‚°κ΄‘μ—­μ‹œ" πŸ‘₯ Target Group (trgterIndvdlArray): - "μ €μ†Œλ“μΈ΅", "생계급여" β†’ "010" - "μž₯애인" β†’ "020" - "ν•œλΆ€λͺ¨κ°€μ‘±", "쑰손가정" β†’ "030" - "λ‹€μžλ…€κ°€μ •" β†’ "040" - "μž„μ‹ λΆ€", "μΆœμ‚°" β†’ "050" - "λ³΄ν›ˆλŒ€μƒμž" β†’ "060" πŸ“… Life Stage (lifeArray): - "μ˜μœ μ•„", "0-5μ„Έ" β†’ "001" - "아동", "6-12μ„Έ" β†’ "002" - "μ²­μ†Œλ…„", "13-18μ„Έ" β†’ "003" - "μ²­λ…„", "19-34μ„Έ" β†’ "004" - "쀑μž₯λ…„", "35-64μ„Έ" β†’ "005" - "λ…Έλ…„", "65μ„Έ 이상" β†’ "006" - "전체", "μ „ μ—°λ Ή" β†’ "007" 🎯 Interest Theme (intrsThemaArray): - "일자리", "μ·¨μ—…", "μ°½μ—…" β†’ "010" - "μ£Όκ±°", "주택", "μž„λŒ€" β†’ "020" - "ꡐ윑", "ν•™μŠ΅", "자격증" β†’ "030" - "볡지문화", "λ¬Έν™”μƒν™œ" β†’ "040" - "μ•ˆμ „", "λ³΄μ•ˆ" β†’ "050" - "보건", "의료", "건강" β†’ "060" - "ν™˜κ²½" β†’ "070" - "농림좕산어업" β†’ "080" - "기타" β†’ "090" - "μ„œλ―ΌκΈˆμœ΅", "λŒ€μΆœ", "μ§€μ›κΈˆ" β†’ "100" - "신체건강", "μž¬ν™œ" β†’ "110" - "μž„μ‹ Β·μΆœμ‚°" β†’ "120" - "λ³΄μœ‘Β·λŒλ΄„", "μœ‘μ•„" β†’ "130" - "μž…μ–‘Β·μœ„νƒ" β†’ "140" ⚠️ IMPORTANT: - Automatically fetches details for up to 15 services (configurable via maxDetails) - Larger result sets may take longer due to sequential API calls - Each detail fetch has 500ms delay for API stability`, inputSchema: { type: "object", properties: { pageNo: { type: "number", description: "Page number (default: 1)" }, numOfRows: { type: "number", description: "Number of results per page (default: 10, max: 50)" }, maxDetails: { type: "number", description: "Maximum number of services to get details for (default: 15, max: 20)" }, serviceKey: { type: "string", description: "API service key (optional - server has default)" }, lifeArray: { type: "string", description: "Life stage codes: 001(μ˜μœ μ•„), 002(아동), 003(μ²­μ†Œλ…„), 004(μ²­λ…„), 005(쀑μž₯λ…„), 006(λ…Έλ…„), 007(전체)" }, trgterIndvdlArray: { type: "string", description: "Target individual codes: 010(μ €μ†Œλ“), 020(μž₯애인), 030(ν•œλΆ€λͺ¨Β·μ‘°μ†), 040(λ‹€μžλ…€), 050(μž„μ‹ Β·μΆœμ‚°), 060(λ³΄ν›ˆλŒ€μƒμž)" }, intrsThemaArray: { type: "string", description: "Interest theme codes: 010(일자리), 020(μ£Όκ±°), 030(ꡐ윑), 040(볡지문화), 050(μ•ˆμ „), 060(보건), 070(ν™˜κ²½), 080(농림좕산어업), 090(기타), 100(μ„œλ―ΌκΈˆμœ΅), 110(신체건강), 120(μž„μ‹ Β·μΆœμ‚°), 130(λ³΄μœ‘Β·λŒλ΄„), 140(μž…μ–‘Β·μœ„νƒ)" }, ctpvNm: { type: "string", description: "Province/City name (e.g., μ„œμšΈνŠΉλ³„μ‹œ, 경기도, λΆ€μ‚°κ΄‘μ—­μ‹œ)" }, sggNm: { type: "string", description: "District name (e.g., 은평ꡬ, μ„±λ‚¨μ‹œ, ν•΄μš΄λŒ€κ΅¬)" }, srchKeyCode: { type: "string", description: "Search key code: 001(μ„œλΉ„μŠ€λͺ…), 002(μ„œλΉ„μŠ€ μš”μ•½), 003(전체)" }, searchWrd: { type: "string", description: "Search keyword" }, arrgOrd: { type: "string", description: "Sort order: 001(μ΅œμ‹ μˆœ), 002(인기순)" } } } } // { // name: "get_local_welfare_codes", // description: `Get code definitions used in the local welfare services API. // WHEN TO USE: When you need to understand what codes mean or show users available options. // PARAMETER: // codeType (OPTIONAL): Type of codes to retrieve // - "all" (default): All code types // - "lifeCycle": Life stage codes (μ˜μœ μ•„, 아동, μ²­μ†Œλ…„, etc.) // - "householdType": Household situation codes (μ €μ†Œλ“, μž₯애인, ν•œλΆ€λͺ¨, etc.) // - "interestTheme": Interest theme codes (일자리, μ£Όκ±°, ꡐ윑, etc.) // - "sortOrder": Sort order options (μ΅œμ‹ μˆœ, 인기순) // USAGE: Call this when user asks about available options or code meanings.`, // inputSchema: { // type: "object", // properties: { // codeType: { // type: "string", // enum: ["lifeCycle", "householdType", "interestTheme", "sortOrder", "all"], // description: "Type of codes to retrieve (default: all)" // } // } // } // } ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { try { console.error('CallToolRequest received:', JSON.stringify(request)); const { name, arguments: args } = request.params; // argumentsκ°€ μ—†λŠ” 경우 더 μƒμ„Έν•œ λ‘œκΉ… if (!args) { console.error('No arguments provided in request.params:', request.params); throw new McpError(ErrorCode.InvalidParams, "No arguments provided"); } switch (name) { case "search_local_welfare_services": { console.error('search_local_welfare_services called with args:', JSON.stringify(args)); if (!args || typeof args !== 'object') { throw new McpError(ErrorCode.InvalidParams, "Invalid arguments provided"); } const searchParams = { pageNo: args && 'pageNo' in args ? Number(args.pageNo) || 1 : 1, numOfRows: args && 'numOfRows' in args ? Number(args.numOfRows) || 10 : 10 }; // λͺ¨λ“  검색 λ§€κ°œλ³€μˆ˜ 처리 if (args && 'serviceKey' in args && typeof args.serviceKey === 'string' && args.serviceKey.trim()) { searchParams.serviceKey = args.serviceKey; } 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.ctpvNm && args.ctpvNm.trim()) { searchParams.ctpvNm = args.ctpvNm; } if (args.sggNm && args.sggNm.trim()) { searchParams.sggNm = args.sggNm; } if (args.srchKeyCode && ['001', '002', '003'].includes(args.srchKeyCode)) { searchParams.srchKeyCode = args.srchKeyCode; } if (args.searchWrd && args.searchWrd.trim()) { searchParams.searchWrd = args.searchWrd; } if (args.arrgOrd && ['001', '002'].includes(args.arrgOrd)) { searchParams.arrgOrd = args.arrgOrd; } // μƒμ„Έμ‘°νšŒ μ˜΅μ…˜ (항상 ν™œμ„±ν™”) const maxDetails = args && 'maxDetails' in args ? Math.min(Number(args.maxDetails) || 15, 20) : 15; try { // 1단계: λͺ©λ‘ 쑰회 const localWelfareAPI = new LocalWelfareAPI(); const searchResult = await localWelfareAPI.getLocalWelfareList(searchParams); // 2단계: μžλ™ μƒμ„Έμ‘°νšŒ const servList = searchResult.wantedList?.servList; if (!servList || servList.length === 0) { return { content: [ { type: "text", text: JSON.stringify({ searchParams: searchParams, searchResult: searchResult, details: [], message: "No services found" }, null, 2) } ] }; } // μƒμ„Έμ‘°νšŒν•  μ„œλΉ„μŠ€ ID λͺ©λ‘ 생성 (μ΅œλŒ€ maxDetails개) const servIds = (Array.isArray(servList) ? servList : [servList]) .slice(0, maxDetails) .map(service => service.servId) .filter(id => id); console.error(`Auto-fetching details for ${servIds.length} services...`); const detailResults = []; for (let i = 0; i < servIds.length; i++) { const servId = servIds[i]; try { console.error(`Fetching details for ${servId} (${i + 1}/${servIds.length})`); const detailAPI = new LocalWelfareAPI(); const detailResult = await detailAPI.getLocalWelfareDetail({ servId }); detailResults.push({ servId, success: true, data: detailResult.wantedDtl }); // API 호좜 κ°„ 500ms μ§€μ—° (λ§ˆμ§€λ§‰ 호좜 μ œμ™Έ) if (i < servIds.length - 1) { await new Promise(resolve => setTimeout(resolve, 500)); } } catch (error) { console.error(`Error fetching details for ${servId}:`, error); detailResults.push({ servId, success: false, error: error instanceof Error ? error.message : String(error) }); } } return { content: [ { type: "text", text: JSON.stringify({ searchParams: searchParams, searchResult: searchResult, detailsSummary: { total: servIds.length, successful: detailResults.filter(r => r.success).length, failed: detailResults.filter(r => !r.success).length }, details: detailResults }, null, 2) } ] }; } catch (error) { console.error('Error in search_local_welfare_services:', error); throw new McpError(ErrorCode.InternalError, `Failed to search welfare services with details: ${error instanceof Error ? error.message : String(error)}`); } } // case "get_local_welfare_codes": { // 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 "sortOrder": // codes = { sortOrder: SortOrderCode }; // break; // case "all": // default: // codes = { // lifeCycle: LifeCycleCode, // householdType: HouseholdTypeCode, // interestTheme: InterestThemeCode, // sortOrder: SortOrderCode // }; // break; // } // return { // content: [ // { // type: "text", // text: JSON.stringify(codes, null, 2) // } // ] // }; // } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { console.error('Tool execution error:', 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("Local Government Welfare MCP Server running on stdio"); } main().catch((error) => { console.error("Server failed to start:", error); process.exit(1); }); //# sourceMappingURL=index-clean.js.map