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

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
@@ -25,6 +25,10 @@ All tools and features are confirmed functional through real-world testing.
25
25
 
26
26
  ## Features
27
27
 
28
+ ### πŸ†• Latest Updates
29
+
30
+ - **New Places API Integration**: Updated to use Google's new Places API (New) instead of the legacy API to resolve HTTP 403 errors and ensure continued functionality.
31
+
28
32
  ### πŸ—ΊοΈ Google Maps Integration
29
33
 
30
34
  - **Location Search**
@@ -125,8 +129,9 @@ API keys can be provided in three ways (priority order):
125
129
  "mcp-google-map": {
126
130
  "transport": "streamableHttp",
127
131
  "url": "http://localhost:3000/mcp",
132
+ // if your MCP Client support 'headers'
128
133
  "headers": {
129
- "X-Google-Maps-API-Key": "YOUR_API_KEY"
134
+ "X-Google-Maps-API-Key": "YOUR_API_KEY"
130
135
  }
131
136
  }
132
137
  }
@@ -194,7 +199,6 @@ src/
194
199
  β”œβ”€β”€ core/
195
200
  β”‚ └── BaseMcpServer.ts # Base MCP server with streamable HTTP
196
201
  └── tools/
197
- β”œβ”€β”€ echo.ts # Echo service tool
198
202
  └── maps/ # Google Maps tools
199
203
  β”œβ”€β”€ toolclass.ts # Google Maps API client
200
204
  β”œβ”€β”€ searchPlaces.ts # Maps service layer
@@ -247,14 +251,18 @@ If you have any questions or suggestions, feel free to reach out:
247
251
 
248
252
  ## Changelog
249
253
 
250
- ### v0.0.15 (Latest)
254
+ ### v0.0.18 (Latest)
255
+
256
+ - **Error response improvements**: Now all error messages are in English with more detailed information (previously in Chinese)
257
+
258
+ ### v0.0.17
251
259
 
252
260
  - **Added HTTP Header Authentication**: Support for passing API keys via `X-Google-Maps-API-Key` header in MCP Client config
253
261
  - **Fixed Concurrent User Issues**: Each session now uses its own API key without conflicts
254
262
  - **Fixed npx Execution**: Resolved module bundling issues
255
263
  - **Improved Documentation**: Clearer setup instructions
256
264
 
257
- ### v0.0.5
265
+ ### v0.0.14
258
266
 
259
267
  - Added streamable HTTP transport support
260
268
  - Improved CLI interface with emoji indicators
@@ -262,12 +270,6 @@ If you have any questions or suggestions, feel free to reach out:
262
270
  - Added comprehensive tool descriptions for LLM integration
263
271
  - Updated to latest MCP SDK version
264
272
 
265
- ### v0.0.4
266
-
267
- - Initial release with basic Google Maps integration
268
- - Support for location search, geocoding, and directions
269
- - Compatible with MCP protocol
270
-
271
273
  ## Star History
272
274
 
273
275
  [![Star History Chart](https://api.star-history.com/svg?repos=cablate/mcp-google-map&type=Date)](https://www.star-history.com/#cablate/mcp-google-map&Date)
@@ -0,0 +1 @@
1
+ import{Client as x,Language as R}from"@googlemaps/google-maps-services-js";import A from"dotenv";A.config();function y(c){let e=c?.response?.data?.error_message,r=c?.response?.status;return e?`${e} (HTTP ${r})`:c instanceof Error?c.message:String(c)}var P=class{constructor(e){this.defaultLanguage=R.en;if(this.client=new x({}),this.apiKey=e||process.env.GOOGLE_MAPS_API_KEY||"",!this.apiKey)throw new Error("Google Maps API Key is required")}async searchNearbyPlaces(e){let r={location:e.location,radius:e.radius||1e3,keyword:e.keyword,opennow:e.openNow,language:this.defaultLanguage,key:this.apiKey};try{let n=(await this.client.placesNearby({params:r})).data.results;return e.minRating&&(n=n.filter(a=>(a.rating||0)>=(e.minRating||0))),n}catch(t){throw d.error("Error in searchNearbyPlaces:",t),new Error(`Failed to search nearby places: ${y(t)}`)}}async getPlaceDetails(e){try{return(await this.client.placeDetails({params:{place_id:e,fields:["name","rating","formatted_address","opening_hours","reviews","geometry","formatted_phone_number","website","price_level","photos"],language:this.defaultLanguage,key:this.apiKey}})).data.result}catch(r){throw d.error("Error in getPlaceDetails:",r),new Error(`Failed to get place details for ${e}: ${y(r)}`)}}async geocodeAddress(e){try{let r=await this.client.geocode({params:{address:e,key:this.apiKey,language:this.defaultLanguage}});if(r.data.results.length===0)throw new Error(`No location found for address: "${e}"`);let t=r.data.results[0],n=t.geometry.location;return{lat:n.lat,lng:n.lng,formatted_address:t.formatted_address,place_id:t.place_id}}catch(r){throw d.error("Error in geocodeAddress:",r),new Error(`Failed to geocode address "${e}": ${y(r)}`)}}parseCoordinates(e){let r=e.split(",").map(t=>parseFloat(t.trim()));if(r.length!==2||isNaN(r[0])||isNaN(r[1]))throw new Error(`Invalid coordinate format: "${e}". Please use "latitude,longitude" format (e.g., "25.033,121.564"`);return{lat:r[0],lng:r[1]}}async getLocation(e){return e.isCoordinates?this.parseCoordinates(e.value):this.geocodeAddress(e.value)}async geocode(e){try{let r=await this.geocodeAddress(e);return{location:{lat:r.lat,lng:r.lng},formatted_address:r.formatted_address||"",place_id:r.place_id||""}}catch(r){throw d.error("Error in geocode:",r),new Error(`Failed to geocode address "${e}": ${y(r)}`)}}async reverseGeocode(e,r){try{let t=await this.client.reverseGeocode({params:{latlng:{lat:e,lng:r},language:this.defaultLanguage,key:this.apiKey}});if(t.data.results.length===0)throw new Error(`No address found for coordinates: (${e}, ${r})`);let n=t.data.results[0];return{formatted_address:n.formatted_address,place_id:n.place_id,address_components:n.address_components}}catch(t){throw d.error("Error in reverseGeocode:",t),new Error(`Failed to reverse geocode coordinates (${e}, ${r}): ${y(t)}`)}}async calculateDistanceMatrix(e,r,t="driving"){try{let a=(await this.client.distancematrix({params:{origins:e,destinations:r,mode:t,language:this.defaultLanguage,key:this.apiKey}})).data;if(a.status!=="OK")throw new Error(`Distance matrix calculation failed with status: ${a.status}`);let o=[],l=[];return a.rows.forEach(h=>{let u=[],m=[];h.elements.forEach(i=>{i.status==="OK"?(u.push({value:i.distance.value,text:i.distance.text}),m.push({value:i.duration.value,text:i.duration.text})):(u.push(null),m.push(null))}),o.push(u),l.push(m)}),{distances:o,durations:l,origin_addresses:a.origin_addresses,destination_addresses:a.destination_addresses}}catch(n){throw d.error("Error in calculateDistanceMatrix:",n),new Error(`Failed to calculate distance matrix: ${y(n)}`)}}async getDirections(e,r,t="driving",n,a){try{let o;a&&(o=Math.floor(a.getTime()/1e3));let l;o||(n instanceof Date?l=Math.floor(n.getTime()/1e3):n?l=n:l="now");let u=(await this.client.directions({params:{origin:e,destination:r,mode:t,language:this.defaultLanguage,key:this.apiKey,arrival_time:o,departure_time:l}})).data;if(u.status!=="OK")throw new Error(`Failed to get directions with status: ${u.status} (arrival_time: ${o}, departure_time: ${l}`);if(u.routes.length===0)throw new Error(`No route found from "${e}" to "${r}" with mode: ${t}`);let m=u.routes[0],i=m.legs[0],f=s=>{if(!s||typeof s.value!="number")return"";let g=new Date(s.value*1e3),p={year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit",hour12:!1};return s.time_zone&&typeof s.time_zone=="string"&&(p.timeZone=s.time_zone),g.toLocaleString(this.defaultLanguage.toString(),p)};return{routes:u.routes,summary:m.summary,total_distance:{value:i.distance.value,text:i.distance.text},total_duration:{value:i.duration.value,text:i.duration.text},arrival_time:f(i.arrival_time),departure_time:f(i.departure_time)}}catch(o){throw d.error("Error in getDirections:",o),new Error(`Failed to get directions from "${e}" to "${r}": ${y(o)}`)}}async getElevation(e){try{let r=e.map(a=>({lat:a.latitude,lng:a.longitude})),n=(await this.client.elevation({params:{locations:r,key:this.apiKey}})).data;if(n.status!=="OK")throw new Error(`Failed to get elevation data with status: ${n.status}`);return n.results.map((a,o)=>({elevation:a.elevation,location:r[o]}))}catch(r){throw d.error("Error in getElevation:",r),new Error(`Failed to get elevation data for ${e.length} location(s): ${y(r)}`)}}};import{PlacesClient as T}from"@googlemaps/places";var _=class{constructor(e){this.defaultLanguage="en";this.placeFieldMask=["displayName","name","id","formattedAddress","location","utcOffsetMinutes","regularOpeningHours.periods","regularOpeningHours.weekdayDescriptions","currentOpeningHours.openNow","nationalPhoneNumber","websiteUri","priceLevel","rating","userRatingCount","reviews.rating","reviews.text","reviews.publishTime","reviews.authorAttribution.displayName","photos.heightPx","photos.widthPx","photos.name"].join(",");if(this.client=new T({apiKey:e||process.env.GOOGLE_MAPS_API_KEY||""}),!e&&!process.env.GOOGLE_MAPS_API_KEY)throw new Error("Google Maps API Key is required")}async getPlaceDetails(e){try{let r=`places/${e}`,[t]=await this.client.getPlace({name:r,languageCode:this.defaultLanguage},{otherArgs:{headers:{"X-Goog-FieldMask":this.placeFieldMask}}});return this.transformPlaceResponse(t)}catch(r){throw d.error("Error in getPlaceDetails (New API):",r),new Error(`Failed to get place details for ${e}: ${this.extractErrorMessage(r)}`)}}transformPlaceResponse(e){return{name:e.displayName?.text||e.name||"",place_id:this.extractLegacyPlaceId(e),formatted_address:e.formattedAddress||"",geometry:{location:{lat:e.location?.latitude||0,lng:e.location?.longitude||0}},rating:e.rating||0,user_ratings_total:e.userRatingCount||0,opening_hours:e.regularOpeningHours?{open_now:this.isCurrentlyOpen(e.regularOpeningHours,e.utcOffsetMinutes,e.currentOpeningHours),weekday_text:this.formatOpeningHours(e.regularOpeningHours)}:void 0,formatted_phone_number:e.nationalPhoneNumber||"",website:e.websiteUri||"",price_level:e.priceLevel||0,reviews:e.reviews?.map(r=>({rating:r.rating||0,text:r.text?.text||"",time:r.publishTime?.seconds||0,author_name:r.authorAttribution?.displayName||""}))||[],photos:e.photos?.map(r=>({photo_reference:r.name||"",height:r.heightPx||0,width:r.widthPx||0}))||[]}}extractLegacyPlaceId(e){let r=e?.name;if(typeof r=="string"&&r.startsWith("places/")){let t=r.substring(7);if(t)return t}return e?.id||""}isCurrentlyOpen(e,r,t){if(typeof t?.openNow=="boolean")return t.openNow;if(typeof e?.openNow=="boolean")return e.openNow;let n=e?.periods;if(!Array.isArray(n)||n.length===0)return!1;let a=24*60,o=a*7,{day:l,minutes:h}=this.getLocalTimeComponents(r),u=l*a+h,m={SUNDAY:0,MONDAY:1,TUESDAY:2,WEDNESDAY:3,THURSDAY:4,FRIDAY:5,SATURDAY:6},i=s=>{if(typeof s=="number"&&s>=0&&s<=6)return s;if(typeof s=="string"){let g=s.toUpperCase();if(g in m)return m[g]}},f=s=>{if(!s)return;let g=typeof s.hours=="number"?s.hours:Number(s.hours??NaN),p=typeof s.minutes=="number"?s.minutes:Number(s.minutes??NaN);if(!(!Number.isFinite(g)||!Number.isFinite(p)))return g*60+p};for(let s of n){let g=i(s?.openDay),p=i(s?.closeDay??s?.openDay),D=f(s?.openTime),N=f(s?.closeTime);if(g===void 0||D===void 0)continue;let b=g*a+D,w;p===void 0||N===void 0?w=b+a:w=p*a+N,w<=b&&(w+=o);let v=u;for(;v<b;)v+=o;if(v>=b&&v<w)return!0}return!1}getLocalTimeComponents(e){let r=new Date;if(typeof e=="number"&&Number.isFinite(e)){let t=new Date(r.getTime()+e*6e4);return{day:t.getUTCDay(),minutes:t.getUTCHours()*60+t.getUTCMinutes()}}return{day:r.getDay(),minutes:r.getHours()*60+r.getMinutes()}}formatOpeningHours(e){return e?.weekdayDescriptions||[]}extractErrorMessage(e){let r=e?.message||e?.details||e?.status;return r?`${r}`:e instanceof Error?e.message:String(e)}};var E=class{constructor(e){this.mapsTools=new P(e),this.newPlacesService=new _(e)}async searchNearby(e){try{let r=await this.mapsTools.getLocation(e.center),t=await this.mapsTools.searchNearbyPlaces({location:r,keyword:e.keyword,radius:e.radius,openNow:e.openNow,minRating:e.minRating});return{location:r,success:!0,data:t.map(n=>({name:n.name,place_id:n.place_id,address:n.formatted_address,location:n.geometry.location,rating:n.rating,total_ratings:n.user_ratings_total,open_now:n.opening_hours?.open_now}))}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred during search"}}}async getPlaceDetails(e){try{let r=await this.newPlacesService.getPlaceDetails(e);return{success:!0,data:{name:r.name,address:r.formatted_address,location:r.geometry?.location,rating:r.rating,total_ratings:r.user_ratings_total,open_now:r.opening_hours?.open_now,phone:r.formatted_phone_number,website:r.website,price_level:r.price_level,reviews:r.reviews?.map(t=>({rating:t.rating,text:t.text,time:t.time,author_name:t.author_name}))}}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while getting place details"}}}async geocode(e){try{return{success:!0,data:await this.mapsTools.geocode(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while geocoding address"}}}async reverseGeocode(e,r){try{return{success:!0,data:await this.mapsTools.reverseGeocode(e,r)}}catch(t){return{success:!1,error:t instanceof Error?t.message:"An error occurred during reverse geocoding"}}}async calculateDistanceMatrix(e,r,t="driving"){try{return{success:!0,data:await this.mapsTools.calculateDistanceMatrix(e,r,t)}}catch(n){return{success:!1,error:n instanceof Error?n.message:"An error occurred while calculating distance matrix"}}}async getDirections(e,r,t="driving",n,a){try{let o=n?new Date(n):new Date,l=a?new Date(a):void 0;return{success:!0,data:await this.mapsTools.getDirections(e,r,t,o,l)}}catch(o){return{success:!1,error:o instanceof Error?o.message:"An error occurred while getting directions"}}}async getElevation(e){try{return{success:!0,data:await this.mapsTools.getElevation(e)}}catch(r){return{success:!1,error:r instanceof Error?r.message:"An error occurred while getting elevation data"}}}};var d={log:(...c)=>{console.error("[INFO]",...c)},error:(...c)=>{console.error("[ERROR]",...c)}};export{_ as a,d as b,E as c};
package/dist/cli.js CHANGED
@@ -1,3 +1,3 @@
1
1
  #!/usr/bin/env node
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};
2
+ import{b as s,c}from"./chunk-TH44WIYW.js";import{config as K}from"dotenv";import{resolve as N}from"path";import Se from"yargs";import{hideBin as Ee}from"yargs/helpers";import{z as m}from"zod";import{AsyncLocalStorage as D}from"node:async_hooks";var O=new D;function p(){return O.getStore()?.apiKey||process.env.GOOGLE_MAPS_API_KEY}function M(t,e){return O.run(t,e)}var G="search_nearby",H="Search for nearby places based on location, with optional filtering by keywords, distance, rating, and operating hours",z={center:m.object({value:m.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:m.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point"),keyword:m.string().optional().describe("Search keyword (e.g., restaurant, cafe, hotel)"),radius:m.number().default(1e3).describe("Search radius in meters"),openNow:m.boolean().default(!1).describe("Only show places that are currently open"),minRating:m.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")};async function k(t){try{let e=p(),r=await new c(e).searchNearby(t);return r.success?{content:[{type:"text",text:`location: ${JSON.stringify(r.location,null,2)}
3
+ `+JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Search failed"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching nearby places: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var u={NAME:G,DESCRIPTION:H,SCHEMA:z,ACTION:k};import{z as J}from"zod";var L="get_place_details",j="Get detailed information about a specific place including contact details, reviews, ratings, and operating hours",V={placeId:J.string().describe("Google Maps place ID")};async function q(t){try{let e=p(),r=await new c(e).getPlaceDetails(t.placeId);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get place details"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting place details: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var y={NAME:L,DESCRIPTION:j,SCHEMA:V,ACTION:q};import{z as F}from"zod";var W="maps_geocode",Y="Convert addresses or place names to geographic coordinates (latitude and longitude)",Z={address:F.string().describe("Address or place name to convert to coordinates")};async function B(t){try{let e=p(),r=await new c(e).geocode(t.address);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to geocode address"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error geocoding address: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var f={NAME:W,DESCRIPTION:Y,SCHEMA:Z,ACTION:B};import{z as T}from"zod";var U="maps_reverse_geocode",Q="Convert geographic coordinates (latitude and longitude) to a human-readable address",X={latitude:T.number().describe("Latitude coordinate"),longitude:T.number().describe("Longitude coordinate")};async function ee(t){try{let e=p(),r=await new c(e).reverseGeocode(t.latitude,t.longitude);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to reverse geocode coordinates"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error reverse geocoding: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var v={NAME:U,DESCRIPTION:Q,SCHEMA:X,ACTION:ee};import{z as h}from"zod";var re="maps_distance_matrix",te="Calculate travel distances and durations between multiple origins and destinations for different travel modes",oe={origins:h.array(h.string()).describe("List of origin addresses or coordinates"),destinations:h.array(h.string()).describe("List of destination addresses or coordinates"),mode:h.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")};async function se(t){try{let e=p(),r=await new c(e).calculateDistanceMatrix(t.origins,t.destinations,t.mode);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to calculate distance matrix"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error calculating distance matrix: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var S={NAME:re,DESCRIPTION:te,SCHEMA:oe,ACTION:se};import{z as E}from"zod";var ne="maps_directions",ie="Get detailed turn-by-turn navigation directions between two locations with route information",ae={origin:E.string().describe("Starting point address or coordinates"),destination:E.string().describe("Destination address or coordinates"),mode:E.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:E.string().optional().describe("Departure time (ISO string format)"),arrival_time:E.string().optional().describe("Arrival time (ISO string format)")};async function ce(t){try{let e=p(),r=await new c(e).getDirections(t.origin,t.destination,t.mode,t.departure_time,t.arrival_time);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get directions"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting directions: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var P={NAME:ne,DESCRIPTION:ie,SCHEMA:ae,ACTION:ce};import{z as x}from"zod";var pe="maps_elevation",le="Get elevation data (height above sea level) for specific geographic locations",de={locations:x.array(x.object({latitude:x.number().describe("Latitude coordinate"),longitude:x.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")};async function me(t){try{let e=p(),r=await new c(e).getElevation(t.locations);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get elevation data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting elevation data: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var A={NAME:pe,DESCRIPTION:le,SCHEMA:de,ACTION:me};var ge=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:u.NAME,description:u.DESCRIPTION,schema:u.SCHEMA,action:t=>u.ACTION(t)},{name:y.NAME,description:y.DESCRIPTION,schema:y.SCHEMA,action:t=>y.ACTION(t)},{name:f.NAME,description:f.DESCRIPTION,schema:f.SCHEMA,action:t=>f.ACTION(t)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,action:t=>v.ACTION(t)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,action:t=>S.ACTION(t)},{name:P.NAME,description:P.DESCRIPTION,schema:P.SCHEMA,action:t=>P.ACTION(t)},{name:A.NAME,description:A.DESCRIPTION,schema:A.SCHEMA,action:t=>A.ACTION(t)}]}],_=ge;import{McpServer as ue}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as ye}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as fe}from"@modelcontextprotocol/sdk/types.js";import R from"express";import{randomUUID as ve}from"node:crypto";var b=class t{constructor(){this.defaultApiKey=process.env.GOOGLE_MAPS_API_KEY}static getInstance(){return t.instance||(t.instance=new t),t.instance}setDefaultApiKey(e){this.defaultApiKey=e,process.env.GOOGLE_MAPS_API_KEY=e}getApiKey(e,o){if(e){let r=e.headers["x-google-maps-api-key"];if(r)return r;let n=e.headers.authorization;if(n&&n.startsWith("Bearer "))return n.substring(7)}return o||this.defaultApiKey}hasApiKey(e,o){return!!this.getApiKey(e,o)}isValidApiKeyFormat(e){return/^[A-Za-z0-9_-]{20,50}$/.test(e)}};var he="0.0.1",C=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.server=new ue({name:this.serverName,version:he},{capabilities:{logging:{},tools:{}}}),this.registerTools(o)}registerTools(e){e.forEach(o=>{this.server.tool(o.name,o.description,o.schema,async r=>o.action(r))})}async connect(e){await this.server.connect(e);let o=process.stdout.write.bind(process.stdout);process.stdout.write=(r,n,i)=>typeof r=="string"&&!r.startsWith("{")?!0:o(r,n,i),s.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let o=R();o.use(R.json()),o.post("/mcp",async(n,i)=>{let a=n.headers["mcp-session-id"],l,d=b.getInstance().getApiKey(n);if(s.log(`${this.serverName} Get API KEY: ${d}`),a&&this.sessions[a])l=this.sessions[a],d&&(l.apiKey=d);else if(!a&&fe(n.body)){let g=new ye({sessionIdGenerator:()=>ve(),onsessioninitialized:I=>{this.sessions[I]=l,s.log(`[${this.serverName}] New session initialized: ${I}`)}});l={transport:g,apiKey:d},g.onclose=()=>{g.sessionId&&(delete this.sessions[g.sessionId],s.log(`[${this.serverName}] Session closed: ${g.sessionId}`))},await this.server.connect(g)}else{i.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await M({apiKey:l.apiKey,sessionId:a},async()=>{await l.transport.handleRequest(n,i,n.body)})});let r=async(n,i)=>{let a=n.headers["mcp-session-id"];if(!a||!this.sessions[a]){i.status(400).send("Invalid or missing session ID");return}let l=this.sessions[a],d=b.getInstance().getApiKey(n);d&&(l.apiKey=d),await M({apiKey:l.apiKey,sessionId:a},async()=>{await l.transport.handleRequest(n,i)})};o.get("/mcp",r),o.delete("/mcp",r),this.httpServer=o.listen(e,()=>{s.log(`[${this.serverName}] HTTP server listening on port ${e}`),s.log(`[${this.serverName}] MCP endpoint available at http://localhost:${e}/mcp`)})}async stopHttpServer(){if(!this.httpServer){s.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,o)=>{this.httpServer.close(r=>{if(r){s.error(`[${this.serverName}] Error stopping HTTP server:`,r),o(r);return}s.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let n=Object.values(this.sessions).map(i=>(i.transport.sessionId&&delete this.sessions[i.transport.sessionId],Promise.resolve()));Promise.all(n).then(()=>{s.log(`[${this.serverName}] All transports closed.`),e()}).catch(i=>{s.error(`[${this.serverName}] Error during bulk transport closing:`,i),o(i)})})})}};import{fileURLToPath as Pe}from"url";import{dirname as Ae}from"path";import{readFileSync as be}from"fs";var xe=Pe(import.meta.url),w=Ae(xe);K({path:N(process.cwd(),".env")});K({path:N(w,"../.env")});async function Ce(t,e){t&&(process.env.MCP_SERVER_PORT=t.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),s.log("\u{1F680} Starting Google Maps MCP Server..."),s.log("\u{1F4CD} Available tools: search_nearby, get_place_details, maps_geocode, maps_reverse_geocode, maps_distance_matrix, maps_directions, maps_elevation, echo"),s.log("\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."),s.log("");let o=_.map(async r=>{let n=process.env[r.portEnvVar];if(!n){s.error(`\u26A0\uFE0F [${r.name}] Port environment variable ${r.portEnvVar} not set.`),s.log(`\u{1F4A1} Please set ${r.portEnvVar} in your .env file or use --port parameter.`),s.log(` Example: ${r.portEnvVar}=3000 or --port 3000`);return}let i=Number(n);if(isNaN(i)||i<=0){s.error(`\u274C [${r.name}] Invalid port number "${n}" defined in ${r.portEnvVar}.`);return}try{let a=new C(r.name,r.tools);s.log(`\u{1F527} [${r.name}] Initializing MCP Server in HTTP mode on port ${i}...`),await a.startHttpServer(i),s.log(`\u2705 [${r.name}] MCP Server started successfully!`),s.log(` \u{1F310} Endpoint: http://localhost:${i}/mcp`),s.log(` \u{1F4DA} Tools: ${r.tools.length} available`)}catch(a){s.error(`\u274C [${r.name}] Failed to start MCP Server on port ${i}:`,a)}});await Promise.allSettled(o),s.log(""),s.log("\u{1F389} Server initialization completed!"),s.log("\u{1F4A1} Need help? Check the README.md for configuration details.")}var Me=process.argv[1]&&(process.argv[1].endsWith("cli.ts")||process.argv[1].endsWith("cli.js")||process.argv[1].endsWith("mcp-google-map")||process.argv[1].includes("mcp-google-map")),Ne=import.meta.url===`file://${process.argv[1]}`;if(Me||Ne){let t="0.0.0";try{let o=N(w,"../package.json");t=JSON.parse(be(o,"utf-8")).version}catch{t="0.0.0"}let e=Se(Ee(process.argv)).option("port",{alias:"p",type:"number",description:"Port to run the MCP server on",default:process.env.MCP_SERVER_PORT?parseInt(process.env.MCP_SERVER_PORT):3e3}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).option("help",{alias:"h",type:"boolean",description:"Show help"}).version(t).alias("version","v").example([["$0","Start server with default settings"],['$0 --port 3000 --apikey "your_api_key"',"Start server with custom port and API key"],['$0 -p 3001 -k "your_api_key"',"Start server with short options"]]).help().parseSync();s.log("\u{1F5FA}\uFE0F Google Maps MCP Server"),s.log(" A Model Context Protocol server for Google Maps services"),s.log(""),e.apikey||(s.log("\u26A0\uFE0F Google Maps API Key not found!"),s.log(" Please provide --apikey parameter or set GOOGLE_MAPS_API_KEY in your .env file"),s.log(" Example: mcp-google-map --apikey your_api_key_here"),s.log(" Or: GOOGLE_MAPS_API_KEY=your_api_key_here"),s.log("")),Ce(e.port,e.apikey).catch(o=>{s.error("\u274C Failed to start server:",o),process.exit(1)})}export{Ce as startServer};
package/dist/index.d.ts CHANGED
@@ -1,6 +1,135 @@
1
+ interface SearchNearbyResponse {
2
+ success: boolean;
3
+ error?: string;
4
+ data?: any[];
5
+ location?: any;
6
+ }
7
+ interface PlaceDetailsResponse {
8
+ success: boolean;
9
+ error?: string;
10
+ data?: any;
11
+ }
12
+ interface GeocodeResponse {
13
+ success: boolean;
14
+ error?: string;
15
+ data?: {
16
+ location: {
17
+ lat: number;
18
+ lng: number;
19
+ };
20
+ formatted_address: string;
21
+ place_id: string;
22
+ };
23
+ }
24
+ interface ReverseGeocodeResponse {
25
+ success: boolean;
26
+ error?: string;
27
+ data?: {
28
+ formatted_address: string;
29
+ place_id: string;
30
+ address_components: any[];
31
+ };
32
+ }
33
+ interface DistanceMatrixResponse {
34
+ success: boolean;
35
+ error?: string;
36
+ data?: {
37
+ distances: any[][];
38
+ durations: any[][];
39
+ origin_addresses: string[];
40
+ destination_addresses: string[];
41
+ };
42
+ }
43
+ interface DirectionsResponse {
44
+ success: boolean;
45
+ error?: string;
46
+ data?: {
47
+ routes: any[];
48
+ summary: string;
49
+ total_distance: {
50
+ value: number;
51
+ text: string;
52
+ };
53
+ total_duration: {
54
+ value: number;
55
+ text: string;
56
+ };
57
+ };
58
+ }
59
+ interface ElevationResponse {
60
+ success: boolean;
61
+ error?: string;
62
+ data?: Array<{
63
+ elevation: number;
64
+ location: {
65
+ lat: number;
66
+ lng: number;
67
+ };
68
+ }>;
69
+ }
70
+ declare class PlacesSearcher {
71
+ private mapsTools;
72
+ private newPlacesService;
73
+ constructor(apiKey?: string);
74
+ searchNearby(params: {
75
+ center: {
76
+ value: string;
77
+ isCoordinates: boolean;
78
+ };
79
+ keyword?: string;
80
+ radius?: number;
81
+ openNow?: boolean;
82
+ minRating?: number;
83
+ }): Promise<SearchNearbyResponse>;
84
+ getPlaceDetails(placeId: string): Promise<PlaceDetailsResponse>;
85
+ geocode(address: string): Promise<GeocodeResponse>;
86
+ reverseGeocode(latitude: number, longitude: number): Promise<ReverseGeocodeResponse>;
87
+ calculateDistanceMatrix(origins: string[], destinations: string[], mode?: "driving" | "walking" | "bicycling" | "transit"): Promise<DistanceMatrixResponse>;
88
+ getDirections(origin: string, destination: string, mode?: "driving" | "walking" | "bicycling" | "transit", departure_time?: string, arrival_time?: string): Promise<DirectionsResponse>;
89
+ getElevation(locations: Array<{
90
+ latitude: number;
91
+ longitude: number;
92
+ }>): Promise<ElevationResponse>;
93
+ }
94
+
95
+ declare class NewPlacesService {
96
+ private client;
97
+ private readonly defaultLanguage;
98
+ private readonly placeFieldMask;
99
+ constructor(apiKey?: string);
100
+ getPlaceDetails(placeId: string): Promise<{
101
+ name: any;
102
+ place_id: string;
103
+ formatted_address: any;
104
+ geometry: {
105
+ location: {
106
+ lat: any;
107
+ lng: any;
108
+ };
109
+ };
110
+ rating: any;
111
+ user_ratings_total: any;
112
+ opening_hours: {
113
+ open_now: boolean;
114
+ weekday_text: string[];
115
+ } | undefined;
116
+ formatted_phone_number: any;
117
+ website: any;
118
+ price_level: any;
119
+ reviews: any;
120
+ photos: any;
121
+ }>;
122
+ private transformPlaceResponse;
123
+ private extractLegacyPlaceId;
124
+ private isCurrentlyOpen;
125
+ private getLocalTimeComponents;
126
+ private formatOpeningHours;
127
+ private extractErrorMessage;
128
+ }
129
+
1
130
  declare const Logger: {
2
131
  log: (...args: any[]) => void;
3
132
  error: (...args: any[]) => void;
4
133
  };
5
134
 
6
- export { Logger };
135
+ export { Logger, NewPlacesService, PlacesSearcher };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{a}from"./chunk-W2DM2HDK.js";export{a as Logger};
1
+ import{a,b,c}from"./chunk-TH44WIYW.js";export{b as Logger,a as NewPlacesService,c as PlacesSearcher};
package/package.json CHANGED
@@ -1,73 +1,74 @@
1
- {
2
- "name": "@cablate/mcp-google-map",
3
- "version": "0.0.17",
4
- "description": "Google Maps MCP server with streamable HTTP transport support for location services, geocoding, and navigation",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "bin": {
8
- "mcp-google-map": "dist/cli.js"
9
- },
10
- "files": [
11
- "dist",
12
- "dist/**/*.map",
13
- "README.md"
14
- ],
15
- "scripts": {
16
- "build": "tsup --dts",
17
- "start": "node dist/cli.js",
18
- "dev": "cross-env NODE_ENV=development tsup --watch",
19
- "prepublishOnly": "npm run build"
20
- },
21
- "engines": {
22
- "node": ">=18.0.0"
23
- },
24
- "keywords": [
25
- "google",
26
- "map",
27
- "api",
28
- "llm",
29
- "typescript",
30
- "mcp",
31
- "server",
32
- "streamable",
33
- "location",
34
- "geocoding",
35
- "navigation"
36
- ],
37
- "author": "CabLate",
38
- "license": "MIT",
39
- "homepage": "https://github.com/cablate/mcp-google-map#readme",
40
- "repository": {
41
- "type": "git",
42
- "url": "git+https://github.com/cablate/mcp-google-map.git"
43
- },
44
- "bugs": {
45
- "url": "https://github.com/cablate/mcp-google-map/issues"
46
- },
47
- "dependencies": {
48
- "@googlemaps/google-maps-services-js": "^3.4.0",
49
- "@modelcontextprotocol/sdk": "^1.11.0",
50
- "@types/yargs": "^17.0.33",
51
- "cross-env": "^7.0.3",
52
- "dotenv": "^16.4.7",
53
- "express": "^4.21.2",
54
- "yargs": "^17.7.2",
55
- "zod": "^3.24.2"
56
- },
57
- "devDependencies": {
58
- "@types/express": "^5.0.0",
59
- "@types/jest": "^29.5.14",
60
- "@types/js-yaml": "^4.0.9",
61
- "@types/node": "^20.17.0",
62
- "@typescript-eslint/eslint-plugin": "^8.24.0",
63
- "@typescript-eslint/parser": "^8.24.0",
64
- "eslint": "^9.20.1",
65
- "eslint-config-prettier": "^10.0.1",
66
- "jest": "^29.7.0",
67
- "prettier": "^3.5.0",
68
- "ts-jest": "^29.2.5",
69
- "tsup": "^8.4.0",
70
- "tsx": "^4.19.2",
71
- "typescript": "^5.7.3"
72
- }
73
- }
1
+ {
2
+ "name": "@cablate/mcp-google-map",
3
+ "version": "0.0.19",
4
+ "description": "Google Maps MCP server with streamable HTTP transport support for location services, geocoding, and navigation",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mcp-google-map": "dist/cli.js"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "dist/**/*.map",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsup --dts",
17
+ "start": "node dist/cli.js",
18
+ "dev": "cross-env NODE_ENV=development tsup --watch",
19
+ "prepublishOnly": "npm run build"
20
+ },
21
+ "engines": {
22
+ "node": ">=18.0.0"
23
+ },
24
+ "keywords": [
25
+ "google",
26
+ "map",
27
+ "api",
28
+ "llm",
29
+ "typescript",
30
+ "mcp",
31
+ "server",
32
+ "streamable",
33
+ "location",
34
+ "geocoding",
35
+ "navigation"
36
+ ],
37
+ "author": "CabLate",
38
+ "license": "MIT",
39
+ "homepage": "https://github.com/cablate/mcp-google-map#readme",
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "git+https://github.com/cablate/mcp-google-map.git"
43
+ },
44
+ "bugs": {
45
+ "url": "https://github.com/cablate/mcp-google-map/issues"
46
+ },
47
+ "dependencies": {
48
+ "@googlemaps/google-maps-services-js": "^3.4.0",
49
+ "@googlemaps/places": "^2.1.0",
50
+ "@modelcontextprotocol/sdk": "^1.11.0",
51
+ "@types/yargs": "^17.0.33",
52
+ "cross-env": "^7.0.3",
53
+ "dotenv": "^16.4.7",
54
+ "express": "^4.21.2",
55
+ "yargs": "^17.7.2",
56
+ "zod": "^3.24.2"
57
+ },
58
+ "devDependencies": {
59
+ "@types/express": "^5.0.0",
60
+ "@types/jest": "^29.5.14",
61
+ "@types/js-yaml": "^4.0.9",
62
+ "@types/node": "^20.17.0",
63
+ "@typescript-eslint/eslint-plugin": "^8.24.0",
64
+ "@typescript-eslint/parser": "^8.24.0",
65
+ "eslint": "^9.20.1",
66
+ "eslint-config-prettier": "^10.0.1",
67
+ "jest": "^29.7.0",
68
+ "prettier": "^3.5.0",
69
+ "ts-jest": "^29.2.5",
70
+ "tsup": "^8.4.0",
71
+ "tsx": "^4.19.2",
72
+ "typescript": "^5.7.3"
73
+ }
74
+ }
@@ -1 +0,0 @@
1
- var r={log:(...o)=>{console.error("[INFO]",...o)},error:(...o)=>{console.error("[ERROR]",...o)}};export{r as a};