@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 +20 -20
- package/README.md +296 -275
- package/dist/{chunk-TH44WIYW.js β chunk-6ECLWDYH.js} +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.js +1 -1
- package/package.json +81 -74
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
|
-
[](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
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```json
|
|
105
|
-
|
|
106
|
-
{
|
|
107
|
-
|
|
108
|
-
"
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
-
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
##
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
+
---
|
|
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
|
+
[](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-
|
|
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-
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"@
|
|
65
|
-
"
|
|
66
|
-
"
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
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
|
+
}
|