geoapify-mcp-server
Version:
Geoapify API MCP Server for location-based services - 一键部署的地理位置服务
300 lines (284 loc) • 8.93 kB
JavaScript
const Joi = require('joi');
const { logger } = require('../utils/logger.js');
class MCPHandler {
constructor(geoapifyService) {
this.geoapifyService = geoapifyService;
this.tools = this.defineTools();
}
defineTools() {
return [
{
name: "geocode_address",
description: "将地址转换为经纬度坐标,支持全球地址查询",
inputSchema: {
type: "object",
properties: {
address: {
type: "string",
description: "要编码的地址,支持结构化或自由格式地址"
},
country: {
type: "string",
description: "国家过滤器,使用ISO 3166-1 alpha-2代码"
},
language: {
type: "string",
description: "结果语言,使用ISO 639-1代码"
},
limit: {
type: "number",
description: "返回结果数量限制,默认5",
minimum: 1,
maximum: 20
}
},
required: ["address"]
}
},
{
name: "reverse_geocode",
description: "将经纬度坐标转换为地址信息",
inputSchema: {
type: "object",
properties: {
lat: {
type: "number",
description: "纬度",
minimum: -90,
maximum: 90
},
lon: {
type: "number",
description: "经度",
minimum: -180,
maximum: 180
},
language: {
type: "string",
description: "结果语言,使用ISO 639-1代码"
}
},
required: ["lat", "lon"]
}
},
{
name: "calculate_route",
description: "计算两点或多点间的最优路线",
inputSchema: {
type: "object",
properties: {
waypoints: {
type: "array",
description: "路径点数组,每个点为 'lat,lon' 格式",
items: { type: "string" },
minItems: 2
},
mode: {
type: "string",
enum: ["drive", "walk", "bicycle", "truck", "motorcycle", "transit"],
description: "交通方式",
default: "drive"
},
type: {
type: "string",
enum: ["balanced", "short", "less_maneuvers"],
description: "路线优化类型",
default: "balanced"
},
avoid: {
type: "string",
description: "避开的道路类型,如 'tolls|ferries'"
},
details: {
type: "string",
description: "额外详情,如 'instruction_details,elevation'"
}
},
required: ["waypoints"]
}
},
{
name: "search_places",
description: "搜索指定类别的地点或兴趣点",
inputSchema: {
type: "object",
properties: {
categories: {
type: "string",
description: "地点类别,如 'catering.restaurant,catering.cafe'"
},
filter: {
type: "string",
description: "地理过滤器,支持circle:lon,lat,radius 或 rect:lon1,lat1,lon2,lat2"
},
bias: {
type: "string",
description: "位置偏好,如 'proximity:lon,lat'"
},
conditions: {
type: "string",
description: "额外条件,如 'wheelchair,internet_access'"
},
limit: {
type: "number",
description: "结果数量限制,默认20",
minimum: 1,
maximum: 500
},
language: {
type: "string",
description: "结果语言"
}
},
required: ["categories"]
}
},
{
name: "address_autocomplete",
description: "地址自动补全,提供实时地址建议",
inputSchema: {
type: "object",
properties: {
text: {
type: "string",
description: "部分地址文本"
},
country: {
type: "string",
description: "国家过滤器"
},
limit: {
type: "number",
description: "建议数量限制,默认10",
minimum: 1,
maximum: 20
},
language: {
type: "string",
description: "结果语言"
}
},
required: ["text"]
}
},
{
name: "calculate_isoline",
description: "计算等时线或等距线,显示可达性区域",
inputSchema: {
type: "object",
properties: {
lat: { type: "number", description: "起点纬度" },
lon: { type: "number", description: "起点经度" },
type: {
type: "string",
enum: ["time", "distance"],
description: "等线类型:时间或距离",
default: "time"
},
mode: {
type: "string",
enum: ["drive", "walk", "bicycle", "transit"],
description: "交通方式",
default: "drive"
},
range: {
type: "number",
description: "范围值(秒或米)",
minimum: 1
}
},
required: ["lat", "lon", "range"]
}
},
{
name: "get_place_details",
description: "获取地点的详细信息",
inputSchema: {
type: "object",
properties: {
place_id: {
type: "string",
description: "地点ID,从搜索结果中获取"
},
language: {
type: "string",
description: "结果语言"
}
},
required: ["place_id"]
}
}
];
}
getTools() {
return {
tools: this.tools,
server_info: {
name: "Geoapify MCP Server",
version: "1.0.0",
description: "提供地理编码、路线规划、地点搜索等位置服务"
}
};
}
async callTool(toolName, args) {
const tool = this.tools.find(t => t.name === toolName);
if (!tool) {
throw new Error(`Unknown tool: ${toolName}`);
}
// 验证输入参数
const { error, value } = Joi.object(tool.inputSchema.properties).validate(args);
if (error) {
throw new Error(`Invalid arguments: ${error.details[0].message}`);
}
logger.info(`Executing tool: ${toolName}`, { args: value });
try {
switch (toolName) {
case 'geocode_address':
return await this.geoapifyService.geocode(value.address, {
filter: value.country ? `countrycode:${value.country}` : undefined,
lang: value.language,
limit: value.limit
});
case 'reverse_geocode':
return await this.geoapifyService.reverseGeocode(value.lat, value.lon, {
lang: value.language
});
case 'calculate_route':
return await this.geoapifyService.routing(value.waypoints, value.mode, {
type: value.type,
avoid: value.avoid,
details: value.details
});
case 'search_places':
return await this.geoapifyService.searchPlaces(value.categories, value.filter, {
bias: value.bias,
conditions: value.conditions,
limit: value.limit,
lang: value.language
});
case 'address_autocomplete':
return await this.geoapifyService.autocomplete(value.text, {
filter: value.country ? `countrycode:${value.country}` : undefined,
limit: value.limit,
lang: value.language
});
case 'calculate_isoline':
return await this.geoapifyService.isoline(value.lat, value.lon, {
type: value.type,
mode: value.mode,
range: value.range
});
case 'get_place_details':
return await this.geoapifyService.placeDetails(value.place_id, {
lang: value.language
});
default:
throw new Error(`Tool implementation not found: ${toolName}`);
}
} catch (error) {
logger.error(`Tool execution failed: ${toolName}`, { error: error.message, args: value });
throw error;
}
}
}
module.exports = { MCPHandler };