geoapify-mcp-server
Version:
Geoapify API MCP Server for location-based services - 一键部署的地理位置服务
150 lines (128 loc) • 3.94 kB
JavaScript
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;