@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
JavaScript
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);
});