# Directory Structure
```
├── img
│ ├── agent_result.png
│ ├── appbuilder.png
│ ├── claude_final_result.png
│ ├── claude_result.png
│ ├── claude_setting_developer.png
│ ├── claude_setting_result.png
│ ├── claude_setting.png
│ ├── cursor_run_mcp_success.png
│ ├── cursor_setting.png
│ ├── cursor_test_1.png
│ ├── cursor_test_2.png
│ ├── logo.png
│ ├── mcp_run_success.png
│ ├── thinking_progress.png
│ └── uv_install_success.png
├── LICENSE
├── README_zh.md
├── README.md
└── src
└── baidu-map
├── node
│ ├── .gitignore
│ ├── index.ts
│ ├── package-lock.json
│ ├── package.json
│ ├── README.md
│ └── tsconfig.json
└── python
├── .gitignore
├── .python-version
├── LICENSE
├── pyproject.toml
├── README.md
├── src
│ └── mcp_server_baidu_maps
│ ├── __init__.py
│ ├── __main__.py
│ └── map.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/src/baidu-map/python/.python-version:
--------------------------------------------------------------------------------
```
1 | 3.11
2 |
```
--------------------------------------------------------------------------------
/src/baidu-map/node/.gitignore:
--------------------------------------------------------------------------------
```
1 | /dist
2 | /node_modules
```
--------------------------------------------------------------------------------
/src/baidu-map/python/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Python-generated files
2 | __pycache__/
3 | *.py[oc]
4 | build/
5 | dist/
6 | wheels/
7 | *.egg-info
8 |
9 | # Virtual environments
10 | .venv
11 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | <div align="center">
2 | <p>
3 | <img align="center" src="img/logo.png", width=700></a>
4 | </p>
5 |
6 | <!-- language -->
7 | [中文](./README_zh.md)| English
8 |
9 | <!-- icon -->
10 | <br>
11 |
12 | [](https://github.com/baidu-maps/mcp)
13 | 
14 | [](LICENSE)
15 | [](https://pypi.org/project/mcp-server-baidu-maps/)
16 | [](https://www.npmjs.com/package/@baidumap/mcp-server-baidu-map)
17 |
18 | </div>
19 | <br>
20 |
21 | ## 🚀 Introduction
22 |
23 | **Baidu Map MCP Server** is a fully MCP-compliant, open-source Location-Based Service (LBS) solution, providing a comprehensive suite of geospatial APIs and tools for developers and AI agents. As the first map service provider in China to support the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction), Baidu Map MCP Server bridges the gap between large language models (LLMs), AI agents, and real-world location data and services.
24 |
25 | With Baidu Map MCP Server, you can easily empower your applications, LLMs, and agents with advanced mapping, geocoding, POI search, route planning, weather, traffic, and more — all via standardized, developer-friendly MCP interfaces.
26 |
27 | **Key Features:**
28 | - **Full MCP Protocol Support:** Seamless integration with any MCP-compliant agent, LLM, or platform.
29 | - **Rich LBS Capabilities:** Geocoding, reverse geocoding, POI search, route planning (driving, walking, cycling, transit), weather, IP location, real-time traffic, and more.
30 | - **Cross-Platform SDKs:** Official Python and TypeScript SDKs, easy CLI and cloud deployment.
31 | - **Enterprise-Grade Data:** Powered by Baidu Maps' authoritative, up-to-date geospatial data.
32 | - **High Performance & Stability:** Recommended SSE (Server-Sent Events) access for low latency and high reliability.
33 | - **Open Source & Extensible:** MIT licensed, easy to customize and extend.
34 |
35 | Whether you are building a travel assistant, logistics platform, smart city solution, or an LLM-powered agent, Baidu Map MCP Server provides the essential geospatial intelligence and tools you need.
36 |
37 | The MCP Server architecture enables:
38 | - **Seamless AI Integration**: Allows LLMs and agents to understand and process location data naturally
39 | - **Contextual Understanding**: Provides rich geospatial context for more intelligent decision-making
40 | - **Standardized Interfaces**: Consistent API design following MCP principles for easy integration
41 | - **Scalable Implementation**: Suitable for projects of any size, from small applications to enterprise solutions
42 |
43 | Whether you're building a navigation app, delivery service, smart city solution, or enhancing an AI agent with location awareness, Baidu Map MCP Server provides the tools and infrastructure you need to succeed.
44 |
45 |
46 | ## 🛠️ Supported Tools & APIs
47 |
48 | Baidu Map MCP Server provides the following MCP-compliant APIs (tools):
49 |
50 | | Tool Name | Description |
51 | |--------------------------|----------------------------------------------------------------------------------------------|
52 | | `map_geocode` | Convert address to geographic coordinates. |
53 | | `map_reverse_geocode` | Get address, region, and POI info from coordinates. |
54 | | `map_search_places` | Search for global POIs by keyword, type, region, or within a radius. |
55 | | `map_place_details` | Get detailed info for a POI by its unique ID. |
56 | | `map_directions_matrix` | Batch route planning for multiple origins/destinations (driving, walking, cycling). |
57 | | `map_directions` | Plan routes between two points (driving, walking, cycling, transit). |
58 | | `map_weather` | Query real-time and forecast weather by region or coordinates. |
59 | | `map_ip_location` | Locate city and coordinates by IP address. |
60 | | `map_road_traffic` | Query real-time traffic conditions for roads or regions. |
61 | | `map_poi_extract`* | Extract POI info from free text (requires advanced permission). |
62 |
63 | > *Some advanced features require additional permissions. See [Authorization](#authorization) for details.
64 |
65 | All APIs follow the MCP protocol and can be called from any MCP-compliant client, LLM, or agent platform.
66 |
67 | ## ⚡ Quick Start
68 |
69 | ### 1. Get Your API Key
70 |
71 | Register and create a server-side API Key (AK) at [Baidu Maps Open Platform](https://lbsyun.baidu.com/apiconsole/key).
72 | **Be sure to enable “MCP (SSE)” service for best performance.**
73 |
74 | ### 2. Python Integration
75 |
76 | Install the SDK:
77 | ```bash
78 | pip install mcp-server-baidu-maps
79 | ```
80 |
81 | **Run as a script:**
82 | ```bash
83 | python -m mcp_server_baidu_maps
84 | ```
85 |
86 | **Configure in your MCP client (e.g., Claude, Cursor):**
87 | ```json
88 | {
89 | "mcpServers": {
90 | "baidu-maps": {
91 | "command": "python",
92 | "args": ["-m", "mcp_server_baidu_maps"],
93 | "env": {
94 | "BAIDU_MAPS_API_KEY": "<YOUR_API_KEY>"
95 | }
96 | }
97 | }
98 | }
99 | ```
100 |
101 | ### 3. Node.js/TypeScript Integration
102 |
103 | Install:
104 | ```bash
105 | npm install @baidumap/mcp-server-baidu-map
106 | ```
107 |
108 | **Configure in your MCP client:**
109 | ```json
110 | {
111 | "mcpServers": {
112 | "baidu-map": {
113 | "command": "npx",
114 | "args": [
115 | "-y",
116 | "@baidumap/mcp-server-baidu-map"
117 | ],
118 | "env": {
119 | "BAIDU_MAP_API_KEY": "<YOUR_API_KEY>"
120 | }
121 | }
122 | }
123 | }
124 | ```
125 |
126 | ### 4. Recommended: Use SSE for low-latency, stable access
127 |
128 | See [SSE Quickstart](https://lbsyun.baidu.com/faq/api?title=mcpserver/quickstart).
129 |
130 | ### 5. More Platforms
131 |
132 | - **Claude/Agent/千帆AppBuilder**: See [README_zh.md](./README_zh.md) for detailed integration guides and advanced configuration.
133 |
134 | ---
135 |
136 | ## 🚀 Advanced Use Cases
137 |
138 | - **Travel Planning Assistant:**
139 | Use `map_search_places`, `map_directions`, and `map_weather` to build an agent that plans optimal sightseeing routes, checks weather, and recommends POIs.
140 |
141 | - **Batch Route Matrix:**
142 | Use `map_directions_matrix` to calculate multiple routes and durations for logistics or delivery optimization.
143 |
144 | - **Text-to-POI Extraction:**
145 | Use `map_poi_extract` to extract POIs from user input or travel notes (requires advanced permission).
146 |
147 | - **Real-time Traffic & Weather-aware Navigation:**
148 | Combine `map_road_traffic` and `map_weather` for dynamic, context-aware travel suggestions.
149 |
150 | - **Integration with Claude, Qianfan, AppBuilder:**
151 | Seamlessly connect Baidu Map MCP Server to LLMs and agent frameworks for natural language geospatial reasoning.
152 |
153 | **See [README_zh.md](./README_zh.md) for more detailed Chinese documentation, configuration, and examples.**
154 |
155 | ---
156 |
157 | ## ⛰️ Advanced Tutorials
158 | - [Geocoding API Guide](https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-geocoding)
159 | - [POI Search API Guide](https://lbsyun.baidu.com/index.php?title=webapi/guide/webservice-placeapi)
160 | - [Route Planning API Guide](https://lbsyun.baidu.com/index.php?title=webapi/direction-api)
161 | - [MCP Integration Guide](https://lbsyun.baidu.com/index.php?title=mcp/guide)
162 | - [SDK Development Guide](https://lbsyun.baidu.com/index.php?title=mcp/sdk)
163 |
164 | ## 👩👩👧👦 Contributors
165 |
166 | <a href="https://github.com/baidu-maps/mcp/graphs/contributors">
167 | <img src="https://contrib.rocks/image?repo=baidu-maps/mcp&max=400&columns=20" width="200"/>
168 | </a>
169 |
170 |
171 | ## 🌟 Star History
172 |
173 | [](https://star-history.com/#baidu-maps/mcp&Date)
174 |
175 |
176 | ## 📄 License
177 | [MIT](./LICENSE) © baidu-maps
178 |
```
--------------------------------------------------------------------------------
/src/baidu-map/python/src/mcp_server_baidu_maps/__main__.py:
--------------------------------------------------------------------------------
```python
1 | from mcp_server_baidu_maps import main
2 |
3 | main()
```
--------------------------------------------------------------------------------
/src/baidu-map/python/src/mcp_server_baidu_maps/__init__.py:
--------------------------------------------------------------------------------
```python
1 | # __init__.py
2 | from .map import mcp
3 |
4 | def main():
5 | """MCP Baidu Maps Server - HTTP call Baidu Map API for MCP"""
6 | mcp.run()
7 |
8 | if __name__ == "__main__":
9 | main()
```
--------------------------------------------------------------------------------
/src/baidu-map/node/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "strict": true,
7 | "esModuleInterop": true,
8 | "skipLibCheck": true,
9 | "forceConsistentCasingInFileNames": true,
10 | "resolveJsonModule": true,
11 | "outDir": "./dist",
12 | "rootDir": "."
13 | },
14 | "include": [
15 | "./**/*.ts"
16 | ]
17 | }
18 |
```
--------------------------------------------------------------------------------
/src/baidu-map/python/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "mcp-server-baidu-maps"
3 | version = "0.2.4"
4 | description = "MCP Server Baidu Maps"
5 | readme = "README.md"
6 | requires-python = ">=3.11"
7 | authors = [{ name = "baidu-maps" }]
8 | maintainers = [{ name = "Pineapple274", email = "[email protected]" }]
9 | keywords = ["http", "mcp", "map", "baidu"]
10 | license = { text = "MIT" }
11 | dependencies = [
12 | "mcp[cli]>=1.9.0",
13 | ]
14 |
15 | [project.scripts]
16 | mcp-server-baidu-maps = "mcp_server_baidu_maps:main"
17 |
18 |
```
--------------------------------------------------------------------------------
/src/baidu-map/node/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@baidumap/mcp-server-baidu-map",
3 | "version": "1.0.3",
4 | "description": "MCP server for using the Baidu Map API",
5 | "license": "MIT",
6 | "author": "",
7 | "homepage": "",
8 | "bugs": "",
9 | "type": "module",
10 | "bin": {
11 | "mcp-server-baidu-map": "dist/index.js"
12 | },
13 | "files": [
14 | "dist"
15 | ],
16 | "scripts": {
17 | "build": "tsc && shx chmod +x dist/*.js",
18 | "prepare": "npm run build",
19 | "watch": "tsc --watch"
20 | },
21 | "dependencies": {
22 | "@modelcontextprotocol/sdk": "1.0.1",
23 | "@types/node-fetch": "^2.6.12",
24 | "node-fetch": "^3.3.2"
25 | },
26 | "devDependencies": {
27 | "shx": "^0.3.4",
28 | "typescript": "^5.6.2"
29 | }
30 | }
```
--------------------------------------------------------------------------------
/src/baidu-map/node/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5 | import {
6 | CallToolRequestSchema,
7 | ListToolsRequestSchema,
8 | Tool,
9 | } from "@modelcontextprotocol/sdk/types.js";
10 | import fetch from "node-fetch";
11 |
12 | // Response interfaces
13 | interface BaiduMapResponse {
14 | status: number;
15 | msg?: string;
16 | message?: string;
17 | }
18 |
19 | interface GeocodeResponse extends BaiduMapResponse {
20 | result: {
21 | location: {
22 | lat: number;
23 | lng: number;
24 | }
25 | precise: number;
26 | confidence: number;
27 | comprehension: number;
28 | level: string;
29 | };
30 | }
31 |
32 | interface ReverseGeocodeResponse extends BaiduMapResponse {
33 | result: {
34 | location: {
35 | lng: number;
36 | lat: number;
37 | };
38 | formatted_address: string;
39 | edz: {
40 | name: string;
41 | };
42 | business: string;
43 | business_info: Array<{
44 | name: string;
45 | location: {
46 | lng: number;
47 | lat: number;
48 | };
49 | adcode: number;
50 | distance: number;
51 | direction: string;
52 | }>;
53 | addressComponent: {
54 | country: string;
55 | country_code: number;
56 | country_code_iso: string;
57 | country_code_iso2: string;
58 | province: string;
59 | city: string;
60 | city_level: number;
61 | district: string;
62 | town: string;
63 | town_code: string;
64 | distance: string;
65 | direction: string;
66 | adcode: string;
67 | street: string;
68 | street_number: string;
69 | };
70 | pois: any[];
71 | roads: any[];
72 | poiRegions: any[];
73 | sematic_description: string;
74 | formatted_address_poi: string;
75 | cityCode: number;
76 | };
77 | }
78 |
79 | interface PlacesSearchResponse extends BaiduMapResponse {
80 | result_type?: string;
81 | query_type?: string;
82 | results?: Array<{
83 | name: string;
84 | location: {
85 | lat: number;
86 | lng: number;
87 | };
88 | address: string;
89 | province: string;
90 | city: string;
91 | area: string;
92 | street_id?: string;
93 | telephone?: string;
94 | detail: number;
95 | uid: string;
96 | }>;
97 | // region 参数时的返回结构
98 | result?: Array<{
99 | name: string;
100 | location: {
101 | lat: number;
102 | lng: number;
103 | };
104 | address: string;
105 | province: string;
106 | city: string;
107 | area: string;
108 | street_id?: string;
109 | telephone?: string;
110 | detail: number;
111 | uid: string;
112 | }>;
113 | }
114 |
115 | // PlaceDetails Base Response
116 | interface PlaceDetailsBaseResponse extends BaiduMapResponse {
117 | result: {
118 | uid: string;
119 | street_id: string;
120 | name: string;
121 | location: {
122 | lng: number;
123 | lat: number;
124 | };
125 | address: string;
126 | province: string;
127 | city: string;
128 | area: string;
129 | detail: number;
130 | };
131 | }
132 | // scope=2 时的详细信息
133 | interface PlaceDetailsFullResponse extends BaiduMapResponse {
134 | result: PlaceDetailsBaseResponse['result'] & {
135 | detail_info: {
136 | tag: string;
137 | navi_location: {
138 | lng: number;
139 | lat: number;
140 | };
141 | new_catalog: string;
142 | shop_hours: string;
143 | detail_url: string;
144 | type: string;
145 | overall_rating: string;
146 | image_num: string;
147 | comment_num: string;
148 | content_tag: string;
149 | };
150 | };
151 | }
152 |
153 | type PlaceDetailsResponse = PlaceDetailsBaseResponse | PlaceDetailsFullResponse;
154 |
155 | interface DistanceMatrixResponse extends BaiduMapResponse {
156 | result: Array<{
157 | distance: {
158 | text: string;
159 | value: string;
160 | };
161 | duration: {
162 | text: string;
163 | value: string;
164 | };
165 | }>;
166 | }
167 |
168 | interface DirectionsResponse extends BaiduMapResponse {
169 | result: {
170 | routes: Array<{
171 | distance: number;
172 | duration: number;
173 | steps: Array<{
174 | instruction: string;
175 | }>
176 | }>;
177 | };
178 | }
179 |
180 | interface WeatherResponse extends BaiduMapResponse {
181 | result: {
182 | location: {
183 | province: string,
184 | city: string,
185 | name: string,
186 | },
187 | now: {
188 | text: string,
189 | temp: number,
190 | feels_like: number,
191 | rh: number,
192 | wind_class: string,
193 | wind_dir: string,
194 | uptime: number,
195 | },
196 | forecasts: Array<{
197 | text_day: string,
198 | text_night: string,
199 | high: number,
200 | low: number,
201 | wc_day: string,
202 | wd_day: string,
203 | wc_night: string,
204 | wd_night: string,
205 | date: string,
206 | week: string,
207 | }>,
208 | indexes?: Array<{
209 | name: string,
210 | brief: string,
211 | detail: string,
212 | }>,
213 | alerts: Array<{
214 | type: string,
215 | level: string,
216 | title: string,
217 | desc: string,
218 | }>,
219 | forecast_hours?: Array<{
220 | text: string,
221 | temp_fc: number,
222 | wind_class: string,
223 | rh: number,
224 | prec_1h: number,
225 | clouds: number,
226 | data_time: number,
227 | }>
228 | }
229 | }
230 |
231 | interface IPLocationResponse extends BaiduMapResponse {
232 | address: string,
233 | content: {
234 | address: string,
235 | address_detail: {
236 | city: string,
237 | city_code: number,
238 | province: string
239 | },
240 | point: {
241 | x: string,
242 | y: string,
243 | }
244 | }
245 | }
246 |
247 | interface RoadTrafficResponse extends BaiduMapResponse {
248 | description: string,
249 | evaluation: {
250 | status: number,
251 | status_desc: string,
252 | },
253 | road_traffic: {
254 | road_name: string,
255 | congestion_sections?: Array<{
256 | section_desc: string,
257 | status: number,
258 | speed: number,
259 | congestion_distance: number,
260 | congestion_trend: string,
261 | }>
262 | }
263 | }
264 |
265 |
266 | interface MarkSubmitResponse extends BaiduMapResponse {
267 | result: {
268 | session_id: string,
269 | map_id: string,
270 | }
271 | }
272 |
273 | interface MarkResultResponse extends BaiduMapResponse {
274 | result: {
275 | data: Array<{
276 | answer_type: string,
277 | create_time: string,
278 | link: {
279 | title: string,
280 | desc: string,
281 | jump_url: string,
282 | image: string,
283 | poi: Array<{
284 | uid: string,
285 | name: string,
286 | location: any,
287 | admin_info: any,
288 | price: number,
289 | shop_hours: string,
290 | }>
291 | }
292 | }>
293 | }
294 | }
295 |
296 | function getApiKey(): string {
297 | const apiKey = process.env.BAIDU_MAP_API_KEY;
298 | if (!apiKey) {
299 | console.error("BAIDU_MAP_API_KEY environment variable is not set");
300 | process.exit(1);
301 | }
302 | return apiKey;
303 | }
304 |
305 | const BAIDU_MAP_API_KEY = getApiKey();
306 |
307 | // Tool definitions
308 | const GEOCODE_TOOL: Tool = {
309 | name: "map_geocode",
310 | description: "地理编码服务",
311 | inputSchema: {
312 | type: "object",
313 | properties: {
314 | address: {
315 | type: "string",
316 | description: "待解析的地址(最多支持84个字节。可以输入两种样式的值,分别是:1、标准的结构化地址信息,如北京市海淀区上地十街十号【推荐,地址结构越完整,解析精度越高】2、支持“*路与*路交叉口”描述方式,如北一环路和阜阳路的交叉路口第二种方式并不总是有返回结果,只有当地址库中存在该地址描述时才有返回。)"
317 | }
318 | },
319 | required: ["address"]
320 | }
321 | };
322 |
323 | const REVERSE_GEOCODE_TOOL: Tool = {
324 | name: "map_reverse_geocode",
325 | description: "全球逆地理编码",
326 | inputSchema: {
327 | type: "object",
328 | properties: {
329 | latitude: {
330 | type: "number",
331 | description: "Latitude coordinate"
332 | },
333 | longitude: {
334 | type: "number",
335 | description: "Longitude coordinate"
336 | }
337 | },
338 | required: ["latitude", "longitude"]
339 | }
340 | };
341 |
342 | const SEARCH_PLACES_TOOL: Tool = {
343 | name: "map_search_places",
344 | description: "地点检索服务(包括城市检索、圆形区域检索、多边形区域检索)",
345 | inputSchema: {
346 | type: "object",
347 | properties: {
348 | query: {
349 | type: "string",
350 | description: "检索关键字"
351 | },
352 | region: {
353 | type: "string",
354 | description: "检索行政区划区域"
355 | },
356 | bounds: {
357 | type: "string",
358 | description: "检索多边形区域"
359 | },
360 | location: {
361 | type: "string",
362 | description: "圆形区域检索中心点,不支持多个点"
363 | },
364 | },
365 | required: ["query"],
366 | }
367 | };
368 |
369 |
370 | const PLACE_DETAILS_TOOL: Tool = {
371 | name: "map_place_details",
372 | description: "地点详情检索服务",
373 | inputSchema: {
374 | type: "object",
375 | properties: {
376 | uid: {
377 | type: "string",
378 | description: "poi的uid"
379 | },
380 | scope: {
381 | type: "string",
382 | description: "检索结果详细程度。取值为1 或空,则返回基本信息;取值为2,返回检索POI详细信息"
383 | }
384 | },
385 | required: ["uid"]
386 | }
387 | };
388 |
389 |
390 | const DISTANCE_MATRIX_TOOL: Tool = {
391 | name: "map_distance_matrix",
392 | description: "计算多个出发地和目的地的距离和路线用时",
393 | inputSchema: {
394 | type: "object",
395 | properties: {
396 | origins: {
397 | type: "array",
398 | items: { type: "string" },
399 | description: "起点的纬度,经度。"
400 | },
401 | destinations: {
402 | type: "array",
403 | items: { type: "string" },
404 | description: "终点的纬度,经度。"
405 | },
406 | mode: {
407 | type: "string",
408 | description: "路线类型,可选值:driving(驾车)、walking(步行)、riding(骑行)、motorcycle(摩托车)",
409 | enum: ["driving", "walking", "riding", "motorcycle"]
410 | }
411 | },
412 | required: ["origins", "destinations"]
413 | }
414 | };
415 |
416 | const DIRECTIONS_TOOL: Tool = {
417 | name: "map_directions",
418 | description: "路线规划服务, 计算出发地到目的地的距离、路线用时、路线方案",
419 | inputSchema: {
420 | type: "object",
421 | properties: {
422 | origin: {
423 | type: "string",
424 | description: "起点经纬度,格式为:纬度,经度;小数点后不超过6位,40.056878,116.30815"
425 | },
426 | destination: {
427 | type: "string",
428 | description: "终点经纬度,格式为:纬度,经度;小数点后不超过6位,40.056878,116.30815"
429 | },
430 | mode: {
431 | type: "string",
432 | description: "路线规划类型,可选值:driving(驾车)、walking(步行)、riding(骑行)、transit(公交)",
433 | enum: ["driving", "walking", "riding", "transit"]
434 | }
435 | },
436 | required: ["origin", "destination"]
437 | }
438 | };
439 |
440 | const WEATHER_TOOL: Tool = {
441 | name: "map_weather",
442 | description: '通过行政区划代码或者经纬度坐标获取实时天气信息和未来5天天气预报',
443 | inputSchema: {
444 | type: "object",
445 | properties: {
446 | districtId: {
447 | type: "string",
448 | description: "行政区划代码(适用于区、县级别)"
449 | },
450 | location: {
451 | type: "string",
452 | description: "经纬度,经度在前纬度在后,逗号分隔,格式如116.404,39.915"
453 | }
454 | },
455 | }
456 | }
457 |
458 | const IP_LOCATION_TOOL: Tool = {
459 | name: "map_ip_location",
460 | description: "通过IP地址获取位置信息",
461 | inputSchema: {
462 | type: "object",
463 | properties: {
464 | ip: {
465 | type: "string",
466 | description: "IP地址",
467 | }
468 | },
469 | required: ["ip"],
470 | }
471 | }
472 |
473 | const ROAD_TRAFFIC_TOOL: Tool = {
474 | name: "map_road_traffic",
475 | description: "根据城市和道路名称查询具体道路的实时拥堵评价和拥堵路段、拥堵距离、拥堵趋势等信息",
476 | inputSchema: {
477 | type: "object",
478 | properties: {
479 | roadName: {
480 | type: "string",
481 | description: "道路名称",
482 | },
483 | city: {
484 | type: "string",
485 | description: "城市名称"
486 | },
487 | bounds: {
488 | type: "string",
489 | description: "矩形区域,左下角和右上角的经纬度坐标点,坐标对间使用;号分隔,格式为:纬度,经度;纬度,经度,如39.912078,116.464303;39.918276,116.475442"
490 | },
491 | vertexes: {
492 | type: "string",
493 | description: "多边形边界点,经纬度顺序为:纬度,经度; 顶点顺序需按逆时针排列, 格式如vertexes=39.910528,116.472926;39.918276,116.475442;39.916671,116.459056;39.912078,116.464303"
494 | },
495 | center: {
496 | type: "string",
497 | description: "中心点坐标,如39.912078,116.464303"
498 | },
499 | radius: {
500 | type: "number",
501 | description: "查询半径,单位:米"
502 | }
503 | },
504 | }
505 | }
506 |
507 | const MARK_SUBMIT_POI_TOOL: Tool = {
508 | name: "map_poi_extract",
509 | description: "POI智能标注",
510 | inputSchema: {
511 | type: "object",
512 | properties: {
513 | textContent: {
514 | type: "string",
515 | description: "描述POI的文本内容"
516 | }
517 | },
518 | required: ["textContent"],
519 | }
520 | }
521 |
522 | const MAPS_TOOLS = [
523 | GEOCODE_TOOL,
524 | REVERSE_GEOCODE_TOOL,
525 | SEARCH_PLACES_TOOL,
526 | PLACE_DETAILS_TOOL,
527 | DISTANCE_MATRIX_TOOL,
528 | DIRECTIONS_TOOL,
529 | WEATHER_TOOL,
530 | IP_LOCATION_TOOL,
531 | ROAD_TRAFFIC_TOOL,
532 | MARK_SUBMIT_POI_TOOL
533 | ] as const;
534 |
535 | // API handlers
536 | async function handleGeocode(address: string) {
537 | const url = new URL("https://api.map.baidu.com/geocoding/v3/");
538 | url.searchParams.append("address", address);
539 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
540 | url.searchParams.append("output", "json");
541 | url.searchParams.append("from", "node_mcp");
542 |
543 | const response = await fetch(url.toString());
544 | const data = await response.json() as GeocodeResponse;
545 | if (data.status !== 0) {
546 | return {
547 | content: [{
548 | type: "text",
549 | text: `Geocoding failed: ${data.message || data.status}`
550 | }],
551 | isError: true
552 | };
553 | }
554 |
555 | return {
556 | content: [{
557 | type: "text",
558 | text: JSON.stringify({
559 | location: data.result.location,
560 | precise: data.result.precise,
561 | confidence: data.result.confidence,
562 | comprehension: data.result.comprehension,
563 | level: data.result.level
564 | }, null, 2)
565 | }],
566 | isError: false
567 | };
568 | }
569 |
570 | async function handleReverseGeocode(latitude: number, longitude: number) {
571 | const url = new URL("https://api.map.baidu.com/reverse_geocoding/v3/");
572 | url.searchParams.append("location", `${latitude},${longitude}`);
573 | url.searchParams.append("extensions_poi", "1");
574 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
575 | url.searchParams.append("output", "json");
576 | url.searchParams.append("from", "node_mcp");
577 |
578 | const response = await fetch(url.toString());
579 | const data = await response.json() as ReverseGeocodeResponse;
580 |
581 | if (data.status !== 0) {
582 | return {
583 | content: [{
584 | type: "text",
585 | text: `Reverse geocoding failed: ${data.message || data.status}`
586 | }],
587 | isError: true
588 | };
589 | }
590 |
591 | return {
592 | content: [{
593 | type: "text",
594 | text: JSON.stringify({
595 | place_id: data.result.pois[0] ? data.result.pois[0].uid : null,
596 | location: data.result.location,
597 | formatted_address: data.result.formatted_address,
598 | formatted_address_poi: data.result.formatted_address_poi,
599 | business: data.result.business,
600 | business_info: data.result.business_info,
601 | addressComponent: data.result.addressComponent,
602 | edz: data.result.edz,
603 | pois: data.result.pois,
604 | roads: data.result.roads,
605 | poiRegions: data.result.poiRegions,
606 | sematic_description: data.result.sematic_description,
607 | cityCode: data.result.cityCode
608 | }, null, 2)
609 | }],
610 | isError: false
611 | };
612 | }
613 |
614 | async function handlePlaceSearch(
615 | query: string,
616 | region?: string,
617 | bounds?: string,
618 | location?: string
619 | ) {
620 | const url = new URL("https://api.map.baidu.com/place/v2/search");
621 | url.searchParams.append("query", query);
622 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
623 | url.searchParams.append("output", "json");
624 | url.searchParams.append("from", "node_mcp");
625 | if (region) {
626 | url.searchParams.append("region", region);
627 | }
628 | if (bounds) {
629 | url.searchParams.append("bounds", bounds);
630 | }
631 | if (location) {
632 | url.searchParams.append("location", location);
633 | }
634 |
635 | const response = await fetch(url.toString());
636 | const data = await response.json() as PlacesSearchResponse;
637 | if (data.status !== 0) {
638 | return {
639 | content: [{
640 | type: "text",
641 | text: `Place search failed: ${data.message || data.status}`
642 | }],
643 | isError: true
644 | };
645 | }
646 |
647 | // 处理不同参数返回的数据结构
648 | const places = data.results || data.result || [];
649 |
650 | return {
651 | content: [{
652 | type: "text",
653 | text: JSON.stringify({
654 | result_type: data.result_type,
655 | query_type: data.query_type,
656 | results: places.map((place) => ({
657 | name: place.name,
658 | location: place.location,
659 | address: place.address,
660 | province: place.province,
661 | city: place.city,
662 | area: place.area,
663 | street_id: place.street_id,
664 | telephone: place.telephone,
665 | detail: place.detail,
666 | uid: place.uid
667 | }))
668 | }, null, 2)
669 | }],
670 | isError: false
671 | };
672 | }
673 |
674 | async function handlePlaceDetails(uid: string, scope?: string) {
675 | const url = new URL("https://api.map.baidu.com/place/v2/detail");
676 | url.searchParams.append("uid", uid);
677 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
678 | url.searchParams.append("output", "json");
679 | url.searchParams.append("from", "node_mcp");
680 | if (scope) {
681 | url.searchParams.append("scope", scope);
682 | }
683 |
684 | const response = await fetch(url.toString());
685 | const data = await response.json() as PlaceDetailsResponse;
686 |
687 | if (data.status !== 0) {
688 | return {
689 | content: [{
690 | type: "text",
691 | text: `Place details request failed: ${data.message || data.status}`
692 | }],
693 | isError: true
694 | };
695 | }
696 |
697 | return {
698 | content: [{
699 | type: "text",
700 | text: JSON.stringify({
701 | uid: data.result.uid,
702 | name: data.result.name,
703 | location: data.result.location,
704 | address: data.result.address,
705 | province: data.result.province,
706 | city: data.result.city,
707 | area: data.result.area,
708 | street_id: data.result.street_id,
709 | detail: data.result.detail,
710 | ...(('detail_info' in data.result) ? {
711 | detail_info: data.result.detail_info
712 | } : {})
713 | }, null, 2)
714 | }],
715 | isError: false
716 | };
717 | }
718 |
719 | async function handleDistanceMatrix(
720 | origins: string[],
721 | destinations: string[],
722 | mode: "driving" | "walking" | "riding" | "motorcycle" = "driving"
723 | ) {
724 | const url = new URL("https://api.map.baidu.com/routematrix/v2/" + mode);
725 | url.searchParams.append("origins", origins.join("|"));
726 | url.searchParams.append("destinations", destinations.join("|"));
727 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
728 | url.searchParams.append("output", "json");
729 | url.searchParams.append("from", "node_mcp");
730 |
731 | const response = await fetch(url.toString());
732 | const data = await response.json() as DistanceMatrixResponse;
733 |
734 | if (data.status !== 0) {
735 | return {
736 | content: [{
737 | type: "text",
738 | text: `Distance matrix request failed: ${data.msg || data.status}`
739 | }],
740 | isError: true
741 | };
742 | }
743 |
744 | return {
745 | content: [{
746 | type: "text",
747 | text: JSON.stringify({
748 | results: data.result.map((row) => ({
749 | elements: {
750 | duration: row.duration,
751 | distance: row.distance
752 | }
753 | }))
754 | }, null, 2)
755 | }],
756 | isError: false
757 | };
758 | }
759 |
760 | async function handleDirections(
761 | origin: string,
762 | destination: string,
763 | mode: "driving" | "walking" | "riding" | "transit" = "driving"
764 | ) {
765 | const url = new URL("https://api.map.baidu.com/directionlite/v1/" + mode);
766 | url.searchParams.append("origin", origin);
767 | url.searchParams.append("destination", destination);
768 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
769 | url.searchParams.append("from", "node_mcp");
770 |
771 | const response = await fetch(url.toString());
772 | const data = await response.json() as DirectionsResponse;
773 |
774 | if (data.status !== 0) {
775 | return {
776 | content: [{
777 | type: "text",
778 | text: `Directions request failed: ${data.msg || data.status}`
779 | }],
780 | isError: true
781 | };
782 | }
783 | return {
784 | content: [{
785 | type: "text",
786 | text: JSON.stringify({
787 | routes: data.result.routes.map((route) => ({
788 | distance: route.distance,
789 | duration: route.duration,
790 | steps: route.steps.map((step) => ({
791 | instructions: step.instruction,
792 | }))
793 | }))
794 | }, null, 2)
795 | }],
796 | isError: false
797 | };
798 | }
799 |
800 | async function handleWeather(
801 | districtId?: string,
802 | location?: string,
803 | ) {
804 | const url = new URL("https://api.map.baidu.com/weather/v1/");
805 | url.searchParams.append("data_type", "all");
806 | url.searchParams.append("coordtype", "bd09ll");
807 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
808 | url.searchParams.append("from", "node_mcp");
809 | if (location) {
810 | url.searchParams.append("location", location);
811 | }
812 | if (districtId) {
813 | url.searchParams.append("district_id", districtId);
814 | }
815 |
816 | const response = await fetch(url.toString());
817 | const data = await response.json() as WeatherResponse;
818 |
819 | if (data.status !== 0) {
820 | return {
821 | content: [{
822 | type: "text",
823 | text: `Weather searth failed: ${data.message || data.status}`
824 | }],
825 | isError: true
826 | }
827 | }
828 |
829 | return {
830 | content: [{
831 | type: "text",
832 | text: JSON.stringify({
833 | location: data.result.location,
834 | now: data.result.now,
835 | forecasts: data.result.forecasts,
836 | forecast_hours: data.result.forecast_hours,
837 | indexes: data.result.indexes,
838 | alerts: data.result.alerts,
839 | }, null, 2)
840 | }],
841 | isError: false
842 | }
843 | }
844 |
845 | async function handleIPLocation(
846 | ip: string,
847 | ) {
848 | const url = new URL("https://api.map.baidu.com/location/ip");
849 | url.searchParams.append("ip", ip);
850 | url.searchParams.append("coor", "bd09ll");
851 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
852 | url.searchParams.append("from", "node_mcp");
853 |
854 | const response = await fetch(url.toString());
855 | const data = await response.json() as IPLocationResponse;
856 |
857 | if (data.status !== 0) {
858 | return {
859 | content: [{
860 | type: "text",
861 | text: `IP address searth failed: ${data.message || data.status}`
862 | }],
863 | isError: true
864 | }
865 | }
866 |
867 | return {
868 | content: [{
869 | type: "text",
870 | text: JSON.stringify({
871 | formatted_address: data.address,
872 | address_detail: data.content.address_detail,
873 | point: data.content.point,
874 | }, null, 2)
875 | }],
876 | isError: false
877 | }
878 | }
879 |
880 | async function handleRoadTraffic(
881 | roadName?: string,
882 | city?: string,
883 | bounds?: string,
884 | vertexes?: string,
885 | center?: string,
886 | radius?: number,
887 | ) {
888 | const url = new URL("https://api.map.baidu.com");
889 | if (roadName && city) {
890 | url.pathname = "/traffic/v1/road";
891 | url.searchParams.append("road_name", roadName);
892 | url.searchParams.append("city", city);
893 | }
894 | if (bounds) {
895 | url.pathname = "/traffic/v1/bound";
896 | url.searchParams.append("bounds", bounds);
897 | }
898 | if (vertexes) {
899 | url.pathname = "/traffic/v1/polygon";
900 | url.searchParams.append("vertexes", vertexes);
901 | }
902 | if (center && radius) {
903 | url.pathname = "/traffic/v1/around";
904 | url.searchParams.append("center", center);
905 | url.searchParams.append("radius", String(radius));
906 | }
907 | url.searchParams.append("ak", BAIDU_MAP_API_KEY);
908 | url.searchParams.append("from", "node_mcp");
909 |
910 | const response = await fetch(url.toString());
911 | const data = await response.json() as RoadTrafficResponse;
912 |
913 | if (data.status !== 0) {
914 | return {
915 | content: [{
916 | type: "text",
917 | text: `road traffic search failed: ${data.message || data.status}`,
918 | }],
919 | isError: true
920 | }
921 | }
922 |
923 | return {
924 | content: [{
925 | type: "text",
926 | text: JSON.stringify({
927 | description: data.description,
928 | evaluation: data.evaluation,
929 | road_traffic: data.road_traffic,
930 | }, null, 2)
931 | }],
932 | isError: false
933 | }
934 | }
935 |
936 | async function handlePoiExtract(
937 | textContent: string
938 | ) {
939 | const submitUrl = "https://api.map.baidu.com/api_mark/v1/submit";
940 | const params = new URLSearchParams();
941 | params.append("text_content", textContent);
942 | params.append("id", "75274677"); // 设备id
943 | params.append("msg_type", "text");
944 | params.append("ak", BAIDU_MAP_API_KEY);
945 | params.append("from", "node_mcp");
946 |
947 | const submitResponse = await fetch(submitUrl, {
948 | method: "POST",
949 | headers: {
950 | "Content-Type": "application/x-www-form-urlencoded"
951 | },
952 | body: params.toString(),
953 | });
954 | const submitData = await submitResponse.json() as MarkSubmitResponse;
955 |
956 | if (submitData.status !== 0) {
957 | return {
958 | content: [{
959 | type: "text",
960 | text: `mark submit failed: ${submitData.message || submitData.status}`
961 | }],
962 | isError: true
963 | }
964 | }
965 |
966 | const url = "https://api.map.baidu.com/api_mark/v1/result";
967 | const mapId = submitData.result.map_id;
968 | params.delete("text_content");
969 | params.delete("msg_type");
970 | params.append("map_id", mapId);
971 |
972 | // 每1s轮询查找数据,等待时间最长20s
973 | const maxTime = 20 * 1000; // 20s
974 | const intervalTime = 1000; // 1s
975 | let elapsedTime = 0;
976 | async function checkResult() {
977 | const response = await fetch(url, {
978 | method: "POST",
979 | headers: {
980 | "Content-Type": "application/x-www-form-urlencoded"
981 | },
982 | body: params.toString(),
983 | });
984 | const data = await response.json() as MarkResultResponse;
985 | const result = data.result;
986 | if (result) {
987 | return data;
988 | }
989 | return null;
990 | }
991 |
992 | let data = await checkResult();
993 | while (!data && elapsedTime < maxTime) {
994 | await new Promise(resolve => setTimeout(resolve, intervalTime));
995 | elapsedTime += intervalTime;
996 | data = await checkResult();
997 | }
998 |
999 | if (!data) {
1000 | return {
1001 | content: [{
1002 | type: "text",
1003 | text: `poi result is null`
1004 | }],
1005 | isError: true
1006 | }
1007 | }
1008 | return {
1009 | content: [{
1010 | type: "text",
1011 | text: JSON.stringify({
1012 | jumpUrl: data.result.data[0].link.jump_url,
1013 | title: data.result.data[0].link.title,
1014 | desc: data.result.data[0].link.desc,
1015 | image: data.result.data[0].link.image,
1016 | poi: data.result.data[0].link.poi,
1017 | }, null, 2)
1018 | }],
1019 | isError: false
1020 | }
1021 |
1022 | }
1023 |
1024 | // Server setup
1025 | const server = new Server(
1026 | {
1027 | name: "mcp-server/baidu-map",
1028 | version: "1.0.0",
1029 | },
1030 | {
1031 | capabilities: {
1032 | tools: {},
1033 | },
1034 | },
1035 | );
1036 |
1037 | // Set up request handlers
1038 | server.setRequestHandler(ListToolsRequestSchema, async () => ({
1039 | tools: MAPS_TOOLS,
1040 | }));
1041 |
1042 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
1043 | try {
1044 | switch (request.params.name) {
1045 | case "map_geocode": {
1046 | const { address } = request.params.arguments as { address: string };
1047 | return await handleGeocode(address);
1048 | }
1049 |
1050 | case "map_reverse_geocode": {
1051 | const { latitude, longitude } = request.params.arguments as {
1052 | latitude: number;
1053 | longitude: number;
1054 | };
1055 | return await handleReverseGeocode(latitude, longitude);
1056 | }
1057 |
1058 | case "map_search_places": {
1059 | const { query, region, bounds, location } = request.params.arguments as {
1060 | query: string;
1061 | region?: string;
1062 | bounds?: string;
1063 | location?: string;
1064 | };
1065 | return await handlePlaceSearch(query, region, bounds, location);
1066 | }
1067 |
1068 | case "map_place_details": {
1069 | const { uid, scope } = request.params.arguments as {
1070 | uid: string;
1071 | scope?: string;
1072 | };
1073 | return await handlePlaceDetails(uid, scope);
1074 | }
1075 |
1076 | case "map_distance_matrix": {
1077 | const { origins, destinations, mode } = request.params.arguments as {
1078 | origins: string[];
1079 | destinations: string[];
1080 | mode?: "driving" | "walking" | "riding" | "motorcycle";
1081 | };
1082 | return await handleDistanceMatrix(origins, destinations, mode);
1083 | }
1084 |
1085 | case "map_directions": {
1086 | const { origin, destination, mode } = request.params.arguments as {
1087 | origin: string;
1088 | destination: string;
1089 | mode?: "driving" | "walking" | "riding" | "transit";
1090 | };
1091 | return await handleDirections(origin, destination, mode);
1092 | }
1093 | case "map_weather": {
1094 | const {districtId, location} = request.params.arguments as {
1095 | districtId: string;
1096 | location: string,
1097 | };
1098 | return await handleWeather(districtId, location);
1099 | }
1100 | case "map_ip_location": {
1101 | const {ip} = request.params.arguments as {
1102 | ip: string;
1103 | };
1104 | return await handleIPLocation(ip);
1105 | }
1106 | case "map_road_traffic": {
1107 | const {roadName, city, bounds, vertexes, center, radius} = request.params.arguments as {
1108 | roadName?: string;
1109 | city?: string;
1110 | bounds?: string;
1111 | vertexes?: string;
1112 | center?: string;
1113 | radius?: number;
1114 | };
1115 | return await handleRoadTraffic(roadName, city, bounds, vertexes, center, radius);
1116 | }
1117 | case "map_poi_extract": {
1118 | const {textContent} = request.params.arguments as {
1119 | textContent: string;
1120 | };
1121 | return await handlePoiExtract(textContent);
1122 | }
1123 |
1124 | default:
1125 | return {
1126 | content: [{
1127 | type: "text",
1128 | text: `Unknown tool: ${request.params.name}`
1129 | }],
1130 | isError: true
1131 | };
1132 | }
1133 | } catch (error) {
1134 | return {
1135 | content: [{
1136 | type: "text",
1137 | text: `Error: ${error instanceof Error ? error.message : String(error)}`
1138 | }],
1139 | isError: true
1140 | };
1141 | }
1142 | });
1143 |
1144 | async function runServer() {
1145 | const transport = new StdioServerTransport();
1146 | await server.connect(transport);
1147 | console.error("Baidu Map MCP Server running on stdio");
1148 | }
1149 |
1150 | runServer().catch((error) => {
1151 | console.error("Fatal error running server:", error);
1152 | process.exit(1);
1153 | });
1154 |
```