@cablate/mcp-google-map 0.0.14 β†’ 0.0.16

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.
Files changed (3) hide show
  1. package/README.md +87 -19
  2. package/dist/cli.js +2 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -16,6 +16,7 @@ Special thanks to [@junyinnnn](https://github.com/junyinnnn) for helping add sup
16
16
  ## βœ… Testing Status
17
17
 
18
18
  **This MCP server has been tested and verified to work correctly with:**
19
+
19
20
  - Claude Desktop
20
21
  - Dive Desktop
21
22
  - MCP protocol implementations
@@ -27,14 +28,17 @@ All tools and features are confirmed functional through real-world testing.
27
28
  ### πŸ—ΊοΈ Google Maps Integration
28
29
 
29
30
  - **Location Search**
31
+
30
32
  - Search for places near a specific location with customizable radius and filters
31
33
  - Get detailed place information including ratings, opening hours, and contact details
32
34
 
33
35
  - **Geocoding Services**
36
+
34
37
  - Convert addresses to coordinates (geocoding)
35
38
  - Convert coordinates to addresses (reverse geocoding)
36
39
 
37
40
  - **Distance & Directions**
41
+
38
42
  - Calculate distances and travel times between multiple origins and destinations
39
43
  - Get detailed turn-by-turn directions between two points
40
44
  - Support for different travel modes (driving, walking, bicycling, transit)
@@ -51,43 +55,94 @@ All tools and features are confirmed functional through real-world testing.
51
55
 
52
56
  ## Installation
53
57
 
54
- ### 1. via NPM
58
+ > ⚠️ **Important Notice**: This server uses HTTP transport, not stdio. Direct npx usage in MCP Server Settings is **NOT supported**.
59
+
60
+ ### Method 1: Global Installation (Recommended)
55
61
 
56
62
  ```bash
63
+ # Install globally
57
64
  npm install -g @cablate/mcp-google-map
65
+
66
+ # Run the server
67
+ mcp-google-map --port 3000 --apikey "your_api_key_here"
68
+
69
+ # Using short options
70
+ mcp-google-map -p 3000 -k "your_api_key_here"
58
71
  ```
59
72
 
60
- ### 2. Run the Server
73
+ ### Method 2: Using npx (Quick Start)
74
+
75
+ > ⚠️ **Warning**: Cannot be used directly in MCP Server Settings with stdio mode
76
+
77
+ **Step 1: Launch HTTP Server in Terminal**
61
78
 
62
79
  ```bash
80
+ # Run in a separate terminal
81
+ npx @cablate/mcp-google-map --port 3000 --apikey "YOUR_API_KEY"
63
82
 
64
- mcp-google-map --port 3000 --apikey "your_api_key_here"
83
+ # Or with environment variable
84
+ GOOGLE_MAPS_API_KEY=YOUR_API_KEY npx @cablate/mcp-google-map
85
+ ```
65
86
 
66
- # Using short options
67
- mcp-google-map -p 3000 -k "your_api_key_here"
87
+ **Step 2: Configure MCP Client to Use HTTP**
68
88
 
69
- # Show help information
70
- mcp-google-map --help
89
+ ```json
90
+ {
91
+ "mcp-google-map": {
92
+ "transport": "http",
93
+ "url": "http://localhost:3000/mcp"
94
+ }
95
+ }
71
96
  ```
72
97
 
73
- ### 3. Server Endpoints
98
+ ### ❌ Common Mistake to Avoid
74
99
 
75
- - **Main MCP Endpoint**: `http://localhost:3000/mcp`
76
- - **Available Tools**: 8 tools including Google Maps services and echo
100
+ ```json
101
+ // This WILL NOT WORK - stdio mode not supported with npx
102
+ {
103
+ "mcp-google-map": {
104
+ "command": "npx",
105
+ "args": ["@cablate/mcp-google-map"]
106
+ }
107
+ }
108
+ ```
77
109
 
78
- ### Environment Variables
110
+ ### Server Information
79
111
 
80
- Alternatively, create a `.env` file in your working directory:
112
+ - **Endpoint**: `http://localhost:3000/mcp`
113
+ - **Transport**: HTTP (not stdio)
114
+ - **Tools**: 8 Google Maps tools available
81
115
 
82
- ```env
83
- # Required
84
- GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
116
+ ### API Key Configuration
85
117
 
86
- # Optional
87
- MCP_SERVER_PORT=3000
88
- ```
118
+ API keys can be provided in three ways (priority order):
119
+
120
+ 1. **HTTP Headers** (Highest priority)
121
+
122
+ ```json
123
+ // MCP Client config
124
+ {
125
+ "mcp-google-map": {
126
+ "transport": "streamableHttp",
127
+ "url": "http://localhost:3000/mcp",
128
+ "headers": {
129
+ "X-Google-Maps-API-Key": "YOUR_API_KEY"
130
+ }
131
+ }
132
+ }
133
+ ```
134
+
135
+ 2. **Command Line**
89
136
 
90
- **Note**: Command line options take precedence over environment variables.
137
+ ```bash
138
+ mcp-google-map --apikey YOUR_API_KEY
139
+ ```
140
+
141
+ 3. **Environment Variable** (.env file or command line)
142
+ ```env
143
+ GOOGLE_MAPS_API_KEY=your_api_key_here
144
+ MCP_SERVER_PORT=3000
145
+ ```
91
146
 
92
147
  ## Available Tools
93
148
 
@@ -192,7 +247,15 @@ If you have any questions or suggestions, feel free to reach out:
192
247
 
193
248
  ## Changelog
194
249
 
250
+ ### v0.0.15 (Latest)
251
+
252
+ - **Added HTTP Header Authentication**: Support for passing API keys via `X-Google-Maps-API-Key` header in MCP Client config
253
+ - **Fixed Concurrent User Issues**: Each session now uses its own API key without conflicts
254
+ - **Fixed npx Execution**: Resolved module bundling issues
255
+ - **Improved Documentation**: Clearer setup instructions
256
+
195
257
  ### v0.0.5
258
+
196
259
  - Added streamable HTTP transport support
197
260
  - Improved CLI interface with emoji indicators
198
261
  - Enhanced error handling and logging
@@ -200,6 +263,11 @@ If you have any questions or suggestions, feel free to reach out:
200
263
  - Updated to latest MCP SDK version
201
264
 
202
265
  ### v0.0.4
266
+
203
267
  - Initial release with basic Google Maps integration
204
268
  - Support for location search, geocoding, and directions
205
269
  - Compatible with MCP protocol
270
+
271
+ ## Star History
272
+
273
+ [![Star History Chart](https://api.star-history.com/svg?repos=cablate/mcp-google-map&type=Date)](https://www.star-history.com/#cablate/mcp-google-map&Date)
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 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.en;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};
2
+ import{a as n}from"./chunk-W2DM2HDK.js";import{config as k}from"dotenv";import{resolve as R}from"path";import Me from"yargs";import{hideBin as Ne}from"yargs/helpers";import{z as m}from"zod";import{Client as z,Language as J}from"@googlemaps/google-maps-services-js";import j from"dotenv";j.config();var M=class{constructor(e){this.defaultLanguage=J.en;if(this.client=new z({}),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 s=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(s=s.filter(a=>(a.rating||0)>=(e.minRating||0))),s}catch(t){throw n.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:this.apiKey}})).data.result}catch(r){throw n.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:this.apiKey,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 n.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 n.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:this.apiKey}});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 n.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 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(`\u8DDD\u96E2\u77E9\u9663\u8A08\u7B97\u5931\u6557: ${a.status}`);let i=[],c=[];return a.rows.forEach(f=>{let l=[],g=[];f.elements.forEach(p=>{p.status==="OK"?(l.push({value:p.distance.value,text:p.distance.text}),g.push({value:p.duration.value,text:p.duration.text})):(l.push(null),g.push(null))}),i.push(l),c.push(g)}),{distances:i,durations:c,origin_addresses:a.origin_addresses,destination_addresses:a.destination_addresses}}catch(s){throw n.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,a){try{let i;a&&(i=Math.floor(a.getTime()/1e3));let c;i||(s instanceof Date?c=Math.floor(s.getTime()/1e3):s?c=s:c="now");let l=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:this.apiKey,arrival_time:i,departure_time:c}})).data;if(l.status!=="OK")throw new Error(`\u8DEF\u7DDA\u6307\u5F15\u7372\u53D6\u5931\u6557: ${l.status} (arrival_time: ${i}, departure_time: ${c})`);if(l.routes.length===0)throw new Error("\u627E\u4E0D\u5230\u8DEF\u7DDA");let g=l.routes[0],p=g.legs[0],O=y=>{if(!y||typeof y.value!="number")return"";let H=new Date(y.value*1e3),I={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return y.time_zone&&typeof y.time_zone=="string"&&(I.timeZone=y.time_zone),H.toLocaleString(this.defaultLanguage.toString(),I)};return{routes:l.routes,summary:g.summary,total_distance:{value:p.distance.value,text:p.distance.text},total_duration:{value:p.duration.value,text:p.duration.text},arrival_time:O(p.arrival_time),departure_time:O(p.departure_time)}}catch(i){throw n.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(a=>({lat:a.latitude,lng:a.longitude})),s=(await this.client.elevation({params:{locations:r,key:this.apiKey}})).data;if(s.status!=="OK")throw new Error(`\u6D77\u62D4\u6578\u64DA\u7372\u53D6\u5931\u6557: ${s.status}`);return s.results.map((a,i)=>({elevation:a.elevation,location:r[i]}))}catch(r){throw n.error("Error in getElevation:",r),new Error("\u7372\u53D6\u6D77\u62D4\u6578\u64DA\u6642\u767C\u751F\u932F\u8AA4")}}};var d=class{constructor(e){this.mapsTools=new M(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(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,a){try{let i=s?new Date(s):new Date,c=a?new Date(a):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"}}}};import{AsyncLocalStorage as V}from"node:async_hooks";var D=new V;function u(){return D.getStore()?.apiKey||process.env.GOOGLE_MAPS_API_KEY}function T(o,e){return D.run(o,e)}var q="search_nearby",W="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",Y={center:m.object({value:m.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:m.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point"),keyword:m.string().optional().describe("Search keyword (e.g., restaurant, cafe, hotel)"),radius:m.number().default(1e3).describe("Search radius in meters"),openNow:m.boolean().default(!1).describe("Only show places that are currently open"),minRating:m.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")};async function Z(o){try{let e=u(),t=await new d(e).searchNearby(o);return t.success?{content:[{type:"text",text:`location: ${JSON.stringify(t.location,null,2)}
3
+ `+JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.error||"\u641C\u5C0B\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u641C\u5C0B\u9644\u8FD1\u5730\u9EDE\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var h={NAME:q,DESCRIPTION:W,SCHEMA:Y,ACTION:Z};import{z as F}from"zod";var B="get_place_details",U="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",Q={placeId:F.string().describe("Google Maps place ID")};async function X(o){try{let e=u(),t=await new d(e).getPlaceDetails(o.placeId);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.error||"\u7372\u53D6\u8A73\u7D30\u8CC7\u8A0A\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u5730\u9EDE\u8A73\u7D30\u8CC7\u8A0A\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:B,DESCRIPTION:U,SCHEMA:Q,ACTION:X};import{z as ee}from"zod";var re="maps_geocode",te="Convert addresses or place names to geographic coordinates (latitude and longitude)",se={address:ee.string().describe("Address or place name to convert to coordinates")};async function oe(o){try{let e=u(),t=await new d(e).geocode(o.address);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.error||"\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u5730\u5740\u8F49\u63DB\u5EA7\u6A19\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var E={NAME:re,DESCRIPTION:te,SCHEMA:se,ACTION:oe};import{z as K}from"zod";var ne="maps_reverse_geocode",ae="Convert geographic coordinates (latitude and longitude) to a human-readable address",ie={latitude:K.number().describe("Latitude coordinate"),longitude:K.number().describe("Longitude coordinate")};async function ce(o){try{let e=u(),t=await new d(e).reverseGeocode(o.latitude,o.longitude);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 b={NAME:ne,DESCRIPTION:ae,SCHEMA:ie,ACTION:ce};import{z as P}from"zod";var le="maps_distance_matrix",de="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")};async function ue(o){try{let e=u(),t=await new d(e).calculateDistanceMatrix(o.origins,o.destinations,o.mode);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 S={NAME:le,DESCRIPTION:de,SCHEMA:pe,ACTION:ue};import{z as _}from"zod";var ge="maps_directions",me="Get detailed turn-by-turn navigation directions between two locations with route information",ye={origin:_.string().describe("Starting point address or coordinates"),destination:_.string().describe("Destination address or coordinates"),mode:_.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:_.string().optional().describe("Departure time (ISO string format)"),arrival_time:_.string().optional().describe("Arrival time (ISO string format)")};async function fe(o){try{let e=u(),t=await new d(e).getDirections(o.origin,o.destination,o.mode,o.departure_time,o.arrival_time);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.error||"\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u5931\u6557"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`\u7372\u53D6\u8DEF\u7DDA\u6307\u5F15\u932F\u8AA4: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var w={NAME:ge,DESCRIPTION:me,SCHEMA:ye,ACTION:fe};import{z as N}from"zod";var he="maps_elevation",ve="Get elevation data (height above sea level) for specific geographic locations",Ee={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")};async function be(o){try{let e=u(),t=await new d(e).getElevation(o.locations);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 x={NAME:he,DESCRIPTION:ve,SCHEMA:Ee,ACTION:be};var Pe=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:h.NAME,description:h.DESCRIPTION,schema:h.SCHEMA,action:o=>h.ACTION(o)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:o=>v.ACTION(o)},{name:E.NAME,description:E.DESCRIPTION,schema:E.SCHEMA,action:o=>E.ACTION(o)},{name:b.NAME,description:b.DESCRIPTION,schema:b.SCHEMA,action:o=>b.ACTION(o)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,action:o=>S.ACTION(o)},{name:w.NAME,description:w.DESCRIPTION,schema:w.SCHEMA,action:o=>w.ACTION(o)},{name:x.NAME,description:x.DESCRIPTION,schema:x.SCHEMA,action:o=>x.ACTION(o)}]}],G=Pe;import{McpServer as Se}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as _e}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as we}from"@modelcontextprotocol/sdk/types.js";import $ from"express";import{randomUUID as xe}from"node:crypto";var A=class o{constructor(){this.defaultApiKey=process.env.GOOGLE_MAPS_API_KEY}static getInstance(){return o.instance||(o.instance=new o),o.instance}setDefaultApiKey(e){this.defaultApiKey=e,process.env.GOOGLE_MAPS_API_KEY=e}getApiKey(e,r){if(e){let t=e.headers["x-google-maps-api-key"];if(t)return t;let s=e.headers.authorization;if(s&&s.startsWith("Bearer "))return s.substring(7)}return r||this.defaultApiKey}hasApiKey(e,r){return!!this.getApiKey(e,r)}isValidApiKeyFormat(e){return/^[A-Za-z0-9_-]{20,50}$/.test(e)}};var Ae="0.0.1",C=class{constructor(e,r){this.sessions={};this.httpServer=null;this.serverName=e,this.server=new Se({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,a)=>typeof t=="string"&&!t.startsWith("{")?!0:r(t,s,a),n.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let r=$();r.use($.json()),r.post("/mcp",async(s,a)=>{let i=s.headers["mcp-session-id"],c,l=A.getInstance().getApiKey(s);if(n.log(`${this.serverName} Get API KEY: ${l}`),i&&this.sessions[i])c=this.sessions[i],l&&(c.apiKey=l);else if(!i&&we(s.body)){let g=new _e({sessionIdGenerator:()=>xe(),onsessioninitialized:p=>{this.sessions[p]=c,n.log(`[${this.serverName}] New session initialized: ${p}`)}});c={transport:g,apiKey:l},g.onclose=()=>{g.sessionId&&(delete this.sessions[g.sessionId],n.log(`[${this.serverName}] Session closed: ${g.sessionId}`))},await this.server.connect(g)}else{a.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await T({apiKey:c.apiKey,sessionId:i},async()=>{await c.transport.handleRequest(s,a,s.body)})});let t=async(s,a)=>{let i=s.headers["mcp-session-id"];if(!i||!this.sessions[i]){a.status(400).send("Invalid or missing session ID");return}let c=this.sessions[i],l=A.getInstance().getApiKey(s);l&&(c.apiKey=l),await T({apiKey:c.apiKey,sessionId:i},async()=>{await c.transport.handleRequest(s,a)})};r.get("/mcp",t),r.delete("/mcp",t),this.httpServer=r.listen(e,()=>{n.log(`[${this.serverName}] HTTP server listening on port ${e}`),n.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){n.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,r)=>{this.httpServer.close(t=>{if(t){n.error(`[${this.serverName}] Error stopping HTTP server:`,t),r(t);return}n.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let s=Object.values(this.sessions).map(a=>(a.transport.sessionId&&delete this.sessions[a.transport.sessionId],Promise.resolve()));Promise.all(s).then(()=>{n.log(`[${this.serverName}] All transports closed.`),e()}).catch(a=>{n.error(`[${this.serverName}] Error during bulk transport closing:`,a),r(a)})})})}};import{fileURLToPath as Ce}from"url";import{dirname as Te}from"path";import{readFileSync as Re}from"fs";var Oe=Ce(import.meta.url),L=Te(Oe);k({path:R(process.cwd(),".env")});k({path:R(L,"../.env")});async function Ie(o,e){o&&(process.env.MCP_SERVER_PORT=o.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),n.log("\u{1F680} Starting Google Maps MCP Server..."),n.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),n.log("");let r=G.map(async t=>{let s=process.env[t.portEnvVar];if(!s){n.error(`\u26A0\uFE0F [${t.name}] Port environment variable ${t.portEnvVar} not set.`),n.log(`\u{1F4A1} Please set ${t.portEnvVar} in your .env file or use --port parameter.`),n.log(` Example: ${t.portEnvVar}=3000 or --port 3000`);return}let a=Number(s);if(isNaN(a)||a<=0){n.error(`\u274C [${t.name}] Invalid port number "${s}" defined in ${t.portEnvVar}.`);return}try{let i=new C(t.name,t.tools);n.log(`\u{1F527} [${t.name}] Initializing MCP Server in HTTP mode on port ${a}...`),await i.startHttpServer(a),n.log(`\u2705 [${t.name}] MCP Server started successfully!`),n.log(` \u{1F310} Endpoint: http://localhost:${a}/mcp`),n.log(` \u{1F4DA} Tools: ${t.tools.length} available`)}catch(i){n.error(`\u274C [${t.name}] Failed to start MCP Server on port ${a}:`,i)}});await Promise.allSettled(r),n.log(""),n.log("\u{1F389} Server initialization completed!"),n.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}var De=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(De||Ke){let o="0.0.0";try{let r=R(L,"../package.json");o=JSON.parse(Re(r,"utf-8")).version}catch{o="0.0.0"}let e=Me(Ne(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(o).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();n.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),n.log(" A Model Context Protocol server for Google Maps services"),n.log(""),e.apikey||(n.log("\u26A0\uFE0F Google Maps API Key not found!"),n.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),n.log(" Example: mcp-google-map --apikey your_api_key_here"),n.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),n.log("")),Ie(e.port,e.apikey).catch(r=>{n.error("\u274C Failed to start server:",r),process.exit(1)})}export{Ie as startServer};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cablate/mcp-google-map",
3
- "version": "0.0.14",
3
+ "version": "0.0.16",
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",