Skip to main content
Glama
server.py33.8 kB
import os import sys import argparse from typing import Any, Dict, List, Optional import requests from mcp.server.fastmcp import FastMCP def get_api_key() -> str: """Get the Amap Maps API key from environment variables""" api_key = os.getenv("AMAP_MAPS_API_KEY") if not api_key: raise ValueError("AMAP_MAPS_API_KEY environment variable is required") return api_key AMAP_MAPS_API_KEY = get_api_key() mcp = FastMCP("amap-maps") @mcp.tool() def maps_regeocode(location: str) -> Dict[str, Any]: """将一个高德经纬度坐标转换为行政区划地址信息""" try: response = requests.get( "https://restapi.amap.com/v3/geocode/regeo", params={ "key": AMAP_MAPS_API_KEY, "location": location } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"RGeocoding failed: {data.get('info') or data.get('infocode')}"} return { "province": data["regeocode"]["addressComponent"]["province"], "city": data["regeocode"]["addressComponent"]["city"], "district": data["regeocode"]["addressComponent"]["district"] } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_geo(address: str, city: Optional[str] = None) -> Dict[str, Any]: """将详细的结构化地址转换为经纬度坐标。支持对地标性名胜景区、建筑物名称解析为经纬度坐标""" try: params = { "key": AMAP_MAPS_API_KEY, "address": address } if city: params["city"] = city response = requests.get( "https://restapi.amap.com/v3/geocode/geo", params=params ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Geocoding failed: {data.get('info') or data.get('infocode')}"} geocodes = data.get("geocodes", []) results = [] for geo in geocodes: results.append({ "country": geo.get("country"), "province": geo.get("province"), "city": geo.get("city"), "citycode": geo.get("citycode"), "district": geo.get("district"), "street": geo.get("street"), "number": geo.get("number"), "adcode": geo.get("adcode"), "location": geo.get("location"), "level": geo.get("level") }) return {"return": results} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_ip_location(ip: str) -> Dict[str, Any]: """IP 定位根据用户输入的 IP 地址,定位 IP 的所在位置""" try: response = requests.get( "https://restapi.amap.com/v3/ip", params={ "key": AMAP_MAPS_API_KEY, "ip": ip } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"IP Location failed: {data.get('info') or data.get('infocode')}"} return { "province": data.get("province"), "city": data.get("city"), "adcode": data.get("adcode"), "rectangle": data.get("rectangle") } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_weather(city: str) -> Dict[str, Any]: """根据城市名称或者标准adcode查询指定城市的天气""" try: response = requests.get( "https://restapi.amap.com/v3/weather/weatherInfo", params={ "key": AMAP_MAPS_API_KEY, "city": city, "extensions": "all" } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Get weather failed: {data.get('info') or data.get('infocode')}"} forecasts = data.get("forecasts", []) if not forecasts: return {"error": "No forecast data available"} return { "city": forecasts[0]["city"], "forecasts": forecasts[0]["casts"] } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_bicycling_by_address(origin_address: str, destination_address: str, origin_city: Optional[str] = None, destination_city: Optional[str] = None) -> Dict[str, Any]: """Plans a bicycle route between two locations using addresses. Unless you have a specific reason to use coordinates, it's recommended to use this tool. Args: origin_address (str): Starting point address (e.g. "北京市朝阳区阜通东大街6号") destination_address (str): Ending point address (e.g. "北京市海淀区上地十街10号") origin_city (Optional[str]): Optional city name for the origin address to improve geocoding accuracy destination_city (Optional[str]): Optional city name for the destination address to improve geocoding accuracy Returns: Dict[str, Any]: Route information including distance, duration, and turn-by-turn instructions. Considers bridges, one-way streets, and road closures. Supports routes up to 500km. """ try: # Convert origin address to coordinates origin_result = maps_geo(origin_address, origin_city) if "error" in origin_result: return {"error": f"Failed to geocode origin address: {origin_result['error']}"} if not origin_result.get("return") or not origin_result["return"]: return {"error": "No geocoding results found for origin address"} origin_location = origin_result["return"][0].get("location") if not origin_location: return {"error": "Could not extract coordinates from origin geocoding result"} # Convert destination address to coordinates destination_result = maps_geo(destination_address, destination_city) if "error" in destination_result: return {"error": f"Failed to geocode destination address: {destination_result['error']}"} if not destination_result.get("return") or not destination_result["return"]: return {"error": "No geocoding results found for destination address"} destination_location = destination_result["return"][0].get("location") if not destination_location: return {"error": "Could not extract coordinates from destination geocoding result"} # Use the coordinates to plan the bicycle route route_result = maps_bicycling_by_coordinates(origin_location, destination_location) # Add address information to the result if "error" not in route_result: route_result["addresses"] = { "origin": { "address": origin_address, "coordinates": origin_location }, "destination": { "address": destination_address, "coordinates": destination_location } } return route_result except Exception as e: return {"error": f"Route planning failed: {str(e)}"} @mcp.tool() def maps_bicycling_by_coordinates(origin_coordinates: str, destination_coordinates: str) -> Dict[str, Any]: """Plans a bicycle route between two coordinates. Args: origin_coordinates (str): Starting point coordinates in the format "longitude,latitude" (e.g. "116.434307,39.90909") destination_coordinates (str): Ending point coordinates in the format "longitude,latitude" (e.g. "116.434307,39.90909") Returns: Dict[str, Any]: Route information including distance, duration, and turn-by-turn instructions. Considers bridges, one-way streets, and road closures. Supports routes up to 500km. """ try: response = requests.get( "https://restapi.amap.com/v4/direction/bicycling", params={ "key": AMAP_MAPS_API_KEY, "origin": origin_coordinates, "destination": destination_coordinates } ) response.raise_for_status() data = response.json() if data.get("errcode") != 0: return {"error": f"Direction bicycling failed: {data.get('info') or data.get('infocode')}"} paths = [] for path in data["data"]["paths"]: steps = [] for step in path["steps"]: steps.append({ "instruction": step.get("instruction"), "road": step.get("road"), "distance": step.get("distance"), "orientation": step.get("orientation"), "duration": step.get("duration") }) paths.append({ "distance": path.get("distance"), "duration": path.get("duration"), "steps": steps }) return { "data": { "origin": data["data"]["origin"], "destination": data["data"]["destination"], "paths": paths } } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_direction_walking_by_address(origin_address: str, destination_address: str, origin_city: Optional[str] = None, destination_city: Optional[str] = None) -> Dict[str, Any]: """Plans a walking route between two locations using addresses. Unless you have a specific reason to use coordinates, it's recommended to use this tool. Args: origin_address (str): Starting point address (e.g. "北京市朝阳区阜通东大街6号") destination_address (str): Ending point address (e.g. "北京市海淀区上地十街10号") origin_city (Optional[str]): Optional city name for the origin address to improve geocoding accuracy destination_city (Optional[str]): Optional city name for the destination address to improve geocoding accuracy Returns: Dict[str, Any]: Route information including distance, duration, and turn-by-turn instructions. Supports routes up to 100km. """ try: # Convert origin address to coordinates origin_result = maps_geo(origin_address, origin_city) if "error" in origin_result: return {"error": f"Failed to geocode origin address: {origin_result['error']}"} if not origin_result.get("return") or not origin_result["return"]: return {"error": "No geocoding results found for origin address"} origin_location = origin_result["return"][0].get("location") if not origin_location: return {"error": "Could not extract coordinates from origin geocoding result"} # Convert destination address to coordinates destination_result = maps_geo(destination_address, destination_city) if "error" in destination_result: return {"error": f"Failed to geocode destination address: {destination_result['error']}"} if not destination_result.get("return") or not destination_result["return"]: return {"error": "No geocoding results found for destination address"} destination_location = destination_result["return"][0].get("location") if not destination_location: return {"error": "Could not extract coordinates from destination geocoding result"} # Use the coordinates to plan the walking route route_result = maps_direction_walking_by_coordinates(origin_location, destination_location) # Add address information to the result if "error" not in route_result: route_result["addresses"] = { "origin": { "address": origin_address, "coordinates": origin_location }, "destination": { "address": destination_address, "coordinates": destination_location } } return route_result except Exception as e: return {"error": f"Route planning failed: {str(e)}"} @mcp.tool() def maps_direction_walking_by_coordinates(origin: str, destination: str) -> Dict[str, Any]: """步行路径规划 API 可以根据输入起点终点经纬度坐标规划100km 以内的步行通勤方案,并且返回通勤方案的数据 Args: origin (str): 起点经纬度坐标,格式为"经度,纬度" (例如:"116.434307,39.90909") destination (str): 终点经纬度坐标,格式为"经度,纬度" (例如:"116.434307,39.90909") Returns: Dict[str, Any]: 包含距离、时长和详细导航信息的路线数据 """ try: response = requests.get( "https://restapi.amap.com/v3/direction/walking", params={ "key": AMAP_MAPS_API_KEY, "origin": origin, "destination": destination } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Direction Walking failed: {data.get('info') or data.get('infocode')}"} paths = [] for path in data["route"]["paths"]: steps = [] for step in path["steps"]: steps.append({ "instruction": step.get("instruction"), "road": step.get("road"), "distance": step.get("distance"), "orientation": step.get("orientation"), "duration": step.get("duration") }) paths.append({ "distance": path.get("distance"), "duration": path.get("duration"), "steps": steps }) return { "route": { "origin": data["route"]["origin"], "destination": data["route"]["destination"], "paths": paths } } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_direction_driving_by_address(origin_address: str, destination_address: str, origin_city: Optional[str] = None, destination_city: Optional[str] = None) -> Dict[str, Any]: """Plans a driving route between two locations using addresses. Unless you have a specific reason to use coordinates, it's recommended to use this tool. Args: origin_address (str): Starting point address (e.g. "北京市朝阳区阜通东大街6号") destination_address (str): Ending point address (e.g. "北京市海淀区上地十街10号") origin_city (Optional[str]): Optional city name for the origin address to improve geocoding accuracy destination_city (Optional[str]): Optional city name for the destination address to improve geocoding accuracy Returns: Dict[str, Any]: Route information including distance, duration, and turn-by-turn instructions. Considers traffic conditions and road restrictions. """ try: # Convert origin address to coordinates origin_result = maps_geo(origin_address, origin_city) if "error" in origin_result: return {"error": f"Failed to geocode origin address: {origin_result['error']}"} if not origin_result.get("return") or not origin_result["return"]: return {"error": "No geocoding results found for origin address"} origin_location = origin_result["return"][0].get("location") if not origin_location: return {"error": "Could not extract coordinates from origin geocoding result"} # Convert destination address to coordinates destination_result = maps_geo(destination_address, destination_city) if "error" in destination_result: return {"error": f"Failed to geocode destination address: {destination_result['error']}"} if not destination_result.get("return") or not destination_result["return"]: return {"error": "No geocoding results found for destination address"} destination_location = destination_result["return"][0].get("location") if not destination_location: return {"error": "Could not extract coordinates from destination geocoding result"} # Use the coordinates to plan the driving route route_result = maps_direction_driving_by_coordinates(origin_location, destination_location) # Add address information to the result if "error" not in route_result: route_result["addresses"] = { "origin": { "address": origin_address, "coordinates": origin_location }, "destination": { "address": destination_address, "coordinates": destination_location } } return route_result except Exception as e: return {"error": f"Route planning failed: {str(e)}"} @mcp.tool() def maps_direction_driving_by_coordinates(origin: str, destination: str) -> Dict[str, Any]: """驾车路径规划 API 可以根据用户起终点经纬度坐标规划以小客车、轿车通勤出行的方案,并且返回通勤方案的数据 Args: origin (str): 起点经纬度坐标,格式为"经度,纬度" (例如:"116.434307,39.90909") destination (str): 终点经纬度坐标,格式为"经度,纬度" (例如:"116.434307,39.90909") Returns: Dict[str, Any]: 包含距离、时长和详细导航信息的路线数据 """ try: response = requests.get( "https://restapi.amap.com/v3/direction/driving", params={ "key": AMAP_MAPS_API_KEY, "origin": origin, "destination": destination } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Direction Driving failed: {data.get('info') or data.get('infocode')}"} paths = [] for path in data["route"]["paths"]: steps = [] for step in path["steps"]: steps.append({ "instruction": step.get("instruction"), "road": step.get("road"), "distance": step.get("distance"), "orientation": step.get("orientation"), "duration": step.get("duration") }) paths.append({ "path": path.get("path"), "distance": path.get("distance"), "duration": path.get("duration"), "steps": steps }) return { "route": { "origin": data["route"]["origin"], "destination": data["route"]["destination"], "paths": paths } } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_direction_transit_integrated_by_address(origin_address: str, destination_address: str, origin_city: str, destination_city: str) -> Dict[str, Any]: """Plans a public transit route between two locations using addresses. Unless you have a specific reason to use coordinates, it's recommended to use this tool. Args: origin_address (str): Starting point address (e.g. "北京市朝阳区阜通东大街6号") destination_address (str): Ending point address (e.g. "北京市海淀区上地十街10号") origin_city (str): City name for the origin address (required for cross-city transit) destination_city (str): City name for the destination address (required for cross-city transit) Returns: Dict[str, Any]: Route information including distance, duration, and detailed transit instructions. Considers various public transit options including buses, subways, and trains. """ try: # Convert origin address to coordinates origin_result = maps_geo(origin_address, origin_city) if "error" in origin_result: return {"error": f"Failed to geocode origin address: {origin_result['error']}"} if not origin_result.get("return") or not origin_result["return"]: return {"error": "No geocoding results found for origin address"} origin_location = origin_result["return"][0].get("location") if not origin_location: return {"error": "Could not extract coordinates from origin geocoding result"} # Convert destination address to coordinates destination_result = maps_geo(destination_address, destination_city) if "error" in destination_result: return {"error": f"Failed to geocode destination address: {destination_result['error']}"} if not destination_result.get("return") or not destination_result["return"]: return {"error": "No geocoding results found for destination address"} destination_location = destination_result["return"][0].get("location") if not destination_location: return {"error": "Could not extract coordinates from destination geocoding result"} # Use the coordinates to plan the transit route route_result = maps_direction_transit_integrated_by_coordinates(origin_location, destination_location, origin_city, destination_city) # Add address information to the result if "error" not in route_result: route_result["addresses"] = { "origin": { "address": origin_address, "coordinates": origin_location }, "destination": { "address": destination_address, "coordinates": destination_location } } return route_result except Exception as e: return {"error": f"Route planning failed: {str(e)}"} @mcp.tool() def maps_direction_transit_integrated_by_coordinates(origin: str, destination: str, city: str, cityd: str) -> Dict[str, Any]: """根据用户起终点经纬度坐标规划综合各类公共(火车、公交、地铁)交通方式的通勤方案,并且返回通勤方案的数据,跨城场景下必须传起点城市与终点城市 Args: origin (str): 起点经纬度坐标,格式为"经度,纬度" (例如:"116.434307,39.90909") destination (str): 终点经纬度坐标,格式为"经度,纬度" (例如:"116.434307,39.90909") city (str): 起点城市名称 cityd (str): 终点城市名称 Returns: Dict[str, Any]: 包含距离、时长和详细公共交通信息的路线数据 """ try: response = requests.get( "https://restapi.amap.com/v3/direction/transit/integrated", params={ "key": AMAP_MAPS_API_KEY, "origin": origin, "destination": destination, "city": city, "cityd": cityd } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Direction Transit Integrated failed: {data.get('info') or data.get('infocode')}"} transits = [] if data["route"].get("transits"): for transit in data["route"]["transits"]: segments = [] if transit.get("segments"): for segment in transit["segments"]: walking_steps = [] if segment.get("walking", {}).get("steps"): for step in segment["walking"]["steps"]: walking_steps.append({ "instruction": step.get("instruction"), "road": step.get("road"), "distance": step.get("distance"), "action": step.get("action"), "assistant_action": step.get("assistant_action") }) buslines = [] if segment.get("bus", {}).get("buslines"): for busline in segment["bus"]["buslines"]: via_stops = [] if busline.get("via_stops"): for stop in busline["via_stops"]: via_stops.append({"name": stop.get("name")}) buslines.append({ "name": busline.get("name"), "departure_stop": {"name": busline.get("departure_stop", {}).get("name")}, "arrival_stop": {"name": busline.get("arrival_stop", {}).get("name")}, "distance": busline.get("distance"), "duration": busline.get("duration"), "via_stops": via_stops }) segments.append({ "walking": { "origin": segment.get("walking", {}).get("origin"), "destination": segment.get("walking", {}).get("destination"), "distance": segment.get("walking", {}).get("distance"), "duration": segment.get("walking", {}).get("duration"), "steps": walking_steps }, "bus": {"buslines": buslines}, "entrance": {"name": segment.get("entrance", {}).get("name")}, "exit": {"name": segment.get("exit", {}).get("name")}, "railway": { "name": segment.get("railway", {}).get("name"), "trip": segment.get("railway", {}).get("trip") } }) transits.append({ "duration": transit.get("duration"), "walking_distance": transit.get("walking_distance"), "segments": segments }) return { "route": { "origin": data["route"]["origin"], "destination": data["route"]["destination"], "distance": data["route"].get("distance"), "transits": transits } } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_distance(origins: str, destination: str, type: str = "1") -> Dict[str, Any]: """测量两个经纬度坐标之间的距离,支持驾车、步行以及球面距离测量""" try: response = requests.get( "https://restapi.amap.com/v3/distance", params={ "key": AMAP_MAPS_API_KEY, "origins": origins, "destination": destination, "type": type } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Direction Distance failed: {data.get('info') or data.get('infocode')}"} results = [] for result in data["results"]: results.append({ "origin_id": result.get("origin_id"), "dest_id": result.get("dest_id"), "distance": result.get("distance"), "duration": result.get("duration") }) return {"results": results} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_text_search(keywords: str, city: str = "", citylimit: str = "false") -> Dict[str, Any]: """关键词搜索 API 根据用户输入的关键字进行 POI 搜索,并返回相关的信息""" try: response = requests.get( "https://restapi.amap.com/v3/place/text", params={ "key": AMAP_MAPS_API_KEY, "keywords": keywords, "city": city, "citylimit": citylimit } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Text Search failed: {data.get('info') or data.get('infocode')}"} suggestion_cities = [] if data.get("suggestion", {}).get("cities"): for city in data["suggestion"]["cities"]: suggestion_cities.append({"name": city.get("name")}) pois = [] for poi in data.get("pois", []): pois.append({ "id": poi.get("id"), "name": poi.get("name"), "address": poi.get("address"), "typecode": poi.get("typecode") }) return { "suggestion": { "keywords": data.get("suggestion", {}).get("keywords"), "cities": suggestion_cities }, "pois": pois } except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_around_search(location: str, radius: str = "1000", keywords: str = "") -> Dict[str, Any]: """周边搜,根据用户传入关键词以及坐标location,搜索出radius半径范围的POI""" try: response = requests.get( "https://restapi.amap.com/v3/place/around", params={ "key": AMAP_MAPS_API_KEY, "location": location, "radius": radius, "keywords": keywords } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Around Search failed: {data.get('info') or data.get('infocode')}"} pois = [] for poi in data.get("pois", []): pois.append({ "id": poi.get("id"), "name": poi.get("name"), "address": poi.get("address"), "typecode": poi.get("typecode") }) return {"pois": pois} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} @mcp.tool() def maps_search_detail(id: str) -> Dict[str, Any]: """查询关键词搜或者周边搜获取到的POI ID的详细信息""" try: response = requests.get( "https://restapi.amap.com/v3/place/detail", params={ "key": AMAP_MAPS_API_KEY, "id": id } ) response.raise_for_status() data = response.json() if data["status"] != "1": return {"error": f"Get poi detail failed: {data.get('info') or data.get('infocode')}"} if not data.get("pois"): return {"error": "No POI found"} poi = data["pois"][0] result = { "id": poi.get("id"), "name": poi.get("name"), "location": poi.get("location"), "address": poi.get("address"), "business_area": poi.get("business_area"), "city": poi.get("cityname"), "type": poi.get("type"), "alias": poi.get("alias") } # Add biz_ext data if available if poi.get("biz_ext"): result.update(poi["biz_ext"]) return result except requests.exceptions.RequestException as e: return {"error": f"Request failed: {str(e)}"} if __name__ == "__main__": # Parse command line arguments parser = argparse.ArgumentParser(description="Amap MCP Server") parser.add_argument('transport', nargs='?', default='stdio', choices=['stdio', 'sse', 'streamable-http'], help='Transport type (stdio, sse, or streamable-http)') args = parser.parse_args() # Run the MCP server with the specified transport mcp.run(transport=args.transport)

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sugarforever/amap-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server