@cablate/mcp-google-map 0.0.19 β†’ 0.0.20

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/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 CabLate
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2025 CabLate
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/README.md CHANGED
@@ -1,275 +1,296 @@
1
- [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cablate-mcp-google-map-badge.png)](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
- ## πŸ™Œ Special Thanks
12
-
13
- This project has received contributions from the community.
14
- Special thanks to [@junyinnnn](https://github.com/junyinnnn) for helping add support for `streamablehttp`.
15
-
16
- ## βœ… Testing Status
17
-
18
- **This MCP server has been tested and verified to work correctly with:**
19
-
20
- - Claude Desktop
21
- - Dive Desktop
22
- - MCP protocol implementations
23
-
24
- All tools and features are confirmed functional through real-world testing.
25
-
26
- ## Features
27
-
28
- ### πŸ†• Latest Updates
29
-
30
- - **New Places API Integration**: Updated to use Google's new Places API (New) instead of the legacy API to resolve HTTP 403 errors and ensure continued functionality.
31
-
32
- ### πŸ—ΊοΈ Google Maps Integration
33
-
34
- - **Location Search**
35
-
36
- - Search for places near a specific location with customizable radius and filters
37
- - Get detailed place information including ratings, opening hours, and contact details
38
-
39
- - **Geocoding Services**
40
-
41
- - Convert addresses to coordinates (geocoding)
42
- - Convert coordinates to addresses (reverse geocoding)
43
-
44
- - **Distance & Directions**
45
-
46
- - Calculate distances and travel times between multiple origins and destinations
47
- - Get detailed turn-by-turn directions between two points
48
- - Support for different travel modes (driving, walking, bicycling, transit)
49
-
50
- - **Elevation Data**
51
- - Retrieve elevation data for specific locations
52
-
53
- ### πŸš€ Advanced Features
54
-
55
- - **Streamable HTTP Transport**: Latest MCP protocol with real-time streaming capabilities
56
- - **Session Management**: Stateful sessions with UUID-based identification
57
- - **Multiple Connection Support**: Handle multiple concurrent client connections
58
- - **Echo Service**: Built-in testing tool for MCP server functionality
59
-
60
- ## Installation
61
-
62
- > ⚠️ **Important Notice**: This server uses HTTP transport, not stdio. Direct npx usage in MCP Server Settings is **NOT supported**.
63
-
64
- ### Method 1: Global Installation (Recommended)
65
-
66
- ```bash
67
- # Install globally
68
- npm install -g @cablate/mcp-google-map
69
-
70
- # Run the server
71
- mcp-google-map --port 3000 --apikey "your_api_key_here"
72
-
73
- # Using short options
74
- mcp-google-map -p 3000 -k "your_api_key_here"
75
- ```
76
-
77
- ### Method 2: Using npx (Quick Start)
78
-
79
- > ⚠️ **Warning**: Cannot be used directly in MCP Server Settings with stdio mode
80
-
81
- **Step 1: Launch HTTP Server in Terminal**
82
-
83
- ```bash
84
- # Run in a separate terminal
85
- npx @cablate/mcp-google-map --port 3000 --apikey "YOUR_API_KEY"
86
-
87
- # Or with environment variable
88
- GOOGLE_MAPS_API_KEY=YOUR_API_KEY npx @cablate/mcp-google-map
89
- ```
90
-
91
- **Step 2: Configure MCP Client to Use HTTP**
92
-
93
- ```json
94
- {
95
- "mcp-google-map": {
96
- "transport": "http",
97
- "url": "http://localhost:3000/mcp"
98
- }
99
- }
100
- ```
101
-
102
- ### ❌ Common Mistake to Avoid
103
-
104
- ```json
105
- // This WILL NOT WORK - stdio mode not supported with npx
106
- {
107
- "mcp-google-map": {
108
- "command": "npx",
109
- "args": ["@cablate/mcp-google-map"]
110
- }
111
- }
112
- ```
113
-
114
- ### Server Information
115
-
116
- - **Endpoint**: `http://localhost:3000/mcp`
117
- - **Transport**: HTTP (not stdio)
118
- - **Tools**: 8 Google Maps tools available
119
-
120
- ### API Key Configuration
121
-
122
- API keys can be provided in three ways (priority order):
123
-
124
- 1. **HTTP Headers** (Highest priority)
125
-
126
- ```json
127
- // MCP Client config
128
- {
129
- "mcp-google-map": {
130
- "transport": "streamableHttp",
131
- "url": "http://localhost:3000/mcp",
132
- // if your MCP Client support 'headers'
133
- "headers": {
134
- "X-Google-Maps-API-Key": "YOUR_API_KEY"
135
- }
136
- }
137
- }
138
- ```
139
-
140
- 2. **Command Line**
141
-
142
- ```bash
143
- mcp-google-map --apikey YOUR_API_KEY
144
- ```
145
-
146
- 3. **Environment Variable** (.env file or command line)
147
- ```env
148
- GOOGLE_MAPS_API_KEY=your_api_key_here
149
- MCP_SERVER_PORT=3000
150
- ```
151
-
152
- ## Available Tools
153
-
154
- The server provides the following tools:
155
-
156
- ### Google Maps Tools
157
-
158
- 1. **search_nearby** - Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours
159
- 2. **get_place_details** - Get detailed information about a specific place including contact details, reviews, ratings, and operating hours
160
- 3. **maps_geocode** - Convert addresses or place names to geographic coordinates (latitude and longitude)
161
- 4. **maps_reverse_geocode** - Convert geographic coordinates to a human-readable address
162
- 5. **maps_distance_matrix** - Calculate travel distances and durations between multiple origins and destinations
163
- 6. **maps_directions** - Get detailed turn-by-turn navigation directions between two locations
164
- 7. **maps_elevation** - Get elevation data (height above sea level) for specific geographic locations
165
-
166
- ## Development
167
-
168
- ### Local Development
169
-
170
- ```bash
171
- # Clone the repository
172
- git clone https://github.com/cablate/mcp-google-map.git
173
- cd mcp-google-map
174
-
175
- # Install dependencies
176
- npm install
177
-
178
- # Set up environment variables
179
- cp .env.example .env
180
- # Edit .env with your API key
181
-
182
- # Build the project
183
- npm run build
184
-
185
- # Start the server
186
- npm start
187
-
188
- # Or run in development mode
189
- npm run dev
190
- ```
191
-
192
- ### Project Structure
193
-
194
- ```
195
- src/
196
- β”œβ”€β”€ cli.ts # Main CLI entry point
197
- β”œβ”€β”€ config.ts # Server configuration
198
- β”œβ”€β”€ index.ts # Package exports
199
- β”œβ”€β”€ core/
200
- β”‚ └── BaseMcpServer.ts # Base MCP server with streamable HTTP
201
- └── tools/
202
- └── maps/ # Google Maps tools
203
- β”œβ”€β”€ toolclass.ts # Google Maps API client
204
- β”œβ”€β”€ searchPlaces.ts # Maps service layer
205
- β”œβ”€β”€ searchNearby.ts # Search nearby places
206
- β”œβ”€β”€ placeDetails.ts # Place details
207
- β”œβ”€β”€ geocode.ts # Geocoding
208
- β”œβ”€β”€ reverseGeocode.ts # Reverse geocoding
209
- β”œβ”€β”€ distanceMatrix.ts # Distance matrix
210
- β”œβ”€β”€ directions.ts # Directions
211
- └── elevation.ts # Elevation data
212
- ```
213
-
214
- ## Tech Stack
215
-
216
- - **TypeScript** - Type-safe development
217
- - **Node.js** - Runtime environment
218
- - **Google Maps Services JS** - Google Maps API integration
219
- - **Model Context Protocol SDK** - MCP protocol implementation
220
- - **Express.js** - HTTP server framework
221
- - **Zod** - Schema validation
222
-
223
- ## Security Considerations
224
-
225
- - API keys are handled server-side for security
226
- - DNS rebinding protection available for production
227
- - Input validation using Zod schemas
228
- - Error handling and logging
229
-
230
- ## License
231
-
232
- MIT
233
-
234
- ## Contributing
235
-
236
- Community participation and contributions are welcome! Here's how you can contribute:
237
-
238
- - ⭐️ Star the project if you find it helpful
239
- - πŸ› Submit Issues: Report bugs or provide suggestions
240
- - πŸ”§ Create Pull Requests: Submit code improvements
241
- - πŸ“– Documentation: Help improve documentation
242
-
243
- ## Contact
244
-
245
- If you have any questions or suggestions, feel free to reach out:
246
-
247
- - πŸ“§ Email: [reahtuoo310109@gmail.com](mailto:reahtuoo310109@gmail.com)
248
- - πŸ’» GitHub: [CabLate](https://github.com/cablate/)
249
- - 🀝 Collaboration: Welcome to discuss project cooperation
250
- - πŸ“š Technical Guidance: Sincere welcome for suggestions and guidance
251
-
252
- ## Changelog
253
-
254
- ### v0.0.18 (Latest)
255
-
256
- - **Error response improvements**: Now all error messages are in English with more detailed information (previously in Chinese)
257
-
258
- ### v0.0.17
259
-
260
- - **Added HTTP Header Authentication**: Support for passing API keys via `X-Google-Maps-API-Key` header in MCP Client config
261
- - **Fixed Concurrent User Issues**: Each session now uses its own API key without conflicts
262
- - **Fixed npx Execution**: Resolved module bundling issues
263
- - **Improved Documentation**: Clearer setup instructions
264
-
265
- ### v0.0.14
266
-
267
- - Added streamable HTTP transport support
268
- - Improved CLI interface with emoji indicators
269
- - Enhanced error handling and logging
270
- - Added comprehensive tool descriptions for LLM integration
271
- - Updated to latest MCP SDK version
272
-
273
- ## Star History
274
-
275
- [![Star History Chart](https://api.star-history.com/svg?repos=cablate/mcp-google-map&type=Date)](https://www.star-history.com/#cablate/mcp-google-map&Date)
1
+ [![MseeP.ai Security Assessment Badge](https://mseep.net/pr/cablate-mcp-google-map-badge.png)](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
+ ---
10
+
11
+ > **πŸ“’ Important Notice**
12
+ >
13
+ > Google officially announced MCP support for Google Maps on December 10, 2025, introducing **[Maps Grounding Lite](https://cloud.google.com/blog/products/ai-machine-learning/announcing-official-mcp-support-for-google-services)** - a fully-managed MCP server for geospatial data and routing.
14
+ >
15
+ > This community project remains actively maintained as an alternative with different features and deployment options.
16
+
17
+ ---
18
+
19
+ A powerful Model Context Protocol (MCP) server providing comprehensive Google Maps API integration with streamable HTTP transport support and LLM processing capabilities.
20
+
21
+ ## πŸ™Œ Special Thanks
22
+
23
+ This project has received contributions from the community.
24
+ Special thanks to [@junyinnnn](https://github.com/junyinnnn) for helping add support for `streamablehttp`.
25
+
26
+ ## βœ… Testing Status
27
+
28
+ **This MCP server has been tested and verified to work correctly with:**
29
+
30
+ - Claude Desktop
31
+ - Dive Desktop
32
+ - MCP protocol implementations
33
+
34
+ All tools and features are confirmed functional through real-world testing.
35
+
36
+ ## Features
37
+
38
+ ### πŸ†• Latest Updates
39
+
40
+ - ℹ️ **Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features.**
41
+
42
+
43
+ ### πŸ—ΊοΈ Google Maps Integration
44
+
45
+ - **Location Search**
46
+
47
+ - Search for places near a specific location with customizable radius and filters
48
+ - Get detailed place information including ratings, opening hours, and contact details
49
+
50
+ - **Geocoding Services**
51
+
52
+ - Convert addresses to coordinates (geocoding)
53
+ - Convert coordinates to addresses (reverse geocoding)
54
+
55
+ - **Distance & Directions**
56
+
57
+ - Calculate distances and travel times between multiple origins and destinations
58
+ - Get detailed turn-by-turn directions between two points
59
+ - Support for different travel modes (driving, walking, bicycling, transit)
60
+
61
+ - **Elevation Data**
62
+ - Retrieve elevation data for specific locations
63
+
64
+ ### πŸš€ Advanced Features
65
+
66
+ - **Streamable HTTP Transport**: Latest MCP protocol with real-time streaming capabilities
67
+ - **Session Management**: Stateful sessions with UUID-based identification
68
+ - **Multiple Connection Support**: Handle multiple concurrent client connections
69
+ - **Echo Service**: Built-in testing tool for MCP server functionality
70
+
71
+ ## Installation
72
+
73
+ > ⚠️ **Important Notice**: This server uses HTTP transport, not stdio. Direct npx usage in MCP Server Settings is **NOT supported**.
74
+
75
+ ### Method 1: Global Installation (Recommended)
76
+
77
+ ```bash
78
+ # Install globally
79
+ npm install -g @cablate/mcp-google-map
80
+
81
+ # Run the server
82
+ mcp-google-map --port 3000 --apikey "your_api_key_here"
83
+
84
+ # Using short options
85
+ mcp-google-map -p 3000 -k "your_api_key_here"
86
+ ```
87
+
88
+ ### Method 2: Using npx (Quick Start)
89
+
90
+ > ⚠️ **Warning**: Cannot be used directly in MCP Server Settings with stdio mode
91
+
92
+ **Step 1: Launch HTTP Server in Terminal**
93
+
94
+ ```bash
95
+ # Run in a separate terminal
96
+ npx @cablate/mcp-google-map --port 3000 --apikey "YOUR_API_KEY"
97
+
98
+ # Or with environment variable
99
+ GOOGLE_MAPS_API_KEY=YOUR_API_KEY npx @cablate/mcp-google-map
100
+ ```
101
+
102
+ **Step 2: Configure MCP Client to Use HTTP**
103
+
104
+ ```json
105
+ {
106
+ "mcp-google-map": {
107
+ "transport": "http",
108
+ "url": "http://localhost:3000/mcp"
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### ❌ Common Mistake to Avoid
114
+
115
+ ```json
116
+ // This WILL NOT WORK - stdio mode not supported with npx
117
+ {
118
+ "mcp-google-map": {
119
+ "command": "npx",
120
+ "args": ["@cablate/mcp-google-map"]
121
+ }
122
+ }
123
+ ```
124
+
125
+ ### Server Information
126
+
127
+ - **Endpoint**: `http://localhost:3000/mcp`
128
+ - **Transport**: HTTP (not stdio)
129
+ - **Tools**: 8 Google Maps tools available
130
+
131
+ ### API Key Configuration
132
+
133
+ API keys can be provided in three ways (priority order):
134
+
135
+ 1. **HTTP Headers** (Highest priority)
136
+
137
+ ```json
138
+ // MCP Client config
139
+ {
140
+ "mcp-google-map": {
141
+ "transport": "streamableHttp",
142
+ "url": "http://localhost:3000/mcp",
143
+ // if your MCP Client support 'headers'
144
+ "headers": {
145
+ "X-Google-Maps-API-Key": "YOUR_API_KEY"
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ 2. **Command Line**
152
+
153
+ ```bash
154
+ mcp-google-map --apikey YOUR_API_KEY
155
+ ```
156
+
157
+ 3. **Environment Variable** (.env file or command line)
158
+ ```env
159
+ GOOGLE_MAPS_API_KEY=your_api_key_here
160
+ MCP_SERVER_PORT=3000
161
+ ```
162
+
163
+ ## Available Tools
164
+
165
+ The server provides the following tools:
166
+
167
+ ### Google Maps Tools
168
+
169
+ 1. **search_nearby** - Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours
170
+ 2. **get_place_details** - Get detailed information about a specific place including contact details, reviews, ratings, and operating hours
171
+ 3. **maps_geocode** - Convert addresses or place names to geographic coordinates (latitude and longitude)
172
+ 4. **maps_reverse_geocode** - Convert geographic coordinates to a human-readable address
173
+ 5. **maps_distance_matrix** - Calculate travel distances and durations between multiple origins and destinations
174
+ 6. **maps_directions** - Get detailed turn-by-turn navigation directions between two locations
175
+ 7. **maps_elevation** - Get elevation data (height above sea level) for specific geographic locations
176
+
177
+ ## Development
178
+
179
+ ### Local Development
180
+
181
+ ```bash
182
+ # Clone the repository
183
+ git clone https://github.com/cablate/mcp-google-map.git
184
+ cd mcp-google-map
185
+
186
+ # Install dependencies
187
+ npm install
188
+
189
+ # Set up environment variables
190
+ cp .env.example .env
191
+ # Edit .env with your API key
192
+
193
+ # Build the project
194
+ npm run build
195
+
196
+ # Start the server
197
+ npm start
198
+
199
+ # Or run in development mode
200
+ npm run dev
201
+ ```
202
+
203
+ ### Project Structure
204
+
205
+ ```
206
+ src/
207
+ β”œβ”€β”€ cli.ts # Main CLI entry point
208
+ β”œβ”€β”€ config.ts # Server configuration
209
+ β”œβ”€β”€ index.ts # Package exports
210
+ β”œβ”€β”€ core/
211
+ β”‚ └── BaseMcpServer.ts # Base MCP server with streamable HTTP
212
+ └── tools/
213
+ └── maps/ # Google Maps tools
214
+ β”œβ”€β”€ toolclass.ts # Google Maps API client
215
+ β”œβ”€β”€ searchPlaces.ts # Maps service layer
216
+ β”œβ”€β”€ searchNearby.ts # Search nearby places
217
+ β”œβ”€β”€ placeDetails.ts # Place details
218
+ β”œβ”€β”€ geocode.ts # Geocoding
219
+ β”œβ”€β”€ reverseGeocode.ts # Reverse geocoding
220
+ β”œβ”€β”€ distanceMatrix.ts # Distance matrix
221
+ β”œβ”€β”€ directions.ts # Directions
222
+ └── elevation.ts # Elevation data
223
+ ```
224
+
225
+ ## Tech Stack
226
+
227
+ - **TypeScript** - Type-safe development
228
+ - **Node.js** - Runtime environment
229
+ - **Google Maps Services JS** - Google Maps API integration
230
+ - **Model Context Protocol SDK** - MCP protocol implementation
231
+ - **Express.js** - HTTP server framework
232
+ - **Zod** - Schema validation
233
+
234
+ ## Security Considerations
235
+
236
+ - API keys are handled server-side for security
237
+ - DNS rebinding protection available for production
238
+ - Input validation using Zod schemas
239
+ - Error handling and logging
240
+
241
+ ### Security Assessment Clarifications (2026-03)
242
+
243
+ For enterprise security reviews, see the standalone document:
244
+
245
+ - [Security Assessment Clarifications (23 items)](./SECURITY_ASSESSMENT.md)
246
+
247
+ ## License
248
+
249
+ MIT
250
+
251
+ ## Contributing
252
+
253
+ Community participation and contributions are welcome! Here's how you can contribute:
254
+
255
+ - ⭐️ Star the project if you find it helpful
256
+ - πŸ› Submit Issues: Report bugs or provide suggestions
257
+ - πŸ”§ Create Pull Requests: Submit code improvements
258
+ - πŸ“– Documentation: Help improve documentation
259
+
260
+ ## Contact
261
+
262
+ If you have any questions or suggestions, feel free to reach out:
263
+
264
+ - πŸ“§ Email: [reahtuoo310109@gmail.com](mailto:reahtuoo310109@gmail.com)
265
+ - πŸ’» GitHub: [CabLate](https://github.com/cablate/)
266
+ - 🀝 Collaboration: Welcome to discuss project cooperation
267
+ - πŸ“š Technical Guidance: Sincere welcome for suggestions and guidance
268
+
269
+ ## Changelog
270
+
271
+ ### v0.0.19 (Latest)
272
+
273
+ - **New Places API Integration**: Updated to use Google's new Places API (New) instead of the legacy API to resolve HTTP 403 errors and ensure continued functionality.
274
+
275
+ ### v0.0.18
276
+
277
+ - **Error response improvements**: Now all error messages are in English with more detailed information (previously in Chinese)
278
+
279
+ ### v0.0.17
280
+
281
+ - **Added HTTP Header Authentication**: Support for passing API keys via `X-Google-Maps-API-Key` header in MCP Client config
282
+ - **Fixed Concurrent User Issues**: Each session now uses its own API key without conflicts
283
+ - **Fixed npx Execution**: Resolved module bundling issues
284
+ - **Improved Documentation**: Clearer setup instructions
285
+
286
+ ### v0.0.14
287
+
288
+ - Added streamable HTTP transport support
289
+ - Improved CLI interface with emoji indicators
290
+ - Enhanced error handling and logging
291
+ - Added comprehensive tool descriptions for LLM integration
292
+ - Updated to latest MCP SDK version
293
+
294
+ ## Star History
295
+
296
+ [![Star History Chart](https://api.star-history.com/svg?repos=cablate/mcp-google-map&type=Date)](https://www.star-history.com/#cablate/mcp-google-map&Date)
@@ -1 +1 @@
1
- import{Client as x,Language as R}from"@googlemaps/google-maps-services-js";import A from"dotenv";A.config();function y(c){let e=c?.response?.data?.error_message,r=c?.response?.status;return e?`${e} (HTTP ${r})`:c instanceof Error?c.message:String(c)}var P=class{constructor(e){this.defaultLanguage=R.en;if(this.client=new x({}),this.apiKey=e||process.env.GOOGLE_MAPS_API_KEY||"",!this.apiKey)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:this.apiKey};try{let n=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(n=n.filter(a=>(a.rating||0)>=(e.minRating||0))),n}catch(t){throw d.error("Error in searchNearbyPlaces:",t),new Error(`Failed to search nearby places: ${y(t)}`)}}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:this.apiKey}})).data.result}catch(r){throw d.error("Error in getPlaceDetails:",r),new Error(`Failed to get place details for ${e}: ${y(r)}`)}}async geocodeAddress(e){try{let r=await this.client.geocode({params:{address:e,key:this.apiKey,language:this.defaultLanguage}});if(r.data.results.length===0)throw new Error(`No location found for address: "${e}"`);let t=r.data.results[0],n=t.geometry.location;return{lat:n.lat,lng:n.lng,formatted_address:t.formatted_address,place_id:t.place_id}}catch(r){throw d.error("Error in geocodeAddress:",r),new Error(`Failed to geocode address "${e}": ${y(r)}`)}}parseCoordinates(e){let r=e.split(",").map(t=>parseFloat(t.trim()));if(r.length!==2||isNaN(r[0])||isNaN(r[1]))throw new Error(`Invalid coordinate format: "${e}". Please use "latitude,longitude" format (e.g., "25.033,121.564"`);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 d.error("Error in geocode:",r),new Error(`Failed to geocode address "${e}": ${y(r)}`)}}async reverseGeocode(e,r){try{let t=await this.client.reverseGeocode({params:{latlng:{lat:e,lng:r},language:this.defaultLanguage,key:this.apiKey}});if(t.data.results.length===0)throw new Error(`No address found for coordinates: (${e}, ${r})`);let n=t.data.results[0];return{formatted_address:n.formatted_address,place_id:n.place_id,address_components:n.address_components}}catch(t){throw d.error("Error in reverseGeocode:",t),new Error(`Failed to reverse geocode coordinates (${e}, ${r}): ${y(t)}`)}}async calculateDistanceMatrix(e,r,t="driving"){try{let a=(await this.client.distancematrix({params:{origins:e,destinations:r,mode:t,language:this.defaultLanguage,key:this.apiKey}})).data;if(a.status!=="OK")throw new Error(`Distance matrix calculation failed with status: ${a.status}`);let o=[],l=[];return a.rows.forEach(h=>{let u=[],m=[];h.elements.forEach(i=>{i.status==="OK"?(u.push({value:i.distance.value,text:i.distance.text}),m.push({value:i.duration.value,text:i.duration.text})):(u.push(null),m.push(null))}),o.push(u),l.push(m)}),{distances:o,durations:l,origin_addresses:a.origin_addresses,destination_addresses:a.destination_addresses}}catch(n){throw d.error("Error in calculateDistanceMatrix:",n),new Error(`Failed to calculate distance matrix: ${y(n)}`)}}async getDirections(e,r,t="driving",n,a){try{let o;a&&(o=Math.floor(a.getTime()/1e3));let l;o||(n instanceof Date?l=Math.floor(n.getTime()/1e3):n?l=n:l="now");let u=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:this.apiKey,arrival_time:o,departure_time:l}})).data;if(u.status!=="OK")throw new Error(`Failed to get directions with status: ${u.status} (arrival_time: ${o}, departure_time: ${l}`);if(u.routes.length===0)throw new Error(`No route found from "${e}" to "${r}" with mode: ${t}`);let m=u.routes[0],i=m.legs[0],f=s=>{if(!s||typeof s.value!="number")return"";let g=new Date(s.value*1e3),p={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return s.time_zone&&typeof s.time_zone=="string"&&(p.timeZone=s.time_zone),g.toLocaleString(this.defaultLanguage.toString(),p)};return{routes:u.routes,summary:m.summary,total_distance:{value:i.distance.value,text:i.distance.text},total_duration:{value:i.duration.value,text:i.duration.text},arrival_time:f(i.arrival_time),departure_time:f(i.departure_time)}}catch(o){throw d.error("Error in getDirections:",o),new Error(`Failed to get directions from "${e}" to "${r}": ${y(o)}`)}}async getElevation(e){try{let r=e.map(a=>({lat:a.latitude,lng:a.longitude})),n=(await this.client.elevation({params:{locations:r,key:this.apiKey}})).data;if(n.status!=="OK")throw new Error(`Failed to get elevation data with status: ${n.status}`);return n.results.map((a,o)=>({elevation:a.elevation,location:r[o]}))}catch(r){throw d.error("Error in getElevation:",r),new Error(`Failed to get elevation data for ${e.length} location(s): ${y(r)}`)}}};import{PlacesClient as T}from"@googlemaps/places";var _=class{constructor(e){this.defaultLanguage="en";this.placeFieldMask=["displayName","name","id","formattedAddress","location","utcOffsetMinutes","regularOpeningHours.periods","regularOpeningHours.weekdayDescriptions","currentOpeningHours.openNow","nationalPhoneNumber","websiteUri","priceLevel","rating","userRatingCount","reviews.rating","reviews.text","reviews.publishTime","reviews.authorAttribution.displayName","photos.heightPx","photos.widthPx","photos.name"].join(",");if(this.client=new T({apiKey:e||process.env.GOOGLE_MAPS_API_KEY||""}),!e&&!process.env.GOOGLE_MAPS_API_KEY)throw new Error("Google Maps API Key is required")}async getPlaceDetails(e){try{let r=`places/${e}`,[t]=await this.client.getPlace({name:r,languageCode:this.defaultLanguage},{otherArgs:{headers:{"X-Goog-FieldMask":this.placeFieldMask}}});return this.transformPlaceResponse(t)}catch(r){throw d.error("Error in getPlaceDetails (New API):",r),new Error(`Failed to get place details for ${e}: ${this.extractErrorMessage(r)}`)}}transformPlaceResponse(e){return{name:e.displayName?.text||e.name||"",place_id:this.extractLegacyPlaceId(e),formatted_address:e.formattedAddress||"",geometry:{location:{lat:e.location?.latitude||0,lng:e.location?.longitude||0}},rating:e.rating||0,user_ratings_total:e.userRatingCount||0,opening_hours:e.regularOpeningHours?{open_now:this.isCurrentlyOpen(e.regularOpeningHours,e.utcOffsetMinutes,e.currentOpeningHours),weekday_text:this.formatOpeningHours(e.regularOpeningHours)}:void 0,formatted_phone_number:e.nationalPhoneNumber||"",website:e.websiteUri||"",price_level:e.priceLevel||0,reviews:e.reviews?.map(r=>({rating:r.rating||0,text:r.text?.text||"",time:r.publishTime?.seconds||0,author_name:r.authorAttribution?.displayName||""}))||[],photos:e.photos?.map(r=>({photo_reference:r.name||"",height:r.heightPx||0,width:r.widthPx||0}))||[]}}extractLegacyPlaceId(e){let r=e?.name;if(typeof r=="string"&&r.startsWith("places/")){let t=r.substring(7);if(t)return t}return e?.id||""}isCurrentlyOpen(e,r,t){if(typeof t?.openNow=="boolean")return t.openNow;if(typeof e?.openNow=="boolean")return e.openNow;let n=e?.periods;if(!Array.isArray(n)||n.length===0)return!1;let a=24*60,o=a*7,{day:l,minutes:h}=this.getLocalTimeComponents(r),u=l*a+h,m={SUNDAY:0,MONDAY:1,TUESDAY:2,WEDNESDAY:3,THURSDAY:4,FRIDAY:5,SATURDAY:6},i=s=>{if(typeof s=="number"&&s>=0&&s<=6)return s;if(typeof s=="string"){let g=s.toUpperCase();if(g in m)return m[g]}},f=s=>{if(!s)return;let g=typeof s.hours=="number"?s.hours:Number(s.hours??NaN),p=typeof s.minutes=="number"?s.minutes:Number(s.minutes??NaN);if(!(!Number.isFinite(g)||!Number.isFinite(p)))return g*60+p};for(let s of n){let g=i(s?.openDay),p=i(s?.closeDay??s?.openDay),D=f(s?.openTime),N=f(s?.closeTime);if(g===void 0||D===void 0)continue;let b=g*a+D,w;p===void 0||N===void 0?w=b+a:w=p*a+N,w<=b&&(w+=o);let v=u;for(;v<b;)v+=o;if(v>=b&&v<w)return!0}return!1}getLocalTimeComponents(e){let r=new Date;if(typeof e=="number"&&Number.isFinite(e)){let t=new Date(r.getTime()+e*6e4);return{day:t.getUTCDay(),minutes:t.getUTCHours()*60+t.getUTCMinutes()}}return{day:r.getDay(),minutes:r.getHours()*60+r.getMinutes()}}formatOpeningHours(e){return e?.weekdayDescriptions||[]}extractErrorMessage(e){let r=e?.message||e?.details||e?.status;return r?`${r}`:e instanceof Error?e.message:String(e)}};var E=class{constructor(e){this.mapsTools=new P(e),this.newPlacesService=new _(e)}async searchNearby(e){try{let r=await this.mapsTools.getLocation(e.center),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(n=>({name:n.name,place_id:n.place_id,address:n.formatted_address,location:n.geometry.location,rating:n.rating,total_ratings:n.user_ratings_total,open_now:n.opening_hours?.open_now}))}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred during search"}}}async getPlaceDetails(e){try{let r=await this.newPlacesService.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:"An error occurred while getting place details"}}}async geocode(e){try{return{success:!0,data:await this.mapsTools.geocode(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while geocoding address"}}}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:"An error occurred during reverse geocoding"}}}async calculateDistanceMatrix(e,r,t="driving"){try{return{success:!0,data:await this.mapsTools.calculateDistanceMatrix(e,r,t)}}catch(n){return{success:!1,error:n instanceof Error?n.message:"An error occurred while calculating distance matrix"}}}async getDirections(e,r,t="driving",n,a){try{let o=n?new Date(n):new Date,l=a?new Date(a):void 0;return{success:!0,data:await this.mapsTools.getDirections(e,r,t,o,l)}}catch(o){return{success:!1,error:o instanceof Error?o.message:"An error occurred while getting directions"}}}async getElevation(e){try{return{success:!0,data:await this.mapsTools.getElevation(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while getting elevation data"}}}};var d={log:(...c)=>{console.error("[INFO]",...c)},error:(...c)=>{console.error("[ERROR]",...c)}};export{_ as a,d as b,E as c};
1
+ import{Client as x,Language as R}from"@googlemaps/google-maps-services-js";import A from"dotenv";A.config();function y(c){let e=c?.response?.data?.error_message,r=c?.response?.status;return e?`${e} (HTTP ${r})`:c instanceof Error?c.message:String(c)}var P=class{constructor(e){this.defaultLanguage=R.en;if(this.client=new x({}),this.apiKey=e||process.env.GOOGLE_MAPS_API_KEY||"",!this.apiKey)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:this.apiKey};try{let n=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(n=n.filter(a=>(a.rating||0)>=(e.minRating||0))),n}catch(t){throw d.error("Error in searchNearbyPlaces:",t),new Error(`Failed to search nearby places: ${y(t)}`)}}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:this.apiKey}})).data.result}catch(r){throw d.error("Error in getPlaceDetails:",r),new Error(`Failed to get place details for ${e}: ${y(r)}`)}}async geocodeAddress(e){try{let r=await this.client.geocode({params:{address:e,key:this.apiKey,language:this.defaultLanguage}});if(r.data.results.length===0)throw new Error(`No location found for address: "${e}"`);let t=r.data.results[0],n=t.geometry.location;return{lat:n.lat,lng:n.lng,formatted_address:t.formatted_address,place_id:t.place_id}}catch(r){throw d.error("Error in geocodeAddress:",r),new Error(`Failed to geocode address "${e}": ${y(r)}`)}}parseCoordinates(e){let r=e.split(",").map(t=>parseFloat(t.trim()));if(r.length!==2||isNaN(r[0])||isNaN(r[1]))throw new Error(`Invalid coordinate format: "${e}". Please use "latitude,longitude" format (e.g., "25.033,121.564"`);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 d.error("Error in geocode:",r),new Error(`Failed to geocode address "${e}": ${y(r)}`)}}async reverseGeocode(e,r){try{let t=await this.client.reverseGeocode({params:{latlng:{lat:e,lng:r},language:this.defaultLanguage,key:this.apiKey}});if(t.data.results.length===0)throw new Error(`No address found for coordinates: (${e}, ${r})`);let n=t.data.results[0];return{formatted_address:n.formatted_address,place_id:n.place_id,address_components:n.address_components}}catch(t){throw d.error("Error in reverseGeocode:",t),new Error(`Failed to reverse geocode coordinates (${e}, ${r}): ${y(t)}`)}}async calculateDistanceMatrix(e,r,t="driving"){try{let a=(await this.client.distancematrix({params:{origins:e,destinations:r,mode:t,language:this.defaultLanguage,key:this.apiKey}})).data;if(a.status!=="OK")throw new Error(`Distance matrix calculation failed with status: ${a.status}`);let o=[],l=[];return a.rows.forEach(h=>{let u=[],m=[];h.elements.forEach(i=>{i.status==="OK"?(u.push({value:i.distance.value,text:i.distance.text}),m.push({value:i.duration.value,text:i.duration.text})):(u.push(null),m.push(null))}),o.push(u),l.push(m)}),{distances:o,durations:l,origin_addresses:a.origin_addresses,destination_addresses:a.destination_addresses}}catch(n){throw d.error("Error in calculateDistanceMatrix:",n),new Error(`Failed to calculate distance matrix: ${y(n)}`)}}async getDirections(e,r,t="driving",n,a){try{let o;a&&(o=Math.floor(a.getTime()/1e3));let l;o||(n instanceof Date?l=Math.floor(n.getTime()/1e3):n?l=n:l="now");let u=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:this.apiKey,arrival_time:o,departure_time:l}})).data;if(u.status!=="OK")throw new Error(`Failed to get directions with status: ${u.status} (arrival_time: ${o}, departure_time: ${l}`);if(u.routes.length===0)throw new Error(`No route found from "${e}" to "${r}" with mode: ${t}`);let m=u.routes[0],i=m.legs[0],f=s=>{if(!s||typeof s.value!="number")return"";let g=new Date(s.value*1e3),p={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return s.time_zone&&typeof s.time_zone=="string"&&(p.timeZone=s.time_zone),g.toLocaleString(this.defaultLanguage.toString(),p)};return{routes:u.routes,summary:m.summary,total_distance:{value:i.distance.value,text:i.distance.text},total_duration:{value:i.duration.value,text:i.duration.text},arrival_time:f(i.arrival_time),departure_time:f(i.departure_time)}}catch(o){throw d.error("Error in getDirections:",o),new Error(`Failed to get directions from "${e}" to "${r}": ${y(o)}`)}}async getElevation(e){try{let r=e.map(a=>({lat:a.latitude,lng:a.longitude})),n=(await this.client.elevation({params:{locations:r,key:this.apiKey}})).data;if(n.status!=="OK")throw new Error(`Failed to get elevation data with status: ${n.status}`);return n.results.map((a,o)=>({elevation:a.elevation,location:r[o]}))}catch(r){throw d.error("Error in getElevation:",r),new Error(`Failed to get elevation data for ${e.length} location(s): ${y(r)}`)}}};import{PlacesClient as T}from"@googlemaps/places";var _=class{constructor(e){this.defaultLanguage="en";this.placeFieldMask=["displayName","name","id","formattedAddress","location","utcOffsetMinutes","regularOpeningHours.periods","regularOpeningHours.weekdayDescriptions","currentOpeningHours.openNow","nationalPhoneNumber","websiteUri","priceLevel","rating","userRatingCount","reviews.rating","reviews.text","reviews.publishTime","reviews.authorAttribution.displayName","photos.heightPx","photos.widthPx","photos.name"].join(",");if(this.client=new T({apiKey:e||process.env.GOOGLE_MAPS_API_KEY||""}),!e&&!process.env.GOOGLE_MAPS_API_KEY)throw new Error("Google Maps API Key is required")}async getPlaceDetails(e){try{let r=`places/${e}`,[t]=await this.client.getPlace({name:r,languageCode:this.defaultLanguage},{otherArgs:{headers:{"X-Goog-FieldMask":this.placeFieldMask}}});return this.transformPlaceResponse(t)}catch(r){throw d.error("Error in getPlaceDetails (New API):",r),new Error(`Failed to get place details for ${e}: ${this.extractErrorMessage(r)}`)}}transformPlaceResponse(e){return{name:e.displayName?.text||e.name||"",place_id:this.extractLegacyPlaceId(e),formatted_address:e.formattedAddress||"",geometry:{location:{lat:e.location?.latitude||0,lng:e.location?.longitude||0}},rating:e.rating||0,user_ratings_total:e.userRatingCount||0,opening_hours:e.regularOpeningHours?{open_now:this.isCurrentlyOpen(e.regularOpeningHours,e.utcOffsetMinutes,e.currentOpeningHours),weekday_text:this.formatOpeningHours(e.regularOpeningHours)}:void 0,formatted_phone_number:e.nationalPhoneNumber||"",website:e.websiteUri||"",price_level:e.priceLevel||0,reviews:e.reviews?.map(r=>({rating:r.rating||0,text:r.text?.text||"",time:r.publishTime?.seconds||0,author_name:r.authorAttribution?.displayName||""}))||[],photos:e.photos?.map(r=>({photo_reference:r.name||"",height:r.heightPx||0,width:r.widthPx||0}))||[]}}extractLegacyPlaceId(e){let r=e?.name;if(typeof r=="string"&&r.startsWith("places/")){let t=r.substring(7);if(t)return t}return e?.id||""}isCurrentlyOpen(e,r,t){if(typeof t?.openNow=="boolean")return t.openNow;if(typeof e?.openNow=="boolean")return e.openNow;let n=e?.periods;if(!Array.isArray(n)||n.length===0)return!1;let a=1440,o=a*7,{day:l,minutes:h}=this.getLocalTimeComponents(r),u=l*a+h,m={SUNDAY:0,MONDAY:1,TUESDAY:2,WEDNESDAY:3,THURSDAY:4,FRIDAY:5,SATURDAY:6},i=s=>{if(typeof s=="number"&&s>=0&&s<=6)return s;if(typeof s=="string"){let g=s.toUpperCase();if(g in m)return m[g]}},f=s=>{if(!s)return;let g=typeof s.hours=="number"?s.hours:Number(s.hours??NaN),p=typeof s.minutes=="number"?s.minutes:Number(s.minutes??NaN);if(!(!Number.isFinite(g)||!Number.isFinite(p)))return g*60+p};for(let s of n){let g=i(s?.openDay),p=i(s?.closeDay??s?.openDay),D=f(s?.openTime),N=f(s?.closeTime);if(g===void 0||D===void 0)continue;let b=g*a+D,w;p===void 0||N===void 0?w=b+a:w=p*a+N,w<=b&&(w+=o);let v=u;for(;v<b;)v+=o;if(v>=b&&v<w)return!0}return!1}getLocalTimeComponents(e){let r=new Date;if(typeof e=="number"&&Number.isFinite(e)){let t=new Date(r.getTime()+e*6e4);return{day:t.getUTCDay(),minutes:t.getUTCHours()*60+t.getUTCMinutes()}}return{day:r.getDay(),minutes:r.getHours()*60+r.getMinutes()}}formatOpeningHours(e){return e?.weekdayDescriptions||[]}extractErrorMessage(e){let r=e?.message||e?.details||e?.status;return r?`${r}`:e instanceof Error?e.message:String(e)}};var E=class{constructor(e){this.mapsTools=new P(e),this.newPlacesService=new _(e)}async searchNearby(e){try{let r=await this.mapsTools.getLocation(e.center),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(n=>({name:n.name,place_id:n.place_id,address:n.formatted_address,location:n.geometry.location,rating:n.rating,total_ratings:n.user_ratings_total,open_now:n.opening_hours?.open_now}))}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred during search"}}}async getPlaceDetails(e){try{let r=await this.newPlacesService.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:"An error occurred while getting place details"}}}async geocode(e){try{return{success:!0,data:await this.mapsTools.geocode(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while geocoding address"}}}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:"An error occurred during reverse geocoding"}}}async calculateDistanceMatrix(e,r,t="driving"){try{return{success:!0,data:await this.mapsTools.calculateDistanceMatrix(e,r,t)}}catch(n){return{success:!1,error:n instanceof Error?n.message:"An error occurred while calculating distance matrix"}}}async getDirections(e,r,t="driving",n,a){try{let o=n?new Date(n):new Date,l=a?new Date(a):void 0;return{success:!0,data:await this.mapsTools.getDirections(e,r,t,o,l)}}catch(o){return{success:!1,error:o instanceof Error?o.message:"An error occurred while getting directions"}}}async getElevation(e){try{return{success:!0,data:await this.mapsTools.getElevation(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while getting elevation data"}}}};var d={log:(...c)=>{console.error("[INFO]",...c)},error:(...c)=>{console.error("[ERROR]",...c)}};export{_ as a,d as b,E as c};
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{b as s,c}from"./chunk-TH44WIYW.js";import{config as K}from"dotenv";import{resolve as N}from"path";import Se from"yargs";import{hideBin as Ee}from"yargs/helpers";import{z as m}from"zod";import{AsyncLocalStorage as D}from"node:async_hooks";var O=new D;function p(){return O.getStore()?.apiKey||process.env.GOOGLE_MAPS_API_KEY}function M(t,e){return O.run(t,e)}var G="search_nearby",H="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",z={center:m.object({value:m.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:m.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point"),keyword:m.string().optional().describe("Search keyword (e.g., restaurant, cafe, hotel)"),radius:m.number().default(1e3).describe("Search radius in meters"),openNow:m.boolean().default(!1).describe("Only show places that are currently open"),minRating:m.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")};async function k(t){try{let e=p(),r=await new c(e).searchNearby(t);return r.success?{content:[{type:"text",text:`location: ${JSON.stringify(r.location,null,2)}
3
- `+JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Search failed"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching nearby places: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var u={NAME:G,DESCRIPTION:H,SCHEMA:z,ACTION:k};import{z as J}from"zod";var L="get_place_details",j="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",V={placeId:J.string().describe("Google Maps place ID")};async function q(t){try{let e=p(),r=await new c(e).getPlaceDetails(t.placeId);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get place details"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting place details: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var y={NAME:L,DESCRIPTION:j,SCHEMA:V,ACTION:q};import{z as F}from"zod";var W="maps_geocode",Y="Convert addresses or place names to geographic coordinates (latitude and longitude)",Z={address:F.string().describe("Address or place name to convert to coordinates")};async function B(t){try{let e=p(),r=await new c(e).geocode(t.address);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to geocode address"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error geocoding address: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var f={NAME:W,DESCRIPTION:Y,SCHEMA:Z,ACTION:B};import{z as T}from"zod";var U="maps_reverse_geocode",Q="Convert geographic coordinates (latitude and longitude) to a human-readable address",X={latitude:T.number().describe("Latitude coordinate"),longitude:T.number().describe("Longitude coordinate")};async function ee(t){try{let e=p(),r=await new c(e).reverseGeocode(t.latitude,t.longitude);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to reverse geocode coordinates"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error reverse geocoding: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:U,DESCRIPTION:Q,SCHEMA:X,ACTION:ee};import{z as h}from"zod";var re="maps_distance_matrix",te="Calculate travel distances and durations between multiple origins and destinations for different travel modes",oe={origins:h.array(h.string()).describe("List of origin addresses or coordinates"),destinations:h.array(h.string()).describe("List of destination addresses or coordinates"),mode:h.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")};async function se(t){try{let e=p(),r=await new c(e).calculateDistanceMatrix(t.origins,t.destinations,t.mode);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to calculate distance matrix"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error calculating distance matrix: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var S={NAME:re,DESCRIPTION:te,SCHEMA:oe,ACTION:se};import{z as E}from"zod";var ne="maps_directions",ie="Get detailed turn-by-turn navigation directions between two locations with route information",ae={origin:E.string().describe("Starting point address or coordinates"),destination:E.string().describe("Destination address or coordinates"),mode:E.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:E.string().optional().describe("Departure time (ISO string format)"),arrival_time:E.string().optional().describe("Arrival time (ISO string format)")};async function ce(t){try{let e=p(),r=await new c(e).getDirections(t.origin,t.destination,t.mode,t.departure_time,t.arrival_time);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get directions"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting directions: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var P={NAME:ne,DESCRIPTION:ie,SCHEMA:ae,ACTION:ce};import{z as x}from"zod";var pe="maps_elevation",le="Get elevation data (height above sea level) for specific geographic locations",de={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")};async function me(t){try{let e=p(),r=await new c(e).getElevation(t.locations);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get elevation data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting elevation data: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var A={NAME:pe,DESCRIPTION:le,SCHEMA:de,ACTION:me};var ge=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:u.NAME,description:u.DESCRIPTION,schema:u.SCHEMA,action:t=>u.ACTION(t)},{name:y.NAME,description:y.DESCRIPTION,schema:y.SCHEMA,action:t=>y.ACTION(t)},{name:f.NAME,description:f.DESCRIPTION,schema:f.SCHEMA,action:t=>f.ACTION(t)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:t=>v.ACTION(t)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,action:t=>S.ACTION(t)},{name:P.NAME,description:P.DESCRIPTION,schema:P.SCHEMA,action:t=>P.ACTION(t)},{name:A.NAME,description:A.DESCRIPTION,schema:A.SCHEMA,action:t=>A.ACTION(t)}]}],_=ge;import{McpServer as ue}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as ye}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as fe}from"@modelcontextprotocol/sdk/types.js";import R from"express";import{randomUUID as ve}from"node:crypto";var b=class t{constructor(){this.defaultApiKey=process.env.GOOGLE_MAPS_API_KEY}static getInstance(){return t.instance||(t.instance=new t),t.instance}setDefaultApiKey(e){this.defaultApiKey=e,process.env.GOOGLE_MAPS_API_KEY=e}getApiKey(e,o){if(e){let r=e.headers["x-google-maps-api-key"];if(r)return r;let n=e.headers.authorization;if(n&&n.startsWith("Bearer "))return n.substring(7)}return o||this.defaultApiKey}hasApiKey(e,o){return!!this.getApiKey(e,o)}isValidApiKeyFormat(e){return/^[A-Za-z0-9_-]{20,50}$/.test(e)}};var he="0.0.1",C=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.server=new ue({name:this.serverName,version:he},{capabilities:{logging:{},tools:{}}}),this.registerTools(o)}registerTools(e){e.forEach(o=>{this.server.tool(o.name,o.description,o.schema,async r=>o.action(r))})}async connect(e){await this.server.connect(e);let o=process.stdout.write.bind(process.stdout);process.stdout.write=(r,n,i)=>typeof r=="string"&&!r.startsWith("{")?!0:o(r,n,i),s.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let o=R();o.use(R.json()),o.post("/mcp",async(n,i)=>{let a=n.headers["mcp-session-id"],l,d=b.getInstance().getApiKey(n);if(s.log(`${this.serverName} Get API KEY: ${d}`),a&&this.sessions[a])l=this.sessions[a],d&&(l.apiKey=d);else if(!a&&fe(n.body)){let g=new ye({sessionIdGenerator:()=>ve(),onsessioninitialized:I=>{this.sessions[I]=l,s.log(`[${this.serverName}] New session initialized: ${I}`)}});l={transport:g,apiKey:d},g.onclose=()=>{g.sessionId&&(delete this.sessions[g.sessionId],s.log(`[${this.serverName}] Session closed: ${g.sessionId}`))},await this.server.connect(g)}else{i.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await M({apiKey:l.apiKey,sessionId:a},async()=>{await l.transport.handleRequest(n,i,n.body)})});let r=async(n,i)=>{let a=n.headers["mcp-session-id"];if(!a||!this.sessions[a]){i.status(400).send("Invalid or missing session ID");return}let l=this.sessions[a],d=b.getInstance().getApiKey(n);d&&(l.apiKey=d),await M({apiKey:l.apiKey,sessionId:a},async()=>{await l.transport.handleRequest(n,i)})};o.get("/mcp",r),o.delete("/mcp",r),this.httpServer=o.listen(e,()=>{s.log(`[${this.serverName}] HTTP server listening on port ${e}`),s.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){s.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,o)=>{this.httpServer.close(r=>{if(r){s.error(`[${this.serverName}] Error stopping HTTP server:`,r),o(r);return}s.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let n=Object.values(this.sessions).map(i=>(i.transport.sessionId&&delete this.sessions[i.transport.sessionId],Promise.resolve()));Promise.all(n).then(()=>{s.log(`[${this.serverName}] All transports closed.`),e()}).catch(i=>{s.error(`[${this.serverName}] Error during bulk transport closing:`,i),o(i)})})})}};import{fileURLToPath as Pe}from"url";import{dirname as Ae}from"path";import{readFileSync as be}from"fs";var xe=Pe(import.meta.url),w=Ae(xe);K({path:N(process.cwd(),".env")});K({path:N(w,"../.env")});async function Ce(t,e){t&&(process.env.MCP_SERVER_PORT=t.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),s.log("\u{1F680} Starting Google Maps MCP Server..."),s.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),s.log("\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."),s.log("");let o=_.map(async r=>{let n=process.env[r.portEnvVar];if(!n){s.error(`\u26A0\uFE0F [${r.name}] Port environment variable ${r.portEnvVar} not set.`),s.log(`\u{1F4A1} Please set ${r.portEnvVar} in your .env file or use --port parameter.`),s.log(` Example: ${r.portEnvVar}=3000 or --port 3000`);return}let i=Number(n);if(isNaN(i)||i<=0){s.error(`\u274C [${r.name}] Invalid port number "${n}" defined in ${r.portEnvVar}.`);return}try{let a=new C(r.name,r.tools);s.log(`\u{1F527} [${r.name}] Initializing MCP Server in HTTP mode on port ${i}...`),await a.startHttpServer(i),s.log(`\u2705 [${r.name}] MCP Server started successfully!`),s.log(` \u{1F310} Endpoint: http://localhost:${i}/mcp`),s.log(` \u{1F4DA} Tools: ${r.tools.length} available`)}catch(a){s.error(`\u274C [${r.name}] Failed to start MCP Server on port ${i}:`,a)}});await Promise.allSettled(o),s.log(""),s.log("\u{1F389} Server initialization completed!"),s.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}var Me=process.argv[1]&&(process.argv[1].endsWith("cli.ts")||process.argv[1].endsWith("cli.js")||process.argv[1].endsWith("mcp-google-map")||process.argv[1].includes("mcp-google-map")),Ne=import.meta.url===`file://${process.argv[1]}`;if(Me||Ne){let t="0.0.0";try{let o=N(w,"../package.json");t=JSON.parse(be(o,"utf-8")).version}catch{t="0.0.0"}let e=Se(Ee(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"}).version(t).alias("version","v").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();s.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),s.log(" A Model Context Protocol server for Google Maps services"),s.log(""),e.apikey||(s.log("\u26A0\uFE0F Google Maps API Key not found!"),s.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),s.log(" Example: mcp-google-map --apikey your_api_key_here"),s.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),s.log("")),Ce(e.port,e.apikey).catch(o=>{s.error("\u274C Failed to start server:",o),process.exit(1)})}export{Ce as startServer};
2
+ import{b as s,c}from"./chunk-6ECLWDYH.js";import{config as w}from"dotenv";import{resolve as N}from"path";import Se from"yargs";import{hideBin as Ee}from"yargs/helpers";import{z as d}from"zod";import{AsyncLocalStorage as D}from"async_hooks";var O=new D;function p(){return O.getStore()?.apiKey||process.env.GOOGLE_MAPS_API_KEY}function C(t,e){return O.run(t,e)}var G="search_nearby",H="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",z={center:d.object({value:d.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:d.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point (e.g. value: 49.3268778,-123.0585982, isCoordinates: true)"),keyword:d.string().optional().describe("Search keyword (e.g., restaurant, cafe, hotel)"),radius:d.number().default(1e3).describe("Search radius in meters"),openNow:d.boolean().default(!1).describe("Only show places that are currently open"),minRating:d.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")};async function k(t){try{let e=p(),r=await new c(e).searchNearby(t);return r.success?{content:[{type:"text",text:`location: ${JSON.stringify(r.location,null,2)}
3
+ `+JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Search failed"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching nearby places: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var u={NAME:G,DESCRIPTION:H,SCHEMA:z,ACTION:k};import{z as J}from"zod";var L="get_place_details",j="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",V={placeId:J.string().describe("Google Maps place ID")};async function q(t){try{let e=p(),r=await new c(e).getPlaceDetails(t.placeId);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get place details"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting place details: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var y={NAME:L,DESCRIPTION:j,SCHEMA:V,ACTION:q};import{z as F}from"zod";var W="maps_geocode",Z="Convert addresses or place names to geographic coordinates (latitude and longitude)",Y={address:F.string().describe("Address or place name to convert to coordinates")};async function B(t){try{let e=p(),r=await new c(e).geocode(t.address);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to geocode address"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error geocoding address: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var f={NAME:W,DESCRIPTION:Z,SCHEMA:Y,ACTION:B};import{z as T}from"zod";var U="maps_reverse_geocode",Q="Convert geographic coordinates (latitude and longitude) to a human-readable address",X={latitude:T.number().describe("Latitude coordinate"),longitude:T.number().describe("Longitude coordinate")};async function ee(t){try{let e=p(),r=await new c(e).reverseGeocode(t.latitude,t.longitude);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to reverse geocode coordinates"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error reverse geocoding: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:U,DESCRIPTION:Q,SCHEMA:X,ACTION:ee};import{z as h}from"zod";var re="maps_distance_matrix",te="Calculate travel distances and durations between multiple origins and destinations for different travel modes",oe={origins:h.array(h.string()).describe("List of origin addresses or coordinates"),destinations:h.array(h.string()).describe("List of destination addresses or coordinates"),mode:h.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")};async function se(t){try{let e=p(),r=await new c(e).calculateDistanceMatrix(t.origins,t.destinations,t.mode);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to calculate distance matrix"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error calculating distance matrix: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var S={NAME:re,DESCRIPTION:te,SCHEMA:oe,ACTION:se};import{z as E}from"zod";var ne="maps_directions",ie="Get detailed turn-by-turn navigation directions between two locations with route information",ae={origin:E.string().describe("Starting point address or coordinates"),destination:E.string().describe("Destination address or coordinates"),mode:E.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:E.string().optional().describe("Departure time (ISO string format)"),arrival_time:E.string().optional().describe("Arrival time (ISO string format)")};async function ce(t){try{let e=p(),r=await new c(e).getDirections(t.origin,t.destination,t.mode,t.departure_time,t.arrival_time);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get directions"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting directions: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var P={NAME:ne,DESCRIPTION:ie,SCHEMA:ae,ACTION:ce};import{z as M}from"zod";var pe="maps_elevation",le="Get elevation data (height above sea level) for specific geographic locations",de={locations:M.array(M.object({latitude:M.number().describe("Latitude coordinate"),longitude:M.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")};async function me(t){try{let e=p(),r=await new c(e).getElevation(t.locations);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get elevation data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting elevation data: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var A={NAME:pe,DESCRIPTION:le,SCHEMA:de,ACTION:me};var ge=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:u.NAME,description:u.DESCRIPTION,schema:u.SCHEMA,action:t=>u.ACTION(t)},{name:y.NAME,description:y.DESCRIPTION,schema:y.SCHEMA,action:t=>y.ACTION(t)},{name:f.NAME,description:f.DESCRIPTION,schema:f.SCHEMA,action:t=>f.ACTION(t)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:t=>v.ACTION(t)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,action:t=>S.ACTION(t)},{name:P.NAME,description:P.DESCRIPTION,schema:P.SCHEMA,action:t=>P.ACTION(t)},{name:A.NAME,description:A.DESCRIPTION,schema:A.SCHEMA,action:t=>A.ACTION(t)}]}],_=ge;import{McpServer as ue}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as ye}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as fe}from"@modelcontextprotocol/sdk/types.js";import R from"express";import{randomUUID as ve}from"crypto";var x=class t{constructor(){this.defaultApiKey=process.env.GOOGLE_MAPS_API_KEY}static getInstance(){return t.instance||(t.instance=new t),t.instance}setDefaultApiKey(e){this.defaultApiKey=e,process.env.GOOGLE_MAPS_API_KEY=e}getApiKey(e,o){if(e){let r=e.headers["x-google-maps-api-key"];if(r)return r;let n=e.headers.authorization;if(n&&n.startsWith("Bearer "))return n.substring(7)}return o||this.defaultApiKey}hasApiKey(e,o){return!!this.getApiKey(e,o)}isValidApiKeyFormat(e){return/^[A-Za-z0-9_-]{20,50}$/.test(e)}};var he="0.0.1",b=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.tools=o,this.server=this.createMcpServer()}createMcpServer(){let e=new ue({name:this.serverName,version:he},{capabilities:{logging:{},tools:{}}});return this.tools.forEach(o=>{e.tool(o.name,o.description,o.schema,async r=>o.action(r))}),e}async connect(e){await this.server.connect(e);let o=process.stdout.write.bind(process.stdout);process.stdout.write=(r,n,i)=>typeof r=="string"&&!r.startsWith("{")?!0:o(r,n,i),s.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let o=R();o.use(R.json()),o.post("/mcp",async(n,i)=>{let a=n.headers["mcp-session-id"],l,m=x.getInstance().getApiKey(n);if(s.log(`${this.serverName} API key received from request context`),a&&this.sessions[a])l=this.sessions[a],m&&(l.apiKey=m);else if(!a&&fe(n.body)){let g=new ye({sessionIdGenerator:()=>ve(),onsessioninitialized:I=>{this.sessions[I]=l,s.log(`[${this.serverName}] New session initialized: ${I}`)}});l={transport:g,apiKey:m},g.onclose=()=>{g.sessionId&&(delete this.sessions[g.sessionId],s.log(`[${this.serverName}] Session closed: ${g.sessionId}`))},await this.createMcpServer().connect(g)}else{i.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await C({apiKey:l.apiKey,sessionId:a},async()=>{await l.transport.handleRequest(n,i,n.body)})});let r=async(n,i)=>{let a=n.headers["mcp-session-id"];if(!a||!this.sessions[a]){i.status(400).send("Invalid or missing session ID");return}let l=this.sessions[a],m=x.getInstance().getApiKey(n);m&&(l.apiKey=m),await C({apiKey:l.apiKey,sessionId:a},async()=>{await l.transport.handleRequest(n,i)})};o.get("/mcp",r),o.delete("/mcp",r),this.httpServer=o.listen(e,()=>{s.log(`[${this.serverName}] HTTP server listening on port ${e}`),s.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){s.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,o)=>{this.httpServer.close(r=>{if(r){s.error(`[${this.serverName}] Error stopping HTTP server:`,r),o(r);return}s.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let n=Object.values(this.sessions).map(i=>(i.transport.sessionId&&delete this.sessions[i.transport.sessionId],Promise.resolve()));Promise.all(n).then(()=>{s.log(`[${this.serverName}] All transports closed.`),e()}).catch(i=>{s.error(`[${this.serverName}] Error during bulk transport closing:`,i),o(i)})})})}};import{fileURLToPath as Pe}from"url";import{dirname as Ae}from"path";import{readFileSync as xe}from"fs";var Me=Pe(import.meta.url),K=Ae(Me);w({path:N(process.cwd(),".env")});w({path:N(K,"../.env")});async function be(t,e){t&&(process.env.MCP_SERVER_PORT=t.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),s.log("\u{1F680} Starting Google Maps MCP Server..."),s.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),s.log("\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."),s.log("");let o=_.map(async r=>{let n=process.env[r.portEnvVar];if(!n){s.error(`\u26A0\uFE0F [${r.name}] Port environment variable ${r.portEnvVar} not set.`),s.log(`\u{1F4A1} Please set ${r.portEnvVar} in your .env file or use --port parameter.`),s.log(` Example: ${r.portEnvVar}=3000 or --port 3000`);return}let i=Number(n);if(isNaN(i)||i<=0){s.error(`\u274C [${r.name}] Invalid port number "${n}" defined in ${r.portEnvVar}.`);return}try{let a=new b(r.name,r.tools);s.log(`\u{1F527} [${r.name}] Initializing MCP Server in HTTP mode on port ${i}...`),await a.startHttpServer(i),s.log(`\u2705 [${r.name}] MCP Server started successfully!`),s.log(` \u{1F310} Endpoint: http://localhost:${i}/mcp`),s.log(` \u{1F4DA} Tools: ${r.tools.length} available`)}catch(a){s.error(`\u274C [${r.name}] Failed to start MCP Server on port ${i}:`,a)}});await Promise.allSettled(o),s.log(""),s.log("\u{1F389} Server initialization completed!"),s.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}var Ce=process.argv[1]&&(process.argv[1].endsWith("cli.ts")||process.argv[1].endsWith("cli.js")||process.argv[1].endsWith("mcp-google-map")||process.argv[1].includes("mcp-google-map")),Ne=import.meta.url===`file://${process.argv[1]}`;if(Ce||Ne){let t="0.0.0";try{let o=N(K,"../package.json");t=JSON.parse(xe(o,"utf-8")).version}catch{t="0.0.0"}let e=Se(Ee(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"}).version(t).alias("version","v").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();s.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),s.log(" A Model Context Protocol server for Google Maps services"),s.log(""),e.apikey||(s.log("\u26A0\uFE0F Google Maps API Key not found!"),s.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),s.log(" Example: mcp-google-map --apikey your_api_key_here"),s.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),s.log("")),be(e.port,e.apikey).catch(o=>{s.error("\u274C Failed to start server:",o),process.exit(1)})}export{be as startServer};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a,b,c}from"./chunk-TH44WIYW.js";export{b as Logger,a as NewPlacesService,c as PlacesSearcher};
1
+ import{a,b,c}from"./chunk-6ECLWDYH.js";export{b as Logger,a as NewPlacesService,c as PlacesSearcher};
package/package.json CHANGED
@@ -1,74 +1,81 @@
1
- {
2
- "name": "@cablate/mcp-google-map",
3
- "version": "0.0.19",
4
- "description": "Google Maps MCP server with streamable HTTP transport support for location services, geocoding, and navigation",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "mcp-google-map": "dist/cli.js"
9
- },
10
- "files": [
11
- "dist",
12
- "dist/**/*.map",
13
- "README.md"
14
- ],
15
- "scripts": {
16
- "build": "tsup --dts",
17
- "start": "node dist/cli.js",
18
- "dev": "cross-env NODE_ENV=development tsup --watch",
19
- "prepublishOnly": "npm run build"
20
- },
21
- "engines": {
22
- "node": ">=18.0.0"
23
- },
24
- "keywords": [
25
- "google",
26
- "map",
27
- "api",
28
- "llm",
29
- "typescript",
30
- "mcp",
31
- "server",
32
- "streamable",
33
- "location",
34
- "geocoding",
35
- "navigation"
36
- ],
37
- "author": "CabLate",
38
- "license": "MIT",
39
- "homepage": "https://github.com/cablate/mcp-google-map#readme",
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/cablate/mcp-google-map.git"
43
- },
44
- "bugs": {
45
- "url": "https://github.com/cablate/mcp-google-map/issues"
46
- },
47
- "dependencies": {
48
- "@googlemaps/google-maps-services-js": "^3.4.0",
49
- "@googlemaps/places": "^2.1.0",
50
- "@modelcontextprotocol/sdk": "^1.11.0",
51
- "@types/yargs": "^17.0.33",
52
- "cross-env": "^7.0.3",
53
- "dotenv": "^16.4.7",
54
- "express": "^4.21.2",
55
- "yargs": "^17.7.2",
56
- "zod": "^3.24.2"
57
- },
58
- "devDependencies": {
59
- "@types/express": "^5.0.0",
60
- "@types/jest": "^29.5.14",
61
- "@types/js-yaml": "^4.0.9",
62
- "@types/node": "^20.17.0",
63
- "@typescript-eslint/eslint-plugin": "^8.24.0",
64
- "@typescript-eslint/parser": "^8.24.0",
65
- "eslint": "^9.20.1",
66
- "eslint-config-prettier": "^10.0.1",
67
- "jest": "^29.7.0",
68
- "prettier": "^3.5.0",
69
- "ts-jest": "^29.2.5",
70
- "tsup": "^8.4.0",
71
- "tsx": "^4.19.2",
72
- "typescript": "^5.7.3"
73
- }
74
- }
1
+ {
2
+ "name": "@cablate/mcp-google-map",
3
+ "version": "0.0.20",
4
+ "description": "Google Maps MCP server with streamable HTTP transport support for location services, geocoding, and navigation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-google-map": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "dist/**/*.map",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsup --dts",
17
+ "start": "node dist/cli.js",
18
+ "dev": "cross-env NODE_ENV=development tsup --watch",
19
+ "test": "tsx tests/smoke.test.ts",
20
+ "test:e2e": "tsx tests/smoke.test.ts",
21
+ "lint": "eslint .",
22
+ "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\"",
23
+ "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\"",
24
+ "prepublishOnly": "npm run build"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "keywords": [
30
+ "google",
31
+ "map",
32
+ "api",
33
+ "llm",
34
+ "typescript",
35
+ "mcp",
36
+ "server",
37
+ "streamable",
38
+ "location",
39
+ "geocoding",
40
+ "navigation"
41
+ ],
42
+ "author": "CabLate",
43
+ "license": "MIT",
44
+ "homepage": "https://github.com/cablate/mcp-google-map#readme",
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/cablate/mcp-google-map.git"
48
+ },
49
+ "bugs": {
50
+ "url": "https://github.com/cablate/mcp-google-map/issues"
51
+ },
52
+ "dependencies": {
53
+ "@googlemaps/google-maps-services-js": "^3.4.0",
54
+ "@googlemaps/places": "^2.1.0",
55
+ "@modelcontextprotocol/sdk": "^1.27.1",
56
+ "@types/yargs": "^17.0.33",
57
+ "cross-env": "^7.0.3",
58
+ "dotenv": "^16.4.7",
59
+ "express": "^4.21.2",
60
+ "yargs": "^17.7.2",
61
+ "zod": "^3.25.0"
62
+ },
63
+ "devDependencies": {
64
+ "@eslint/js": "^9.39.4",
65
+ "@types/express": "^4.17.21",
66
+ "@types/jest": "^29.5.14",
67
+ "@types/js-yaml": "^4.0.9",
68
+ "@types/node": "^20.17.0",
69
+ "@typescript-eslint/eslint-plugin": "^8.24.0",
70
+ "@typescript-eslint/parser": "^8.24.0",
71
+ "eslint": "^9.20.1",
72
+ "eslint-config-prettier": "^10.0.1",
73
+ "jest": "^29.7.0",
74
+ "prettier": "^3.5.0",
75
+ "ts-jest": "^29.2.5",
76
+ "tsup": "^8.4.0",
77
+ "tsx": "^4.19.2",
78
+ "typescript": "^5.7.3",
79
+ "typescript-eslint": "^8.57.0"
80
+ }
81
+ }