UNPKG

koreandict-mcp-server

Version:

국립국어원 표준국어대사전 MCP 서버

225 lines (224 loc) 10.1 kB
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { z } from 'zod'; import { DictionaryService } from './services/dictionary-api.js'; /** * 국립국어원 표준국어대사전 MCP 서버 */ export class KoreanDictionaryMcpServer { constructor() { // MCP 서버 인스턴스 생성 this.server = new McpServer({ name: '국립국어원 표준국어대사전', version: '1.0.0' }); // 사전 서비스 인스턴스 생성 this.dictionaryService = new DictionaryService(); // 리소스와 도구 초기화 this.initializeResources(); this.initializeTools(); } /** * 서버 인스턴스를 반환합니다. */ getServer() { return this.server; } /** * MCP 리소스를 초기화합니다. */ initializeResources() { // 단어 검색 리소스 this.server.resource('word-search', new ResourceTemplate('koreandict://word/{word}', { list: undefined }), async (uri, { word }) => { try { const searchWord = typeof word === 'string' ? word : word[0]; const result = await this.dictionaryService.searchDictionary(searchWord); let textContent = `"${searchWord}" 검색 결과\n\n`; if (!result.channel?.item || result.channel.item.length === 0) { textContent += '검색 결과가 없습니다.'; } else { result.channel.item.forEach((item, index) => { textContent += `${index + 1}. ${item.word}`; if (item.sup_no) textContent += ` (${item.sup_no})`; if (item.pos) textContent += ` [${item.pos}]`; textContent += '\n'; const senses = Array.isArray(item.sense) ? item.sense : [item.sense]; senses.forEach((sense, idx) => { textContent += ` ${idx + 1}) ${sense.definition}\n`; if (sense.type) textContent += ` (${sense.type})\n`; }); textContent += '\n'; }); } return { contents: [{ uri: uri.href, text: textContent }] }; } catch (error) { return { contents: [{ uri: uri.href, text: `오류: ${error instanceof Error ? error.message : '알 수 없는 오류'}` }] }; } }); // 품사별 검색 리소스 this.server.resource('pos-search', new ResourceTemplate('koreandict://pos/{pos}/word/{word}', { list: undefined }), async (uri, { pos, word }) => { try { // 품사 코드 매핑 const posMap = { '명사': 1, '대명사': 2, '수사': 3, '조사': 4, '동사': 5, '형용사': 6, '관형사': 7, '부사': 8, '감탄사': 9, '접사': 10, '의존명사': 11, '보조동사': 12, '보조형용사': 13, '어미': 14, '품사없음': 15 }; const posValue = typeof pos === 'string' ? pos : pos[0]; const searchWord = typeof word === 'string' ? word : word[0]; const posCode = posMap[posValue] || 0; const result = await this.dictionaryService.searchDictionary(searchWord, { advanced: 'y', pos: posCode.toString(), method: 'include' }); let textContent = `"${searchWord}" 품사: ${posValue} 검색 결과\n\n`; if (!result.channel?.item || result.channel.item.length === 0) { textContent += '검색 결과가 없습니다.'; } else { result.channel.item.forEach((item, index) => { textContent += `${index + 1}. ${item.word}`; if (item.sup_no) textContent += ` (${item.sup_no})`; if (item.pos) textContent += ` [${item.pos}]`; textContent += '\n'; const senses = Array.isArray(item.sense) ? item.sense : [item.sense]; senses.forEach((sense, idx) => { textContent += ` ${idx + 1}) ${sense.definition}\n`; }); textContent += '\n'; }); } return { contents: [{ uri: uri.href, text: textContent }] }; } catch (error) { return { contents: [{ uri: uri.href, text: `오류: ${error instanceof Error ? error.message : '알 수 없는 오류'}` }] }; } }); } /** * MCP 도구를 초기화합니다. */ initializeTools() { // 단어 검색 도구 this.server.tool('search-word', { word: z.string().describe('검색할 단어'), method: z.enum(['exact', 'include', 'start', 'end', 'wildcard']).optional().describe('검색 방식'), limit: z.number().min(1).max(100).optional().describe('결과 개수 제한') }, async ({ word, method = 'exact', limit = 10 }) => { try { const result = await this.dictionaryService.searchDictionary(word, { method, num: limit }); if (!result.channel?.item || result.channel.item.length === 0) { return { content: [{ type: 'text', text: '검색 결과가 없습니다.' }] }; } let response = `"${word}" 검색 결과:\n\n`; result.channel.item.forEach((item, index) => { response += `${index + 1}. ${item.word}`; if (item.sup_no) response += ` (${item.sup_no})`; if (item.pos) response += ` [${item.pos}]`; response += '\n'; const senses = Array.isArray(item.sense) ? item.sense : [item.sense]; senses.forEach((sense, idx) => { response += ` ${idx + 1}) ${sense.definition}\n`; }); response += '\n'; }); return { content: [{ type: 'text', text: response }] }; } catch (error) { return { content: [{ type: 'text', text: `오류: ${error instanceof Error ? error.message : '알 수 없는 오류'}` }], isError: true }; } }); // 단어 품사별 검색 도구 this.server.tool('search-by-pos', { word: z.string().describe('검색할 단어'), pos: z.enum([ '명사', '대명사', '수사', '조사', '동사', '형용사', '관형사', '부사', '감탄사', '접사', '의존명사', '보조동사', '보조형용사', '어미', '품사없음' ]).describe('검색할 품사'), limit: z.number().min(1).max(100).optional().describe('결과 개수 제한') }, async ({ word, pos, limit = 10 }) => { try { // 품사 코드 매핑 const posMap = { '명사': 1, '대명사': 2, '수사': 3, '조사': 4, '동사': 5, '형용사': 6, '관형사': 7, '부사': 8, '감탄사': 9, '접사': 10, '의존명사': 11, '보조동사': 12, '보조형용사': 13, '어미': 14, '품사없음': 15 }; const posCode = posMap[pos]; const result = await this.dictionaryService.searchDictionary(word, { advanced: 'y', pos: posCode.toString(), method: 'include', num: limit }); if (!result.channel?.item || result.channel.item.length === 0) { return { content: [{ type: 'text', text: '검색 결과가 없습니다.' }] }; } let response = `"${word}" 품사: ${pos} 검색 결과:\n\n`; result.channel.item.forEach((item, index) => { response += `${index + 1}. ${item.word}`; if (item.sup_no) response += ` (${item.sup_no})`; if (item.pos) response += ` [${item.pos}]`; response += '\n'; const senses = Array.isArray(item.sense) ? item.sense : [item.sense]; senses.forEach((sense, idx) => { response += ` ${idx + 1}) ${sense.definition}\n`; }); response += '\n'; }); return { content: [{ type: 'text', text: response }] }; } catch (error) { return { content: [{ type: 'text', text: `오류: ${error instanceof Error ? error.message : '알 수 없는 오류'}` }], isError: true }; } }); } }