UNPKG

geoapify-mcp-server

Version:

Geoapify API MCP Server for location-based services - 一键部署的地理位置服务

150 lines (128 loc) 3.94 kB
import express from 'express'; import cors from 'cors'; import helmet from 'helmet'; import compression from 'compression'; import rateLimit from 'express-rate-limit'; import dotenv from 'dotenv'; import { GeoapifyService } from './services/geoapifyService.js'; import { MCPHandler } from './handlers/mcpHandler.js'; import { logger } from './utils/logger.js'; import { errorHandler } from './middleware/errorHandler.js'; import { validateApiKey } from './middleware/auth.js'; dotenv.config(); const app = express(); const PORT = process.env.PORT || 50001; // 安全中间件 app.use(helmet()); app.use(compression()); app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') || '*', credentials: true })); // 速率限制 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15分钟 max: parseInt(process.env.RATE_LIMIT_MAX) || 100, message: 'Too many requests from this IP', standardHeaders: true, legacyHeaders: false, }); app.use(limiter); app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true })); // 初始化服务 const geoapifyService = new GeoapifyService(process.env.GEOAPIFY_API_KEY); const mcpHandler = new MCPHandler(geoapifyService); // 健康检查 app.get('/health', (req, res) => { res.json({ status: 'healthy', timestamp: new Date().toISOString(), version: process.env.npm_package_version || '1.0.0' }); }); // MCP工具列表 app.get('/tools', (req, res) => { res.json(mcpHandler.getTools()); }); // MCP工具调用 app.post('/tools/:toolName', validateApiKey, async (req, res, next) => { try { const { toolName } = req.params; const { arguments: args } = req.body; logger.info(`Tool called: ${toolName}`, { args, ip: req.ip }); const result = await mcpHandler.callTool(toolName, args); res.json({ success: true, data: result, timestamp: new Date().toISOString() }); } catch (error) { next(error); } }); // 地理编码端点 app.post('/geocode', validateApiKey, async (req, res, next) => { try { const result = await geoapifyService.geocode(req.body.address, req.body.options); res.json({ success: true, data: result }); } catch (error) { next(error); } }); // 反向地理编码端点 app.post('/reverse-geocode', validateApiKey, async (req, res, next) => { try { const { lat, lon, options } = req.body; const result = await geoapifyService.reverseGeocode(lat, lon, options); res.json({ success: true, data: result }); } catch (error) { next(error); } }); // 路线规划端点 app.post('/routing', validateApiKey, async (req, res, next) => { try { const { waypoints, mode, options } = req.body; const result = await geoapifyService.routing(waypoints, mode, options); res.json({ success: true, data: result }); } catch (error) { next(error); } }); // 地点搜索端点 app.post('/places', validateApiKey, async (req, res, next) => { try { const { categories, filter, options } = req.body; const result = await geoapifyService.searchPlaces(categories, filter, options); res.json({ success: true, data: result }); } catch (error) { next(error); } }); // 错误处理中间件 app.use(errorHandler); // 404处理 app.use('*', (req, res) => { res.status(404).json({ success: false, error: 'Endpoint not found', message: `Cannot ${req.method} ${req.originalUrl}` }); }); // 启动服务器 app.listen(PORT, () => { logger.info(`Geoapify MCP Server running on port ${PORT}`); logger.info(`Health check: http://localhost:${PORT}/health`); logger.info(`Tools list: http://localhost:${PORT}/tools`); }); // 优雅关闭 process.on('SIGTERM', () => { logger.info('SIGTERM received, shutting down gracefully'); process.exit(0); }); process.on('SIGINT', () => { logger.info('SIGINT received, shutting down gracefully'); process.exit(0); }); export default app;