# Directory Structure ``` ├── amap-coordinate-server │ ├── .gitignore │ ├── package-lock.json │ ├── package.json │ ├── README.md │ ├── src │ │ └── index.ts │ └── tsconfig.json └── amap-route-server ├── .gitignore ├── package-lock.json ├── package.json ├── README.md ├── src │ └── index.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /amap-coordinate-server/.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* ``` -------------------------------------------------------------------------------- /amap-route-server/.gitignore: -------------------------------------------------------------------------------- ``` 1 | node_modules/ 2 | build/ 3 | *.log 4 | .env* ``` -------------------------------------------------------------------------------- /amap-route-server/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # amap-route-server MCP Server 2 | 3 | A Model Context Protocol server for AMap Route Planning. 4 | 5 | This server provides tools to calculate routes using the AMap API for different transportation modes. 6 | 7 | ## Features 8 | 9 | ### Tools 10 | 11 | - `walking_route`: Calculates a walking route between two points. 12 | - `transit_route`: Calculates a transit route between two points. 13 | - `driving_route`: Calculates a driving route between two points. 14 | - `bicycling_route`: Calculates a bicycling route between two points. 15 | - `distance`: Calculates the distance between two points. 16 | 17 | ## Development 18 | 19 | Install dependencies: 20 | 21 | ```bash 22 | npm install 23 | ``` 24 | 25 | Build the server: 26 | 27 | ```bash 28 | npm run build 29 | ``` 30 | 31 | For development with auto-rebuild: 32 | 33 | ```bash 34 | npm run watch 35 | ``` 36 | 37 | ## Installation 38 | 39 | To use with Claude Desktop, add the server config: 40 | 41 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 42 | 43 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json` 44 | 45 | ```json 46 | { 47 | "mcpServers": { 48 | "amap-route-server": { 49 | "command": "/path/to/amap-route-server/build/index.js", 50 | "env": { 51 | "AMAP_API_KEY": "YOUR_AMAP_API_KEY" 52 | } 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | **Note:** You need to obtain an AMap API key and set it as the `AMAP_API_KEY` environment variable. 59 | 60 | ### Debugging 61 | 62 | Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the \[MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script: 63 | 64 | ```bash 65 | npm run inspector 66 | ``` 67 | 68 | The Inspector will provide a URL to access debugging tools in your browser. 69 | ``` -------------------------------------------------------------------------------- /amap-coordinate-server/README.md: -------------------------------------------------------------------------------- ```markdown 1 | # amap-coordinate-server MCP Server 2 | 3 | A Model Context Protocol server for AMap Coordinate Conversion and Place Search. 4 | 5 | This server provides tools to convert coordinates between different systems and search for places using the AMap API. 6 | 7 | ## Features 8 | 9 | ### Tools 10 | 11 | - `coordinate_convert`: Converts coordinates between different coordinate systems (GPS, mapbar, baidu, autonavi). 12 | - `keyword_search`: Searches for places by keyword. 13 | - `around_search`: Searches for places around a specific location. 14 | - `polygon_search`: Searches for places within a given polygon. 15 | - `id_search`: Searches for a place by its ID. 16 | - `aoi_boundary_query`: Queries the boundary of an Area of Interest (AOI). 17 | 18 | ## Development 19 | 20 | Install dependencies: 21 | 22 | ```bash 23 | npm install 24 | ``` 25 | 26 | Build the server: 27 | 28 | ```bash 29 | npm run build 30 | ``` 31 | 32 | For development with auto-rebuild: 33 | 34 | ```bash 35 | npm run watch 36 | ``` 37 | 38 | ## Installation 39 | 40 | To use with Claude Desktop, add the server config: 41 | 42 | On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 43 | 44 | On Windows: `%APPDATA%/Claude/claude_desktop_config.json` 45 | 46 | ```json 47 | { 48 | "mcpServers": { 49 | "amap-coordinate-server": { 50 | "command": "/path/to/amap-coordinate-server/build/index.js", 51 | "env": { 52 | "AMAP_API_KEY": "YOUR_AMAP_API_KEY" 53 | } 54 | } 55 | } 56 | } 57 | ``` 58 | 59 | **Note:** You need to obtain an AMap API key and set it as the `AMAP_API_KEY` environment variable. 60 | 61 | ### Debugging 62 | 63 | Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the \[MCP Inspector](https://github.com/modelcontextprotocol/inspector), which is available as a package script: 64 | 65 | ```bash 66 | npm run inspector 67 | ``` 68 | 69 | The Inspector will provide a URL to access debugging tools in your browser. 70 | ``` -------------------------------------------------------------------------------- /amap-coordinate-server/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /amap-route-server/tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "Node16", 5 | "moduleResolution": "Node16", 6 | "outDir": "./build", 7 | "rootDir": "./src", 8 | "strict": true, 9 | "esModuleInterop": true, 10 | "skipLibCheck": true, 11 | "forceConsistentCasingInFileNames": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | ``` -------------------------------------------------------------------------------- /amap-route-server/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "GAODENEW", 3 | "version": "0.1.0", 4 | "description": "CHINA!", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "GAODENEW": "./build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "0.6.0", 21 | "node-fetch": "^3.3.2" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20.11.24", 25 | "typescript": "^5.3.3" 26 | } 27 | } 28 | ``` -------------------------------------------------------------------------------- /amap-coordinate-server/package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "amap-coordinate-server", 3 | "version": "0.1.0", 4 | "description": "A Model Context Protocol server", 5 | "private": true, 6 | "type": "module", 7 | "bin": { 8 | "amap-coordinate-server": "build/index.js" 9 | }, 10 | "files": [ 11 | "build" 12 | ], 13 | "scripts": { 14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 15 | "prepare": "npm run build", 16 | "watch": "tsc --watch", 17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js" 18 | }, 19 | "dependencies": { 20 | "@modelcontextprotocol/sdk": "^0.6.0", 21 | "axios": "^1.8.4" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "^20.11.24", 25 | "typescript": "^5.3.3" 26 | }, 27 | "main": "index.js", 28 | "keywords": [], 29 | "author": "", 30 | "license": "ISC" 31 | } 32 | ``` -------------------------------------------------------------------------------- /amap-route-server/src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | 3 | /** 4 | * This is a template MCP server that implements a simple notes system. 5 | * It demonstrates core MCP concepts like resources and tools by allowing: 6 | * - Listing notes as resources 7 | * - Reading individual notes 8 | * - Creating new notes via a tool 9 | * - Summarizing all notes via a prompt 10 | */ 11 | 12 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; 13 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; 14 | import { 15 | CallToolRequestSchema, 16 | ListResourcesRequestSchema, 17 | ListToolsRequestSchema, 18 | ReadResourceRequestSchema, 19 | ListPromptsRequestSchema, 20 | GetPromptRequestSchema, 21 | } from "@modelcontextprotocol/sdk/types.js"; 22 | import fetch from 'node-fetch'; 23 | 24 | /** 25 | * Type alias for a note object. 26 | */ 27 | type Note = { title: string, content: string }; 28 | 29 | /** 30 | * Simple in-memory storage for notes. 31 | * In a real implementation, this would likely be backed by a database. 32 | */ 33 | const notes: { [id: string]: Note } = { 34 | "1": { title: "First Note", content: "This is note 1" }, 35 | "2": { title: "Second Note", content: "This is note 2" } 36 | }; 37 | 38 | /** 39 | * Create an MCP server with capabilities for resources (to list/read notes), 40 | * tools (to create new notes), and prompts (to summarize notes). 41 | */ 42 | const server = new Server( 43 | { 44 | name: "amap-route-server", 45 | version: "0.1.0", 46 | }, 47 | { 48 | capabilities: { 49 | resources: {}, 50 | tools: {}, 51 | prompts: {}, 52 | }, 53 | } 54 | ); 55 | 56 | /** 57 | * Handler for listing available notes as resources. 58 | * Each note is exposed as a resource with: 59 | * - A note:// URI scheme 60 | * - Plain text MIME type 61 | * - Human readable name and description (now including the note title) 62 | */ 63 | server.setRequestHandler(ListResourcesRequestSchema, async () => { 64 | return { 65 | resources: Object.entries(notes).map(([id, note]) => ({ 66 | uri: `note:///${id}`, 67 | mimeType: "text/plain", 68 | name: note.title, 69 | description: `A text note: ${note.title}` 70 | })) 71 | }; 72 | }); 73 | 74 | /** 75 | * Handler for reading the contents of a specific note. 76 | * Takes a note:// URI and returns the note content as plain text. 77 | */ 78 | server.setRequestHandler(ReadResourceRequestSchema, async (request) => { 79 | const url = new URL(request.params.uri); 80 | const id = url.pathname.replace(/^\//, ''); 81 | const note = notes[id]; 82 | 83 | if (!note) { 84 | throw new Error(`Note ${id} not found`); 85 | } 86 | 87 | return { 88 | contents: [{ 89 | uri: request.params.uri, 90 | mimeType: "text/plain", 91 | text: note.content 92 | }] 93 | }; 94 | }); 95 | 96 | /** 97 | * Handler that lists available tools. 98 | */ 99 | server.setRequestHandler(ListToolsRequestSchema, async () => { 100 | return { 101 | tools: [ 102 | { 103 | name: "walking_route", 104 | description: "Get walking route", 105 | inputSchema: { 106 | type: "object", 107 | properties: { 108 | origin: { 109 | type: "string", 110 | description: "Origin longitude and latitude" 111 | }, 112 | destination: { 113 | type: "string", 114 | description: "Destination longitude and latitude" 115 | } 116 | }, 117 | required: ["origin", "destination"] 118 | } 119 | }, 120 | { 121 | name: "transit_route", 122 | description: "Get transit route", 123 | inputSchema: { 124 | type: "object", 125 | properties: { 126 | origin: { 127 | type: "string", 128 | description: "Origin longitude and latitude" 129 | }, 130 | destination: { 131 | type: "string", 132 | description: "Destination longitude and latitude" 133 | }, 134 | city: { 135 | type: "string", 136 | description: "City name" 137 | } 138 | }, 139 | required: ["origin", "destination", "city"] 140 | } 141 | }, 142 | { 143 | name: "driving_route", 144 | description: "Get driving route", 145 | inputSchema: { 146 | type: "object", 147 | properties: { 148 | origin: { 149 | type: "string", 150 | description: "Origin longitude and latitude" 151 | }, 152 | destination: { 153 | type: "string", 154 | description: "Destination longitude and latitude" 155 | } 156 | }, 157 | required: ["origin", "destination"] 158 | } 159 | }, 160 | { 161 | name: "bicycling_route", 162 | description: "Get bicycling route", 163 | inputSchema: { 164 | type: "object", 165 | properties: { 166 | origin: { 167 | type: "string", 168 | description: "Origin longitude and latitude" 169 | }, 170 | destination: { 171 | type: "string", 172 | description: "Destination longitude and latitude" 173 | } 174 | }, 175 | required: ["origin", "destination"] 176 | } 177 | }, 178 | { 179 | name: "distance", 180 | description: "Get distance between two points", 181 | inputSchema: { 182 | type: "object", 183 | properties: { 184 | origins: { 185 | type: "string", 186 | description: "Origin longitude and latitude, separated by |" 187 | }, 188 | destination: { 189 | type: "string", 190 | description: "Destination longitude and latitude" 191 | } 192 | }, 193 | required: ["origins", "destination"] 194 | } 195 | } 196 | ] 197 | }; 198 | }); 199 | 200 | /** 201 | * Handler for the create_note tool. 202 | * Creates a new note with the provided title and content, and returns success message. 203 | */ 204 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 205 | const apiKey = "ae4775b98c50d997cab4bcd6c769bf9b"; 206 | switch (request.params.name) { 207 | case "walking_route": { 208 | const origin = String(request.params.arguments?.origin); 209 | const destination = String(request.params.arguments?.destination); 210 | if (!origin || !destination) { 211 | throw new Error("Origin and destination are required"); 212 | } 213 | const url = `https://restapi.amap.com/v3/direction/walking?origin=${origin}&destination=${destination}&key=${apiKey}`; 214 | const response = await fetch(url); 215 | const data = await response.json(); 216 | return { 217 | content: [{ 218 | type: "text", 219 | text: JSON.stringify(data) 220 | }] 221 | }; 222 | } 223 | case "transit_route": { 224 | const origin = String(request.params.arguments?.origin); 225 | const destination = String(request.params.arguments?.destination); 226 | const city = String(request.params.arguments?.city); 227 | if (!origin || !destination || !city) { 228 | throw new Error("Origin, destination and city are required"); 229 | } 230 | const url = `https://restapi.amap.com/v3/direction/transit/integrated?origin=${origin}&destination=${destination}&city=${city}&key=${apiKey}`; 231 | const response = await fetch(url); 232 | const data = await response.json(); 233 | return { 234 | content: [{ 235 | type: "text", 236 | text: JSON.stringify(data) 237 | }] 238 | }; 239 | } 240 | case "driving_route": { 241 | const origin = String(request.params.arguments?.origin); 242 | const destination = String(request.params.arguments?.destination); 243 | if (!origin || !destination) { 244 | throw new Error("Origin and destination are required"); 245 | } 246 | const url = `https://restapi.amap.com/v3/direction/driving?origin=${origin}&destination=${destination}&key=${apiKey}`; 247 | const response = await fetch(url); 248 | const data = await response.json(); 249 | return { 250 | content: [{ 251 | type: "text", 252 | text: JSON.stringify(data) 253 | }] 254 | }; 255 | } 256 | case "bicycling_route": { 257 | const origin = String(request.params.arguments?.origin); 258 | const destination = String(request.params.arguments?.destination); 259 | if (!origin || !destination) { 260 | throw new Error("Origin and destination are required"); 261 | } 262 | const url = `https://restapi.amap.com/v4/direction/bicycling?origin=${origin}&destination=${destination}&key=${apiKey}`; 263 | const response = await fetch(url); 264 | const data = await response.json(); 265 | return { 266 | content: [{ 267 | type: "text", 268 | text: JSON.stringify(data) 269 | }] 270 | }; 271 | } 272 | case "distance": { 273 | const origins = String(request.params.arguments?.origins); 274 | const destination = String(request.params.arguments?.destination); 275 | if (!origins || !destination) { 276 | throw new Error("Origins and destination are required"); 277 | } 278 | const url = `https://restapi.amap.com/v3/distance?origins=${origins}&destination=${destination}&key=${apiKey}`; 279 | const response = await fetch(url); 280 | const data = await response.json(); 281 | return { 282 | content: [{ 283 | type: "text", 284 | text: JSON.stringify(data) 285 | }] 286 | }; 287 | } 288 | default: 289 | throw new Error("Unknown tool"); 290 | } 291 | }); 292 | 293 | /** 294 | * Handler that lists available prompts. 295 | * Exposes a single "summarize_notes" prompt that summarizes all notes. 296 | */ 297 | server.setRequestHandler(ListPromptsRequestSchema, async () => { 298 | return { 299 | prompts: [ 300 | { 301 | name: "summarize_notes", 302 | description: "Summarize all notes", 303 | } 304 | ] 305 | }; 306 | }); 307 | 308 | /** 309 | * Handler for the summarize_notes prompt. 310 | * Returns a prompt that requests summarization of all notes, with the notes' contents embedded as resources. 311 | */ 312 | server.setRequestHandler(GetPromptRequestSchema, async (request) => { 313 | if (request.params.name !== "summarize_notes") { 314 | throw new Error("Unknown prompt"); 315 | } 316 | 317 | const embeddedNotes = Object.entries(notes).map(([id, note]) => ({ 318 | type: "resource" as const, 319 | resource: { 320 | uri: `note:///${id}`, 321 | mimeType: "text/plain", 322 | text: note.content 323 | } 324 | })); 325 | 326 | return { 327 | messages: [ 328 | { 329 | role: "user", 330 | content: { 331 | type: "text", 332 | text: "Please summarize the following notes:" 333 | } 334 | }, 335 | ...embeddedNotes.map(note => ({ 336 | role: "user" as const, 337 | content: note 338 | })), 339 | { 340 | role: "user", 341 | content: { 342 | type: "text", 343 | text: "Provide a concise summary of all the notes above." 344 | } 345 | } 346 | ] 347 | }; 348 | }); 349 | 350 | /** 351 | * Start the server using stdio transport. 352 | * This allows the server to communicate via standard input/output streams. 353 | */ 354 | async function main() { 355 | const transport = new StdioServerTransport(); 356 | await server.connect(transport); 357 | console.log("Amap route server started"); 358 | } 359 | 360 | main().catch((error) => { 361 | console.error("Server error:", error); 362 | process.exit(1); 363 | }); 364 | ``` -------------------------------------------------------------------------------- /amap-coordinate-server/src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | #!/usr/bin/env node 2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js'; 3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; 4 | import { 5 | CallToolRequestSchema, 6 | ErrorCode, 7 | ListToolsRequestSchema, 8 | McpError, 9 | } from '@modelcontextprotocol/sdk/types.js'; 10 | import axios from 'axios'; 11 | 12 | const API_KEY = process.env.AMAP_API_KEY; 13 | 14 | if (!API_KEY) { 15 | throw new Error('AMAP_API_KEY environment variable is required'); 16 | } 17 | 18 | interface CoordinateConvertArgs { 19 | locations: string; 20 | coordsys?: string; 21 | output?: string; 22 | } 23 | 24 | interface KeywordSearchArgs { 25 | keywords: string; 26 | types?: string; 27 | city?: string; 28 | citylimit?: boolean; 29 | children?: number; 30 | offset?: number; 31 | page?: number; 32 | extensions?: string; 33 | } 34 | 35 | interface AroundSearchArgs { 36 | location: string; 37 | keywords?: string; 38 | types?: string; 39 | city?: string; 40 | radius?: number; 41 | sortrule?: string; 42 | offset?: number; 43 | page?: number; 44 | extensions?: string; 45 | } 46 | 47 | interface PolygonSearchArgs { 48 | polygon: string; 49 | keywords?: string; 50 | types?: string; 51 | offset?: number; 52 | page?: number; 53 | extensions?: string; 54 | } 55 | 56 | interface IDSearchArgs { 57 | id: string; 58 | } 59 | 60 | interface AOIBoundaryQueryArgs { 61 | id: string; 62 | } 63 | 64 | type SearchArgs = 65 | | CoordinateConvertArgs 66 | | KeywordSearchArgs 67 | | AroundSearchArgs 68 | | PolygonSearchArgs 69 | | IDSearchArgs 70 | | AOIBoundaryQueryArgs; 71 | 72 | const isValidSearchArgs = (args: any): args is SearchArgs => { 73 | if (typeof args !== 'object' || args === null) { 74 | return false; 75 | } 76 | 77 | if ('locations' in args) { 78 | return ( 79 | typeof args.locations === 'string' && 80 | (args.coordsys === undefined || typeof args.coordsys === 'string') && 81 | (args.output === undefined || typeof args.output === 'string') 82 | ); 83 | } else if ('keywords' in args) { 84 | return typeof args.keywords === 'string'; 85 | } else if ('location' in args) { 86 | return typeof args.location === 'string'; 87 | } else if ('polygon' in args) { 88 | return typeof args.polygon === 'string'; 89 | } else if ('id' in args) { 90 | return typeof args.id === 'string'; 91 | } 92 | 93 | return false; 94 | }; 95 | 96 | class AmapCoordinateServer { 97 | private server: Server; 98 | 99 | constructor() { 100 | this.server = new Server( 101 | { 102 | name: 'amap-coordinate-server', 103 | version: '0.1.0', 104 | }, 105 | { 106 | capabilities: { 107 | resources: {}, 108 | tools: {}, 109 | }, 110 | } 111 | ); 112 | 113 | this.setupToolHandlers(); 114 | 115 | this.server.onerror = (error) => console.error('[MCP Error]', error); 116 | process.on('SIGINT', async () => { 117 | await this.server.close(); 118 | process.exit(0); 119 | }); 120 | } 121 | 122 | private setupToolHandlers() { 123 | this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ 124 | tools: [ 125 | { 126 | name: 'coordinate_convert', 127 | description: 'Convert coordinates using AMap API', 128 | inputSchema: { 129 | type: 'object', 130 | properties: { 131 | locations: { 132 | type: 'string', 133 | description: 134 | 'Coordinates to convert (longitude,latitude|longitude,latitude)', 135 | }, 136 | coordsys: { 137 | type: 'string', 138 | description: 'Original coordinate system (gps, mapbar, baidu, autonavi)', 139 | enum: ['gps', 'mapbar', 'baidu', 'autonavi'], 140 | }, 141 | output: { 142 | type: 'string', 143 | description: 'Output format (JSON, XML)', 144 | enum: ['JSON', 'XML'], 145 | }, 146 | }, 147 | required: ['locations'], 148 | }, 149 | }, 150 | { 151 | name: 'keyword_search', 152 | description: 'Search for places by keyword using AMap API', 153 | inputSchema: { 154 | type: 'object', 155 | properties: { 156 | keywords: { 157 | type: 'string', 158 | description: 'Keywords for the search', 159 | }, 160 | types: { 161 | type: 'string', 162 | description: 'POI types', 163 | }, 164 | city: { 165 | type: 'string', 166 | description: 'City to search in', 167 | }, 168 | citylimit: { 169 | type: 'boolean', 170 | description: 'Limit results to the specified city', 171 | }, 172 | children: { 173 | type: 'number', 174 | description: 'Whether to show child POIs', 175 | }, 176 | offset: { 177 | type: 'number', 178 | description: 'Number of results per page', 179 | }, 180 | page: { 181 | type: 'number', 182 | description: 'Page number', 183 | }, 184 | extensions: { 185 | type: 'string', 186 | description: 'Return extensions', 187 | }, 188 | }, 189 | required: ['keywords'], 190 | }, 191 | }, 192 | { 193 | name: 'around_search', 194 | description: 'Search for places around a location using AMap API', 195 | inputSchema: { 196 | type: 'object', 197 | properties: { 198 | location: { 199 | type: 'string', 200 | description: 'Location to search around (longitude,latitude)', 201 | }, 202 | keywords: { 203 | type: 'string', 204 | description: 'Keywords for the search', 205 | }, 206 | types: { 207 | type: 'string', 208 | description: 'POI types', 209 | }, 210 | city: { 211 | type: 'string', 212 | description: 'City to search in', 213 | }, 214 | radius: { 215 | type: 'number', 216 | description: 'Search radius', 217 | }, 218 | sortrule: { 219 | type: 'string', 220 | description: 'Sort rule', 221 | }, 222 | offset: { 223 | type: 'number', 224 | description: 'Number of results per page', 225 | }, 226 | page: { 227 | type: 'number', 228 | description: 'Page number', 229 | }, 230 | extensions: { 231 | type: 'string', 232 | description: 'Return extensions', 233 | }, 234 | }, 235 | required: ['location'], 236 | }, 237 | }, 238 | { 239 | name: 'polygon_search', 240 | description: 'Search for places within a polygon using AMap API', 241 | inputSchema: { 242 | type: 'object', 243 | properties: { 244 | polygon: { 245 | type: 'string', 246 | description: 247 | 'Polygon to search within (longitude,latitude|longitude,latitude|...)', 248 | }, 249 | keywords: { 250 | type: 'string', 251 | description: 'Keywords for the search', 252 | }, 253 | types: { 254 | type: 'string', 255 | description: 'POI types', 256 | }, 257 | offset: { 258 | type: 'number', 259 | description: 'Number of results per page', 260 | }, 261 | page: { 262 | type: 'number', 263 | description: 'Page number', 264 | }, 265 | extensions: { 266 | type: 'string', 267 | description: 'Return extensions', 268 | }, 269 | }, 270 | required: ['polygon'], 271 | }, 272 | }, 273 | { 274 | name: 'id_search', 275 | description: 'Search for a place by ID using AMap API', 276 | inputSchema: { 277 | type: 'object', 278 | properties: { 279 | id: { 280 | type: 'string', 281 | description: 'ID of the place to search for', 282 | }, 283 | }, 284 | required: ['id'], 285 | }, 286 | }, 287 | { 288 | name: 'aoi_boundary_query', 289 | description: 'Query AOI boundary using AMap API', 290 | inputSchema: { 291 | type: 'object', 292 | properties: { 293 | id: { 294 | type: 'string', 295 | description: 'ID of the AOI to query', 296 | }, 297 | }, 298 | required: ['id'], 299 | }, 300 | }, 301 | ], 302 | })); 303 | 304 | this.server.setRequestHandler(CallToolRequestSchema, async (request) => { 305 | const { name, arguments: args } = request.params; 306 | 307 | switch (name) { 308 | case 'coordinate_convert': 309 | if (!isValidSearchArgs(args)) { 310 | throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); 311 | } 312 | 313 | const { locations, coordsys, output } = args as CoordinateConvertArgs; 314 | 315 | try { 316 | const response = await axios.get( 317 | 'https://restapi.amap.com/v3/assistant/coordinate/convert', 318 | { 319 | params: { 320 | locations: locations, 321 | coordsys: coordsys, 322 | output: output, 323 | key: API_KEY, 324 | }, 325 | } 326 | ); 327 | 328 | return { 329 | content: [ 330 | { 331 | type: 'text', 332 | text: JSON.stringify(response.data, null, 2), 333 | }, 334 | ], 335 | }; 336 | } catch (error: any) { 337 | console.error(error); 338 | return { 339 | content: [ 340 | { 341 | type: 'text', 342 | text: `AMap API error: ${error.message}`, 343 | }, 344 | ], 345 | isError: true, 346 | }; 347 | } 348 | break; 349 | case 'keyword_search': 350 | if (!isValidSearchArgs(args)) { 351 | throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); 352 | } 353 | 354 | const { keywords: keywords_ks, types: types_ks, city: city_ks, citylimit: citylimit_ks, children: children_ks, offset: offset_ks, page: page_ks, extensions: extensions_ks } = 355 | args as KeywordSearchArgs; 356 | 357 | try { 358 | const response = await axios.get( 359 | 'https://restapi.amap.com/v3/place/text', 360 | { 361 | params: { 362 | key: API_KEY, 363 | keywords: keywords_ks, 364 | types: types_ks, 365 | city: city_ks, 366 | citylimit: citylimit_ks, 367 | children: children_ks, 368 | offset: offset_ks, 369 | page: page_ks, 370 | extensions: extensions_ks, 371 | }, 372 | } 373 | ); 374 | 375 | return { 376 | content: [ 377 | { 378 | type: 'text', 379 | text: JSON.stringify(response.data, null, 2), 380 | }, 381 | ], 382 | }; 383 | } catch (error: any) { 384 | console.error(error); 385 | return { 386 | content: [ 387 | { 388 | type: 'text', 389 | text: `AMap API error: ${error.message}`, 390 | }, 391 | ], 392 | isError: true, 393 | }; 394 | } 395 | break; 396 | case 'around_search': 397 | if (!isValidSearchArgs(args)) { 398 | throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); 399 | } 400 | 401 | const { location, keywords: keywords_as, types: types_as, city: city_as, radius: radius_as, sortrule: sortrule_as, offset: offset_as, page: page_as, extensions: extensions_as } = 402 | args as AroundSearchArgs; 403 | 404 | try { 405 | const response = await axios.get( 406 | 'https://restapi.amap.com/v3/place/around', 407 | { 408 | params: { 409 | key: API_KEY, 410 | location: location, 411 | keywords: keywords_as, 412 | types: types_as, 413 | city: city_as, 414 | radius: radius_as, 415 | sortrule: sortrule_as, 416 | offset: offset_as, 417 | page: page_as, 418 | extensions: extensions_as, 419 | }, 420 | } 421 | ); 422 | 423 | return { 424 | content: [ 425 | { 426 | type: 'text', 427 | text: JSON.stringify(response.data, null, 2), 428 | }, 429 | ], 430 | }; 431 | } catch (error: any) { 432 | console.error(error); 433 | return { 434 | content: [ 435 | { 436 | type: 'text', 437 | text: `AMap API error: ${error.message}`, 438 | }, 439 | ], 440 | isError: true, 441 | }; 442 | } 443 | break; 444 | case 'polygon_search': 445 | if (!isValidSearchArgs(args)) { 446 | throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); 447 | } 448 | 449 | const { polygon, keywords: keywords_ps, types: types_ps, offset: offset_ps, page: page_ps, extensions: extensions_ps } = 450 | args as PolygonSearchArgs; 451 | 452 | try { 453 | const response = await axios.get( 454 | 'https://restapi.amap.com/v3/place/polygon', 455 | { 456 | params: { 457 | key: API_KEY, 458 | polygon: polygon, 459 | keywords: keywords_ps, 460 | types: types_ps, 461 | offset: offset_ps, 462 | page: page_ps, 463 | extensions: extensions_ps, 464 | }, 465 | } 466 | ); 467 | 468 | return { 469 | content: [ 470 | { 471 | type: 'text', 472 | text: JSON.stringify(response.data, null, 2), 473 | }, 474 | ], 475 | }; 476 | } catch (error: any) { 477 | console.error(error); 478 | return { 479 | content: [ 480 | { 481 | type: 'text', 482 | text: `AMap API error: ${error.message}`, 483 | }, 484 | ], 485 | isError: true, 486 | }; 487 | } 488 | break; 489 | case 'id_search': 490 | if (!isValidSearchArgs(args)) { 491 | throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); 492 | } 493 | 494 | const { id: id_is } = args as IDSearchArgs; 495 | 496 | try { 497 | const response = await axios.get( 498 | 'https://restapi.amap.com/v3/place/detail', 499 | { 500 | params: { 501 | key: API_KEY, 502 | id: id_is, 503 | }, 504 | } 505 | ); 506 | 507 | return { 508 | content: [ 509 | { 510 | type: 'text', 511 | text: JSON.stringify(response.data, null, 2), 512 | }, 513 | ], 514 | }; 515 | } catch (error: any) { 516 | console.error(error); 517 | return { 518 | content: [ 519 | { 520 | type: 'text', 521 | text: `AMap API error: ${error.message}`, 522 | }, 523 | ], 524 | isError: true, 525 | }; 526 | } 527 | break; 528 | case 'aoi_boundary_query': 529 | if (!isValidSearchArgs(args)) { 530 | throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); 531 | } 532 | 533 | const { id: id_abq } = args as AOIBoundaryQueryArgs; 534 | 535 | try { 536 | const response = await axios.get( 537 | 'https://restapi.amap.com/v5/aoi/polyline', 538 | { 539 | params: { 540 | key: API_KEY, 541 | id: id_abq, 542 | }, 543 | } 544 | ); 545 | 546 | return { 547 | content: [ 548 | { 549 | type: 'text', 550 | text: JSON.stringify(response.data, null, 2), 551 | }, 552 | ], 553 | }; 554 | } catch (error: any) { 555 | console.error(error); 556 | return { 557 | content: [ 558 | { 559 | type: 'text', 560 | text: `AMap API error: ${error.message}`, 561 | }, 562 | ], 563 | isError: true, 564 | }; 565 | } 566 | break; 567 | default: 568 | throw new McpError( 569 | ErrorCode.MethodNotFound, 570 | `Unknown tool: ${request.params.name}` 571 | ); 572 | } 573 | }); 574 | } 575 | 576 | async run() { 577 | const transport = new StdioServerTransport(); 578 | await this.server.connect(transport); 579 | console.error('AMap Coordinate MCP server running on stdio'); 580 | } 581 | } 582 | 583 | const server = new AmapCoordinateServer(); 584 | server.run().catch(console.error); 585 | ```