@cablate/mcp-google-map 0.0.11 β†’ 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,200 +1,205 @@
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
- ## βœ… Testing Status
12
-
13
- **This MCP server has been tested and verified to work correctly with:**
14
- - Claude Desktop
15
- - Dive Desktop
16
- - MCP protocol implementations
17
-
18
- All tools and features are confirmed functional through real-world testing.
19
-
20
- ## Features
21
-
22
- ### πŸ—ΊοΈ Google Maps Integration
23
-
24
- - **Location Search**
25
- - Search for places near a specific location with customizable radius and filters
26
- - Get detailed place information including ratings, opening hours, and contact details
27
-
28
- - **Geocoding Services**
29
- - Convert addresses to coordinates (geocoding)
30
- - Convert coordinates to addresses (reverse geocoding)
31
-
32
- - **Distance & Directions**
33
- - Calculate distances and travel times between multiple origins and destinations
34
- - Get detailed turn-by-turn directions between two points
35
- - Support for different travel modes (driving, walking, bicycling, transit)
36
-
37
- - **Elevation Data**
38
- - Retrieve elevation data for specific locations
39
-
40
- ### πŸš€ Advanced Features
41
-
42
- - **Streamable HTTP Transport**: Latest MCP protocol with real-time streaming capabilities
43
- - **Session Management**: Stateful sessions with UUID-based identification
44
- - **Multiple Connection Support**: Handle multiple concurrent client connections
45
- - **Echo Service**: Built-in testing tool for MCP server functionality
46
-
47
- ## Installation
48
-
49
- ### 1. via NPM
50
-
51
- ```bash
52
- npm install -g @cablate/mcp-google-map
53
- ```
54
-
55
- ### 2. Run the Server
56
-
57
- ```bash
58
-
59
- mcp-google-map --port 3000 --apikey "your_api_key_here"
60
-
61
- # Using short options
62
- mcp-google-map -p 3000 -k "your_api_key_here"
63
-
64
- # Show help information
65
- mcp-google-map --help
66
- ```
67
-
68
- ### 3. Server Endpoints
69
-
70
- - **Main MCP Endpoint**: `http://localhost:3000/mcp`
71
- - **Available Tools**: 8 tools including Google Maps services and echo
72
-
73
- ### Environment Variables
74
-
75
- Alternatively, create a `.env` file in your working directory:
76
-
77
- ```env
78
- # Required
79
- GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
80
-
81
- # Optional
82
- MCP_SERVER_PORT=3000
83
- ```
84
-
85
- **Note**: Command line options take precedence over environment variables.
86
-
87
- ## Available Tools
88
-
89
- The server provides the following tools:
90
-
91
- ### Google Maps Tools
92
-
93
- 1. **search_nearby** - Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours
94
- 2. **get_place_details** - Get detailed information about a specific place including contact details, reviews, ratings, and operating hours
95
- 3. **maps_geocode** - Convert addresses or place names to geographic coordinates (latitude and longitude)
96
- 4. **maps_reverse_geocode** - Convert geographic coordinates to a human-readable address
97
- 5. **maps_distance_matrix** - Calculate travel distances and durations between multiple origins and destinations
98
- 6. **maps_directions** - Get detailed turn-by-turn navigation directions between two locations
99
- 7. **maps_elevation** - Get elevation data (height above sea level) for specific geographic locations
100
-
101
- ## Development
102
-
103
- ### Local Development
104
-
105
- ```bash
106
- # Clone the repository
107
- git clone https://github.com/cablate/mcp-google-map.git
108
- cd mcp-google-map
109
-
110
- # Install dependencies
111
- npm install
112
-
113
- # Set up environment variables
114
- cp .env.example .env
115
- # Edit .env with your API key
116
-
117
- # Build the project
118
- npm run build
119
-
120
- # Start the server
121
- npm start
122
-
123
- # Or run in development mode
124
- npm run dev
125
- ```
126
-
127
- ### Project Structure
128
-
129
- ```
130
- src/
131
- β”œβ”€β”€ cli.ts # Main CLI entry point
132
- β”œβ”€β”€ config.ts # Server configuration
133
- β”œβ”€β”€ index.ts # Package exports
134
- β”œβ”€β”€ core/
135
- β”‚ └── BaseMcpServer.ts # Base MCP server with streamable HTTP
136
- └── tools/
137
- β”œβ”€β”€ echo.ts # Echo service tool
138
- └── maps/ # Google Maps tools
139
- β”œβ”€β”€ toolclass.ts # Google Maps API client
140
- β”œβ”€β”€ searchPlaces.ts # Maps service layer
141
- β”œβ”€β”€ searchNearby.ts # Search nearby places
142
- β”œβ”€β”€ placeDetails.ts # Place details
143
- β”œβ”€β”€ geocode.ts # Geocoding
144
- β”œβ”€β”€ reverseGeocode.ts # Reverse geocoding
145
- β”œβ”€β”€ distanceMatrix.ts # Distance matrix
146
- β”œβ”€β”€ directions.ts # Directions
147
- └── elevation.ts # Elevation data
148
- ```
149
-
150
- ## Tech Stack
151
-
152
- - **TypeScript** - Type-safe development
153
- - **Node.js** - Runtime environment
154
- - **Google Maps Services JS** - Google Maps API integration
155
- - **Model Context Protocol SDK** - MCP protocol implementation
156
- - **Express.js** - HTTP server framework
157
- - **Zod** - Schema validation
158
-
159
- ## Security Considerations
160
-
161
- - API keys are handled server-side for security
162
- - DNS rebinding protection available for production
163
- - Input validation using Zod schemas
164
- - Error handling and logging
165
-
166
- ## License
167
-
168
- MIT
169
-
170
- ## Contributing
171
-
172
- Community participation and contributions are welcome! Here's how you can contribute:
173
-
174
- - ⭐️ Star the project if you find it helpful
175
- - πŸ› Submit Issues: Report bugs or provide suggestions
176
- - πŸ”§ Create Pull Requests: Submit code improvements
177
- - πŸ“– Documentation: Help improve documentation
178
-
179
- ## Contact
180
-
181
- If you have any questions or suggestions, feel free to reach out:
182
-
183
- - πŸ“§ Email: [reahtuoo310109@gmail.com](mailto:reahtuoo310109@gmail.com)
184
- - πŸ’» GitHub: [CabLate](https://github.com/cablate/)
185
- - 🀝 Collaboration: Welcome to discuss project cooperation
186
- - πŸ“š Technical Guidance: Sincere welcome for suggestions and guidance
187
-
188
- ## Changelog
189
-
190
- ### v0.0.5
191
- - Added streamable HTTP transport support
192
- - Improved CLI interface with emoji indicators
193
- - Enhanced error handling and logging
194
- - Added comprehensive tool descriptions for LLM integration
195
- - Updated to latest MCP SDK version
196
-
197
- ### v0.0.4
198
- - Initial release with basic Google Maps integration
199
- - Support for location search, geocoding, and directions
200
- - Compatible with MCP protocol
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
+ - Claude Desktop
20
+ - Dive Desktop
21
+ - MCP protocol implementations
22
+
23
+ All tools and features are confirmed functional through real-world testing.
24
+
25
+ ## Features
26
+
27
+ ### πŸ—ΊοΈ Google Maps Integration
28
+
29
+ - **Location Search**
30
+ - Search for places near a specific location with customizable radius and filters
31
+ - Get detailed place information including ratings, opening hours, and contact details
32
+
33
+ - **Geocoding Services**
34
+ - Convert addresses to coordinates (geocoding)
35
+ - Convert coordinates to addresses (reverse geocoding)
36
+
37
+ - **Distance & Directions**
38
+ - Calculate distances and travel times between multiple origins and destinations
39
+ - Get detailed turn-by-turn directions between two points
40
+ - Support for different travel modes (driving, walking, bicycling, transit)
41
+
42
+ - **Elevation Data**
43
+ - Retrieve elevation data for specific locations
44
+
45
+ ### πŸš€ Advanced Features
46
+
47
+ - **Streamable HTTP Transport**: Latest MCP protocol with real-time streaming capabilities
48
+ - **Session Management**: Stateful sessions with UUID-based identification
49
+ - **Multiple Connection Support**: Handle multiple concurrent client connections
50
+ - **Echo Service**: Built-in testing tool for MCP server functionality
51
+
52
+ ## Installation
53
+
54
+ ### 1. via NPM
55
+
56
+ ```bash
57
+ npm install -g @cablate/mcp-google-map
58
+ ```
59
+
60
+ ### 2. Run the Server
61
+
62
+ ```bash
63
+
64
+ mcp-google-map --port 3000 --apikey "your_api_key_here"
65
+
66
+ # Using short options
67
+ mcp-google-map -p 3000 -k "your_api_key_here"
68
+
69
+ # Show help information
70
+ mcp-google-map --help
71
+ ```
72
+
73
+ ### 3. Server Endpoints
74
+
75
+ - **Main MCP Endpoint**: `http://localhost:3000/mcp`
76
+ - **Available Tools**: 8 tools including Google Maps services and echo
77
+
78
+ ### Environment Variables
79
+
80
+ Alternatively, create a `.env` file in your working directory:
81
+
82
+ ```env
83
+ # Required
84
+ GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
85
+
86
+ # Optional
87
+ MCP_SERVER_PORT=3000
88
+ ```
89
+
90
+ **Note**: Command line options take precedence over environment variables.
91
+
92
+ ## Available Tools
93
+
94
+ The server provides the following tools:
95
+
96
+ ### Google Maps Tools
97
+
98
+ 1. **search_nearby** - Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours
99
+ 2. **get_place_details** - Get detailed information about a specific place including contact details, reviews, ratings, and operating hours
100
+ 3. **maps_geocode** - Convert addresses or place names to geographic coordinates (latitude and longitude)
101
+ 4. **maps_reverse_geocode** - Convert geographic coordinates to a human-readable address
102
+ 5. **maps_distance_matrix** - Calculate travel distances and durations between multiple origins and destinations
103
+ 6. **maps_directions** - Get detailed turn-by-turn navigation directions between two locations
104
+ 7. **maps_elevation** - Get elevation data (height above sea level) for specific geographic locations
105
+
106
+ ## Development
107
+
108
+ ### Local Development
109
+
110
+ ```bash
111
+ # Clone the repository
112
+ git clone https://github.com/cablate/mcp-google-map.git
113
+ cd mcp-google-map
114
+
115
+ # Install dependencies
116
+ npm install
117
+
118
+ # Set up environment variables
119
+ cp .env.example .env
120
+ # Edit .env with your API key
121
+
122
+ # Build the project
123
+ npm run build
124
+
125
+ # Start the server
126
+ npm start
127
+
128
+ # Or run in development mode
129
+ npm run dev
130
+ ```
131
+
132
+ ### Project Structure
133
+
134
+ ```
135
+ src/
136
+ β”œβ”€β”€ cli.ts # Main CLI entry point
137
+ β”œβ”€β”€ config.ts # Server configuration
138
+ β”œβ”€β”€ index.ts # Package exports
139
+ β”œβ”€β”€ core/
140
+ β”‚ └── BaseMcpServer.ts # Base MCP server with streamable HTTP
141
+ └── tools/
142
+ β”œβ”€β”€ echo.ts # Echo service tool
143
+ └── maps/ # Google Maps tools
144
+ β”œβ”€β”€ toolclass.ts # Google Maps API client
145
+ β”œβ”€β”€ searchPlaces.ts # Maps service layer
146
+ β”œβ”€β”€ searchNearby.ts # Search nearby places
147
+ β”œβ”€β”€ placeDetails.ts # Place details
148
+ β”œβ”€β”€ geocode.ts # Geocoding
149
+ β”œβ”€β”€ reverseGeocode.ts # Reverse geocoding
150
+ β”œβ”€β”€ distanceMatrix.ts # Distance matrix
151
+ β”œβ”€β”€ directions.ts # Directions
152
+ └── elevation.ts # Elevation data
153
+ ```
154
+
155
+ ## Tech Stack
156
+
157
+ - **TypeScript** - Type-safe development
158
+ - **Node.js** - Runtime environment
159
+ - **Google Maps Services JS** - Google Maps API integration
160
+ - **Model Context Protocol SDK** - MCP protocol implementation
161
+ - **Express.js** - HTTP server framework
162
+ - **Zod** - Schema validation
163
+
164
+ ## Security Considerations
165
+
166
+ - API keys are handled server-side for security
167
+ - DNS rebinding protection available for production
168
+ - Input validation using Zod schemas
169
+ - Error handling and logging
170
+
171
+ ## License
172
+
173
+ MIT
174
+
175
+ ## Contributing
176
+
177
+ Community participation and contributions are welcome! Here's how you can contribute:
178
+
179
+ - ⭐️ Star the project if you find it helpful
180
+ - πŸ› Submit Issues: Report bugs or provide suggestions
181
+ - πŸ”§ Create Pull Requests: Submit code improvements
182
+ - πŸ“– Documentation: Help improve documentation
183
+
184
+ ## Contact
185
+
186
+ If you have any questions or suggestions, feel free to reach out:
187
+
188
+ - πŸ“§ Email: [reahtuoo310109@gmail.com](mailto:reahtuoo310109@gmail.com)
189
+ - πŸ’» GitHub: [CabLate](https://github.com/cablate/)
190
+ - 🀝 Collaboration: Welcome to discuss project cooperation
191
+ - πŸ“š Technical Guidance: Sincere welcome for suggestions and guidance
192
+
193
+ ## Changelog
194
+
195
+ ### v0.0.5
196
+ - Added streamable HTTP transport support
197
+ - Improved CLI interface with emoji indicators
198
+ - Enhanced error handling and logging
199
+ - Added comprehensive tool descriptions for LLM integration
200
+ - Updated to latest MCP SDK version
201
+
202
+ ### v0.0.4
203
+ - Initial release with basic Google Maps integration
204
+ - Support for location search, geocoding, and directions
205
+ - Compatible with MCP protocol
@@ -0,0 +1,3 @@
1
+ var o={log:(...e)=>{process.stderr.write(`[INFO] ${e.map(t=>typeof t=="object"?JSON.stringify(t):String(t)).join(" ")}
2
+ `)},error:(...e)=>{process.stderr.write(`[ERROR] ${e.map(t=>typeof t=="object"?JSON.stringify(t):String(t)).join(" ")}
3
+ `)}};export{o as a};
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
2
- import{a as o}from"./chunk-W2DM2HDK.js";import{config as J}from"dotenv";import{resolve as G}from"path";import Oe from"yargs";import{hideBin as Te}from"yargs/helpers";import{z as m}from"zod";import{Client as V,Language as Y}from"@googlemaps/google-maps-services-js";import Z from"dotenv";Z.config();var M=class{constructor(){this.defaultLanguage=Y.zh_TW;if(this.client=new V({}),!process.env.GOOGLE_MAPS_API_KEY)throw new Error("Google Maps API Key is required")}async searchNearbyPlaces(e){let r={location:e.location,radius:e.radius||1e3,keyword:e.keyword,opennow:e.openNow,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""};try{let s=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(s=s.filter(n=>(n.rating||0)>=(e.minRating||0))),s}catch(t){throw o.error("Error in searchNearbyPlaces:",t),new Error("\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u6642\u767C\u751F\u932F\u8AA4")}}async getPlaceDetails(e){try{return(await this.client.placeDetails({params:{place_id:e,fields:["name","rating","formatted_address","opening_hours","reviews","geometry","formatted_phone_number","website","price_level","photos"],language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data.result}catch(r){throw o.error("Error in getPlaceDetails:",r),new Error("\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u6642\u767C\u751F\u932F\u8AA4")}}async geocodeAddress(e){try{let r=await this.client.geocode({params:{address:e,key:process.env.GOOGLE_MAPS_API_KEY||"",language:this.defaultLanguage}});if(r.data.results.length===0)throw new Error("\u627E\u4E0D\u5230\u8A72\u5730\u5740\u7684\u4F4D\u7F6E");let t=r.data.results[0],s=t.geometry.location;return{lat:s.lat,lng:s.lng,formatted_address:t.formatted_address,place_id:t.place_id}}catch(r){throw o.error("Error in geocodeAddress:",r),new Error("\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4")}}parseCoordinates(e){let r=e.split(",").map(t=>parseFloat(t.trim()));if(r.length!==2||isNaN(r[0])||isNaN(r[1]))throw new Error("\u7121\u6548\u7684\u5EA7\u6A19\u683C\u5F0F\uFF0C\u8ACB\u4F7F\u7528\u300C\u7DEF\u5EA6,\u7D93\u5EA6\u300D\u683C\u5F0F");return{lat:r[0],lng:r[1]}}async getLocation(e){return e.isCoordinates?this.parseCoordinates(e.value):this.geocodeAddress(e.value)}async geocode(e){try{let r=await this.geocodeAddress(e);return{location:{lat:r.lat,lng:r.lng},formatted_address:r.formatted_address||"",place_id:r.place_id||""}}catch(r){throw o.error("Error in geocode:",r),new Error("\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4")}}async reverseGeocode(e,r){try{let t=await this.client.reverseGeocode({params:{latlng:{lat:e,lng:r},language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}});if(t.data.results.length===0)throw new Error("\u627E\u4E0D\u5230\u8A72\u5EA7\u6A19\u7684\u5730\u5740");let s=t.data.results[0];return{formatted_address:s.formatted_address,place_id:s.place_id,address_components:s.address_components}}catch(t){throw o.error("Error in reverseGeocode:",t),new Error("\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u6642\u767C\u751F\u932F\u8AA4")}}async calculateDistanceMatrix(e,r,t="driving"){try{let n=(await this.client.distancematrix({params:{origins:e,destinations:r,mode:t,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data;if(n.status!=="OK")throw new Error(`\u8DDD\u96E2\u77E9\u9663\u8A08\u7B97\u5931\u6557: ${n.status}`);let i=[],c=[];return n.rows.forEach(p=>{let u=[],g=[];p.elements.forEach(d=>{d.status==="OK"?(u.push({value:d.distance.value,text:d.distance.text}),g.push({value:d.duration.value,text:d.duration.text})):(u.push(null),g.push(null))}),i.push(u),c.push(g)}),{distances:i,durations:c,origin_addresses:n.origin_addresses,destination_addresses:n.destination_addresses}}catch(s){throw o.error("Error in calculateDistanceMatrix:",s),new Error("\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u6642\u767C\u751F\u932F\u8AA4")}}async getDirections(e,r,t="driving",s,n){try{let i;n&&(i=Math.floor(n.getTime()/1e3));let c;i||(s instanceof Date?c=Math.floor(s.getTime()/1e3):s?c=s:c="now");let u=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||"",arrival_time:i,departure_time:c}})).data;if(u.status!=="OK")throw new Error(`\u8DEF\u7DDA\u6307\u5F15\u7372\u53D6\u5931\u6557: ${u.status} (arrival_time: ${i}, departure_time: ${c})`);if(u.routes.length===0)throw new Error("\u627E\u4E0D\u5230\u8DEF\u7DDA");let g=u.routes[0],d=g.legs[0],$=f=>{if(!f||typeof f.value!="number")return"";let j=new Date(f.value*1e3),k={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return f.time_zone&&typeof f.time_zone=="string"&&(k.timeZone=f.time_zone),j.toLocaleString(this.defaultLanguage.toString(),k)};return{routes:u.routes,summary:g.summary,total_distance:{value:d.distance.value,text:d.distance.text},total_duration:{value:d.duration.value,text:d.duration.text},arrival_time:$(d.arrival_time),departure_time:$(d.departure_time)}}catch(i){throw o.error("Error in getDirections:",i),new Error("\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u6642\u767C\u751F\u932F\u8AA4"+i)}}async getElevation(e){try{let r=e.map(n=>({lat:n.latitude,lng:n.longitude})),s=(await this.client.elevation({params:{locations:r,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data;if(s.status!=="OK")throw new Error(`\u6D77\u62D4\u6578\u64DA\u7372\u53D6\u5931\u6557: ${s.status}`);return s.results.map((n,i)=>({elevation:n.elevation,location:r[i]}))}catch(r){throw o.error("Error in getElevation:",r),new Error("\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4")}}};var l=class{constructor(){this.mapsTools=new M}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(s=>({name:s.name,place_id:s.place_id,address:s.formatted_address,location:s.geometry.location,rating:s.rating,total_ratings:s.user_ratings_total,open_now:s.opening_hours?.open_now}))}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u641C\u5C0B\u6642\u767C\u751F\u932F\u8AA4"}}}async getPlaceDetails(e){try{let r=await this.mapsTools.getPlaceDetails(e);return{success:!0,data:{name:r.name,address:r.formatted_address,location:r.geometry?.location,rating:r.rating,total_ratings:r.user_ratings_total,open_now:r.opening_hours?.open_now,phone:r.formatted_phone_number,website:r.website,price_level:r.price_level,reviews:r.reviews?.map(t=>({rating:t.rating,text:t.text,time:t.time,author_name:t.author_name}))}}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u6642\u767C\u751F\u932F\u8AA4"}}}async geocode(e){try{return{success:!0,data:await this.mapsTools.geocode(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4"}}}async reverseGeocode(e,r){try{return{success:!0,data:await this.mapsTools.reverseGeocode(e,r)}}catch(t){return{success:!1,error:t instanceof Error?t.message:"\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u6642\u767C\u751F\u932F\u8AA4"}}}async calculateDistanceMatrix(e,r,t="driving"){try{return{success:!0,data:await this.mapsTools.calculateDistanceMatrix(e,r,t)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u6642\u767C\u751F\u932F\u8AA4"}}}async getDirections(e,r,t="driving",s,n){try{let i=s?new Date(s):new Date,c=n?new Date(n):void 0;return{success:!0,data:await this.mapsTools.getDirections(e,r,t,i,c)}}catch(i){return{success:!1,error:i instanceof Error?i.message:"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u6642\u767C\u751F\u932F\u8AA4"}}}async getElevation(e){try{return{success:!0,data:await this.mapsTools.getElevation(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4"}}}};var q="search_nearby",W="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",F={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)")},A=null;async function U(a){try{A||(A=new l);let e=await A.searchNearby(a);return e.success?{content:[{type:"text",text:`location: ${JSON.stringify(e.location,null,2)}
3
- `+JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u641C\u5C0B\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var y={NAME:q,DESCRIPTION:W,SCHEMA:F,ACTION:U};import{z as B}from"zod";var Q="get_place_details",X="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",ee={placeId:B.string().describe("Google Maps place ID")},O=null;async function re(a){try{O||(O=new l);let e=await O.getPlaceDetails(a.placeId);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var h={NAME:Q,DESCRIPTION:X,SCHEMA:ee,ACTION:re};import{z as te}from"zod";var se="maps_geocode",oe="Convert addresses or place names to geographic coordinates (latitude and longitude)",ae={address:te.string().describe("Address or place name to convert to coordinates")},T=null;async function ne(a){try{T||(T=new l);let e=await T.geocode(a.address);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:se,DESCRIPTION:oe,SCHEMA:ae,ACTION:ne};import{z as L}from"zod";var ie="maps_reverse_geocode",ce="Convert geographic coordinates (latitude and longitude) to a human-readable address",le={latitude:L.number().describe("Latitude coordinate"),longitude:L.number().describe("Longitude coordinate")},C=null;async function de(a){try{C||(C=new l);let e=await C.reverseGeocode(a.latitude,a.longitude);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var E={NAME:ie,DESCRIPTION:ce,SCHEMA:le,ACTION:de};import{z as P}from"zod";var ue="maps_distance_matrix",me="Calculate travel distances and durations between multiple origins and destinations for different travel modes",pe={origins:P.array(P.string()).describe("List of origin addresses or coordinates"),destinations:P.array(P.string()).describe("List of destination addresses or coordinates"),mode:P.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")},I=null;async function ge(a){try{I||(I=new l);let e=await I.calculateDistanceMatrix(a.origins,a.destinations,a.mode);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var _={NAME:ue,DESCRIPTION:me,SCHEMA:pe,ACTION:ge};import{z as b}from"zod";var fe="maps_directions",ye="Get detailed turn-by-turn navigation directions between two locations with route information",he={origin:b.string().describe("Starting point address or coordinates"),destination:b.string().describe("Destination address or coordinates"),mode:b.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:b.string().optional().describe("Departure time (ISO string format)"),arrival_time:b.string().optional().describe("Arrival time (ISO string format)")},R=null;async function ve(a){try{R||(R=new l);let e=await R.getDirections(a.origin,a.destination,a.mode,a.departure_time,a.arrival_time);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var S={NAME:fe,DESCRIPTION:ye,SCHEMA:he,ACTION:ve};import{z as N}from"zod";var Ee="maps_elevation",Pe="Get elevation data (height above sea level) for specific geographic locations",_e={locations:N.array(N.object({latitude:N.number().describe("Latitude coordinate"),longitude:N.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")},D=null;async function be(a){try{D||(D=new l);let e=await D.getElevation(a.locations);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var w={NAME:Ee,DESCRIPTION:Pe,SCHEMA:_e,ACTION:be};var Se=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:y.NAME,description:y.DESCRIPTION,schema:y.SCHEMA,action:a=>y.ACTION(a)},{name:h.NAME,description:h.DESCRIPTION,schema:h.SCHEMA,action:a=>h.ACTION(a)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:a=>v.ACTION(a)},{name:E.NAME,description:E.DESCRIPTION,schema:E.SCHEMA,action:a=>E.ACTION(a)},{name:_.NAME,description:_.DESCRIPTION,schema:_.SCHEMA,action:a=>_.ACTION(a)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,action:a=>S.ACTION(a)},{name:w.NAME,description:w.DESCRIPTION,schema:w.SCHEMA,action:a=>w.ACTION(a)}]}],H=Se;import{McpServer as we}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as Me}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as Ne}from"@modelcontextprotocol/sdk/types.js";import z from"express";import{randomUUID as xe}from"node:crypto";var Ae="0.0.1",x=class{constructor(e,r){this.transports={};this.httpServer=null;this.serverName=e,this.server=new we({name:this.serverName,version:Ae},{capabilities:{logging:{},tools:{}}}),this.registerTools(r)}registerTools(e){e.forEach(r=>{this.server.tool(r.name,r.description,r.schema,async t=>r.action(t))})}async connect(e){await this.server.connect(e);let r=process.stdout.write.bind(process.stdout);process.stdout.write=(t,s,n)=>typeof t=="string"&&!t.startsWith("{")?!0:r(t,s,n),o.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let r=z();r.use(z.json()),r.post("/mcp",async(s,n)=>{let i=s.headers["mcp-session-id"],c;if(i&&this.transports[i])c=this.transports[i];else if(!i&&Ne(s.body))c=new Me({sessionIdGenerator:()=>xe(),onsessioninitialized:p=>{this.transports[p]=c,o.log(`[${this.serverName}] New session initialized: ${p}`)}}),c.onclose=()=>{c.sessionId&&(delete this.transports[c.sessionId],o.log(`[${this.serverName}] Session closed: ${c.sessionId}`))},await this.server.connect(c);else{n.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await c.handleRequest(s,n,s.body)});let t=async(s,n)=>{let i=s.headers["mcp-session-id"];if(!i||!this.transports[i]){n.status(400).send("Invalid or missing session ID");return}await this.transports[i].handleRequest(s,n)};r.get("/mcp",t),r.delete("/mcp",t),this.httpServer=r.listen(e,()=>{o.log(`[${this.serverName}] HTTP server listening on port ${e}`),o.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){o.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,r)=>{this.httpServer.close(t=>{if(t){o.error(`[${this.serverName}] Error stopping HTTP server:`,t),r(t);return}o.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let s=Object.values(this.transports).map(n=>(n.sessionId&&delete this.transports[n.sessionId],Promise.resolve()));Promise.all(s).then(()=>{o.log(`[${this.serverName}] All transports closed.`),e()}).catch(n=>{o.error(`[${this.serverName}] Error during bulk transport closing:`,n),r(n)})})})}};import{fileURLToPath as Ce}from"url";import{dirname as Ie}from"path";import{readFileSync as Re}from"fs";var De=Ce(import.meta.url),K=Ie(De);J({path:G(process.cwd(),".env")});J({path:G(K,"../.env")});async function Ge(a,e){a&&(process.env.MCP_SERVER_PORT=a.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),o.log("\u{1F680} Starting Google Maps MCP Server..."),o.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),o.log("");let r=H.map(async t=>{let s=process.env[t.portEnvVar];if(!s){o.error(`\u26A0\uFE0F [${t.name}] Port environment variable ${t.portEnvVar} not set.`),o.log(`\u{1F4A1} Please set ${t.portEnvVar} in your .env file or use --port parameter.`),o.log(` Example: ${t.portEnvVar}=3000 or --port 3000`);return}let n=Number(s);if(isNaN(n)||n<=0){o.error(`\u274C [${t.name}] Invalid port number "${s}" defined in ${t.portEnvVar}.`);return}try{let i=new x(t.name,t.tools);o.log(`\u{1F527} [${t.name}] Initializing MCP Server in HTTP mode on port ${n}...`),await i.startHttpServer(n),o.log(`\u2705 [${t.name}] MCP Server started successfully!`),o.log(` \u{1F310} Endpoint: http://localhost:${n}/mcp`),o.log(` \u{1F4DA} Tools: ${t.tools.length} available`)}catch(i){o.error(`\u274C [${t.name}] Failed to start MCP Server on port ${n}:`,i)}});await Promise.allSettled(r),o.log(""),o.log("\u{1F389} Server initialization completed!"),o.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}if(process.argv[1]&&(process.argv[1].endsWith("cli.ts")||process.argv[1].endsWith("cli.js"))){let a="0.0.0";try{let r=G(K,"../package.json");a=JSON.parse(Re(r,"utf-8")).version}catch{a="0.0.0"}let e=Oe(Te(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(a).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();o.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),o.log(" A Model Context Protocol server for Google Maps services"),o.log(""),e.apikey||(o.log("\u26A0\uFE0F Google Maps API Key not found!"),o.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),o.log(" Example: mcp-google-map --apikey your_api_key_here"),o.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),o.log("")),Ge(e.port,e.apikey).catch(r=>{o.error("\u274C Failed to start server:",r),process.exit(1)})}export{Ge as startServer};
2
+ import{a as o}from"./chunk-V2GKYT7F.js";import{config as J}from"dotenv";import{resolve as G}from"path";import Oe from"yargs";import{hideBin as Te}from"yargs/helpers";import{z as p}from"zod";import{Client as V,Language as Y}from"@googlemaps/google-maps-services-js";import Z from"dotenv";Z.config();var M=class{constructor(){this.defaultLanguage=Y.zh_TW;if(this.client=new V({}),!process.env.GOOGLE_MAPS_API_KEY)throw new Error("Google Maps API Key is required")}async searchNearbyPlaces(e){let r={location:e.location,radius:e.radius||1e3,keyword:e.keyword,opennow:e.openNow,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""};try{let s=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(s=s.filter(n=>(n.rating||0)>=(e.minRating||0))),s}catch(t){throw o.error("Error in searchNearbyPlaces:",t),new Error("\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u6642\u767C\u751F\u932F\u8AA4")}}async getPlaceDetails(e){try{return(await this.client.placeDetails({params:{place_id:e,fields:["name","rating","formatted_address","opening_hours","reviews","geometry","formatted_phone_number","website","price_level","photos"],language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data.result}catch(r){throw o.error("Error in getPlaceDetails:",r),new Error("\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u6642\u767C\u751F\u932F\u8AA4")}}async geocodeAddress(e){try{let r=await this.client.geocode({params:{address:e,key:process.env.GOOGLE_MAPS_API_KEY||"",language:this.defaultLanguage}});if(r.data.results.length===0)throw new Error("\u627E\u4E0D\u5230\u8A72\u5730\u5740\u7684\u4F4D\u7F6E");let t=r.data.results[0],s=t.geometry.location;return{lat:s.lat,lng:s.lng,formatted_address:t.formatted_address,place_id:t.place_id}}catch(r){throw o.error("Error in geocodeAddress:",r),new Error("\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4")}}parseCoordinates(e){let r=e.split(",").map(t=>parseFloat(t.trim()));if(r.length!==2||isNaN(r[0])||isNaN(r[1]))throw new Error("\u7121\u6548\u7684\u5EA7\u6A19\u683C\u5F0F\uFF0C\u8ACB\u4F7F\u7528\u300C\u7DEF\u5EA6,\u7D93\u5EA6\u300D\u683C\u5F0F");return{lat:r[0],lng:r[1]}}async getLocation(e){return e.isCoordinates?this.parseCoordinates(e.value):this.geocodeAddress(e.value)}async geocode(e){try{let r=await this.geocodeAddress(e);return{location:{lat:r.lat,lng:r.lng},formatted_address:r.formatted_address||"",place_id:r.place_id||""}}catch(r){throw o.error("Error in geocode:",r),new Error("\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4")}}async reverseGeocode(e,r){try{let t=await this.client.reverseGeocode({params:{latlng:{lat:e,lng:r},language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}});if(t.data.results.length===0)throw new Error("\u627E\u4E0D\u5230\u8A72\u5EA7\u6A19\u7684\u5730\u5740");let s=t.data.results[0];return{formatted_address:s.formatted_address,place_id:s.place_id,address_components:s.address_components}}catch(t){throw o.error("Error in reverseGeocode:",t),new Error("\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u6642\u767C\u751F\u932F\u8AA4")}}async calculateDistanceMatrix(e,r,t="driving"){try{let n=(await this.client.distancematrix({params:{origins:e,destinations:r,mode:t,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data;if(n.status!=="OK")throw new Error(`\u8DDD\u96E2\u77E9\u9663\u8A08\u7B97\u5931\u6557: ${n.status}`);let i=[],c=[];return n.rows.forEach(m=>{let u=[],g=[];m.elements.forEach(d=>{d.status==="OK"?(u.push({value:d.distance.value,text:d.distance.text}),g.push({value:d.duration.value,text:d.duration.text})):(u.push(null),g.push(null))}),i.push(u),c.push(g)}),{distances:i,durations:c,origin_addresses:n.origin_addresses,destination_addresses:n.destination_addresses}}catch(s){throw o.error("Error in calculateDistanceMatrix:",s),new Error("\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u6642\u767C\u751F\u932F\u8AA4")}}async getDirections(e,r,t="driving",s,n){try{let i;n&&(i=Math.floor(n.getTime()/1e3));let c;i||(s instanceof Date?c=Math.floor(s.getTime()/1e3):s?c=s:c="now");let u=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:process.env.GOOGLE_MAPS_API_KEY||"",arrival_time:i,departure_time:c}})).data;if(u.status!=="OK")throw new Error(`\u8DEF\u7DDA\u6307\u5F15\u7372\u53D6\u5931\u6557: ${u.status} (arrival_time: ${i}, departure_time: ${c})`);if(u.routes.length===0)throw new Error("\u627E\u4E0D\u5230\u8DEF\u7DDA");let g=u.routes[0],d=g.legs[0],$=f=>{if(!f||typeof f.value!="number")return"";let j=new Date(f.value*1e3),k={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return f.time_zone&&typeof f.time_zone=="string"&&(k.timeZone=f.time_zone),j.toLocaleString(this.defaultLanguage.toString(),k)};return{routes:u.routes,summary:g.summary,total_distance:{value:d.distance.value,text:d.distance.text},total_duration:{value:d.duration.value,text:d.duration.text},arrival_time:$(d.arrival_time),departure_time:$(d.departure_time)}}catch(i){throw o.error("Error in getDirections:",i),new Error("\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u6642\u767C\u751F\u932F\u8AA4"+i)}}async getElevation(e){try{let r=e.map(n=>({lat:n.latitude,lng:n.longitude})),s=(await this.client.elevation({params:{locations:r,key:process.env.GOOGLE_MAPS_API_KEY||""}})).data;if(s.status!=="OK")throw new Error(`\u6D77\u62D4\u6578\u64DA\u7372\u53D6\u5931\u6557: ${s.status}`);return s.results.map((n,i)=>({elevation:n.elevation,location:r[i]}))}catch(r){throw o.error("Error in getElevation:",r),new Error("\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4")}}};var l=class{constructor(){this.mapsTools=new M}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(s=>({name:s.name,place_id:s.place_id,address:s.formatted_address,location:s.geometry.location,rating:s.rating,total_ratings:s.user_ratings_total,open_now:s.opening_hours?.open_now}))}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u641C\u5C0B\u6642\u767C\u751F\u932F\u8AA4"}}}async getPlaceDetails(e){try{let r=await this.mapsTools.getPlaceDetails(e);return{success:!0,data:{name:r.name,address:r.formatted_address,location:r.geometry?.location,rating:r.rating,total_ratings:r.user_ratings_total,open_now:r.opening_hours?.open_now,phone:r.formatted_phone_number,website:r.website,price_level:r.price_level,reviews:r.reviews?.map(t=>({rating:t.rating,text:t.text,time:t.time,author_name:t.author_name}))}}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u6642\u767C\u751F\u932F\u8AA4"}}}async geocode(e){try{return{success:!0,data:await this.mapsTools.geocode(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u6642\u767C\u751F\u932F\u8AA4"}}}async reverseGeocode(e,r){try{return{success:!0,data:await this.mapsTools.reverseGeocode(e,r)}}catch(t){return{success:!1,error:t instanceof Error?t.message:"\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u6642\u767C\u751F\u932F\u8AA4"}}}async calculateDistanceMatrix(e,r,t="driving"){try{return{success:!0,data:await this.mapsTools.calculateDistanceMatrix(e,r,t)}}catch(s){return{success:!1,error:s instanceof Error?s.message:"\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u6642\u767C\u751F\u932F\u8AA4"}}}async getDirections(e,r,t="driving",s,n){try{let i=s?new Date(s):new Date,c=n?new Date(n):void 0;return{success:!0,data:await this.mapsTools.getDirections(e,r,t,i,c)}}catch(i){return{success:!1,error:i instanceof Error?i.message:"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u6642\u767C\u751F\u932F\u8AA4"}}}async getElevation(e){try{return{success:!0,data:await this.mapsTools.getElevation(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4"}}}};var q="search_nearby",W="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",F={center:p.object({value:p.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:p.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point"),keyword:p.string().optional().describe("Search keyword (e.g., restaurant, cafe, hotel)"),radius:p.number().default(1e3).describe("Search radius in meters"),openNow:p.boolean().default(!1).describe("Only show places that are currently open"),minRating:p.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")},A=null;async function U(a){try{A||(A=new l);let e=await A.searchNearby(a);return e.success?{content:[{type:"text",text:`location: ${JSON.stringify(e.location,null,2)}
3
+ `+JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u641C\u5C0B\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var y={NAME:q,DESCRIPTION:W,SCHEMA:F,ACTION:U};import{z as B}from"zod";var Q="get_place_details",X="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",ee={placeId:B.string().describe("Google Maps place ID")},O=null;async function re(a){try{O||(O=new l);let e=await O.getPlaceDetails(a.placeId);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var h={NAME:Q,DESCRIPTION:X,SCHEMA:ee,ACTION:re};import{z as te}from"zod";var se="maps_geocode",oe="Convert addresses or place names to geographic coordinates (latitude and longitude)",ae={address:te.string().describe("Address or place name to convert to coordinates")},T=null;async function ne(a){try{T||(T=new l);let e=await T.geocode(a.address);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:se,DESCRIPTION:oe,SCHEMA:ae,ACTION:ne};import{z as L}from"zod";var ie="maps_reverse_geocode",ce="Convert geographic coordinates (latitude and longitude) to a human-readable address",le={latitude:L.number().describe("Latitude coordinate"),longitude:L.number().describe("Longitude coordinate")},C=null;async function de(a){try{C||(C=new l);let e=await C.reverseGeocode(a.latitude,a.longitude);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5EA7\u6A19\u8F49\u63DB\u5730\u5740\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var E={NAME:ie,DESCRIPTION:ce,SCHEMA:le,ACTION:de};import{z as P}from"zod";var ue="maps_distance_matrix",pe="Calculate travel distances and durations between multiple origins and destinations for different travel modes",me={origins:P.array(P.string()).describe("List of origin addresses or coordinates"),destinations:P.array(P.string()).describe("List of destination addresses or coordinates"),mode:P.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")},R=null;async function ge(a){try{R||(R=new l);let e=await R.calculateDistanceMatrix(a.origins,a.destinations,a.mode);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u8A08\u7B97\u8DDD\u96E2\u77E9\u9663\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var _={NAME:ue,DESCRIPTION:pe,SCHEMA:me,ACTION:ge};import{z as b}from"zod";var fe="maps_directions",ye="Get detailed turn-by-turn navigation directions between two locations with route information",he={origin:b.string().describe("Starting point address or coordinates"),destination:b.string().describe("Destination address or coordinates"),mode:b.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:b.string().optional().describe("Departure time (ISO string format)"),arrival_time:b.string().optional().describe("Arrival time (ISO string format)")},I=null;async function ve(a){try{I||(I=new l);let e=await I.getDirections(a.origin,a.destination,a.mode,a.departure_time,a.arrival_time);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var S={NAME:fe,DESCRIPTION:ye,SCHEMA:he,ACTION:ve};import{z as N}from"zod";var Ee="maps_elevation",Pe="Get elevation data (height above sea level) for specific geographic locations",_e={locations:N.array(N.object({latitude:N.number().describe("Latitude coordinate"),longitude:N.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")},D=null;async function be(a){try{D||(D=new l);let e=await D.getElevation(a.locations);return e.success?{content:[{type:"text",text:JSON.stringify(e.data,null,2)}],isError:!1}:{content:[{type:"text",text:e.error||"\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var w={NAME:Ee,DESCRIPTION:Pe,SCHEMA:_e,ACTION:be};var Se=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:y.NAME,description:y.DESCRIPTION,schema:y.SCHEMA,action:a=>y.ACTION(a)},{name:h.NAME,description:h.DESCRIPTION,schema:h.SCHEMA,action:a=>h.ACTION(a)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:a=>v.ACTION(a)},{name:E.NAME,description:E.DESCRIPTION,schema:E.SCHEMA,action:a=>E.ACTION(a)},{name:_.NAME,description:_.DESCRIPTION,schema:_.SCHEMA,action:a=>_.ACTION(a)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,action:a=>S.ACTION(a)},{name:w.NAME,description:w.DESCRIPTION,schema:w.SCHEMA,action:a=>w.ACTION(a)}]}],H=Se;import{McpServer as we}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as Me}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as Ne}from"@modelcontextprotocol/sdk/types.js";import z from"express";import{randomUUID as xe}from"node:crypto";var Ae="0.0.1",x=class{constructor(e,r){this.transports={};this.httpServer=null;this.serverName=e,this.server=new we({name:this.serverName,version:Ae},{capabilities:{logging:{},tools:{}}}),this.registerTools(r)}registerTools(e){e.forEach(r=>{this.server.tool(r.name,r.description,r.schema,async t=>r.action(t))})}async connect(e){await this.server.connect(e);let r=process.stdout.write.bind(process.stdout);process.stdout.write=(t,s,n)=>typeof t=="string"&&!t.startsWith("{")?!0:r(t,s,n),o.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let r=z();r.use(z.json()),r.post("/mcp",async(s,n)=>{let i=s.headers["mcp-session-id"],c;if(i&&this.transports[i])c=this.transports[i];else if(!i&&Ne(s.body))c=new Me({sessionIdGenerator:()=>xe(),onsessioninitialized:m=>{this.transports[m]=c,o.log(`[${this.serverName}] New session initialized: ${m}`)}}),c.onclose=()=>{c.sessionId&&(delete this.transports[c.sessionId],o.log(`[${this.serverName}] Session closed: ${c.sessionId}`))},await this.server.connect(c);else{n.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await c.handleRequest(s,n,s.body)});let t=async(s,n)=>{let i=s.headers["mcp-session-id"];if(!i||!this.transports[i]){n.status(400).send("Invalid or missing session ID");return}await this.transports[i].handleRequest(s,n)};r.get("/mcp",t),r.delete("/mcp",t),this.httpServer=r.listen(e,()=>{o.log(`[${this.serverName}] HTTP server listening on port ${e}`),o.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){o.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,r)=>{this.httpServer.close(t=>{if(t){o.error(`[${this.serverName}] Error stopping HTTP server:`,t),r(t);return}o.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let s=Object.values(this.transports).map(n=>(n.sessionId&&delete this.transports[n.sessionId],Promise.resolve()));Promise.all(s).then(()=>{o.log(`[${this.serverName}] All transports closed.`),e()}).catch(n=>{o.error(`[${this.serverName}] Error during bulk transport closing:`,n),r(n)})})})}};import{fileURLToPath as Ce}from"url";import{dirname as Re}from"path";import{readFileSync as Ie}from"fs";var De=Ce(import.meta.url),K=Re(De);J({path:G(process.cwd(),".env")});J({path:G(K,"../.env")});async function Ge(a,e){a&&(process.env.MCP_SERVER_PORT=a.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),o.log("\u{1F680} Starting Google Maps MCP Server..."),o.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),o.log("");let r=H.map(async t=>{let s=process.env[t.portEnvVar];if(!s){o.error(`\u26A0\uFE0F [${t.name}] Port environment variable ${t.portEnvVar} not set.`),o.log(`\u{1F4A1} Please set ${t.portEnvVar} in your .env file or use --port parameter.`),o.log(` Example: ${t.portEnvVar}=3000 or --port 3000`);return}let n=Number(s);if(isNaN(n)||n<=0){o.error(`\u274C [${t.name}] Invalid port number "${s}" defined in ${t.portEnvVar}.`);return}try{let i=new x(t.name,t.tools);o.log(`\u{1F527} [${t.name}] Initializing MCP Server in HTTP mode on port ${n}...`),await i.startHttpServer(n),o.log(`\u2705 [${t.name}] MCP Server started successfully!`),o.log(` \u{1F310} Endpoint: http://localhost:${n}/mcp`),o.log(` \u{1F4DA} Tools: ${t.tools.length} available`)}catch(i){o.error(`\u274C [${t.name}] Failed to start MCP Server on port ${n}:`,i)}});await Promise.allSettled(r),o.log(""),o.log("\u{1F389} Server initialization completed!"),o.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}var $e=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")),ke=import.meta.url===`file://${process.argv[1]}`;if($e||ke){let a="0.0.0";try{let r=G(K,"../package.json");a=JSON.parse(Ie(r,"utf-8")).version}catch{a="0.0.0"}let e=Oe(Te(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(a).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();o.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),o.log(" A Model Context Protocol server for Google Maps services"),o.log(""),e.apikey||(o.log("\u26A0\uFE0F Google Maps API Key not found!"),o.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),o.log(" Example: mcp-google-map --apikey your_api_key_here"),o.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),o.log("")),Ge(e.port,e.apikey).catch(r=>{o.error("\u274C Failed to start server:",r),process.exit(1)})}export{Ge as startServer};
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a}from"./chunk-W2DM2HDK.js";export{a as Logger};
1
+ import{a}from"./chunk-V2GKYT7F.js";export{a as Logger};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cablate/mcp-google-map",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "description": "Google Maps MCP server with streamable HTTP transport support for location services, geocoding, and navigation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -1 +0,0 @@
1
- var r={log:(...o)=>{console.error("[INFO]",...o)},error:(...o)=>{console.error("[ERROR]",...o)}};export{r as a};