UNPKG

@valueofspace/mcp

Version:
135 lines (134 loc) 4.8 kB
#!/usr/bin/env node import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { z } from 'zod'; import axios from 'axios'; const VOS_OPEN_API_BASE = 'https://prod-api.vos.land/v1-open'; const VOS_API_KEY = process.env.VOS_API_KEY; // Create server instance const server = new McpServer({ name: '공간의가치 MCP 서버', version: '1.0.0', capabilities: { resources: {}, tools: {} } }); async function makeVosOpenAPIRequest(url) { if (!VOS_API_KEY) { return { data: null, error: { message: 'VOS_API_KEY가 설정되지 않았습니다', url } }; } const headers = { Accept: 'application/json', 'vos-api-key': VOS_API_KEY, 'Content-Type': 'application/json' }; try { const response = await axios.get(url, { headers }); return { data: response.data }; } catch (error) { if (axios.isAxiosError(error)) { return { data: null, error: { status: error.response?.status, message: `HTTP 요청 실패! 상태 코드: ${error.response?.status || 'N/A'}`, responseText: JSON.stringify(error.response?.data || ''), url } }; } return { data: null, error: { message: `네트워크 요청 실패: ${error instanceof Error ? error.message : '알 수 없는 에러'}`, url } }; } } // Format alert data function formatAssetData(data) { return [ `AddressName: ${data.addressName || 'Unknown'}`, `AddressRoadName: ${data.addressRoadName || 'Unknown'}`, `AssetName: ${data.assetName || 'Unknown'}`, `EstimatePrice: ${data.estimatePrice || 'Unknown'}`, `IsCollective: ${data.isCollective || 'Unknown'}`, `X: ${data.x || 'Unknown'}`, `Y: ${data.y || 'Unknown'}`, `GroundFloorArea: ${data.groundFloorArea || 'Unknown'}`, `LandArea: ${data.landArea || 'Unknown'}`, `MaxLandUse: ${data.maxLandUse || 'Unknown'}`, `MaxUseName: ${data.maxUseName || 'Unknown'}`, `ZipNo: ${data.zipNo || 'Unknown'}`, `BdMgtSn: ${data.bdMgtSn || 'Unknown'}`, `RoadAddrPart2: ${data.roadAddrPart2 || 'Unknown'}`, '---' ].join('\n'); } // Register weather tools server.tool('search-asset-by-keyword', 'Search for asset information (address, building name, estimated price, AI estimated price, area size, land area, maximum land use, maximum use name, zip code, management number, road name address, asset PNU, PNUs, coordinates, ground floor area, collective property status) by keyword (address or building name)', { keyword: z.string().describe('키워드(주소, 건물명)') }, async ({ keyword }) => { const encodedKeyword = encodeURIComponent(keyword); const searchUrl = `${VOS_OPEN_API_BASE}/api/map/search/asset?keyword=${encodedKeyword}`; const result = await makeVosOpenAPIRequest(searchUrl); if (!result.data) { const errorMessage = result.error ? `검색 실패:\n` + `URL: ${result.error.url}\n` + `상태: ${result.error.status || 'N/A'}\n` + `메시지: ${result.error.message}\n` + `응답: ${result.error.responseText || 'N/A'}` : '알 수 없는 에러로 검색에 실패했습니다.'; console.error(errorMessage); // 콘솔에도 에러 로깅 return { content: [ { type: 'text', text: errorMessage } ] }; } const data = result.data.data || []; if (data.length === 0) { return { content: [ { type: 'text', text: `${keyword}에 해당하는 검색 결과가 없습니다.` } ] }; } const formattedAssetList = data.map(formatAssetData); const assetText = `${keyword}에 해당하는 공간의가치 자산 정보:\n\n${formattedAssetList.join('\n')}`; return { content: [ { type: 'text', text: assetText } ] }; }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('공간의가치 MCP 서버 실행'); } main().catch(error => { console.error('Fatal error in main():', error); process.exit(1); });