@cablate/mcp-google-map 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +353 -113
- package/dist/chunk-U2CGP7BJ.js +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.js +3 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1 -197
- package/package.json +73 -47
- package/LICENSE +0 -21
- package/dist/index.cjs +0 -24181
- package/dist/maps-tools/mapsTools.js +0 -167
- package/dist/maps-tools/searchPlaces.js +0 -144
- package/dist/maps-tools/toolclass.js +0 -248
package/README.md
CHANGED
|
@@ -1,113 +1,353 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
|
|
19
|
-
- **
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
3.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
-
|
|
91
|
-
- Google Maps
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
113
|
-
-
|
|
1
|
+
[](https://mseep.ai/app/cablate-mcp-google-map)
|
|
2
|
+
|
|
3
|
+
<a href="https://glama.ai/mcp/servers/@cablate/mcp-google-map">
|
|
4
|
+
<img width="380" height="200" src="https://glama.ai/mcp/servers/@cablate/mcp-google-map/badge" alt="Google Map Server MCP server" />
|
|
5
|
+
</a>
|
|
6
|
+
|
|
7
|
+
# MCP Google Map Server
|
|
8
|
+
|
|
9
|
+
A powerful Model Context Protocol (MCP) server providing comprehensive Google Maps API integration with streamable HTTP transport support and LLM processing capabilities.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
### 🗺️ Google Maps Integration
|
|
14
|
+
|
|
15
|
+
- **Location Search**
|
|
16
|
+
- Search for places near a specific location with customizable radius and filters
|
|
17
|
+
- Get detailed place information including ratings, opening hours, and contact details
|
|
18
|
+
|
|
19
|
+
- **Geocoding Services**
|
|
20
|
+
- Convert addresses to coordinates (geocoding)
|
|
21
|
+
- Convert coordinates to addresses (reverse geocoding)
|
|
22
|
+
|
|
23
|
+
- **Distance & Directions**
|
|
24
|
+
- Calculate distances and travel times between multiple origins and destinations
|
|
25
|
+
- Get detailed turn-by-turn directions between two points
|
|
26
|
+
- Support for different travel modes (driving, walking, bicycling, transit)
|
|
27
|
+
|
|
28
|
+
- **Elevation Data**
|
|
29
|
+
- Retrieve elevation data for specific locations
|
|
30
|
+
|
|
31
|
+
### 🚀 Advanced Features
|
|
32
|
+
|
|
33
|
+
- **Streamable HTTP Transport**: Latest MCP protocol with real-time streaming capabilities
|
|
34
|
+
- **Session Management**: Stateful sessions with UUID-based identification
|
|
35
|
+
- **Multiple Connection Support**: Handle multiple concurrent client connections
|
|
36
|
+
- **Echo Service**: Built-in testing tool for MCP server functionality
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
### Via NPM
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g @cablate/mcp-google-map
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Via NPX (Recommended)
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx @cablate/mcp-google-map
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
### 1. Get Google Maps API Key
|
|
55
|
+
|
|
56
|
+
1. Create a project in [Google Cloud Console](https://console.cloud.google.com/)
|
|
57
|
+
2. Enable the following APIs:
|
|
58
|
+
- Maps JavaScript API
|
|
59
|
+
- Places API
|
|
60
|
+
- Geocoding API
|
|
61
|
+
- Distance Matrix API
|
|
62
|
+
- Directions API
|
|
63
|
+
- Elevation API
|
|
64
|
+
3. Create an API key
|
|
65
|
+
4. Set up your environment:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
export GOOGLE_MAPS_API_KEY="your_api_key_here"
|
|
69
|
+
export MCP_SERVER_PORT=3000
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### 2. Run the Server
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Using NPX (recommended)
|
|
76
|
+
npx @cablate/mcp-google-map
|
|
77
|
+
|
|
78
|
+
# Or if installed globally
|
|
79
|
+
mcp-google-map
|
|
80
|
+
|
|
81
|
+
# With custom port and API key
|
|
82
|
+
mcp-google-map --port 3001 --apikey "your_api_key_here"
|
|
83
|
+
|
|
84
|
+
# Using short options
|
|
85
|
+
mcp-google-map -p 3001 -k "your_api_key_here"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### 3. Server Endpoints
|
|
89
|
+
|
|
90
|
+
- **Main MCP Endpoint**: `http://localhost:3000/mcp`
|
|
91
|
+
- **Available Tools**: 8 tools including Google Maps services and echo
|
|
92
|
+
|
|
93
|
+
## Configuration
|
|
94
|
+
|
|
95
|
+
### Command Line Options
|
|
96
|
+
|
|
97
|
+
The server supports the following command-line options:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Show help
|
|
101
|
+
mcp-google-map --help
|
|
102
|
+
|
|
103
|
+
# Basic usage
|
|
104
|
+
mcp-google-map --port 3000 --apikey "your_api_key"
|
|
105
|
+
|
|
106
|
+
# Using short options
|
|
107
|
+
mcp-google-map -p 3000 -k "your_api_key"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Available options:
|
|
111
|
+
- `--port, -p`: Port to run the MCP server on (default: 3000)
|
|
112
|
+
- `--apikey, -k`: Google Maps API key (required)
|
|
113
|
+
- `--help, -h`: Show help information
|
|
114
|
+
|
|
115
|
+
### Environment Variables
|
|
116
|
+
|
|
117
|
+
Alternatively, create a `.env` file in your working directory:
|
|
118
|
+
|
|
119
|
+
```env
|
|
120
|
+
# Required
|
|
121
|
+
GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
|
|
122
|
+
|
|
123
|
+
# Optional
|
|
124
|
+
MCP_SERVER_PORT=3000
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
**Note**: Command line options take precedence over environment variables.
|
|
128
|
+
|
|
129
|
+
## Available Tools
|
|
130
|
+
|
|
131
|
+
The server provides the following tools:
|
|
132
|
+
|
|
133
|
+
### Google Maps Tools
|
|
134
|
+
|
|
135
|
+
1. **search_nearby** - Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours
|
|
136
|
+
2. **get_place_details** - Get detailed information about a specific place including contact details, reviews, ratings, and operating hours
|
|
137
|
+
3. **maps_geocode** - Convert addresses or place names to geographic coordinates (latitude and longitude)
|
|
138
|
+
4. **maps_reverse_geocode** - Convert geographic coordinates to a human-readable address
|
|
139
|
+
5. **maps_distance_matrix** - Calculate travel distances and durations between multiple origins and destinations
|
|
140
|
+
6. **maps_directions** - Get detailed turn-by-turn navigation directions between two locations
|
|
141
|
+
7. **maps_elevation** - Get elevation data (height above sea level) for specific geographic locations
|
|
142
|
+
|
|
143
|
+
### Utility Tools
|
|
144
|
+
|
|
145
|
+
8. **echo** - Echo back input messages with optional streaming support for testing MCP server functionality
|
|
146
|
+
|
|
147
|
+
## Usage Examples
|
|
148
|
+
|
|
149
|
+
### Search Nearby Places
|
|
150
|
+
|
|
151
|
+
```json
|
|
152
|
+
{
|
|
153
|
+
"tool": "search_nearby",
|
|
154
|
+
"arguments": {
|
|
155
|
+
"center": {
|
|
156
|
+
"value": "Taipei 101",
|
|
157
|
+
"isCoordinates": false
|
|
158
|
+
},
|
|
159
|
+
"keyword": "restaurant",
|
|
160
|
+
"radius": 1000,
|
|
161
|
+
"openNow": true,
|
|
162
|
+
"minRating": 4.0
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Get Directions
|
|
168
|
+
|
|
169
|
+
```json
|
|
170
|
+
{
|
|
171
|
+
"tool": "maps_directions",
|
|
172
|
+
"arguments": {
|
|
173
|
+
"origin": "Taipei Main Station",
|
|
174
|
+
"destination": "Taipei 101",
|
|
175
|
+
"mode": "walking"
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Geocode Address
|
|
181
|
+
|
|
182
|
+
```json
|
|
183
|
+
{
|
|
184
|
+
"tool": "maps_geocode",
|
|
185
|
+
"arguments": {
|
|
186
|
+
"address": "Taipei 101, Taiwan"
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Integration with MCP Clients
|
|
192
|
+
|
|
193
|
+
### [Dive Desktop](https://github.com/OpenAgentPlatform/Dive)
|
|
194
|
+
|
|
195
|
+
1. Click "+ Add MCP Server" in Dive Desktop
|
|
196
|
+
2. Copy and paste the following configuration:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"mcpServers": {
|
|
201
|
+
"google-map": {
|
|
202
|
+
"command": "npx",
|
|
203
|
+
"args": ["-y", "@cablate/mcp-google-map"],
|
|
204
|
+
"env": {
|
|
205
|
+
"GOOGLE_MAPS_API_KEY": "your_api_key",
|
|
206
|
+
"MCP_SERVER_PORT": "3000"
|
|
207
|
+
},
|
|
208
|
+
"enabled": true
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
3. Click "Save" to complete the installation
|
|
215
|
+
|
|
216
|
+
### Claude Desktop
|
|
217
|
+
|
|
218
|
+
Add to your Claude Desktop configuration:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"mcpServers": {
|
|
223
|
+
"google-map": {
|
|
224
|
+
"command": "npx",
|
|
225
|
+
"args": ["@cablate/mcp-google-map"],
|
|
226
|
+
"env": {
|
|
227
|
+
"GOOGLE_MAPS_API_KEY": "your_api_key"
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Development
|
|
235
|
+
|
|
236
|
+
### Local Development
|
|
237
|
+
|
|
238
|
+
```bash
|
|
239
|
+
# Clone the repository
|
|
240
|
+
git clone https://github.com/cablate/mcp-google-map.git
|
|
241
|
+
cd mcp-google-map
|
|
242
|
+
|
|
243
|
+
# Install dependencies
|
|
244
|
+
npm install
|
|
245
|
+
|
|
246
|
+
# Set up environment variables
|
|
247
|
+
cp .env.example .env
|
|
248
|
+
# Edit .env with your API key
|
|
249
|
+
|
|
250
|
+
# Build the project
|
|
251
|
+
npm run build
|
|
252
|
+
|
|
253
|
+
# Start the server
|
|
254
|
+
npm start
|
|
255
|
+
|
|
256
|
+
# Or run in development mode
|
|
257
|
+
npm run dev
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Project Structure
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
src/
|
|
264
|
+
├── cli.ts # Main CLI entry point
|
|
265
|
+
├── config.ts # Server configuration
|
|
266
|
+
├── index.ts # Package exports
|
|
267
|
+
├── core/
|
|
268
|
+
│ └── BaseMcpServer.ts # Base MCP server with streamable HTTP
|
|
269
|
+
└── tools/
|
|
270
|
+
├── echo.ts # Echo service tool
|
|
271
|
+
└── maps/ # Google Maps tools
|
|
272
|
+
├── toolclass.ts # Google Maps API client
|
|
273
|
+
├── searchPlaces.ts # Maps service layer
|
|
274
|
+
├── searchNearby.ts # Search nearby places
|
|
275
|
+
├── placeDetails.ts # Place details
|
|
276
|
+
├── geocode.ts # Geocoding
|
|
277
|
+
├── reverseGeocode.ts # Reverse geocoding
|
|
278
|
+
├── distanceMatrix.ts # Distance matrix
|
|
279
|
+
├── directions.ts # Directions
|
|
280
|
+
└── elevation.ts # Elevation data
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
## Tech Stack
|
|
284
|
+
|
|
285
|
+
- **TypeScript** - Type-safe development
|
|
286
|
+
- **Node.js** - Runtime environment
|
|
287
|
+
- **Google Maps Services JS** - Google Maps API integration
|
|
288
|
+
- **Model Context Protocol SDK** - MCP protocol implementation
|
|
289
|
+
- **Express.js** - HTTP server framework
|
|
290
|
+
- **Zod** - Schema validation
|
|
291
|
+
|
|
292
|
+
## Security Considerations
|
|
293
|
+
|
|
294
|
+
- API keys are handled server-side for security
|
|
295
|
+
- DNS rebinding protection available for production
|
|
296
|
+
- Input validation using Zod schemas
|
|
297
|
+
- Error handling and logging
|
|
298
|
+
|
|
299
|
+
## Troubleshooting
|
|
300
|
+
|
|
301
|
+
### Common Issues
|
|
302
|
+
|
|
303
|
+
1. **API Key Issues**
|
|
304
|
+
```bash
|
|
305
|
+
# Check if API key is set
|
|
306
|
+
echo $GOOGLE_MAPS_API_KEY
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
2. **Port Conflicts**
|
|
310
|
+
```bash
|
|
311
|
+
# Change port in .env file
|
|
312
|
+
MCP_SERVER_PORT=3001
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
3. **API Quotas**
|
|
316
|
+
- Check your Google Cloud Console for API usage
|
|
317
|
+
- Ensure billing is enabled for your project
|
|
318
|
+
|
|
319
|
+
## License
|
|
320
|
+
|
|
321
|
+
MIT
|
|
322
|
+
|
|
323
|
+
## Contributing
|
|
324
|
+
|
|
325
|
+
Community participation and contributions are welcome! Here's how you can contribute:
|
|
326
|
+
|
|
327
|
+
- ⭐️ Star the project if you find it helpful
|
|
328
|
+
- 🐛 Submit Issues: Report bugs or provide suggestions
|
|
329
|
+
- 🔧 Create Pull Requests: Submit code improvements
|
|
330
|
+
- 📖 Documentation: Help improve documentation
|
|
331
|
+
|
|
332
|
+
## Contact
|
|
333
|
+
|
|
334
|
+
If you have any questions or suggestions, feel free to reach out:
|
|
335
|
+
|
|
336
|
+
- 📧 Email: [reahtuoo310109@gmail.com](mailto:reahtuoo310109@gmail.com)
|
|
337
|
+
- 💻 GitHub: [CabLate](https://github.com/cablate/)
|
|
338
|
+
- 🤝 Collaboration: Welcome to discuss project cooperation
|
|
339
|
+
- 📚 Technical Guidance: Sincere welcome for suggestions and guidance
|
|
340
|
+
|
|
341
|
+
## Changelog
|
|
342
|
+
|
|
343
|
+
### v0.0.5
|
|
344
|
+
- Added streamable HTTP transport support
|
|
345
|
+
- Improved CLI interface with emoji indicators
|
|
346
|
+
- Enhanced error handling and logging
|
|
347
|
+
- Added comprehensive tool descriptions for LLM integration
|
|
348
|
+
- Updated to latest MCP SDK version
|
|
349
|
+
|
|
350
|
+
### v0.0.4
|
|
351
|
+
- Initial release with basic Google Maps integration
|
|
352
|
+
- Support for location search, geocoding, and directions
|
|
353
|
+
- Compatible with MCP protocol
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var n=(r=>(r.DISPLAY="display",r.LLM="llm",r))(n||{}),o={log:(...l)=>{},error:(...l)=>{}};export{n as a,o as b};
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import{b as d}from"./chunk-U2CGP7BJ.js";import{config as Ie}from"dotenv";import{resolve as Re}from"path";import De from"yargs";import{hideBin as Ge}from"yargs/helpers";import{z as T}from"zod";var $="echo",k="Echo back the input message with optional streaming support for testing MCP server functionality",H={message:T.string().describe("The message to echo back"),stream:T.boolean().optional().describe("Whether to stream the response as chunks (default: false)")};async function L({message:o,stream:e=!1}){try{return e?{content:o.split(" ").map((s,n)=>({type:"text",text:n===0?s:` ${s}`}))}:{content:[{type:"text",text:`Echo: ${o}`}]}}catch(r){return{isError:!0,content:[{type:"text",text:`Echo error: ${r instanceof Error?r.message:JSON.stringify(r)}`}]}}}var f={NAME:$,DESCRIPTION:k,SCHEMA:H,ACTION:L};import{z as g}from"zod";import{Client as z,Language as K}from"@googlemaps/google-maps-services-js";import J from"dotenv";J.config();var N=class{constructor(){this.defaultLanguage=K.zh_TW;if(this.client=new z({}),!process.env.GOOGLE_MAPS_API_KEY)throw new Error("Google Maps API Key is required")}async searchNearbyPlaces(e){let r={location:e.location,radius:e.radius||1e3,keyword:e.keyword,opennow:e.openNow,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""};try{let s=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(s=s.filter(n=>(n.rating||0)>=(e.minRating||0))),s}catch(t){throw console.error("Error in searchNearbyPlaces:",t),new Error("\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u6642\u767C\u751F\u932F\u8AA4")}}async getPlaceDetails(e){try{return(await this.client.placeDetails({params:{place_id:e,fields:["name","rating","formatted_address","opening_hours","reviews","geometry","formatted_phone_number","website","price_level","photos"],language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data.result}catch(r){throw console.error("Error in getPlaceDetails:",r),new Error("\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u6642\u767C\u751F\u932F\u8AA4")}}async geocodeAddress(e){try{let r=await this.client.geocode({params:{address:e,key:process.env.GOOGLE_MAPS_API_KEY||"",language:this.defaultLanguage}});if(r.data.results.length===0)throw new Error("\u627E\u4E0D\u5230\u8A72\u5730\u5740\u7684\u4F4D\u7F6E");let t=r.data.results[0],s=t.geometry.location;return{lat:s.lat,lng:s.lng,formatted_address:t.formatted_address,place_id:t.place_id}}catch(r){throw console.error("Error in geocodeAddress:",r),new Error("\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4")}}parseCoordinates(e){let r=e.split(",").map(t=>parseFloat(t.trim()));if(r.length!==2||isNaN(r[0])||isNaN(r[1]))throw new Error("\u7121\u6548\u7684\u5EA7\u6A19\u683C\u5F0F\uFF0C\u8ACB\u4F7F\u7528\u300C\u7DEF\u5EA6,\u7D93\u5EA6\u300D\u683C\u5F0F");return{lat:r[0],lng:r[1]}}async getLocation(e){return e.isCoordinates?this.parseCoordinates(e.value):this.geocodeAddress(e.value)}async geocode(e){try{let r=await this.geocodeAddress(e);return{location:{lat:r.lat,lng:r.lng},formatted_address:r.formatted_address||"",place_id:r.place_id||""}}catch(r){throw console.error("Error in geocode:",r),new Error("\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4")}}async reverseGeocode(e,r){try{let t=await this.client.reverseGeocode({params:{latlng:{lat:e,lng:r},language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}});if(t.data.results.length===0)throw new Error("\u627E\u4E0D\u5230\u8A72\u5EA7\u6A19\u7684\u5730\u5740");let s=t.data.results[0];return{formatted_address:s.formatted_address,place_id:s.place_id,address_components:s.address_components}}catch(t){throw console.error("Error in reverseGeocode:",t),new Error("\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u6642\u767C\u751F\u932F\u8AA4")}}async calculateDistanceMatrix(e,r,t="driving"){try{let n=(await this.client.distancematrix({params:{origins:e,destinations:r,mode:t,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data;if(n.status!=="OK")throw new Error(`\u8DDD\u96E2\u77E9\u9663\u8A08\u7B97\u5931\u6557: ${n.status}`);let a=[],c=[];return n.rows.forEach(u=>{let i=[],p=[];u.elements.forEach(m=>{m.status==="OK"?(i.push({value:m.distance.value,text:m.distance.text}),p.push({value:m.duration.value,text:m.duration.text})):(i.push(null),p.push(null))}),a.push(i),c.push(p)}),{distances:a,durations:c,origin_addresses:n.origin_addresses,destination_addresses:n.destination_addresses}}catch(s){throw console.error("Error in calculateDistanceMatrix:",s),new Error("\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u6642\u767C\u751F\u932F\u8AA4")}}async getDirections(e,r,t="driving",s,n){try{let a;n&&(a=Math.floor(n.getTime()/1e3));let c;a||(s instanceof Date?c=Math.floor(s.getTime()/1e3):s?c=s:c="now");let i=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||"",arrival_time:a,departure_time:c}})).data;if(i.status!=="OK")throw new Error(`\u8DEF\u7DDA\u6307\u5F15\u7372\u53D6\u5931\u6557: ${i.status} (arrival_time: ${a}, departure_time: ${c})`);if(i.routes.length===0)throw new Error("\u627E\u4E0D\u5230\u8DEF\u7DDA");let p=i.routes[0],m=p.legs[0],O=y=>{if(!y||typeof y.value!="number")return"";let G=new Date(y.value*1e3),C={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return y.time_zone&&typeof y.time_zone=="string"&&(C.timeZone=y.time_zone),G.toLocaleString(this.defaultLanguage.toString(),C)};return{routes:i.routes,summary:p.summary,total_distance:{value:m.distance.value,text:m.distance.text},total_duration:{value:m.duration.value,text:m.duration.text},arrival_time:O(m.arrival_time),departure_time:O(m.departure_time)}}catch(a){throw console.error("Error in getDirections:",a),new Error("\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u6642\u767C\u751F\u932F\u8AA4"+a)}}async getElevation(e){try{let r=e.map(n=>({lat:n.latitude,lng:n.longitude})),s=(await this.client.elevation({params:{locations:r,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data;if(s.status!=="OK")throw new Error(`\u6D77\u62D4\u6578\u64DA\u7372\u53D6\u5931\u6557: ${s.status}`);return s.results.map((n,a)=>({elevation:n.elevation,location:r[a]}))}catch(r){throw console.error("Error in getElevation:",r),new Error("\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4")}}};var l=class{constructor(){this.mapsTools=new N}async searchNearby(e){try{let r=await this.mapsTools.getLocation(e.center);console.error(r);let t=await this.mapsTools.searchNearbyPlaces({location:r,keyword:e.keyword,radius:e.radius,openNow:e.openNow,minRating:e.minRating});return{location:r,success:!0,data:t.map(s=>({name:s.name,place_id:s.place_id,address:s.formatted_address,location:s.geometry.location,rating:s.rating,total_ratings:s.user_ratings_total,open_now:s.opening_hours?.open_now}))}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u641C\u5C0B\u6642\u767C\u751F\u932F\u8AA4"}}}async getPlaceDetails(e){try{let r=await this.mapsTools.getPlaceDetails(e);return{success:!0,data:{name:r.name,address:r.formatted_address,location:r.geometry?.location,rating:r.rating,total_ratings:r.user_ratings_total,open_now:r.opening_hours?.open_now,phone:r.formatted_phone_number,website:r.website,price_level:r.price_level,reviews:r.reviews?.map(t=>({rating:t.rating,text:t.text,time:t.time,author_name:t.author_name}))}}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u6642\u767C\u751F\u932F\u8AA4"}}}async geocode(e){try{return{success:!0,data:await this.mapsTools.geocode(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4"}}}async reverseGeocode(e,r){try{return{success:!0,data:await this.mapsTools.reverseGeocode(e,r)}}catch(t){return{success:!1,error:t instanceof Error?t.message:"\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u6642\u767C\u751F\u932F\u8AA4"}}}async calculateDistanceMatrix(e,r,t="driving"){try{return{success:!0,data:await this.mapsTools.calculateDistanceMatrix(e,r,t)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u6642\u767C\u751F\u932F\u8AA4"}}}async getDirections(e,r,t="driving",s,n){try{let a=s?new Date(s):new Date,c=n?new Date(n):void 0;return{success:!0,data:await this.mapsTools.getDirections(e,r,t,a,c)}}catch(a){return{success:!1,error:a instanceof Error?a.message:"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u6642\u767C\u751F\u932F\u8AA4"}}}async getElevation(e){try{return{success:!0,data:await this.mapsTools.getElevation(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4"}}}};var j="search_nearby",V="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",Y={center:g.object({value:g.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:g.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point"),keyword:g.string().optional().describe("Search keyword (e.g., restaurant, cafe, hotel)"),radius:g.number().default(1e3).describe("Search radius in meters"),openNow:g.boolean().default(!1).describe("Only show places that are currently open"),minRating:g.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")},Z=new l;async function q(o){try{let e=await Z.searchNearby(o);return e.success?{content:[{type:"text",text:`location: ${JSON.stringify(e.location,null,2)}
|
|
3
|
+
`+JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u641C\u5C0B\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var h={NAME:j,DESCRIPTION:V,SCHEMA:Y,ACTION:q};import{z as W}from"zod";var F="get_place_details",B="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",U={placeId:W.string().describe("Google Maps place ID")},Q=new l;async function X(o){try{let e=await Q.getPlaceDetails(o.placeId);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:F,DESCRIPTION:B,SCHEMA:U,ACTION:X};import{z as ee}from"zod";var re="maps_geocode",te="Convert addresses or place names to geographic coordinates (latitude and longitude)",se={address:ee.string().describe("Address or place name to convert to coordinates")},oe=new l;async function ne(o){try{let e=await oe.geocode(o.address);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var E={NAME:re,DESCRIPTION:te,SCHEMA:se,ACTION:ne};import{z as I}from"zod";var ae="maps_reverse_geocode",ie="Convert geographic coordinates (latitude and longitude) to a human-readable address",ce={latitude:I.number().describe("Latitude coordinate"),longitude:I.number().describe("Longitude coordinate")},le=new l;async function de(o){try{let e=await le.reverseGeocode(o.latitude,o.longitude);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var P={NAME:ae,DESCRIPTION:ie,SCHEMA:ce,ACTION:de};import{z as _}from"zod";var ue="maps_distance_matrix",me="Calculate travel distances and durations between multiple origins and destinations for different travel modes",pe={origins:_.array(_.string()).describe("List of origin addresses or coordinates"),destinations:_.array(_.string()).describe("List of destination addresses or coordinates"),mode:_.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")},ge=new l;async function ye(o){try{let e=await ge.calculateDistanceMatrix(o.origins,o.destinations,o.mode);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var b={NAME:ue,DESCRIPTION:me,SCHEMA:pe,ACTION:ye};import{z as S}from"zod";var fe="maps_directions",he="Get detailed turn-by-turn navigation directions between two locations with route information",ve={origin:S.string().describe("Starting point address or coordinates"),destination:S.string().describe("Destination address or coordinates"),mode:S.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:S.string().optional().describe("Departure time (ISO string format)"),arrival_time:S.string().optional().describe("Arrival time (ISO string format)")},Ee=new l;async function Pe(o){try{let e=await Ee.getDirections(o.origin,o.destination,o.mode,o.departure_time,o.arrival_time);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var w={NAME:fe,DESCRIPTION:he,SCHEMA:ve,ACTION:Pe};import{z as x}from"zod";var _e="maps_elevation",be="Get elevation data (height above sea level) for specific geographic locations",Se={locations:x.array(x.object({latitude:x.number().describe("Latitude coordinate"),longitude:x.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")},we=new l;async function Me(o){try{let e=await we.getElevation(o.locations);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var M={NAME:_e,DESCRIPTION:be,SCHEMA:Se,ACTION:Me};var Ne=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:f.NAME,description:f.DESCRIPTION,schema:f.SCHEMA,action:o=>f.ACTION(o)},{name:h.NAME,description:h.DESCRIPTION,schema:h.SCHEMA,action:o=>h.ACTION(o)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:o=>v.ACTION(o)},{name:E.NAME,description:E.DESCRIPTION,schema:E.SCHEMA,action:o=>E.ACTION(o)},{name:P.NAME,description:P.DESCRIPTION,schema:P.SCHEMA,action:o=>P.ACTION(o)},{name:b.NAME,description:b.DESCRIPTION,schema:b.SCHEMA,action:o=>b.ACTION(o)},{name:w.NAME,description:w.DESCRIPTION,schema:w.SCHEMA,action:o=>w.ACTION(o)},{name:M.NAME,description:M.DESCRIPTION,schema:M.SCHEMA,action:o=>M.ACTION(o)}]}],R=Ne;import{McpServer as xe}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as Ae}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as Oe}from"@modelcontextprotocol/sdk/types.js";import D from"express";import{randomUUID as Ce}from"node:crypto";var Te="0.0.1",A=class{constructor(e,r){this.transports={};this.httpServer=null;this.serverName=e,this.server=new xe({name:this.serverName,version:Te},{capabilities:{logging:{},tools:{}}}),this.registerTools(r)}registerTools(e){e.forEach(r=>{this.server.tool(r.name,r.description,r.schema,async t=>r.action(t))})}async connect(e){await this.server.connect(e),d.log=(...t)=>{console.error("[INFO]",...t)},d.error=(...t)=>{console.error("[ERROR]",...t)};let r=process.stdout.write.bind(process.stdout);process.stdout.write=(t,s,n)=>typeof t=="string"&&!t.startsWith("{")?!0:r(t,s,n),d.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let r=D();r.use(D.json()),r.post("/mcp",async(a,c)=>{let u=a.headers["mcp-session-id"],i;if(u&&this.transports[u])i=this.transports[u];else if(!u&&Oe(a.body))i=new Ae({sessionIdGenerator:()=>Ce(),onsessioninitialized:p=>{this.transports[p]=i,console.log(`[${this.serverName}] New session initialized: ${p}`)}}),i.onclose=()=>{i.sessionId&&(delete this.transports[i.sessionId],console.log(`[${this.serverName}] Session closed: ${i.sessionId}`))},await this.server.connect(i);else{c.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await i.handleRequest(a,c,a.body)});let t=async(a,c)=>{let u=a.headers["mcp-session-id"];if(!u||!this.transports[u]){c.status(400).send("Invalid or missing session ID");return}await this.transports[u].handleRequest(a,c)};r.get("/mcp",t),r.delete("/mcp",t);let s=console.log,n=console.error;d.log=s,d.error=n,this.httpServer=r.listen(e,()=>{d.log(`[${this.serverName}] HTTP server listening on port ${e}`),d.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){d.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,r)=>{this.httpServer.close(t=>{if(t){d.error(`[${this.serverName}] Error stopping HTTP server:`,t),r(t);return}d.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let s=Object.values(this.transports).map(n=>(n.sessionId&&delete this.transports[n.sessionId],Promise.resolve()));Promise.all(s).then(()=>{d.log(`[${this.serverName}] All transports closed.`),e()}).catch(n=>{d.error(`[${this.serverName}] Error during bulk transport closing:`,n),r(n)})})})}};Ie({path:Re(process.cwd(),".env")});async function $e(o,e){o&&(process.env.MCP_SERVER_PORT=o.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),console.log("\u{1F680} Starting Google Maps MCP Server..."),console.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),console.log("");let r=R.map(async t=>{let s=process.env[t.portEnvVar];if(!s){console.warn(`\u26A0\uFE0F [${t.name}] Port environment variable ${t.portEnvVar} not set.`),console.log(`\u{1F4A1} Please set ${t.portEnvVar} in your .env file or use --port parameter.`),console.log(` Example: ${t.portEnvVar}=3000 or --port 3000`);return}let n=Number(s);if(isNaN(n)||n<=0){console.error(`\u274C [${t.name}] Invalid port number "${s}" defined in ${t.portEnvVar}.`);return}try{let a=new A(t.name,t.tools);console.log(`\u{1F527} [${t.name}] Initializing MCP Server in HTTP mode on port ${n}...`),await a.startHttpServer(n),console.log(`\u2705 [${t.name}] MCP Server started successfully!`),console.log(` \u{1F310} Endpoint: http://localhost:${n}/mcp`),console.log(` \u{1F4DA} Tools: ${t.tools.length} available`)}catch(a){console.error(`\u274C [${t.name}] Failed to start MCP Server on port ${n}:`,a)}});await Promise.allSettled(r),console.log(""),console.log("\u{1F389} Server initialization completed!"),console.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}if(process.argv[1]&&(process.argv[1].endsWith("cli.ts")||process.argv[1].endsWith("cli.js"))){let o=De(Ge(process.argv)).option("port",{alias:"p",type:"number",description:"Port to run the MCP server on",default:process.env.MCP_SERVER_PORT?parseInt(process.env.MCP_SERVER_PORT):3e3}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).option("help",{alias:"h",type:"boolean",description:"Show help"}).example([["$0","Start server with default settings"],['$0 --port 3000 --apikey "your_api_key"',"Start server with custom port and API key"],['$0 -p 3001 -k "your_api_key"',"Start server with short options"]]).help().parseSync();console.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),console.log(" A Model Context Protocol server for Google Maps services"),console.log(""),o.apikey||(console.log("\u26A0\uFE0F Google Maps API Key not found!"),console.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),console.log(" Example: mcp-google-map --apikey your_api_key_here"),console.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),console.log("")),$e(o.port,o.apikey).catch(e=>{console.error("\u274C Failed to start server:",e),process.exit(1)})}export{$e as startServer};
|