@cablate/mcp-google-map 0.0.56 → 0.0.58
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/dist/cli.js +4 -4
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import{b as a,c as n}from"./chunk-V2MW4XB5.js";import{config as de}from"dotenv";import{resolve as oe}from"path";import qt from"yargs";import{hideBin as jt}from"yargs/helpers";import{z as x}from"zod";import{AsyncLocalStorage as ye}from"async_hooks";var ae=new ye;function i(){return ae.getStore()?.apiKey||process.env.GOOGLE_MAPS_API_KEY}function ee(t,e){return ae.run(t,e)}var he="maps_search_nearby",fe="Find places near a specific location by type (e.g., restaurants, cafes, hotels). Use when the user wants to discover what's around a given address or coordinates, such as 'find coffee shops near Times Square' or 'what hotels are near the airport'. Supports filtering by place type, search radius, minimum rating, and whether currently open.",Se={center:x.object({value:x.string().describe("Address, landmark name, or coordinates (coordinate format: lat,lng)"),isCoordinates:x.boolean().default(!1).describe("Whether the value is coordinates")}).describe("Search center point (e.g. value: 49.3268778,-123.0585982, isCoordinates: true)"),keyword:x.string().optional().describe("Place type to search for (e.g., restaurant, cafe, hotel, gas_station, hospital)"),radius:x.number().default(1e3).describe("Search radius in meters"),openNow:x.boolean().default(!1).describe("Only show places that are currently open"),minRating:x.number().min(0).max(5).optional().describe("Minimum rating requirement (0-5)")};async function Ee(t){try{let e=i(),r=await new a(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 O={NAME:he,DESCRIPTION:fe,SCHEMA:Se,ACTION:Ee};import{z as ne}from"zod";var be="maps_place_details",Pe="Get comprehensive details for a specific place using its Google Maps place_id. Use after search_nearby or maps_search_places to get full information including reviews, phone number, website, and opening hours. Set maxPhotos (1-10) to include photo URLs \u2014 omit or set to 0 for no photos (saves tokens).",Ae={placeId:ne.string().describe("Google Maps place ID"),maxPhotos:ne.number().int().min(0).max(10).optional().describe("Number of photo URLs to include (0 = none, max 10). Omit to skip photos and save tokens.")};async function xe(t){try{let e=i(),r=await new a(e).getPlaceDetails(t.placeId,t.maxPhotos||0);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 C={NAME:be,DESCRIPTION:Pe,SCHEMA:Ae,ACTION:xe};import{z as ve}from"zod";var we="maps_geocode",Oe="Convert an address, city name, or landmark into GPS coordinates (latitude/longitude). Use when you need coordinates for a location described in text \u2014 for example, to provide a center point for search_nearby or a starting point for maps_directions.",Ce={address:ve.string().describe("Address or place name to convert to coordinates")};async function Me(t){try{let e=i(),r=await new a(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 M={NAME:we,DESCRIPTION:Oe,SCHEMA:Ce,ACTION:Me};import{z as ie}from"zod";var Ne="maps_reverse_geocode",_e="Convert GPS coordinates (latitude/longitude) into a human-readable street address. Use when you have coordinates from another tool's output or a user's shared location and need the actual address.",Te={latitude:ie.number().describe("Latitude coordinate"),longitude:ie.number().describe("Longitude coordinate")};async function Ie(t){try{let e=i(),r=await new a(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 N={NAME:Ne,DESCRIPTION:_e,SCHEMA:Te,ACTION:Ie};import{z as b}from"zod";var ke="maps_distance_matrix",Re="Calculate travel distances and durations between multiple origins and destinations in a single request. Use for comparing travel options \u2014 e.g., 'which hotel is closest to the office?' or batch distance calculations. Supports driving, walking, bicycling, and transit modes.",ze={origins:b.array(b.string()).describe("List of origin addresses or coordinates"),destinations:b.array(b.string()).describe("List of destination addresses or coordinates"),mode:b.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation"),departure_time:b.string().optional().describe("Departure time in ISO 8601 format (e.g. 2026-03-21T09:00:00Z). Enables traffic-aware duration estimates."),avoid_tolls:b.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'),avoid_highways:b.boolean().optional().describe('Avoid highways where reasonable. Only supported with mode "driving".')};async function He(t){try{let e=i(),r=await new a(e).calculateDistanceMatrix(t.origins,t.destinations,t.mode,t.departure_time,t.avoid_tolls,t.avoid_highways);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 _={NAME:ke,DESCRIPTION:Re,SCHEMA:ze,ACTION:He};import{z as v}from"zod";var De="maps_directions",Ke="Get step-by-step navigation directions between two points with route details. Use when the user asks 'how do I get from A to B?' and needs the route summary, total distance, estimated travel time, or turn-by-turn instructions. Supports departure/arrival times and multiple travel modes.",$e={origin:v.string().describe("Starting point address or coordinates"),destination:v.string().describe("Destination address or coordinates"),mode:v.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:v.string().optional().describe("Departure time (ISO string format)"),arrival_time:v.string().optional().describe("Arrival time (ISO string format)"),avoid_tolls:v.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'),avoid_highways:v.boolean().optional().describe('Avoid highways where reasonable. Only supported with mode "driving".')};async function Ge(t){try{let e=i(),r=await new a(e).getDirections(t.origin,t.destination,t.mode,t.departure_time,t.arrival_time,t.avoid_tolls,t.avoid_highways);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 T={NAME:De,DESCRIPTION:Ke,SCHEMA:$e,ACTION:Ge};import{z as Y}from"zod";var Le="maps_elevation",Je="Get elevation (meters above sea level) for geographic coordinates. Use when the user asks 'how high is this place', 'is this area flood-prone', or needs altitude for hiking/cycling route profiles. Also useful for real estate risk assessment \u2014 low elevation near water suggests flood risk.",qe={locations:Y.array(Y.object({latitude:Y.number().describe("Latitude coordinate"),longitude:Y.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")};async function je(t){try{let e=i(),r=await new a(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 I={NAME:Le,DESCRIPTION:Je,SCHEMA:qe,ACTION:je};import{z as P}from"zod";var Ue="maps_search_places",Fe="Search for places using a free-text query like 'sushi restaurants in Tokyo' or 'best coffee shops near Central Park'. More flexible than search_nearby \u2014 supports natural language queries, optional location bias, rating filters, and open-now filtering. Use when the user describes what they're looking for in words rather than by type and coordinates.",Ze={query:P.string().describe("Text search query (e.g., 'Italian restaurants in Manhattan', 'hotels near Taipei 101')"),locationBias:P.object({latitude:P.number().describe("Latitude to bias results toward"),longitude:P.number().describe("Longitude to bias results toward"),radius:P.number().optional().describe("Bias radius in meters (default: 5000)")}).optional().describe("Optional location to bias results toward"),openNow:P.boolean().optional().describe("Only return places that are currently open"),minRating:P.number().optional().describe("Minimum rating filter (1.0 - 5.0)"),includedType:P.string().optional().describe("Filter by place type (e.g., restaurant, cafe, hotel)")};async function Be(t){try{let e=i(),r=await new a(e).searchText({query:t.query,locationBias:t.locationBias,openNow:t.openNow,minRating:t.minRating,includedType:t.includedType});return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to search places"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching places: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var k={NAME:Ue,DESCRIPTION:Fe,SCHEMA:Ze,ACTION:Be};import{z as te}from"zod";var We="maps_timezone",Ve="Get the timezone and current local time for a location. Use when the user asks 'what time is it in Tokyo', needs to coordinate a meeting across timezones, or is planning travel across timezone boundaries. Returns timezone ID, UTC/DST offsets, and computed local time.",Ye={latitude:te.number().describe("Latitude coordinate"),longitude:te.number().describe("Longitude coordinate"),timestamp:te.number().optional().describe("Unix timestamp in ms to query timezone at a specific moment (defaults to now)")};async function Qe(t){try{let e=i(),r=await new a(e).getTimezone(t.latitude,t.longitude,t.timestamp);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get timezone data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting timezone: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var R={NAME:We,DESCRIPTION:Ve,SCHEMA:Ye,ACTION:Qe};import{z}from"zod";var Xe="maps_weather",et="Get weather for a location \u2014 current conditions, daily forecast (10 days), or hourly forecast (240 hours). Use when the user asks 'what's the weather in Paris', is planning outdoor activities, or needs to pack for a trip. Coverage: most regions supported, but China, Japan, South Korea, Cuba, Iran, North Korea, Syria are unavailable.",tt={latitude:z.number().describe("Latitude coordinate"),longitude:z.number().describe("Longitude coordinate"),type:z.enum(["current","forecast_daily","forecast_hourly"]).optional().describe("current = right now, forecast_daily = multi-day outlook, forecast_hourly = hour-by-hour"),forecastDays:z.number().optional().describe("Number of forecast days (1-10, only for forecast_daily, default: 5)"),forecastHours:z.number().optional().describe("Number of forecast hours (1-240, only for forecast_hourly, default: 24)")};async function rt(t){try{let e=i(),r=await new a(e).getWeather(t.latitude,t.longitude,t.type||"current",t.forecastDays,t.forecastHours);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get weather data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting weather: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var H={NAME:Xe,DESCRIPTION:et,SCHEMA:tt,ACTION:rt};import{z as D}from"zod";var ot="maps_explore_area",st="Explore what's around a location in one call \u2014 searches multiple place types, gets details for the top results, and returns a categorized summary. Use when the user asks 'what's around here', 'explore the area near my hotel', or needs a quick overview of a neighborhood. Replaces the manual chain of geocode \u2192 search-nearby \u2192 place-details. For trip planning: use search_places first to get geographically spread anchor points, then call this tool around each anchor (e.g. 'Gion, Kyoto') \u2014 never pass just the city name, as it clusters all results in one area. After results, call static_map to visualize.",at={location:D.string().describe("Address or landmark to explore around"),types:D.array(D.string()).optional().describe("Place types to search (default: restaurant, cafe, tourist_attraction). Must be Places API (New) type names. Examples: hotel, bar, park, museum"),radius:D.number().optional().describe("Search radius in meters (default: 1000)"),topN:D.number().optional().describe("Number of top results per type to get details for (default: 3)")};async function nt(t){try{let e=i(),r=await new a(e).exploreArea(t);return{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error exploring area: ${e.message}`}]}}}var K={NAME:ot,DESCRIPTION:st,SCHEMA:at,ACTION:nt};import{z as w}from"zod";var it="maps_plan_route",ct="Plan an optimized multi-stop route in one call \u2014 geocodes all stops, uses Routes API waypoint optimization (2 to 25 intermediate stops) to find the most efficient visit order, and returns directions for each leg. Use when the user says 'visit these 5 places efficiently', 'plan a route through A, B, C', or needs a multi-stop itinerary. Replaces the manual chain of geocode \u2192 distance-matrix \u2192 directions. Waypoint optimization requires at least 4 stops (2 intermediates); with 2 or 3 stops the route is returned in the original order. For multi-day trips: create one plan_route call per day with stops that follow a geographic arc (e.g. east\u2192west) rather than mixing distant areas. After results, call static_map to visualize the route.",lt={stops:w.array(w.string()).min(2).describe("List of addresses or landmarks to visit (minimum 2)"),mode:w.enum(["driving","walking","bicycling","transit"]).optional().describe("Travel mode (default: driving)"),optimize:w.boolean().optional().describe("Auto-optimize visit order via Routes API waypoint optimization (default: true). Requires at least 4 stops (2 intermediates) \u2014 ignored for 2-3 stops. Set false to keep original order. Not available for transit mode."),departure_time:w.string().optional().describe("Departure time in ISO 8601 format (e.g. 2026-03-21T09:00:00Z). Enables traffic-aware routing."),avoid_tolls:w.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'),avoid_highways:w.boolean().optional().describe('Avoid highways where reasonable. Only supported with mode "driving".')};async function pt(t){try{let e=i(),r=await new a(e).planRoute(t);return{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error planning route: ${e.message}`}]}}}var $={NAME:it,DESCRIPTION:ct,SCHEMA:lt,ACTION:pt};import{z as G}from"zod";var dt="maps_compare_places",ut="Compare multiple places side-by-side in one call \u2014 searches by query, gets details for each result, and optionally calculates distance from your location. Use when the user asks 'which restaurant should I pick', 'compare these hotels', or needs a decision table. Replaces the manual chain of search-places \u2192 place-details \u2192 distance-matrix.",mt={query:G.string().describe("Search query (e.g., 'ramen near Shibuya', 'hotels in Taipei')"),userLocation:G.object({latitude:G.number().describe("Your latitude"),longitude:G.number().describe("Your longitude")}).optional().describe("Your current location \u2014 if provided, adds distance and drive time to each result"),limit:G.number().optional().describe("Max places to compare (default: 5)")};async function gt(t){try{let e=i(),r=await new a(e).comparePlaces(t);return{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error comparing places: ${e.message}`}]}}}var L={NAME:dt,DESCRIPTION:ut,SCHEMA:mt,ACTION:gt};import{z as Q}from"zod";var yt="maps_air_quality",ht="Get air quality for a location \u2014 AQI index, pollutant concentrations, and health recommendations by demographic group (elderly, children, athletes, pregnant women, etc.). Use when the user asks 'is the air safe', 'should I wear a mask', 'good for outdoor exercise', or is planning travel for someone with respiratory/heart conditions. Coverage: global including Japan (unlike weather). Returns both universal AQI and local index (EPA for US, AEROS for Japan, etc.).",ft={latitude:Q.number().describe("Latitude coordinate"),longitude:Q.number().describe("Longitude coordinate"),includeHealthRecommendations:Q.boolean().optional().describe("Include health advice per demographic group (default: true)"),includePollutants:Q.boolean().optional().describe("Include individual pollutant concentrations \u2014 PM2.5, PM10, NO2, O3, CO, SO2 (default: false)")};async function St(t){try{let e=i(),r=await new a(e).getAirQuality(t.latitude,t.longitude,t.includeHealthRecommendations,t.includePollutants);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get air quality data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting air quality: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var J={NAME:yt,DESCRIPTION:ht,SCHEMA:ft,ACTION:St};import{z as A}from"zod";var Et="maps_static_map",bt="Generate a map image with markers, paths, or routes \u2014 returned as an inline image the user can see directly in chat. PROACTIVELY call this tool after explore_area, plan_route, search_nearby, or directions to visualize results on a map \u2014 don't wait for the user to ask. Use markers from search results and path from route data. Supports roadmap, satellite, terrain, and hybrid views. Max 640x640 pixels.",Pt={center:A.string().optional().describe('Map center \u2014 "lat,lng" or address. Optional if markers or path are provided.'),zoom:A.number().optional().describe("Zoom level 0-21 (0 = world, 15 = streets, 21 = buildings). Default: auto-fit."),size:A.string().optional().describe('Image size "WxH" in pixels. Default: "600x400". Max: "640x640".'),maptype:A.enum(["roadmap","satellite","terrain","hybrid"]).optional().describe("Map style. Default: roadmap."),markers:A.array(A.string()).optional().describe('Marker descriptors. Each string: "color:red|label:A|lat,lng" or "color:blue|address". Multiple markers per string separated by |.'),path:A.array(A.string()).optional().describe('Path descriptors. Each string: "color:0x0000ff|weight:3|lat1,lng1|lat2,lng2|..." to draw lines/routes on the map.')};async function At(t){try{let e=i(),r=await new a(e).getStaticMap(t);return r.success?{content:[{type:"image",data:r.data.base64,mimeType:"image/png"},{type:"text",text:`Map generated (${r.data.size} bytes, ${r.data.dimensions})`}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to generate static map"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error generating static map: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var q={NAME:Et,DESCRIPTION:bt,SCHEMA:Pt,ACTION:At};import{z as ce}from"zod";var xt="maps_batch_geocode",vt="Geocode multiple addresses in one call \u2014 up to 50 addresses, returns coordinates for each. Use when the user provides a list of addresses and needs all their coordinates, e.g. 'geocode these 10 offices' or 'get coordinates for all these restaurants'. For more than 50, use the CLI batch-geocode command instead.",wt={addresses:ce.array(ce.string()).min(1).max(50).describe("List of addresses or landmark names to geocode (max 50)")};async function Ot(t){try{let e=i(),o=new a(e),r=t.addresses,s=await Promise.all(r.map(async p=>{try{let d=await o.geocode(p);return{address:p,...d}}catch(d){return{address:p,success:!1,error:d.message}}})),l=s.filter(p=>p.success).length,c=s.filter(p=>!p.success).length;return{content:[{type:"text",text:JSON.stringify({total:r.length,succeeded:l,failed:c,results:s},null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error batch geocoding: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var j={NAME:xt,DESCRIPTION:vt,SCHEMA:wt,ACTION:Ot};import{z as U}from"zod";var Ct="maps_search_along_route",Mt="Search for places along a route between two points \u2014 restaurants, cafes, gas stations, etc. ranked by minimal detour time. Use for trip planning to find meals, rest stops, or attractions between landmarks without backtracking. Internally computes the route, then searches along it. Essential for building itineraries where stops should feel 'on the way' rather than 'detour to'.",Nt={textQuery:U.string().describe("What to search for along the route (e.g. 'restaurant', 'coffee shop', 'temple')"),origin:U.string().describe("Route start point \u2014 address or landmark name"),destination:U.string().describe("Route end point \u2014 address or landmark name"),mode:U.enum(["driving","walking","bicycling","transit"]).optional().describe("Travel mode for the route (default: walking)"),maxResults:U.number().optional().describe("Max results to return (default: 5, max: 20)")};async function _t(t){try{let e=i(),r=await new a(e).searchAlongRoute(t);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to search along route"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching along route: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var F={NAME:Ct,DESCRIPTION:Mt,SCHEMA:Nt,ACTION:_t};import{z as S}from"zod";var Tt="maps_local_rank_tracker",It="Track a business's local search ranking across a geographic grid (like LocalFalcon). Searches the same keyword(s) from multiple coordinates around a center point to see how rank varies by location. Supports up to 3 keywords for batch scanning. Returns rank at each grid point, top-3 competitors per point, and summary metrics (ARP, ATRP, SoLV). Useful for local SEO analysis.",kt={keyword:S.string().optional().describe("Single search keyword (e.g., 'dentist'). Use 'keywords' for multi-keyword scanning."),keywords:S.array(S.string()).min(1).max(3).optional().describe("Array of 1-3 keywords to scan (e.g., ['dentist', 'dental clinic', 'teeth cleaning']). Overrides 'keyword'."),placeId:S.string().describe("Google Maps place_id of the target business to track"),center:S.object({latitude:S.number().describe("Center latitude of the grid"),longitude:S.number().describe("Center longitude of the grid")}).describe("Center coordinate for the grid (typically the business location)"),gridSize:S.number().int().min(3).max(7).optional().describe("Grid dimension (3 = 3\xD73 = 9 points, 5 = 5\xD75 = 25 points, 7 = 7\xD77 = 49 points). Default: 3"),gridSpacing:S.number().min(100).max(1e4).optional().describe("Distance between grid points in meters (100-10000). Default: 1000")};async function Rt(t){try{let e=t.keywords||(t.keyword?[t.keyword]:[]);if(e.length===0)return{content:[{type:"text",text:"Either 'keyword' or 'keywords' must be provided."}],isError:!0};let o=i(),s=await new a(o).localRankTracker({...t,keywords:e});return s.success?{content:[{type:"text",text:JSON.stringify(s.data,null,2)}],isError:!1}:{content:[{type:"text",text:s.error||"Failed to track local rank"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error tracking local rank: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var Z={NAME:Tt,DESCRIPTION:It,SCHEMA:kt,ACTION:Rt};var u={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0},zt=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:O.NAME,description:O.DESCRIPTION,schema:O.SCHEMA,annotations:u,action:t=>O.ACTION(t)},{name:C.NAME,description:C.DESCRIPTION,schema:C.SCHEMA,annotations:u,action:t=>C.ACTION(t)},{name:M.NAME,description:M.DESCRIPTION,schema:M.SCHEMA,annotations:u,action:t=>M.ACTION(t)},{name:N.NAME,description:N.DESCRIPTION,schema:N.SCHEMA,annotations:u,action:t=>N.ACTION(t)},{name:_.NAME,description:_.DESCRIPTION,schema:_.SCHEMA,annotations:u,action:t=>_.ACTION(t)},{name:T.NAME,description:T.DESCRIPTION,schema:T.SCHEMA,annotations:u,action:t=>T.ACTION(t)},{name:I.NAME,description:I.DESCRIPTION,schema:I.SCHEMA,annotations:u,action:t=>I.ACTION(t)},{name:k.NAME,description:k.DESCRIPTION,schema:k.SCHEMA,annotations:u,action:t=>k.ACTION(t)},{name:R.NAME,description:R.DESCRIPTION,schema:R.SCHEMA,annotations:u,action:t=>R.ACTION(t)},{name:H.NAME,description:H.DESCRIPTION,schema:H.SCHEMA,annotations:u,action:t=>H.ACTION(t)},{name:K.NAME,description:K.DESCRIPTION,schema:K.SCHEMA,annotations:u,action:t=>K.ACTION(t)},{name:$.NAME,description:$.DESCRIPTION,schema:$.SCHEMA,annotations:u,action:t=>$.ACTION(t)},{name:L.NAME,description:L.DESCRIPTION,schema:L.SCHEMA,annotations:u,action:t=>L.ACTION(t)},{name:J.NAME,description:J.DESCRIPTION,schema:J.SCHEMA,annotations:u,action:t=>J.ACTION(t)},{name:q.NAME,description:q.DESCRIPTION,schema:q.SCHEMA,annotations:u,action:t=>q.ACTION(t)},{name:j.NAME,description:j.DESCRIPTION,schema:j.SCHEMA,annotations:u,action:t=>j.ACTION(t)},{name:F.NAME,description:F.DESCRIPTION,schema:F.SCHEMA,annotations:u,action:t=>F.ACTION(t)},{name:Z.NAME,description:Z.DESCRIPTION,schema:Z.SCHEMA,annotations:u,action:t=>Z.ACTION(t)}]}];function re(t){let e=process.env.GOOGLE_MAPS_ENABLED_TOOLS?.trim();if(!e||e==="*")return t;let o=new Set(e.split(",").map(s=>s.trim()).filter(Boolean)),r=t.filter(s=>o.has(s.name));return r.length===0?(n.error(`GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools. Available: ${t.map(s=>s.name).join(", ")}`),t):(n.log(`GOOGLE_MAPS_ENABLED_TOOLS: ${r.length}/${t.length} tools active`),r)}var X=zt;import{McpServer as Ht}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Dt}from"@modelcontextprotocol/sdk/server/stdio.js";import{StreamableHTTPServerTransport as Kt}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as $t}from"@modelcontextprotocol/sdk/types.js";import le from"express";import{randomUUID as Gt}from"crypto";import{z as Lt}from"zod";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 s=e.headers.authorization;if(s&&s.startsWith("Bearer "))return s.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 Jt="0.0.1",W=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.tools=o,this.server=this.createMcpServer()}createMcpServer(){let e=new Ht({name:this.serverName,version:Jt},{capabilities:{logging:{},tools:{}}});return this.tools.forEach(o=>{e.registerTool(o.name,{description:o.description,inputSchema:Lt.object(o.schema),annotations:o.annotations},async r=>o.action(r))}),e}async connect(e){await this.server.connect(e);let o=process.stdout.write.bind(process.stdout);process.stdout.write=(r,s,l)=>typeof r=="string"&&!r.startsWith("{")?!0:o(r,s,l),n.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e,o="0.0.0.0"){let r=le();r.use(le.json()),r.post("/mcp",async(c,p)=>{let d=c.headers["mcp-session-id"],g,E=B.getInstance().getApiKey(c);if(n.log(`${this.serverName} API key received from request context`),d&&this.sessions[d])g=this.sessions[d],E&&(g.apiKey=E);else if(!d&&$t(c.body)){let h=new Kt({sessionIdGenerator:()=>Gt(),onsessioninitialized:f=>{this.sessions[f]=g,n.log(`[${this.serverName}] New session initialized: ${f}`)}});g={transport:h,apiKey:E},h.onclose=()=>{h.sessionId&&(delete this.sessions[h.sessionId],n.log(`[${this.serverName}] Session closed: ${h.sessionId}`))},await this.createMcpServer().connect(h)}else{p.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await ee({apiKey:g.apiKey,sessionId:d},async()=>{await g.transport.handleRequest(c,p,c.body)})});let s=async(c,p)=>{let d=c.headers["mcp-session-id"];if(!d||!this.sessions[d]){p.status(400).send("Invalid or missing session ID");return}let g=this.sessions[d],E=B.getInstance().getApiKey(c);E&&(g.apiKey=E),await ee({apiKey:g.apiKey,sessionId:d},async()=>{await g.transport.handleRequest(c,p)})};r.get("/mcp",s),r.delete("/mcp",s);let l=o==="0.0.0.0"?"localhost":o;this.httpServer=r.listen(e,o,()=>{n.log(`[${this.serverName}] HTTP server listening on ${o}:${e}`),n.log(`[${this.serverName}] MCP endpoint available at http://${l}:${e}/mcp`)})}async startStdio(){let e=new Dt;await this.connect(e)}async stopHttpServer(){if(!this.httpServer){n.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,o)=>{this.httpServer.close(r=>{if(r){n.error(`[${this.serverName}] Error stopping HTTP server:`,r),o(r);return}n.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let s=Object.values(this.sessions).map(l=>(l.transport.sessionId&&delete this.sessions[l.transport.sessionId],Promise.resolve()));Promise.all(s).then(()=>{n.log(`[${this.serverName}] All transports closed.`),e()}).catch(l=>{n.error(`[${this.serverName}] Error during bulk transport closing:`,l),o(l)})})})}};import{fileURLToPath as Ut}from"url";import{dirname as Ft}from"path";import{readFileSync as pe,writeFileSync as Zt,existsSync as Bt}from"fs";import{createInterface as Wt}from"readline";var Vt=Ut(import.meta.url),ue=Ft(Vt);de({path:oe(process.cwd(),".env")});de({path:oe(ue,"../.env")});async function Yt(t,e,o){t&&(process.env.MCP_SERVER_PORT=t.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),o&&(process.env.MCP_SERVER_HOST=o),n.log("\u{1F680} Starting Google Maps MCP Server..."),n.log("\u{1F4CD} 18 tools registered (set GOOGLE_MAPS_ENABLED_TOOLS to limit)"),n.log("\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."),n.log("");let r=X.map(async s=>{let l=process.env[s.portEnvVar];if(!l){n.error(`\u26A0\uFE0F [${s.name}] Port environment variable ${s.portEnvVar} not set.`),n.log(`\u{1F4A1} Please set ${s.portEnvVar} in your .env file or use --port parameter.`),n.log(` Example: ${s.portEnvVar}=3000 or --port 3000`);return}let c=Number(l);if(isNaN(c)||c<=0){n.error(`\u274C [${s.name}] Invalid port number "${l}" defined in ${s.portEnvVar}.`);return}try{let p=new W(s.name,re(s.tools)),d=process.env.MCP_SERVER_HOST||"0.0.0.0";n.log(`\u{1F527} [${s.name}] Initializing MCP Server in HTTP mode on ${d}:${c}...`),await p.startHttpServer(c,d);let g=d==="0.0.0.0"?"localhost":d;n.log(`\u2705 [${s.name}] MCP Server started successfully!`),n.log(` \u{1F310} Endpoint: http://${g}:${c}/mcp`),n.log(` \u{1F4DA} Tools: ${s.tools.length} available`)}catch(p){n.error(`\u274C [${s.name}] Failed to start MCP Server on port ${c}:`,p)}});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 me=["geocode","reverse-geocode","search-nearby","search-places","place-details","directions","distance-matrix","elevation","timezone","weather","explore-area","plan-route","compare-places","air-quality","static-map","batch-geocode-tool","search-along-route","local-rank-tracker"];async function Qt(t,e,o){let r=new a(o);switch(t){case"geocode":case"maps_geocode":return r.geocode(e.address);case"reverse-geocode":case"maps_reverse_geocode":return r.reverseGeocode(e.latitude,e.longitude);case"search-nearby":case"search_nearby":case"maps_search_nearby":return r.searchNearby(e);case"search-places":case"maps_search_places":return r.searchText({query:e.query,locationBias:e.locationBias,openNow:e.openNow,minRating:e.minRating,includedType:e.includedType});case"place-details":case"get_place_details":case"maps_place_details":return r.getPlaceDetails(e.placeId,e.maxPhotos||0);case"directions":case"maps_directions":return r.getDirections(e.origin,e.destination,e.mode,e.departure_time,e.arrival_time,e.avoid_tolls,e.avoid_highways);case"distance-matrix":case"maps_distance_matrix":return r.calculateDistanceMatrix(e.origins,e.destinations,e.mode,e.departure_time,e.avoid_tolls,e.avoid_highways);case"elevation":case"maps_elevation":return r.getElevation(e.locations);case"timezone":case"maps_timezone":return r.getTimezone(e.latitude,e.longitude,e.timestamp);case"weather":case"maps_weather":return r.getWeather(e.latitude,e.longitude,e.type,e.forecastDays,e.forecastHours);case"explore-area":case"maps_explore_area":return r.exploreArea(e);case"plan-route":case"maps_plan_route":return r.planRoute(e);case"compare-places":case"maps_compare_places":return r.comparePlaces(e);case"air-quality":case"maps_air_quality":return r.getAirQuality(e.latitude,e.longitude,e.includeHealthRecommendations,e.includePollutants);case"static-map":case"maps_static_map":return r.getStaticMap(e);case"batch-geocode-tool":case"maps_batch_geocode":{let s=await Promise.all(e.addresses.map(async c=>{try{let p=await r.geocode(c);return{address:c,...p}}catch(p){return{address:c,success:!1,error:p.message}}})),l=s.filter(c=>c.success).length;return{success:!0,data:{total:e.addresses.length,succeeded:l,failed:e.addresses.length-l,results:s}}}case"search-along-route":case"maps_search_along_route":return r.searchAlongRoute(e);case"local-rank-tracker":case"maps_local_rank_tracker":return r.localRankTracker(e);default:throw new Error(`Unknown tool: ${t}. Available: ${me.join(", ")}`)}}var Xt=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")),er=import.meta.url===`file://${process.argv[1]}`;if(Xt||er){let t="0.0.0";try{let e=oe(ue,"../package.json");t=JSON.parse(pe(e,"utf-8")).version}catch{t="0.0.0"}qt(jt(process.argv)).command("exec <tool> [params]","Execute a tool directly and output JSON",e=>e.positional("tool",{type:"string",describe:`Tool name: ${me.join(", ")}`}).positional("params",{type:"string",describe:"JSON parameters string"}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).example([[`$0 exec geocode '{"address":"Tokyo Tower"}'`,"Geocode an address"],[`$0 exec search-nearby '{"center":{"value":"35.68,139.74","isCoordinates":true},"keyword":"restaurant"}'`,"Search nearby"],[`$0 exec search-places '{"query":"ramen in Tokyo"}'`,"Text search"]]),async e=>{if(!e.apikey){process.stderr.write(JSON.stringify({error:"GOOGLE_MAPS_API_KEY not set. Use --apikey or set GOOGLE_MAPS_API_KEY environment variable."},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 O={NAME:he,DESCRIPTION:fe,SCHEMA:Se,ACTION:Ee};import{z as ne}from"zod";var be="maps_place_details",Pe="Get comprehensive details for a specific place using its Google Maps place_id. Use after search_nearby or maps_search_places to get full information including reviews, phone number, website, and opening hours. Set maxPhotos (1-10) to include photo URLs \u2014 omit or set to 0 for no photos (saves tokens).",Ae={placeId:ne.string().describe("Google Maps place ID"),maxPhotos:ne.number().int().min(0).max(10).optional().describe("Number of photo URLs to include (0 = none, max 10). Omit to skip photos and save tokens.")};async function xe(t){try{let e=i(),r=await new a(e).getPlaceDetails(t.placeId,t.maxPhotos||0);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 C={NAME:be,DESCRIPTION:Pe,SCHEMA:Ae,ACTION:xe};import{z as ve}from"zod";var we="maps_geocode",Oe="Convert an address, city name, or landmark into GPS coordinates (latitude/longitude). Use when you need coordinates for a location described in text \u2014 for example, to provide a center point for search_nearby or a starting point for maps_directions.",Ce={address:ve.string().describe("Address or place name to convert to coordinates")};async function Me(t){try{let e=i(),r=await new a(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 M={NAME:we,DESCRIPTION:Oe,SCHEMA:Ce,ACTION:Me};import{z as ie}from"zod";var Ne="maps_reverse_geocode",_e="Convert GPS coordinates (latitude/longitude) into a human-readable street address. Use when you have coordinates from another tool's output or a user's shared location and need the actual address.",Te={latitude:ie.number().describe("Latitude coordinate"),longitude:ie.number().describe("Longitude coordinate")};async function Ie(t){try{let e=i(),r=await new a(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 N={NAME:Ne,DESCRIPTION:_e,SCHEMA:Te,ACTION:Ie};import{z as b}from"zod";var ke="maps_distance_matrix",Re="Calculate travel distances and durations between multiple origins and destinations in a single request. Use for comparing travel options \u2014 e.g., 'which hotel is closest to the office?' or batch distance calculations. Supports driving, walking, bicycling, and transit modes.",ze={origins:b.array(b.string()).describe("List of origin addresses or coordinates"),destinations:b.array(b.string()).describe("List of destination addresses or coordinates"),mode:b.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation"),departure_time:b.string().optional().describe("Departure time in ISO 8601 format (e.g. 2026-03-21T09:00:00Z). Enables traffic-aware duration estimates."),avoid_tolls:b.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'),avoid_highways:b.boolean().optional().describe('Avoid highways where reasonable. Only supported with mode "driving".')};async function He(t){try{let e=i(),r=await new a(e).calculateDistanceMatrix(t.origins,t.destinations,t.mode,t.departure_time,t.avoid_tolls,t.avoid_highways);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 _={NAME:ke,DESCRIPTION:Re,SCHEMA:ze,ACTION:He};import{z as v}from"zod";var De="maps_directions",Ke="Get step-by-step navigation directions between two points with route details. Use when the user asks 'how do I get from A to B?' and needs the route summary, total distance, estimated travel time, or turn-by-turn instructions. Supports departure/arrival times and multiple travel modes.",$e={origin:v.string().describe("Starting point address or coordinates"),destination:v.string().describe("Destination address or coordinates"),mode:v.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:v.string().optional().describe("Departure time (ISO string format)"),arrival_time:v.string().optional().describe("Arrival time (ISO string format)"),avoid_tolls:v.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'),avoid_highways:v.boolean().optional().describe('Avoid highways where reasonable. Only supported with mode "driving".')};async function Ge(t){try{let e=i(),r=await new a(e).getDirections(t.origin,t.destination,t.mode,t.departure_time,t.arrival_time,t.avoid_tolls,t.avoid_highways);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 T={NAME:De,DESCRIPTION:Ke,SCHEMA:$e,ACTION:Ge};import{z as Y}from"zod";var Le="maps_elevation",Je="Get elevation (meters above sea level) for geographic coordinates. Use when the user asks 'how high is this place', 'is this area flood-prone', or needs altitude for hiking/cycling route profiles. Also useful for real estate risk assessment \u2014 low elevation near water suggests flood risk.",qe={locations:Y.array(Y.object({latitude:Y.number().describe("Latitude coordinate"),longitude:Y.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")};async function je(t){try{let e=i(),r=await new a(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 I={NAME:Le,DESCRIPTION:Je,SCHEMA:qe,ACTION:je};import{z as P}from"zod";var Ue="maps_search_places",Fe="Search for places using a free-text query like 'sushi restaurants in Tokyo' or 'best coffee shops near Central Park'. More flexible than search_nearby \u2014 supports natural language queries, optional location bias, rating filters, and open-now filtering. Use when the user describes what they're looking for in words rather than by type and coordinates.",Ze={query:P.string().describe("Text search query (e.g., 'Italian restaurants in Manhattan', 'hotels near Taipei 101')"),locationBias:P.object({latitude:P.number().describe("Latitude to bias results toward"),longitude:P.number().describe("Longitude to bias results toward"),radius:P.number().optional().describe("Bias radius in meters (default: 5000)")}).optional().describe("Optional location to bias results toward"),openNow:P.boolean().optional().describe("Only return places that are currently open"),minRating:P.number().optional().describe("Minimum rating filter (1.0 - 5.0)"),includedType:P.string().optional().describe("Filter by place type (e.g., restaurant, cafe, hotel)")};async function Be(t){try{let e=i(),r=await new a(e).searchText({query:t.query,locationBias:t.locationBias,openNow:t.openNow,minRating:t.minRating,includedType:t.includedType});return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to search places"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching places: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var k={NAME:Ue,DESCRIPTION:Fe,SCHEMA:Ze,ACTION:Be};import{z as te}from"zod";var We="maps_timezone",Ve="Get the timezone and current local time for a location. Use when the user asks 'what time is it in Tokyo', needs to coordinate a meeting across timezones, or is planning travel across timezone boundaries. Returns timezone ID, UTC/DST offsets, and computed local time.",Ye={latitude:te.number().describe("Latitude coordinate"),longitude:te.number().describe("Longitude coordinate"),timestamp:te.number().optional().describe("Unix timestamp in ms to query timezone at a specific moment (defaults to now)")};async function Qe(t){try{let e=i(),r=await new a(e).getTimezone(t.latitude,t.longitude,t.timestamp);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get timezone data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting timezone: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var R={NAME:We,DESCRIPTION:Ve,SCHEMA:Ye,ACTION:Qe};import{z}from"zod";var Xe="maps_weather",et="Get weather for a location \u2014 current conditions, daily forecast (10 days), or hourly forecast (240 hours). Use when the user asks 'what's the weather in Paris', is planning outdoor activities, or needs to pack for a trip. Coverage: most regions supported, but China, Japan, South Korea, Cuba, Iran, North Korea, Syria are unavailable.",tt={latitude:z.number().describe("Latitude coordinate"),longitude:z.number().describe("Longitude coordinate"),type:z.enum(["current","forecast_daily","forecast_hourly"]).optional().describe("current = right now, forecast_daily = multi-day outlook, forecast_hourly = hour-by-hour"),forecastDays:z.number().optional().describe("Number of forecast days (1-10, only for forecast_daily, default: 5)"),forecastHours:z.number().optional().describe("Number of forecast hours (1-240, only for forecast_hourly, default: 24)")};async function rt(t){try{let e=i(),r=await new a(e).getWeather(t.latitude,t.longitude,t.type||"current",t.forecastDays,t.forecastHours);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get weather data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting weather: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var H={NAME:Xe,DESCRIPTION:et,SCHEMA:tt,ACTION:rt};import{z as D}from"zod";var ot="maps_explore_area",st="Explore what's around a location in one call \u2014 searches multiple place types, gets details for the top results, and returns a categorized summary. Use when the user asks 'what's around here', 'explore the area near my hotel', or needs a quick overview of a neighborhood. Replaces the manual chain of geocode \u2192 search-nearby \u2192 place-details. For trip planning: use search_places first to get geographically spread anchor points, then call this tool around each anchor (e.g. 'Gion, Kyoto') \u2014 never pass just the city name, as it clusters all results in one area. After results, call static_map to visualize.",at={location:D.string().describe("Address or landmark to explore around"),types:D.array(D.string()).optional().describe("Place types to search (default: restaurant, cafe, tourist_attraction). Must be Places API (New) type names. Examples: hotel, bar, park, museum"),radius:D.number().optional().describe("Search radius in meters (default: 1000)"),topN:D.number().optional().describe("Number of top results per type to get details for (default: 3)")};async function nt(t){try{let e=i(),r=await new a(e).exploreArea(t);return{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error exploring area: ${e.message}`}]}}}var K={NAME:ot,DESCRIPTION:st,SCHEMA:at,ACTION:nt};import{z as w}from"zod";var it="maps_plan_route",ct="Plan an optimized multi-stop route in one call \u2014 geocodes all stops, uses Routes API waypoint optimization (2 to 25 intermediate stops) to find the most efficient visit order, and returns directions for each leg. Use when the user says 'visit these 5 places efficiently', 'plan a route through A, B, C', or needs a multi-stop itinerary. Replaces the manual chain of geocode \u2192 distance-matrix \u2192 directions. Waypoint optimization requires at least 4 stops (2 intermediates); with 2 or 3 stops the route is returned in the original order. For multi-day trips: create one plan_route call per day with stops that follow a geographic arc (e.g. east\u2192west) rather than mixing distant areas. After results, call static_map to visualize the route.",lt={stops:w.array(w.string()).min(2).describe("List of addresses or landmarks to visit (minimum 2)"),mode:w.enum(["driving","walking","bicycling","transit"]).optional().describe("Travel mode (default: driving)"),optimize:w.boolean().optional().describe("Auto-optimize visit order via Routes API waypoint optimization (default: true). Requires at least 4 stops (2 intermediates) \u2014 ignored for 2-3 stops. Set false to keep original order. Not available for transit mode."),departure_time:w.string().optional().describe("Departure time in ISO 8601 format (e.g. 2026-03-21T09:00:00Z). Enables traffic-aware routing."),avoid_tolls:w.boolean().optional().describe('Avoid toll roads where reasonable. Only supported with mode "driving".'),avoid_highways:w.boolean().optional().describe('Avoid highways where reasonable. Only supported with mode "driving".')};async function pt(t){try{let e=i(),r=await new a(e).planRoute(t);return{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error planning route: ${e.message}`}]}}}var $={NAME:it,DESCRIPTION:ct,SCHEMA:lt,ACTION:pt};import{z as G}from"zod";var dt="maps_compare_places",ut="Compare multiple places side-by-side in one call \u2014 searches by query, gets details for each result, and optionally calculates distance from your location. Use when the user asks 'which restaurant should I pick', 'compare these hotels', or needs a decision table. Replaces the manual chain of search-places \u2192 place-details \u2192 distance-matrix.",mt={query:G.string().describe("Search query (e.g., 'ramen near Shibuya', 'hotels in Taipei')"),userLocation:G.object({latitude:G.number().describe("Your latitude"),longitude:G.number().describe("Your longitude")}).optional().describe("Your current location \u2014 if provided, adds distance and drive time to each result"),limit:G.number().optional().describe("Max places to compare (default: 5)")};async function gt(t){try{let e=i(),r=await new a(e).comparePlaces(t);return{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error comparing places: ${e.message}`}]}}}var L={NAME:dt,DESCRIPTION:ut,SCHEMA:mt,ACTION:gt};import{z as Q}from"zod";var yt="maps_air_quality",ht="Get air quality for a location \u2014 AQI index, pollutant concentrations, and health recommendations by demographic group (elderly, children, athletes, pregnant women, etc.). Use when the user asks 'is the air safe', 'should I wear a mask', 'good for outdoor exercise', or is planning travel for someone with respiratory/heart conditions. Coverage: global including Japan (unlike weather). Returns both universal AQI and local index (EPA for US, AEROS for Japan, etc.).",ft={latitude:Q.number().describe("Latitude coordinate"),longitude:Q.number().describe("Longitude coordinate"),includeHealthRecommendations:Q.boolean().optional().describe("Include health advice per demographic group (default: true)"),includePollutants:Q.boolean().optional().describe("Include individual pollutant concentrations \u2014 PM2.5, PM10, NO2, O3, CO, SO2 (default: false)")};async function St(t){try{let e=i(),r=await new a(e).getAirQuality(t.latitude,t.longitude,t.includeHealthRecommendations,t.includePollutants);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to get air quality data"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error getting air quality: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var J={NAME:yt,DESCRIPTION:ht,SCHEMA:ft,ACTION:St};import{z as A}from"zod";var Et="maps_static_map",bt="Generate a map image with markers, paths, or routes \u2014 returned as an inline image the user can see directly in chat. PROACTIVELY call this tool after explore_area, plan_route, search_nearby, or directions to visualize results on a map \u2014 don't wait for the user to ask. Use markers from search results and path from route data. Supports roadmap, satellite, terrain, and hybrid views. Max 640x640 pixels.",Pt={center:A.string().optional().describe('Map center \u2014 "lat,lng" or address. Optional if markers or path are provided.'),zoom:A.number().optional().describe("Zoom level 0-21 (0 = world, 15 = streets, 21 = buildings). Default: auto-fit."),size:A.string().optional().describe('Image size "WxH" in pixels. Default: "600x400". Max: "640x640".'),maptype:A.enum(["roadmap","satellite","terrain","hybrid"]).optional().describe("Map style. Default: roadmap."),markers:A.array(A.string()).optional().describe('Marker descriptors. Each string: "color:red|label:A|lat,lng" or "color:blue|address". Multiple markers per string separated by |.'),path:A.array(A.string()).optional().describe('Path descriptors. Each string: "color:0x0000ff|weight:3|lat1,lng1|lat2,lng2|..." to draw lines/routes on the map.')};async function At(t){try{let e=i(),r=await new a(e).getStaticMap(t);return r.success?{content:[{type:"image",data:r.data.base64,mimeType:"image/png"},{type:"text",text:`Map generated (${r.data.size} bytes, ${r.data.dimensions})`}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to generate static map"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error generating static map: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var q={NAME:Et,DESCRIPTION:bt,SCHEMA:Pt,ACTION:At};import{z as ce}from"zod";var xt="maps_batch_geocode",vt="Geocode multiple addresses in one call \u2014 up to 50 addresses, returns coordinates for each. Use when the user provides a list of addresses and needs all their coordinates, e.g. 'geocode these 10 offices' or 'get coordinates for all these restaurants'. For more than 50, use the CLI batch-geocode command instead.",wt={addresses:ce.array(ce.string()).min(1).max(50).describe("List of addresses or landmark names to geocode (max 50)")};async function Ot(t){try{let e=i(),o=new a(e),r=t.addresses,s=await Promise.all(r.map(async p=>{try{let d=await o.geocode(p);return{address:p,...d}}catch(d){return{address:p,success:!1,error:d.message}}})),l=s.filter(p=>p.success).length,c=s.filter(p=>!p.success).length;return{content:[{type:"text",text:JSON.stringify({total:r.length,succeeded:l,failed:c,results:s},null,2)}],isError:!1}}catch(e){return{isError:!0,content:[{type:"text",text:`Error batch geocoding: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var j={NAME:xt,DESCRIPTION:vt,SCHEMA:wt,ACTION:Ot};import{z as U}from"zod";var Ct="maps_search_along_route",Mt="Search for places along a route between two points \u2014 restaurants, cafes, gas stations, etc. ranked by minimal detour time. Use for trip planning to find meals, rest stops, or attractions between landmarks without backtracking. Internally computes the route, then searches along it. Essential for building itineraries where stops should feel 'on the way' rather than 'detour to'.",Nt={textQuery:U.string().describe("What to search for along the route (e.g. 'restaurant', 'coffee shop', 'temple')"),origin:U.string().describe("Route start point \u2014 address or landmark name"),destination:U.string().describe("Route end point \u2014 address or landmark name"),mode:U.enum(["driving","walking","bicycling","transit"]).optional().describe("Travel mode for the route (default: walking)"),maxResults:U.number().optional().describe("Max results to return (default: 5, max: 20)")};async function _t(t){try{let e=i(),r=await new a(e).searchAlongRoute(t);return r.success?{content:[{type:"text",text:JSON.stringify(r.data,null,2)}],isError:!1}:{content:[{type:"text",text:r.error||"Failed to search along route"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error searching along route: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var F={NAME:Ct,DESCRIPTION:Mt,SCHEMA:Nt,ACTION:_t};import{z as S}from"zod";var Tt="maps_local_rank_tracker",It="Track a business's local search ranking across a geographic grid (like LocalFalcon). Searches the same keyword(s) from multiple coordinates around a center point to see how rank varies by location. Supports up to 3 keywords for batch scanning. Returns rank at each grid point, top-3 competitors per point, and summary metrics (ARP, ATRP, SoLV). Useful for local SEO analysis.",kt={keyword:S.string().optional().describe("Single search keyword (e.g., 'dentist'). Use 'keywords' for multi-keyword scanning."),keywords:S.array(S.string()).min(1).max(3).optional().describe("Array of 1-3 keywords to scan (e.g., ['dentist', 'dental clinic', 'teeth cleaning']). Overrides 'keyword'."),placeId:S.string().describe("Google Maps place_id of the target business to track"),center:S.object({latitude:S.number().describe("Center latitude of the grid"),longitude:S.number().describe("Center longitude of the grid")}).describe("Center coordinate for the grid (typically the business location)"),gridSize:S.number().int().min(3).max(7).optional().describe("Grid dimension (3 = 3\xD73 = 9 points, 5 = 5\xD75 = 25 points, 7 = 7\xD77 = 49 points). Default: 3"),gridSpacing:S.number().min(100).max(1e4).optional().describe("Distance between grid points in meters (100-10000). Default: 1000")};async function Rt(t){try{let e=t.keywords||(t.keyword?[t.keyword]:[]);if(e.length===0)return{content:[{type:"text",text:"Either 'keyword' or 'keywords' must be provided."}],isError:!0};let o=i(),s=await new a(o).localRankTracker({...t,keywords:e});return s.success?{content:[{type:"text",text:JSON.stringify(s.data,null,2)}],isError:!1}:{content:[{type:"text",text:s.error||"Failed to track local rank"}],isError:!0}}catch(e){return{isError:!0,content:[{type:"text",text:`Error tracking local rank: ${e instanceof Error?e.message:JSON.stringify(e)}`}]}}}var Z={NAME:Tt,DESCRIPTION:It,SCHEMA:kt,ACTION:Rt};var u={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0},zt=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:O.NAME,description:O.DESCRIPTION,schema:O.SCHEMA,annotations:u,action:t=>O.ACTION(t)},{name:C.NAME,description:C.DESCRIPTION,schema:C.SCHEMA,annotations:u,action:t=>C.ACTION(t)},{name:M.NAME,description:M.DESCRIPTION,schema:M.SCHEMA,annotations:u,action:t=>M.ACTION(t)},{name:N.NAME,description:N.DESCRIPTION,schema:N.SCHEMA,annotations:u,action:t=>N.ACTION(t)},{name:_.NAME,description:_.DESCRIPTION,schema:_.SCHEMA,annotations:u,action:t=>_.ACTION(t)},{name:T.NAME,description:T.DESCRIPTION,schema:T.SCHEMA,annotations:u,action:t=>T.ACTION(t)},{name:I.NAME,description:I.DESCRIPTION,schema:I.SCHEMA,annotations:u,action:t=>I.ACTION(t)},{name:k.NAME,description:k.DESCRIPTION,schema:k.SCHEMA,annotations:u,action:t=>k.ACTION(t)},{name:R.NAME,description:R.DESCRIPTION,schema:R.SCHEMA,annotations:u,action:t=>R.ACTION(t)},{name:H.NAME,description:H.DESCRIPTION,schema:H.SCHEMA,annotations:u,action:t=>H.ACTION(t)},{name:K.NAME,description:K.DESCRIPTION,schema:K.SCHEMA,annotations:u,action:t=>K.ACTION(t)},{name:$.NAME,description:$.DESCRIPTION,schema:$.SCHEMA,annotations:u,action:t=>$.ACTION(t)},{name:L.NAME,description:L.DESCRIPTION,schema:L.SCHEMA,annotations:u,action:t=>L.ACTION(t)},{name:J.NAME,description:J.DESCRIPTION,schema:J.SCHEMA,annotations:u,action:t=>J.ACTION(t)},{name:q.NAME,description:q.DESCRIPTION,schema:q.SCHEMA,annotations:u,action:t=>q.ACTION(t)},{name:j.NAME,description:j.DESCRIPTION,schema:j.SCHEMA,annotations:u,action:t=>j.ACTION(t)},{name:F.NAME,description:F.DESCRIPTION,schema:F.SCHEMA,annotations:u,action:t=>F.ACTION(t)},{name:Z.NAME,description:Z.DESCRIPTION,schema:Z.SCHEMA,annotations:u,action:t=>Z.ACTION(t)}]}];function re(t){let e=process.env.GOOGLE_MAPS_ENABLED_TOOLS?.trim();if(!e||e==="*")return t;let o=new Set(e.split(",").map(s=>s.trim()).filter(Boolean)),r=t.filter(s=>o.has(s.name));return r.length===0?(n.error(`GOOGLE_MAPS_ENABLED_TOOLS matched 0 tools. Available: ${t.map(s=>s.name).join(", ")}`),t):(n.log(`GOOGLE_MAPS_ENABLED_TOOLS: ${r.length}/${t.length} tools active`),r)}var X=zt;import{McpServer as Ht}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Dt}from"@modelcontextprotocol/sdk/server/stdio.js";import{StreamableHTTPServerTransport as Kt}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as $t}from"@modelcontextprotocol/sdk/types.js";import le from"express";import{randomUUID as Gt}from"crypto";import{z as Lt}from"zod";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 s=e.headers.authorization;if(s&&s.startsWith("Bearer "))return s.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 Jt="0.0.1",W=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.tools=o,this.server=this.createMcpServer()}createMcpServer(){let e=new Ht({name:this.serverName,version:Jt},{capabilities:{logging:{},tools:{}}});return this.tools.forEach(o=>{e.registerTool(o.name,{description:o.description,inputSchema:Lt.object(o.schema),annotations:o.annotations},async r=>o.action(r))}),e}async connect(e){await this.server.connect(e);let o=process.stdout.write.bind(process.stdout);process.stdout.write=(r,s,l)=>typeof r=="string"&&!r.startsWith("{")?!0:o(r,s,l),n.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e,o="0.0.0.0"){let r=le();r.use(le.json()),r.post("/mcp",async(c,p)=>{let d=c.headers["mcp-session-id"],m,E=B.getInstance().getApiKey(c,d?this.sessions[d]?.apiKey:void 0);if(n.log(`${this.serverName} API key received from request context`),d&&this.sessions[d])m=this.sessions[d],E&&(m.apiKey=E);else if(!d&&$t(c.body)){let h=new Kt({sessionIdGenerator:()=>Gt(),onsessioninitialized:f=>{this.sessions[f]=m,n.log(`[${this.serverName}] New session initialized: ${f}`)}});m={transport:h,apiKey:E},h.onclose=()=>{h.sessionId&&(delete this.sessions[h.sessionId],n.log(`[${this.serverName}] Session closed: ${h.sessionId}`))},await this.createMcpServer().connect(h)}else{p.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await ee({apiKey:m.apiKey,sessionId:d},async()=>{await m.transport.handleRequest(c,p,c.body)})});let s=async(c,p)=>{let d=c.headers["mcp-session-id"];if(!d||!this.sessions[d]){p.status(400).send("Invalid or missing session ID");return}let m=this.sessions[d],E=B.getInstance().getApiKey(c,m.apiKey);E&&(m.apiKey=E),await ee({apiKey:m.apiKey,sessionId:d},async()=>{await m.transport.handleRequest(c,p)})};r.get("/mcp",s),r.delete("/mcp",s);let l=o==="0.0.0.0"?"localhost":o;this.httpServer=r.listen(e,o,()=>{n.log(`[${this.serverName}] HTTP server listening on ${o}:${e}`),n.log(`[${this.serverName}] MCP endpoint available at http://${l}:${e}/mcp`)})}async startStdio(){let e=new Dt;await this.connect(e)}async stopHttpServer(){if(!this.httpServer){n.error(`[${this.serverName}] HTTP server is not running or already stopped.`);return}return new Promise((e,o)=>{this.httpServer.close(r=>{if(r){n.error(`[${this.serverName}] Error stopping HTTP server:`,r),o(r);return}n.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let s=Object.values(this.sessions).map(l=>(l.transport.sessionId&&delete this.sessions[l.transport.sessionId],Promise.resolve()));Promise.all(s).then(()=>{n.log(`[${this.serverName}] All transports closed.`),e()}).catch(l=>{n.error(`[${this.serverName}] Error during bulk transport closing:`,l),o(l)})})})}};import{fileURLToPath as Ut}from"url";import{dirname as Ft}from"path";import{readFileSync as pe,writeFileSync as Zt,existsSync as Bt}from"fs";import{createInterface as Wt}from"readline";var Vt=Ut(import.meta.url),ue=Ft(Vt);de({path:oe(process.cwd(),".env")});de({path:oe(ue,"../.env")});async function Yt(t,e,o){t&&(process.env.MCP_SERVER_PORT=t.toString()),e&&(process.env.GOOGLE_MAPS_API_KEY=e),o&&(process.env.MCP_SERVER_HOST=o),n.log("\u{1F680} Starting Google Maps MCP Server..."),n.log("\u{1F4CD} 18 tools registered (set GOOGLE_MAPS_ENABLED_TOOLS to limit)"),n.log("\u2139\uFE0F Reminder: enable Places API (New) in https://console.cloud.google.com before using the new Place features."),n.log("");let r=X.map(async s=>{let l=process.env[s.portEnvVar];if(!l){n.error(`\u26A0\uFE0F [${s.name}] Port environment variable ${s.portEnvVar} not set.`),n.log(`\u{1F4A1} Please set ${s.portEnvVar} in your .env file or use --port parameter.`),n.log(` Example: ${s.portEnvVar}=3000 or --port 3000`);return}let c=Number(l);if(isNaN(c)||c<=0){n.error(`\u274C [${s.name}] Invalid port number "${l}" defined in ${s.portEnvVar}.`);return}try{let p=new W(s.name,re(s.tools)),d=process.env.MCP_SERVER_HOST||"0.0.0.0";n.log(`\u{1F527} [${s.name}] Initializing MCP Server in HTTP mode on ${d}:${c}...`),await p.startHttpServer(c,d);let m=d==="0.0.0.0"?"localhost":d;n.log(`\u2705 [${s.name}] MCP Server started successfully!`),n.log(` \u{1F310} Endpoint: http://${m}:${c}/mcp`),n.log(` \u{1F4DA} Tools: ${s.tools.length} available`)}catch(p){n.error(`\u274C [${s.name}] Failed to start MCP Server on port ${c}:`,p)}});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 me=["geocode","reverse-geocode","search-nearby","search-places","place-details","directions","distance-matrix","elevation","timezone","weather","explore-area","plan-route","compare-places","air-quality","static-map","batch-geocode-tool","search-along-route","local-rank-tracker"];async function Qt(t,e,o){let r=new a(o);switch(t){case"geocode":case"maps_geocode":return r.geocode(e.address);case"reverse-geocode":case"maps_reverse_geocode":return r.reverseGeocode(e.latitude,e.longitude);case"search-nearby":case"search_nearby":case"maps_search_nearby":return r.searchNearby(e);case"search-places":case"maps_search_places":return r.searchText({query:e.query,locationBias:e.locationBias,openNow:e.openNow,minRating:e.minRating,includedType:e.includedType});case"place-details":case"get_place_details":case"maps_place_details":return r.getPlaceDetails(e.placeId,e.maxPhotos||0);case"directions":case"maps_directions":return r.getDirections(e.origin,e.destination,e.mode,e.departure_time,e.arrival_time,e.avoid_tolls,e.avoid_highways);case"distance-matrix":case"maps_distance_matrix":return r.calculateDistanceMatrix(e.origins,e.destinations,e.mode,e.departure_time,e.avoid_tolls,e.avoid_highways);case"elevation":case"maps_elevation":return r.getElevation(e.locations);case"timezone":case"maps_timezone":return r.getTimezone(e.latitude,e.longitude,e.timestamp);case"weather":case"maps_weather":return r.getWeather(e.latitude,e.longitude,e.type,e.forecastDays,e.forecastHours);case"explore-area":case"maps_explore_area":return r.exploreArea(e);case"plan-route":case"maps_plan_route":return r.planRoute(e);case"compare-places":case"maps_compare_places":return r.comparePlaces(e);case"air-quality":case"maps_air_quality":return r.getAirQuality(e.latitude,e.longitude,e.includeHealthRecommendations,e.includePollutants);case"static-map":case"maps_static_map":return r.getStaticMap(e);case"batch-geocode-tool":case"maps_batch_geocode":{let s=await Promise.all(e.addresses.map(async c=>{try{let p=await r.geocode(c);return{address:c,...p}}catch(p){return{address:c,success:!1,error:p.message}}})),l=s.filter(c=>c.success).length;return{success:!0,data:{total:e.addresses.length,succeeded:l,failed:e.addresses.length-l,results:s}}}case"search-along-route":case"maps_search_along_route":return r.searchAlongRoute(e);case"local-rank-tracker":case"maps_local_rank_tracker":return r.localRankTracker(e);default:throw new Error(`Unknown tool: ${t}. Available: ${me.join(", ")}`)}}var Xt=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")),er=import.meta.url===`file://${process.argv[1]}`;if(Xt||er){let t="0.0.0";try{let e=oe(ue,"../package.json");t=JSON.parse(pe(e,"utf-8")).version}catch{t="0.0.0"}qt(jt(process.argv)).command("exec <tool> [params]","Execute a tool directly and output JSON",e=>e.positional("tool",{type:"string",describe:`Tool name: ${me.join(", ")}`}).positional("params",{type:"string",describe:"JSON parameters string"}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).example([[`$0 exec geocode '{"address":"Tokyo Tower"}'`,"Geocode an address"],[`$0 exec search-nearby '{"center":{"value":"35.68,139.74","isCoordinates":true},"keyword":"restaurant"}'`,"Search nearby"],[`$0 exec search-places '{"query":"ramen in Tokyo"}'`,"Text search"]]),async e=>{if(!e.apikey){process.stderr.write(JSON.stringify({error:"GOOGLE_MAPS_API_KEY not set. Use --apikey or set GOOGLE_MAPS_API_KEY environment variable."},null,2)+`
|
|
4
4
|
`),process.exitCode=1;return}try{let o=e.params?JSON.parse(e.params):{},r=await Qt(e.tool,o,e.apikey);process.stdout.write(JSON.stringify(r,null,2)+`
|
|
5
5
|
`)}catch(o){process.stderr.write(JSON.stringify({error:o.message},null,2)+`
|
|
6
|
-
`),process.exitCode=1}}).command("batch-geocode","Geocode multiple addresses from a file (one address per line)",e=>e.option("input",{alias:"i",type:"string",describe:"Input file path (one address per line). Use - for stdin.",demandOption:!0}).option("output",{alias:"o",type:"string",describe:"Output file path (JSON). Defaults to stdout."}).option("concurrency",{alias:"c",type:"number",describe:"Max parallel requests",default:20}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).example([["$0 batch-geocode -i addresses.txt","Geocode to stdout"],["$0 batch-geocode -i addresses.txt -o results.json","Geocode to file"],["cat addresses.txt | $0 batch-geocode -i -","Geocode from stdin"]]),async e=>{e.apikey||(console.error("Error: GOOGLE_MAPS_API_KEY not set. Use --apikey or set env var."),process.exit(1));let o;if(e.input==="-"){let
|
|
7
|
-
`).map(
|
|
8
|
-
`);let
|
|
6
|
+
`),process.exitCode=1}}).command("batch-geocode","Geocode multiple addresses from a file (one address per line)",e=>e.option("input",{alias:"i",type:"string",describe:"Input file path (one address per line). Use - for stdin.",demandOption:!0}).option("output",{alias:"o",type:"string",describe:"Output file path (JSON). Defaults to stdout."}).option("concurrency",{alias:"c",type:"number",describe:"Max parallel requests",default:20}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).example([["$0 batch-geocode -i addresses.txt","Geocode to stdout"],["$0 batch-geocode -i addresses.txt -o results.json","Geocode to file"],["cat addresses.txt | $0 batch-geocode -i -","Geocode from stdin"]]),async e=>{e.apikey||(console.error("Error: GOOGLE_MAPS_API_KEY not set. Use --apikey or set env var."),process.exit(1));let o;if(e.input==="-"){let g=Wt({input:process.stdin});o=[];for await(let f of g){let y=f.trim();y&&o.push(y)}}else Bt(e.input)||(console.error(`Error: File not found: ${e.input}`),process.exit(1)),o=pe(e.input,"utf-8").split(`
|
|
7
|
+
`).map(g=>g.trim()).filter(g=>g.length>0);o.length===0&&(console.error("Error: No addresses found in input."),process.exit(1));let r=new a(e.apikey),s=Math.min(Math.max(e.concurrency,1),50),l=[],c=0,p=async(g,f)=>{let y=[];for(let ge of g){let se=ge().then(()=>{y.splice(y.indexOf(se),1)});y.push(se),y.length>=f&&await Promise.race(y)}await Promise.all(y)},d=o.map((g,f)=>async()=>{try{let y=await r.geocode(g);l[f]={address:g,...y}}catch(y){l[f]={address:g,success:!1,error:y.message}}c++,e.output&&process.stderr.write(`\r ${c}/${o.length} geocoded`)});await p(d,s),e.output&&process.stderr.write(`
|
|
8
|
+
`);let m=l.filter(g=>g.success).length,V=l.filter(g=>!g.success).length,E={total:o.length,succeeded:m,failed:V,results:l},h=JSON.stringify(E,null,2);e.output?(Zt(e.output,h,"utf-8"),console.error(`Done: ${m}/${o.length} succeeded. Output: ${e.output}`)):console.log(h),process.exit(V>0?1:0)}).command("$0","Start the MCP server (HTTP by default, --stdio for stdio mode)",e=>e.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("host",{type:"string",description:"Hostname to bind the server to (e.g. 0.0.0.0 for all interfaces)",default:process.env.MCP_SERVER_HOST||"0.0.0.0"}).option("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).option("stdio",{type:"boolean",description:"Use stdio transport instead of HTTP",default:!1}).example([["$0","Start HTTP server with default settings"],['$0 --port 3000 --apikey "your_api_key"',"Start HTTP with custom port and API key"],["$0 --host 0.0.0.0 --port 3000","Start HTTP accessible from all interfaces"],["$0 --stdio","Start in stdio mode (for Claude Desktop, Cursor, etc.)"]]),async e=>{e.apikey&&(process.env.GOOGLE_MAPS_API_KEY=e.apikey);let o=re(X[0].tools);e.stdio?await new W(X[0].name,o).startStdio():(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("")),Yt(e.port,e.apikey,e.host).catch(r=>{n.error("\u274C Failed to start server:",r),process.exit(1)}))}).version(t).alias("version","v").help().parse()}export{Yt as startServer};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cablate/mcp-google-map",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.58",
|
|
4
4
|
"mcpName": "io.github.cablate/google-map",
|
|
5
5
|
"description": "18 Google Maps tools for AI agents — geocode, search, directions, weather, air quality, local rank tracking, map images via MCP server or standalone CLI",
|
|
6
6
|
"type": "module",
|