# 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: -------------------------------------------------------------------------------- ``` node_modules/ build/ *.log .env* ``` -------------------------------------------------------------------------------- /amap-route-server/.gitignore: -------------------------------------------------------------------------------- ``` node_modules/ build/ *.log .env* ``` -------------------------------------------------------------------------------- /amap-route-server/README.md: -------------------------------------------------------------------------------- ```markdown # amap-route-server MCP Server A Model Context Protocol server for AMap Route Planning. This server provides tools to calculate routes using the AMap API for different transportation modes. ## Features ### Tools - `walking_route`: Calculates a walking route between two points. - `transit_route`: Calculates a transit route between two points. - `driving_route`: Calculates a driving route between two points. - `bicycling_route`: Calculates a bicycling route between two points. - `distance`: Calculates the distance between two points. ## Development Install dependencies: ```bash npm install ``` Build the server: ```bash npm run build ``` For development with auto-rebuild: ```bash npm run watch ``` ## Installation To use with Claude Desktop, add the server config: On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` On Windows: `%APPDATA%/Claude/claude_desktop_config.json` ```json { "mcpServers": { "amap-route-server": { "command": "/path/to/amap-route-server/build/index.js", "env": { "AMAP_API_KEY": "YOUR_AMAP_API_KEY" } } } } ``` **Note:** You need to obtain an AMap API key and set it as the `AMAP_API_KEY` environment variable. ### Debugging 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: ```bash npm run inspector ``` The Inspector will provide a URL to access debugging tools in your browser. ``` -------------------------------------------------------------------------------- /amap-coordinate-server/README.md: -------------------------------------------------------------------------------- ```markdown # amap-coordinate-server MCP Server A Model Context Protocol server for AMap Coordinate Conversion and Place Search. This server provides tools to convert coordinates between different systems and search for places using the AMap API. ## Features ### Tools - `coordinate_convert`: Converts coordinates between different coordinate systems (GPS, mapbar, baidu, autonavi). - `keyword_search`: Searches for places by keyword. - `around_search`: Searches for places around a specific location. - `polygon_search`: Searches for places within a given polygon. - `id_search`: Searches for a place by its ID. - `aoi_boundary_query`: Queries the boundary of an Area of Interest (AOI). ## Development Install dependencies: ```bash npm install ``` Build the server: ```bash npm run build ``` For development with auto-rebuild: ```bash npm run watch ``` ## Installation To use with Claude Desktop, add the server config: On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` On Windows: `%APPDATA%/Claude/claude_desktop_config.json` ```json { "mcpServers": { "amap-coordinate-server": { "command": "/path/to/amap-coordinate-server/build/index.js", "env": { "AMAP_API_KEY": "YOUR_AMAP_API_KEY" } } } } ``` **Note:** You need to obtain an AMap API key and set it as the `AMAP_API_KEY` environment variable. ### Debugging 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: ```bash npm run inspector ``` The Inspector will provide a URL to access debugging tools in your browser. ``` -------------------------------------------------------------------------------- /amap-coordinate-server/tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /amap-route-server/tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /amap-route-server/package.json: -------------------------------------------------------------------------------- ```json { "name": "GAODENEW", "version": "0.1.0", "description": "CHINA!", "private": true, "type": "module", "bin": { "GAODENEW": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "0.6.0", "node-fetch": "^3.3.2" }, "devDependencies": { "@types/node": "^20.11.24", "typescript": "^5.3.3" } } ``` -------------------------------------------------------------------------------- /amap-coordinate-server/package.json: -------------------------------------------------------------------------------- ```json { "name": "amap-coordinate-server", "version": "0.1.0", "description": "A Model Context Protocol server", "private": true, "type": "module", "bin": { "amap-coordinate-server": "build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^0.6.0", "axios": "^1.8.4" }, "devDependencies": { "@types/node": "^20.11.24", "typescript": "^5.3.3" }, "main": "index.js", "keywords": [], "author": "", "license": "ISC" } ``` -------------------------------------------------------------------------------- /amap-route-server/src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node /** * This is a template MCP server that implements a simple notes system. * It demonstrates core MCP concepts like resources and tools by allowing: * - Listing notes as resources * - Reading individual notes * - Creating new notes via a tool * - Summarizing all notes via a prompt */ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import fetch from 'node-fetch'; /** * Type alias for a note object. */ type Note = { title: string, content: string }; /** * Simple in-memory storage for notes. * In a real implementation, this would likely be backed by a database. */ const notes: { [id: string]: Note } = { "1": { title: "First Note", content: "This is note 1" }, "2": { title: "Second Note", content: "This is note 2" } }; /** * Create an MCP server with capabilities for resources (to list/read notes), * tools (to create new notes), and prompts (to summarize notes). */ const server = new Server( { name: "amap-route-server", version: "0.1.0", }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, } ); /** * Handler for listing available notes as resources. * Each note is exposed as a resource with: * - A note:// URI scheme * - Plain text MIME type * - Human readable name and description (now including the note title) */ server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: Object.entries(notes).map(([id, note]) => ({ uri: `note:///${id}`, mimeType: "text/plain", name: note.title, description: `A text note: ${note.title}` })) }; }); /** * Handler for reading the contents of a specific note. * Takes a note:// URI and returns the note content as plain text. */ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const url = new URL(request.params.uri); const id = url.pathname.replace(/^\//, ''); const note = notes[id]; if (!note) { throw new Error(`Note ${id} not found`); } return { contents: [{ uri: request.params.uri, mimeType: "text/plain", text: note.content }] }; }); /** * Handler that lists available tools. */ server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "walking_route", description: "Get walking route", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Origin longitude and latitude" }, destination: { type: "string", description: "Destination longitude and latitude" } }, required: ["origin", "destination"] } }, { name: "transit_route", description: "Get transit route", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Origin longitude and latitude" }, destination: { type: "string", description: "Destination longitude and latitude" }, city: { type: "string", description: "City name" } }, required: ["origin", "destination", "city"] } }, { name: "driving_route", description: "Get driving route", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Origin longitude and latitude" }, destination: { type: "string", description: "Destination longitude and latitude" } }, required: ["origin", "destination"] } }, { name: "bicycling_route", description: "Get bicycling route", inputSchema: { type: "object", properties: { origin: { type: "string", description: "Origin longitude and latitude" }, destination: { type: "string", description: "Destination longitude and latitude" } }, required: ["origin", "destination"] } }, { name: "distance", description: "Get distance between two points", inputSchema: { type: "object", properties: { origins: { type: "string", description: "Origin longitude and latitude, separated by |" }, destination: { type: "string", description: "Destination longitude and latitude" } }, required: ["origins", "destination"] } } ] }; }); /** * Handler for the create_note tool. * Creates a new note with the provided title and content, and returns success message. */ server.setRequestHandler(CallToolRequestSchema, async (request) => { const apiKey = "ae4775b98c50d997cab4bcd6c769bf9b"; switch (request.params.name) { case "walking_route": { const origin = String(request.params.arguments?.origin); const destination = String(request.params.arguments?.destination); if (!origin || !destination) { throw new Error("Origin and destination are required"); } const url = `https://restapi.amap.com/v3/direction/walking?origin=${origin}&destination=${destination}&key=${apiKey}`; const response = await fetch(url); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data) }] }; } case "transit_route": { const origin = String(request.params.arguments?.origin); const destination = String(request.params.arguments?.destination); const city = String(request.params.arguments?.city); if (!origin || !destination || !city) { throw new Error("Origin, destination and city are required"); } const url = `https://restapi.amap.com/v3/direction/transit/integrated?origin=${origin}&destination=${destination}&city=${city}&key=${apiKey}`; const response = await fetch(url); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data) }] }; } case "driving_route": { const origin = String(request.params.arguments?.origin); const destination = String(request.params.arguments?.destination); if (!origin || !destination) { throw new Error("Origin and destination are required"); } const url = `https://restapi.amap.com/v3/direction/driving?origin=${origin}&destination=${destination}&key=${apiKey}`; const response = await fetch(url); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data) }] }; } case "bicycling_route": { const origin = String(request.params.arguments?.origin); const destination = String(request.params.arguments?.destination); if (!origin || !destination) { throw new Error("Origin and destination are required"); } const url = `https://restapi.amap.com/v4/direction/bicycling?origin=${origin}&destination=${destination}&key=${apiKey}`; const response = await fetch(url); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data) }] }; } case "distance": { const origins = String(request.params.arguments?.origins); const destination = String(request.params.arguments?.destination); if (!origins || !destination) { throw new Error("Origins and destination are required"); } const url = `https://restapi.amap.com/v3/distance?origins=${origins}&destination=${destination}&key=${apiKey}`; const response = await fetch(url); const data = await response.json(); return { content: [{ type: "text", text: JSON.stringify(data) }] }; } default: throw new Error("Unknown tool"); } }); /** * Handler that lists available prompts. * Exposes a single "summarize_notes" prompt that summarizes all notes. */ server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: "summarize_notes", description: "Summarize all notes", } ] }; }); /** * Handler for the summarize_notes prompt. * Returns a prompt that requests summarization of all notes, with the notes' contents embedded as resources. */ server.setRequestHandler(GetPromptRequestSchema, async (request) => { if (request.params.name !== "summarize_notes") { throw new Error("Unknown prompt"); } const embeddedNotes = Object.entries(notes).map(([id, note]) => ({ type: "resource" as const, resource: { uri: `note:///${id}`, mimeType: "text/plain", text: note.content } })); return { messages: [ { role: "user", content: { type: "text", text: "Please summarize the following notes:" } }, ...embeddedNotes.map(note => ({ role: "user" as const, content: note })), { role: "user", content: { type: "text", text: "Provide a concise summary of all the notes above." } } ] }; }); /** * Start the server using stdio transport. * This allows the server to communicate via standard input/output streams. */ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.log("Amap route server started"); } main().catch((error) => { console.error("Server error:", error); process.exit(1); }); ``` -------------------------------------------------------------------------------- /amap-coordinate-server/src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; const API_KEY = process.env.AMAP_API_KEY; if (!API_KEY) { throw new Error('AMAP_API_KEY environment variable is required'); } interface CoordinateConvertArgs { locations: string; coordsys?: string; output?: string; } interface KeywordSearchArgs { keywords: string; types?: string; city?: string; citylimit?: boolean; children?: number; offset?: number; page?: number; extensions?: string; } interface AroundSearchArgs { location: string; keywords?: string; types?: string; city?: string; radius?: number; sortrule?: string; offset?: number; page?: number; extensions?: string; } interface PolygonSearchArgs { polygon: string; keywords?: string; types?: string; offset?: number; page?: number; extensions?: string; } interface IDSearchArgs { id: string; } interface AOIBoundaryQueryArgs { id: string; } type SearchArgs = | CoordinateConvertArgs | KeywordSearchArgs | AroundSearchArgs | PolygonSearchArgs | IDSearchArgs | AOIBoundaryQueryArgs; const isValidSearchArgs = (args: any): args is SearchArgs => { if (typeof args !== 'object' || args === null) { return false; } if ('locations' in args) { return ( typeof args.locations === 'string' && (args.coordsys === undefined || typeof args.coordsys === 'string') && (args.output === undefined || typeof args.output === 'string') ); } else if ('keywords' in args) { return typeof args.keywords === 'string'; } else if ('location' in args) { return typeof args.location === 'string'; } else if ('polygon' in args) { return typeof args.polygon === 'string'; } else if ('id' in args) { return typeof args.id === 'string'; } return false; }; class AmapCoordinateServer { private server: Server; constructor() { this.server = new Server( { name: 'amap-coordinate-server', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, }, } ); this.setupToolHandlers(); this.server.onerror = (error) => console.error('[MCP Error]', error); process.on('SIGINT', async () => { await this.server.close(); process.exit(0); }); } private setupToolHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: 'coordinate_convert', description: 'Convert coordinates using AMap API', inputSchema: { type: 'object', properties: { locations: { type: 'string', description: 'Coordinates to convert (longitude,latitude|longitude,latitude)', }, coordsys: { type: 'string', description: 'Original coordinate system (gps, mapbar, baidu, autonavi)', enum: ['gps', 'mapbar', 'baidu', 'autonavi'], }, output: { type: 'string', description: 'Output format (JSON, XML)', enum: ['JSON', 'XML'], }, }, required: ['locations'], }, }, { name: 'keyword_search', description: 'Search for places by keyword using AMap API', inputSchema: { type: 'object', properties: { keywords: { type: 'string', description: 'Keywords for the search', }, types: { type: 'string', description: 'POI types', }, city: { type: 'string', description: 'City to search in', }, citylimit: { type: 'boolean', description: 'Limit results to the specified city', }, children: { type: 'number', description: 'Whether to show child POIs', }, offset: { type: 'number', description: 'Number of results per page', }, page: { type: 'number', description: 'Page number', }, extensions: { type: 'string', description: 'Return extensions', }, }, required: ['keywords'], }, }, { name: 'around_search', description: 'Search for places around a location using AMap API', inputSchema: { type: 'object', properties: { location: { type: 'string', description: 'Location to search around (longitude,latitude)', }, keywords: { type: 'string', description: 'Keywords for the search', }, types: { type: 'string', description: 'POI types', }, city: { type: 'string', description: 'City to search in', }, radius: { type: 'number', description: 'Search radius', }, sortrule: { type: 'string', description: 'Sort rule', }, offset: { type: 'number', description: 'Number of results per page', }, page: { type: 'number', description: 'Page number', }, extensions: { type: 'string', description: 'Return extensions', }, }, required: ['location'], }, }, { name: 'polygon_search', description: 'Search for places within a polygon using AMap API', inputSchema: { type: 'object', properties: { polygon: { type: 'string', description: 'Polygon to search within (longitude,latitude|longitude,latitude|...)', }, keywords: { type: 'string', description: 'Keywords for the search', }, types: { type: 'string', description: 'POI types', }, offset: { type: 'number', description: 'Number of results per page', }, page: { type: 'number', description: 'Page number', }, extensions: { type: 'string', description: 'Return extensions', }, }, required: ['polygon'], }, }, { name: 'id_search', description: 'Search for a place by ID using AMap API', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'ID of the place to search for', }, }, required: ['id'], }, }, { name: 'aoi_boundary_query', description: 'Query AOI boundary using AMap API', inputSchema: { type: 'object', properties: { id: { type: 'string', description: 'ID of the AOI to query', }, }, required: ['id'], }, }, ], })); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; switch (name) { case 'coordinate_convert': if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); } const { locations, coordsys, output } = args as CoordinateConvertArgs; try { const response = await axios.get( 'https://restapi.amap.com/v3/assistant/coordinate/convert', { params: { locations: locations, coordsys: coordsys, output: output, key: API_KEY, }, } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { console.error(error); return { content: [ { type: 'text', text: `AMap API error: ${error.message}`, }, ], isError: true, }; } break; case 'keyword_search': if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); } 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 } = args as KeywordSearchArgs; try { const response = await axios.get( 'https://restapi.amap.com/v3/place/text', { params: { key: API_KEY, keywords: keywords_ks, types: types_ks, city: city_ks, citylimit: citylimit_ks, children: children_ks, offset: offset_ks, page: page_ks, extensions: extensions_ks, }, } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { console.error(error); return { content: [ { type: 'text', text: `AMap API error: ${error.message}`, }, ], isError: true, }; } break; case 'around_search': if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); } 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 } = args as AroundSearchArgs; try { const response = await axios.get( 'https://restapi.amap.com/v3/place/around', { params: { key: API_KEY, location: 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, }, } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { console.error(error); return { content: [ { type: 'text', text: `AMap API error: ${error.message}`, }, ], isError: true, }; } break; case 'polygon_search': if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); } const { polygon, keywords: keywords_ps, types: types_ps, offset: offset_ps, page: page_ps, extensions: extensions_ps } = args as PolygonSearchArgs; try { const response = await axios.get( 'https://restapi.amap.com/v3/place/polygon', { params: { key: API_KEY, polygon: polygon, keywords: keywords_ps, types: types_ps, offset: offset_ps, page: page_ps, extensions: extensions_ps, }, } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { console.error(error); return { content: [ { type: 'text', text: `AMap API error: ${error.message}`, }, ], isError: true, }; } break; case 'id_search': if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); } const { id: id_is } = args as IDSearchArgs; try { const response = await axios.get( 'https://restapi.amap.com/v3/place/detail', { params: { key: API_KEY, id: id_is, }, } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { console.error(error); return { content: [ { type: 'text', text: `AMap API error: ${error.message}`, }, ], isError: true, }; } break; case 'aoi_boundary_query': if (!isValidSearchArgs(args)) { throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments'); } const { id: id_abq } = args as AOIBoundaryQueryArgs; try { const response = await axios.get( 'https://restapi.amap.com/v5/aoi/polyline', { params: { key: API_KEY, id: id_abq, }, } ); return { content: [ { type: 'text', text: JSON.stringify(response.data, null, 2), }, ], }; } catch (error: any) { console.error(error); return { content: [ { type: 'text', text: `AMap API error: ${error.message}`, }, ], isError: true, }; } break; default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('AMap Coordinate MCP server running on stdio'); } } const server = new AmapCoordinateServer(); server.run().catch(console.error); ```