Skip to main content
Glama

Youth Activity Information MCP Server

by hyunhoonj
index.ts21.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, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import dotenv from "dotenv"; import { YouthApiClient } from "./youthApiClient.js"; // 환경 변수 로드 dotenv.config(); /** * MCP 청소년 활동 정보 서버 * 공공데이터포털 청소년 활동 정보 API를 활용한 MCP 서버 */ // 환경 변수 검증 const serviceKey = process.env.YOUTH_API_SERVICE_KEY; if (!serviceKey) { console.error("오류: YOUTH_API_SERVICE_KEY 환경 변수가 설정되지 않았습니다."); console.error(".env 파일을 생성하고 API 키를 설정해주세요."); process.exit(1); } // API 클라이언트 초기화 const youthApiClient = new YouthApiClient({ serviceKey }); // 서버 인스턴스 생성 const server = new Server( { name: "youth-activity-server", version: "2.0.0", }, { capabilities: { tools: {}, resources: {}, prompts: {}, }, } ); /** * Tools 핸들러 등록 * 사용 가능한 도구 목록 반환 */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // 청소년 활동 API 관련 도구 { name: "get_sido_list", description: "청소년 활동 정보 시도(광역자치단체) 목록을 조회합니다", inputSchema: { type: "object", properties: { pageNo: { type: "number", description: "페이지 번호 (기본값: 1)", }, numOfRows: { type: "number", description: "한 페이지 결과 수 (기본값: 100)", }, }, }, }, { name: "get_sigungu_list", description: "특정 시도의 시군구(기초자치단체) 목록을 조회합니다", inputSchema: { type: "object", properties: { sido: { type: "string", description: "시도명 (예: 서울, 부산광역시, 경기도)", }, pageNo: { type: "number", description: "페이지 번호 (기본값: 1)", }, numOfRows: { type: "number", description: "한 페이지 결과 수 (기본값: 100)", }, }, required: ["sido"], }, }, { name: "search_youth_activities", description: "청소년 활동 프로그램을 검색합니다. 프로그램명, 기관명, 지역, 기간 등으로 필터링 가능합니다", inputSchema: { type: "object", properties: { pageNo: { type: "number", description: "페이지 번호 (기본값: 1)", }, numOfRows: { type: "number", description: "한 페이지 결과 수 (기본값: 10)", }, atName: { type: "string", description: "프로그램명 (선택사항)", }, orgName: { type: "string", description: "주최자(기관명) (선택사항)", }, sido: { type: "string", description: "시도명 (선택사항, 예: 서울, 부산광역시)", }, startDate: { type: "string", description: "일활동기간시작일 (선택사항, YYYYMMDD 형식)", }, endDate: { type: "string", description: "일활동기간종료일 (선택사항, YYYYMMDD 형식)", }, }, }, }, { name: "get_facility_group_list", description: "청소년 시설 그룹 목록을 조회합니다. 시도, 기관명, 기관유형으로 필터링 가능합니다", inputSchema: { type: "object", properties: { pageNo: { type: "number", description: "페이지 번호 (기본값: 1)", }, numOfRows: { type: "number", description: "한 페이지 결과 수 (기본값: 10)", }, sido: { type: "string", description: "시도명 (선택사항)", }, stName: { type: "string", description: "기관명 (선택사항)", }, gName: { type: "string", description: "기관유형명 (선택사항)", }, }, }, }, // 기본 유틸리티 도구 { name: "echo", description: "입력받은 메시지를 그대로 반환합니다", inputSchema: { type: "object", properties: { message: { type: "string", description: "반환할 메시지", }, }, required: ["message"], }, }, { name: "get_time", description: "현재 시간을 반환합니다", inputSchema: { type: "object", properties: {}, }, }, ], }; }); /** * Tool 실행 핸들러 */ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { // 청소년 활동 API 도구들 case "get_sido_list": { const pageNo = (args?.pageNo as number) || 1; const numOfRows = (args?.numOfRows as number) || 100; const result = await youthApiClient.getSidoList(pageNo, numOfRows); // 결과 포맷팅 let resultText = `📍 시도 목록 (전체 ${result.totalCount}개)\n\n`; if (Array.isArray(result.items)) { result.items.forEach((item: any, index: number) => { resultText += `${index + 1}. ${item.ctpvNm || "N/A"} (코드: ${ item.ctpvCode || "N/A" })\n`; }); } else if (result.items) { // 단일 항목인 경우 resultText += `1. ${result.items.ctpvNm || "N/A"} (코드: ${ result.items.ctpvCode || "N/A" })\n`; } else { resultText += "조회된 항목이 없습니다.\n"; } resultText += `\n페이지: ${pageNo}/${Math.ceil( result.totalCount / numOfRows )}`; return { content: [ { type: "text", text: resultText, }, ], }; } case "get_sigungu_list": { const sido = args?.sido as string; const pageNo = (args?.pageNo as number) || 1; const numOfRows = (args?.numOfRows as number) || 100; const result = await youthApiClient.getSigunguList( sido, pageNo, numOfRows ); let resultText = `📍 시군구 목록 (전체 ${result.totalCount}개)\n\n`; if (Array.isArray(result.items)) { result.items.forEach((item: any, index: number) => { resultText += `${index + 1}. ${item.sigunguNm || "N/A"} (코드: ${ item.sigunguCode || "N/A" })\n`; }); } else if (result.items) { resultText += `1. ${result.items.sigunguNm || "N/A"} (코드: ${ result.items.sigunguCode || "N/A" })\n`; } else { resultText += "조회된 항목이 없습니다.\n"; } resultText += `\n페이지: ${pageNo}/${Math.ceil( result.totalCount / numOfRows )}`; return { content: [ { type: "text", text: resultText, }, ], }; } case "search_youth_activities": { const params = { pageNo: (args?.pageNo as number) || 1, numOfRows: (args?.numOfRows as number) || 10, atName: args?.atName as string | undefined, orgName: args?.orgName as string | undefined, sido: args?.sido as string | undefined, startDate: args?.startDate as string | undefined, endDate: args?.endDate as string | undefined, }; const result = await youthApiClient.searchActivities(params); let resultText = `🎯 청소년 활동 검색 결과 (전체 ${result.totalCount}개)\n\n`; if (Array.isArray(result.items)) { result.items.forEach((item: any, index: number) => { const itemNum = (params.pageNo - 1) * params.numOfRows + index + 1; resultText += `${itemNum}. ${item.actTitle || "제목 없음"}\n`; if (item.organNm) resultText += ` 기관: ${item.organNm}\n`; if (item.actBeginDt || item.actEndDt) resultText += ` 기간: ${item.actBeginDt || "미정"} ~ ${ item.actEndDt || "미정" }\n`; if (item.actPlace) resultText += ` 장소: ${item.actPlace}\n`; if (item.actTarget) resultText += ` 대상: ${item.actTarget}\n`; if (item.actPart) resultText += ` 분야: ${item.actPart}\n`; if (item.youthPolicyShortIntro) resultText += ` 소개: ${item.youthPolicyShortIntro}\n`; resultText += "\n"; }); } else if (result.items) { resultText += `1. ${result.items.actTitle || "제목 없음"}\n`; if (result.items.organNm) resultText += ` 기관: ${result.items.organNm}\n`; if (result.items.actBeginDt || result.items.actEndDt) resultText += ` 기간: ${result.items.actBeginDt || "미정"} ~ ${ result.items.actEndDt || "미정" }\n`; if (result.items.actPlace) resultText += ` 장소: ${result.items.actPlace}\n`; resultText += "\n"; } else { resultText += "검색된 활동이 없습니다.\n\n"; } resultText += `페이지: ${params.pageNo}/${Math.ceil( result.totalCount / params.numOfRows )}`; return { content: [ { type: "text", text: resultText, }, ], }; } case "get_facility_group_list": { const params = { pageNo: (args?.pageNo as number) || 1, numOfRows: (args?.numOfRows as number) || 10, sido: args?.sido as string | undefined, stName: args?.stName as string | undefined, gName: args?.gName as string | undefined, }; const result = await youthApiClient.getFacilityGroupList(params); let resultText = `🏢 청소년 시설 그룹 목록 (전체 ${result.totalCount}개)\n\n`; if (Array.isArray(result.items)) { result.items.forEach((item: any, index: number) => { const itemNum = (params.pageNo - 1) * params.numOfRows + index + 1; resultText += `${itemNum}. ${item.faciNm || "시설명 없음"}\n`; if (item.instlNm) resultText += ` 기관명: ${item.instlNm}\n`; if (item.gNm) resultText += ` 유형: ${item.gNm}\n`; if (item.rdnmadr) resultText += ` 주소: ${item.rdnmadr}\n`; if (item.phoneNumber) resultText += ` 전화: ${item.phoneNumber}\n`; resultText += "\n"; }); } else if (result.items) { resultText += `1. ${result.items.faciNm || "시설명 없음"}\n`; if (result.items.instlNm) resultText += ` 기관명: ${result.items.instlNm}\n`; if (result.items.gNm) resultText += ` 유형: ${result.items.gNm}\n`; resultText += "\n"; } else { resultText += "검색된 시설이 없습니다.\n\n"; } resultText += `페이지: ${params.pageNo}/${Math.ceil( result.totalCount / params.numOfRows )}`; return { content: [ { type: "text", text: resultText, }, ], }; } // 기본 유틸리티 도구들 case "echo": { const message = args?.message as string; return { content: [ { type: "text", text: `Echo: ${message}`, }, ], }; } case "get_time": { const now = new Date(); return { content: [ { type: "text", text: `현재 시간: ${now.toISOString()}\n로컬 시간: ${now.toLocaleString( "ko-KR" )}`, }, ], }; } default: throw new Error(`알 수 없는 도구: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `오류 발생: ${errorMessage}`, }, ], isError: true, }; } }); /** * Resources 핸들러 등록 * 사용 가능한 리소스 목록 반환 */ server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: "youth://info", mimeType: "text/plain", name: "서버 정보", description: "청소년 활동 정보 서버의 기본 정보", }, { uri: "youth://api-guide", mimeType: "text/plain", name: "API 가이드", description: "공공데이터포털 청소년 활동 정보 API 사용 가이드", }, { uri: "youth://sido-codes", mimeType: "text/plain", name: "시도 코드표", description: "주요 시도 코드 참조표", }, ], }; }); /** * Resource 읽기 핸들러 */ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; switch (uri) { case "youth://info": return { contents: [ { uri, mimeType: "text/plain", text: `청소년 활동 정보 MCP 서버 v2.0.0 이 서버는 공공데이터포털의 청소년 활동 정보 API를 활용한 MCP 서버입니다. 📋 제공 기능: 🔧 Tools (도구): - get_sido_list: 시도 목록 조회 - get_sigungu_list: 시군구 목록 조회 - search_youth_activities: 청소년 활동 검색 - echo: 메시지 에코 - get_time: 현재 시간 조회 📦 Resources (리소스): - youth://info: 서버 정보 - youth://api-guide: API 사용 가이드 - youth://sido-codes: 시도 코드표 💬 Prompts (프롬프트): - search-guide: 활동 검색 가이드 - region-guide: 지역 코드 가이드 📊 데이터 출처: 공공데이터포털 - 여성가족부 청소년 활동 정보 https://www.data.go.kr/`, }, ], }; case "youth://api-guide": return { contents: [ { uri, mimeType: "text/plain", text: `청소년 활동 정보 API 사용 가이드 📌 기본 사용법: 1. 시도 목록 조회: - Tool: get_sido_list - 전국의 시도(광역자치단체) 목록을 조회합니다 - 시도 코드를 확인하여 다음 단계에서 사용 2. 시군구 목록 조회: - Tool: get_sigungu_list - 특정 시도의 시군구(기초자치단체) 목록을 조회합니다 - 파라미터: ctpvCode (시도코드) 3. 청소년 활동 검색: - Tool: search_youth_activities - 다양한 조건으로 청소년 활동 정보를 검색합니다 - 선택 파라미터: * keyword: 검색어 * schCtpvCode: 시도코드 * schSigunguCode: 시군구코드 * pageNo: 페이지 번호 * numOfRows: 페이지당 결과 수 🔑 환경 설정: - .env 파일에 YOUTH_API_SERVICE_KEY 설정 필요 - 공공데이터포털(www.data.go.kr)에서 API 키 발급 💡 사용 예시: 1. 서울시의 모든 활동 검색 schCtpvCode: "11" 2. 키워드로 활동 검색 keyword: "봉사" 3. 특정 지역의 특정 활동 검색 schCtpvCode: "11", keyword: "문화"`, }, ], }; case "youth://sido-codes": return { contents: [ { uri, mimeType: "text/plain", text: `주요 시도 코드표 📍 광역자치단체 코드: 11 - 서울특별시 21 - 부산광역시 22 - 대구광역시 23 - 인천광역시 24 - 광주광역시 25 - 대전광역시 26 - 울산광역시 29 - 세종특별자치시 31 - 경기도 32 - 강원도 33 - 충청북도 34 - 충청남도 35 - 전라북도 36 - 전라남도 37 - 경상북도 38 - 경상남도 39 - 제주특별자치도 💡 사용법: - get_sigungu_list에서 ctpvCode로 사용 - search_youth_activities에서 schCtpvCode로 사용 예: 서울시의 시군구 조회 get_sigungu_list(ctpvCode: "11")`, }, ], }; default: throw new Error(`알 수 없는 리소스: ${uri}`); } }); /** * Prompts 핸들러 등록 * 사용 가능한 프롬프트 목록 반환 */ server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: "search-guide", description: "청소년 활동 검색 방법을 안내하는 프롬프트", arguments: [ { name: "region", description: "관심 지역 (선택사항)", required: false, }, ], }, { name: "region-guide", description: "지역 코드 조회 방법을 안내하는 프롬프트", }, ], }; }); /** * Prompt 가져오기 핸들러 */ server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case "search-guide": { const region = args?.region || ""; const regionText = region ? `${region} 지역의 ` : ""; return { messages: [ { role: "user", content: { type: "text", text: `청소년 활동 정보 검색 가이드 ${regionText}청소년 활동 정보를 검색하는 방법을 안내합니다. 📋 검색 순서: 1️⃣ 시도 목록 조회 (선택사항) - get_sido_list 도구 사용 - 전국의 시도 목록과 코드를 확인합니다 ${region ? `- "${region}"의 코드를 확인하세요` : ""} 2️⃣ 시군구 목록 조회 (선택사항) - get_sigungu_list 도구 사용 - 특정 시도의 시군구 목록을 확인합니다 - 파라미터: ctpvCode (시도코드) 3️⃣ 청소년 활동 검색 - search_youth_activities 도구 사용 - 다양한 조건으로 활동을 검색합니다 검색 옵션: - keyword: 관심 키워드 (예: "봉사", "문화", "체험" 등) - schCtpvCode: 시도 코드 (예: "11" - 서울) - schSigunguCode: 시군구 코드 - numOfRows: 한 번에 볼 결과 수 (기본 10개) 💡 검색 예시: - 서울시의 봉사활동 찾기: schCtpvCode: "11", keyword: "봉사" - 전국의 문화 활동 찾기: keyword: "문화" - 더 많은 결과 보기: numOfRows: 20 시작해볼까요? 어떤 활동을 찾고 계신가요?`, }, }, ], }; } case "region-guide": { return { messages: [ { role: "user", content: { type: "text", text: `청소년 활동 지역 코드 조회 가이드 지역별 청소년 활동을 찾기 위해 필요한 지역 코드를 조회하는 방법입니다. 🗺️ 시도(광역자치단체) 코드 확인: 방법 1: 직접 조회 - get_sido_list 도구를 사용하여 전체 시도 목록과 코드를 확인 방법 2: 리소스 참조 - youth://sido-codes 리소스에서 주요 시도 코드 참조 주요 시도 코드: • 11 - 서울특별시 • 21 - 부산광역시 • 23 - 인천광역시 • 31 - 경기도 • 그 외 리소스에서 확인 🏘️ 시군구(기초자치단체) 코드 확인: get_sigungu_list 도구 사용: - 파라미터: ctpvCode (시도코드) - 예: ctpvCode: "11" (서울시의 모든 구 조회) 📝 사용 흐름: 1. 관심 지역이 "서울 강남구"인 경우 ① get_sido_list로 서울시 코드 확인 → "11" ② get_sigungu_list(ctpvCode: "11")로 강남구 코드 확인 ③ search_youth_activities에서 두 코드 모두 사용 2. 관심 지역이 "경기도"인 경우 ① get_sido_list로 경기도 코드 확인 → "31" ② search_youth_activities에서 schCtpvCode: "31" 사용 어떤 지역의 청소년 활동을 찾고 계신가요?`, }, }, ], }; } default: throw new Error(`알 수 없는 프롬프트: ${name}`); } }); /** * 서버 시작 */ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // stderr로 로그 출력 (stdout은 MCP 프로토콜용으로 사용) console.error("MCP 테스트 서버가 시작되었습니다"); } main().catch((error) => { console.error("서버 오류:", error); process.exit(1); });

Latest Blog Posts

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/hyunhoonj/mcp-test'

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