@cablate/mcp-google-map 0.0.24 → 0.0.26
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 +23 -37
- package/dist/cli.js +2 -2
- package/package.json +17 -10
- package/skills/google-maps/SKILL.md +78 -0
- package/skills/google-maps/references/tools-api.md +180 -0
package/README.md
CHANGED
|
@@ -61,62 +61,48 @@ All tools are annotated with `readOnlyHint: true` and `destructiveHint: false`
|
|
|
61
61
|
|
|
62
62
|
## Installation
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
### Method 1: stdio (Recommended for most clients)
|
|
65
65
|
|
|
66
|
-
|
|
66
|
+
Works with Claude Desktop, Cursor, VS Code, and any MCP client that supports stdio:
|
|
67
67
|
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
mcp-google-map --
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"mcpServers": {
|
|
71
|
+
"google-maps": {
|
|
72
|
+
"command": "npx",
|
|
73
|
+
"args": ["-y", "@cablate/mcp-google-map", "--stdio"],
|
|
74
|
+
"env": {
|
|
75
|
+
"GOOGLE_MAPS_API_KEY": "YOUR_API_KEY"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
77
80
|
```
|
|
78
81
|
|
|
79
|
-
### Method 2:
|
|
80
|
-
|
|
81
|
-
> Cannot be used directly in MCP Server Settings with stdio mode
|
|
82
|
+
### Method 2: HTTP Server
|
|
82
83
|
|
|
83
|
-
|
|
84
|
+
For multi-session deployments, per-request API key isolation, or remote access:
|
|
84
85
|
|
|
85
86
|
```bash
|
|
86
|
-
# Run in a separate terminal
|
|
87
87
|
npx @cablate/mcp-google-map --port 3000 --apikey "YOUR_API_KEY"
|
|
88
|
-
|
|
89
|
-
# Or with environment variable
|
|
90
|
-
GOOGLE_MAPS_API_KEY=YOUR_API_KEY npx @cablate/mcp-google-map
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
**Step 2: Configure MCP Client to Use HTTP**
|
|
94
|
-
|
|
95
|
-
```json
|
|
96
|
-
{
|
|
97
|
-
"mcp-google-map": {
|
|
98
|
-
"transport": "http",
|
|
99
|
-
"url": "http://localhost:3000/mcp"
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
88
|
```
|
|
103
89
|
|
|
104
|
-
|
|
90
|
+
Then configure your MCP client:
|
|
105
91
|
|
|
106
92
|
```json
|
|
107
|
-
// This WILL NOT WORK - stdio mode not supported with npx
|
|
108
93
|
{
|
|
109
|
-
"
|
|
110
|
-
"
|
|
111
|
-
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"google-maps": {
|
|
96
|
+
"type": "http",
|
|
97
|
+
"url": "http://localhost:3000/mcp"
|
|
98
|
+
}
|
|
112
99
|
}
|
|
113
100
|
}
|
|
114
101
|
```
|
|
115
102
|
|
|
116
103
|
### Server Information
|
|
117
104
|
|
|
118
|
-
- **
|
|
119
|
-
- **Transport**: Streamable HTTP (not stdio)
|
|
105
|
+
- **Transport**: stdio (`--stdio`) or Streamable HTTP (default)
|
|
120
106
|
- **Tools**: 8 Google Maps tools
|
|
121
107
|
|
|
122
108
|
### CLI Exec Mode (Agent Skill)
|
package/dist/cli.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{b as s,c as i}from"./chunk-RIT3FLYG.js";import{config as D}from"dotenv";import{resolve as
|
|
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 f={NAME:J,DESCRIPTION:L,SCHEMA:q,ACTION:j};import{z as V}from"zod";var F="get_place_details",U="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, opening hours, and photos. Returns everything needed to evaluate or contact a business.",B={placeId:V.string().describe("Google Maps place ID")};async function W(t){try{let e=c(),r=await new i(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 h={NAME:F,DESCRIPTION:U,SCHEMA:B,ACTION:W};import{z as Z}from"zod";var Y="maps_geocode",X="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.",Q={address:Z.string().describe("Address or place name to convert to coordinates")};async function ee(t){try{let e=c(),r=await new i(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 v={NAME:Y,DESCRIPTION:X,SCHEMA:Q,ACTION:ee};import{z as R}from"zod";var re="maps_reverse_geocode",te="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.",oe={latitude:R.number().describe("Latitude coordinate"),longitude:R.number().describe("Longitude coordinate")};async function se(t){try{let e=c(),r=await new i(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 S={NAME:re,DESCRIPTION:te,SCHEMA:oe,ACTION:se};import{z as E}from"zod";var ne="maps_distance_matrix",ae="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.",ie={origins:E.array(E.string()).describe("List of origin addresses or coordinates"),destinations:E.array(E.string()).describe("List of destination addresses or coordinates"),mode:E.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")};async function ce(t){try{let e=c(),r=await new i(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 P={NAME:ne,DESCRIPTION:ae,SCHEMA:ie,ACTION:ce};import{z as b}from"zod";var le="maps_directions",pe="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.",de={origin:b.string().describe("Starting point address or coordinates"),destination:b.string().describe("Destination address or coordinates"),mode:b.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:b.string().optional().describe("Departure time (ISO string format)"),arrival_time:b.string().optional().describe("Arrival time (ISO string format)")};async function ue(t){try{let e=c(),r=await new i(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 x={NAME:le,DESCRIPTION:pe,SCHEMA:de,ACTION:ue};import{z as C}from"zod";var me="maps_elevation",ge="Get elevation (height above sea level in meters) for one or more geographic coordinates. Use for terrain analysis, hiking/cycling route planning, or when the user asks about altitude at specific locations.",ye={locations:C.array(C.object({latitude:C.number().describe("Latitude coordinate"),longitude:C.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")};async function fe(t){try{let e=c(),r=await new i(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:me,DESCRIPTION:ge,SCHEMA:ye,ACTION:fe};import{z as d}from"zod";var he="maps_search_places",ve="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.",Se={query:d.string().describe("Text search query (e.g., 'Italian restaurants in Manhattan', 'hotels near Taipei 101')"),locationBias:d.object({latitude:d.number().describe("Latitude to bias results toward"),longitude:d.number().describe("Longitude to bias results toward"),radius:d.number().optional().describe("Bias radius in meters (default: 5000)")}).optional().describe("Optional location to bias results toward"),openNow:d.boolean().optional().describe("Only return places that are currently open"),minRating:d.number().optional().describe("Minimum rating filter (1.0 - 5.0)"),includedType:d.string().optional().describe("Filter by place type (e.g., restaurant, cafe, hotel)")};async function Ee(t){try{let e=c(),r=await new i(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 M={NAME:he,DESCRIPTION:ve,SCHEMA:Se,ACTION:Ee};var u={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0},Pe=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:f.NAME,description:f.DESCRIPTION,schema:f.SCHEMA,annotations:u,action:t=>f.ACTION(t)},{name:h.NAME,description:h.DESCRIPTION,schema:h.SCHEMA,annotations:u,action:t=>h.ACTION(t)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,annotations:u,action:t=>v.ACTION(t)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,annotations:u,action:t=>S.ACTION(t)},{name:P.NAME,description:P.DESCRIPTION,schema:P.SCHEMA,annotations:u,action:t=>P.ACTION(t)},{name:x.NAME,description:x.DESCRIPTION,schema:x.SCHEMA,annotations:u,action:t=>x.ACTION(t)},{name:A.NAME,description:A.DESCRIPTION,schema:A.SCHEMA,annotations:u,action:t=>A.ACTION(t)},{name:M.NAME,description:M.DESCRIPTION,schema:M.SCHEMA,annotations:u,action:t=>M.ACTION(t)}]}],K=Pe;import{McpServer as be}from"@modelcontextprotocol/sdk/server/mcp.js";import{StreamableHTTPServerTransport as xe}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as Ae}from"@modelcontextprotocol/sdk/types.js";import $ from"express";import{randomUUID as Me}from"crypto";import{z as Ne}from"zod";var N=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 Ce="0.0.1",O=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.tools=o,this.server=this.createMcpServer()}createMcpServer(){let e=new be({name:this.serverName,version:Ce},{capabilities:{logging:{},tools:{}}});return this.tools.forEach(o=>{e.registerTool(o.name,{description:o.description,inputSchema:Ne.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,n,a)=>typeof r=="string"&&!r.startsWith("{")?!0:o(r,n,a),s.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let o=$();o.use($.json()),o.post("/mcp",async(n,a)=>{let l=n.headers["mcp-session-id"],p,g=N.getInstance().getApiKey(n);if(s.log(`${this.serverName} API key received from request context`),l&&this.sessions[l])p=this.sessions[l],g&&(p.apiKey=g);else if(!l&&Ae(n.body)){let y=new xe({sessionIdGenerator:()=>Me(),onsessioninitialized:_=>{this.sessions[_]=p,s.log(`[${this.serverName}] New session initialized: ${_}`)}});p={transport:y,apiKey:g},y.onclose=()=>{y.sessionId&&(delete this.sessions[y.sessionId],s.log(`[${this.serverName}] Session closed: ${y.sessionId}`))},await this.createMcpServer().connect(y)}else{a.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await I({apiKey:p.apiKey,sessionId:l},async()=>{await p.transport.handleRequest(n,a,n.body)})});let r=async(n,a)=>{let l=n.headers["mcp-session-id"];if(!l||!this.sessions[l]){a.status(400).send("Invalid or missing session ID");return}let p=this.sessions[l],g=N.getInstance().getApiKey(n);g&&(p.apiKey=g),await I({apiKey:p.apiKey,sessionId:l},async()=>{await p.transport.handleRequest(n,a)})};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(a=>(a.transport.sessionId&&delete this.sessions[a.transport.sessionId],Promise.resolve()));Promise.all(n).then(()=>{s.log(`[${this.serverName}] All transports closed.`),e()}).catch(a=>{s.error(`[${this.serverName}] Error during bulk transport closing:`,a),o(a)})})})}};import{fileURLToPath as Te}from"url";import{dirname as _e}from"path";import{readFileSync as we}from"fs";var Re=Te(import.meta.url),G=_e(Re);D({path:T(process.cwd(),".env")});D({path:T(G,"../.env")});async function Ke(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=K.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 a=Number(n);if(isNaN(a)||a<=0){s.error(`\u274C [${r.name}] Invalid port number "${n}" defined in ${r.portEnvVar}.`);return}try{let l=new O(r.name,r.tools);s.log(`\u{1F527} [${r.name}] Initializing MCP Server in HTTP mode on port ${a}...`),await l.startHttpServer(a),s.log(`\u2705 [${r.name}] MCP Server started successfully!`),s.log(` \u{1F310} Endpoint: http://localhost:${a}/mcp`),s.log(` \u{1F4DA} Tools: ${r.tools.length} available`)}catch(l){s.error(`\u274C [${r.name}] Failed to start MCP Server on port ${a}:`,l)}});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 H=["geocode","reverse-geocode","search-nearby","search-places","place-details","directions","distance-matrix","elevation"];async function $e(t,e,o){let r=new i(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":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":return r.getPlaceDetails(e.placeId);case"directions":case"maps_directions":return r.getDirections(e.origin,e.destination,e.mode,e.departure_time,e.arrival_time);case"distance-matrix":case"maps_distance_matrix":return r.calculateDistanceMatrix(e.origins,e.destinations,e.mode);case"elevation":case"maps_elevation":return r.getElevation(e.locations);default:throw new Error(`Unknown tool: ${t}. Available: ${H.join(", ")}`)}}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")),Ge=import.meta.url===`file://${process.argv[1]}`;if(De||Ge){let t="0.0.0";try{let e=T(G,"../package.json");t=JSON.parse(we(e,"utf-8")).version}catch{t="0.0.0"}Oe(Ie(process.argv)).command("exec <tool> [params]","Execute a tool directly and output JSON",e=>e.positional("tool",{type:"string",describe:`Tool name: ${H.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=>{e.apikey||(console.error(JSON.stringify({error:"GOOGLE_MAPS_API_KEY not set. Use --apikey or set GOOGLE_MAPS_API_KEY environment variable."},null,2)),process.exit(1));try{let o=e.params?JSON.parse(e.params):{},r=await $e(e.tool,o,e.apikey);console.log(JSON.stringify(r,null,2)),process.exit(0)}catch(o){console.error(JSON.stringify({error:o.message},null,2)),process.exit(1)}}).command("$0","Start the MCP server",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("apikey",{alias:"k",type:"string",description:"Google Maps API key",default:process.env.GOOGLE_MAPS_API_KEY}).example([["$0","Start server with default settings"],['$0 --port 3000 --apikey "your_api_key"',"Start with custom port and API key"]]),async e=>{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("")),Ke(e.port,e.apikey).catch(o=>{s.error("\u274C Failed to start server:",o),process.exit(1)})}).version(t).alias("version","v").help().parse()}export{Ke as startServer};
|
|
2
|
+
import{b as s,c as i}from"./chunk-RIT3FLYG.js";import{config as D}from"dotenv";import{resolve as _}from"path";import Te from"yargs";import{hideBin as Ie}from"yargs/helpers";import{z as m}from"zod";import{AsyncLocalStorage as z}from"async_hooks";var R=new z;function c(){return R.getStore()?.apiKey||process.env.GOOGLE_MAPS_API_KEY}function I(r,e){return R.run(r,e)}var J="search_nearby",L="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.",q={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 (e.g. value: 49.3268778,-123.0585982, isCoordinates: true)"),keyword:m.string().optional().describe("Place type to search for (e.g., restaurant, cafe, hotel, gas_station, hospital)"),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 j(r){try{let e=c(),t=await new i(e).searchNearby(r);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||"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 f={NAME:J,DESCRIPTION:L,SCHEMA:q,ACTION:j};import{z as U}from"zod";var V="get_place_details",F="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, opening hours, and photos. Returns everything needed to evaluate or contact a business.",B={placeId:U.string().describe("Google Maps place ID")};async function W(r){try{let e=c(),t=await new i(e).getPlaceDetails(r.placeId);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 h={NAME:V,DESCRIPTION:F,SCHEMA:B,ACTION:W};import{z as Y}from"zod";var Z="maps_geocode",X="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.",Q={address:Y.string().describe("Address or place name to convert to coordinates")};async function ee(r){try{let e=c(),t=await new i(e).geocode(r.address);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 v={NAME:Z,DESCRIPTION:X,SCHEMA:Q,ACTION:ee};import{z as K}from"zod";var te="maps_reverse_geocode",re="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.",oe={latitude:K.number().describe("Latitude coordinate"),longitude:K.number().describe("Longitude coordinate")};async function se(r){try{let e=c(),t=await new i(e).reverseGeocode(r.latitude,r.longitude);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 S={NAME:te,DESCRIPTION:re,SCHEMA:oe,ACTION:se};import{z as E}from"zod";var ne="maps_distance_matrix",ae="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.",ie={origins:E.array(E.string()).describe("List of origin addresses or coordinates"),destinations:E.array(E.string()).describe("List of destination addresses or coordinates"),mode:E.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for calculation")};async function ce(r){try{let e=c(),t=await new i(e).calculateDistanceMatrix(r.origins,r.destinations,r.mode);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 P={NAME:ne,DESCRIPTION:ae,SCHEMA:ie,ACTION:ce};import{z as b}from"zod";var le="maps_directions",pe="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.",de={origin:b.string().describe("Starting point address or coordinates"),destination:b.string().describe("Destination address or coordinates"),mode:b.enum(["driving","walking","bicycling","transit"]).default("driving").describe("Travel mode for directions"),departure_time:b.string().optional().describe("Departure time (ISO string format)"),arrival_time:b.string().optional().describe("Arrival time (ISO string format)")};async function ue(r){try{let e=c(),t=await new i(e).getDirections(r.origin,r.destination,r.mode,r.departure_time,r.arrival_time);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 x={NAME:le,DESCRIPTION:pe,SCHEMA:de,ACTION:ue};import{z as O}from"zod";var me="maps_elevation",ge="Get elevation (height above sea level in meters) for one or more geographic coordinates. Use for terrain analysis, hiking/cycling route planning, or when the user asks about altitude at specific locations.",ye={locations:O.array(O.object({latitude:O.number().describe("Latitude coordinate"),longitude:O.number().describe("Longitude coordinate")})).describe("List of locations to get elevation data for")};async function fe(r){try{let e=c(),t=await new i(e).getElevation(r.locations);return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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:me,DESCRIPTION:ge,SCHEMA:ye,ACTION:fe};import{z as d}from"zod";var he="maps_search_places",ve="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.",Se={query:d.string().describe("Text search query (e.g., 'Italian restaurants in Manhattan', 'hotels near Taipei 101')"),locationBias:d.object({latitude:d.number().describe("Latitude to bias results toward"),longitude:d.number().describe("Longitude to bias results toward"),radius:d.number().optional().describe("Bias radius in meters (default: 5000)")}).optional().describe("Optional location to bias results toward"),openNow:d.boolean().optional().describe("Only return places that are currently open"),minRating:d.number().optional().describe("Minimum rating filter (1.0 - 5.0)"),includedType:d.string().optional().describe("Filter by place type (e.g., restaurant, cafe, hotel)")};async function Ee(r){try{let e=c(),t=await new i(e).searchText({query:r.query,locationBias:r.locationBias,openNow:r.openNow,minRating:r.minRating,includedType:r.includedType});return t.success?{content:[{type:"text",text:JSON.stringify(t.data,null,2)}],isError:!1}:{content:[{type:"text",text:t.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 M={NAME:he,DESCRIPTION:ve,SCHEMA:Se,ACTION:Ee};var u={readOnlyHint:!0,destructiveHint:!1,idempotentHint:!0,openWorldHint:!0},Pe=[{name:"MCP-Server",portEnvVar:"MCP_SERVER_PORT",tools:[{name:f.NAME,description:f.DESCRIPTION,schema:f.SCHEMA,annotations:u,action:r=>f.ACTION(r)},{name:h.NAME,description:h.DESCRIPTION,schema:h.SCHEMA,annotations:u,action:r=>h.ACTION(r)},{name:v.NAME,description:v.DESCRIPTION,schema:v.SCHEMA,annotations:u,action:r=>v.ACTION(r)},{name:S.NAME,description:S.DESCRIPTION,schema:S.SCHEMA,annotations:u,action:r=>S.ACTION(r)},{name:P.NAME,description:P.DESCRIPTION,schema:P.SCHEMA,annotations:u,action:r=>P.ACTION(r)},{name:x.NAME,description:x.DESCRIPTION,schema:x.SCHEMA,annotations:u,action:r=>x.ACTION(r)},{name:A.NAME,description:A.DESCRIPTION,schema:A.SCHEMA,annotations:u,action:r=>A.ACTION(r)},{name:M.NAME,description:M.DESCRIPTION,schema:M.SCHEMA,annotations:u,action:r=>M.ACTION(r)}]}],T=Pe;import{McpServer as be}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as xe}from"@modelcontextprotocol/sdk/server/stdio.js";import{StreamableHTTPServerTransport as Ae}from"@modelcontextprotocol/sdk/server/streamableHttp.js";import{isInitializeRequest as Me}from"@modelcontextprotocol/sdk/types.js";import $ from"express";import{randomUUID as Ne}from"crypto";import{z as Ce}from"zod";var N=class r{constructor(){this.defaultApiKey=process.env.GOOGLE_MAPS_API_KEY}static getInstance(){return r.instance||(r.instance=new r),r.instance}setDefaultApiKey(e){this.defaultApiKey=e,process.env.GOOGLE_MAPS_API_KEY=e}getApiKey(e,o){if(e){let t=e.headers["x-google-maps-api-key"];if(t)return t;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 Oe="0.0.1",C=class{constructor(e,o){this.sessions={};this.httpServer=null;this.serverName=e,this.tools=o,this.server=this.createMcpServer()}createMcpServer(){let e=new be({name:this.serverName,version:Oe},{capabilities:{logging:{},tools:{}}});return this.tools.forEach(o=>{e.registerTool(o.name,{description:o.description,inputSchema:Ce.object(o.schema),annotations:o.annotations},async t=>o.action(t))}),e}async connect(e){await this.server.connect(e);let o=process.stdout.write.bind(process.stdout);process.stdout.write=(t,n,a)=>typeof t=="string"&&!t.startsWith("{")?!0:o(t,n,a),s.log(`${this.serverName} connected and ready to process requests`)}async startHttpServer(e){let o=$();o.use($.json()),o.post("/mcp",async(n,a)=>{let l=n.headers["mcp-session-id"],p,g=N.getInstance().getApiKey(n);if(s.log(`${this.serverName} API key received from request context`),l&&this.sessions[l])p=this.sessions[l],g&&(p.apiKey=g);else if(!l&&Me(n.body)){let y=new Ae({sessionIdGenerator:()=>Ne(),onsessioninitialized:w=>{this.sessions[w]=p,s.log(`[${this.serverName}] New session initialized: ${w}`)}});p={transport:y,apiKey:g},y.onclose=()=>{y.sessionId&&(delete this.sessions[y.sessionId],s.log(`[${this.serverName}] Session closed: ${y.sessionId}`))},await this.createMcpServer().connect(y)}else{a.status(400).json({jsonrpc:"2.0",error:{code:-32e3,message:"Bad Request: No valid session ID provided"},id:null});return}await I({apiKey:p.apiKey,sessionId:l},async()=>{await p.transport.handleRequest(n,a,n.body)})});let t=async(n,a)=>{let l=n.headers["mcp-session-id"];if(!l||!this.sessions[l]){a.status(400).send("Invalid or missing session ID");return}let p=this.sessions[l],g=N.getInstance().getApiKey(n);g&&(p.apiKey=g),await I({apiKey:p.apiKey,sessionId:l},async()=>{await p.transport.handleRequest(n,a)})};o.get("/mcp",t),o.delete("/mcp",t),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 startStdio(){let e=new xe;await this.connect(e)}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(t=>{if(t){s.error(`[${this.serverName}] Error stopping HTTP server:`,t),o(t);return}s.log(`[${this.serverName}] HTTP server stopped.`),this.httpServer=null;let n=Object.values(this.sessions).map(a=>(a.transport.sessionId&&delete this.sessions[a.transport.sessionId],Promise.resolve()));Promise.all(n).then(()=>{s.log(`[${this.serverName}] All transports closed.`),e()}).catch(a=>{s.error(`[${this.serverName}] Error during bulk transport closing:`,a),o(a)})})})}};import{fileURLToPath as _e}from"url";import{dirname as we}from"path";import{readFileSync as Re}from"fs";var Ke=_e(import.meta.url),G=we(Ke);D({path:_(process.cwd(),".env")});D({path:_(G,"../.env")});async function $e(r,e){r&&(process.env.MCP_SERVER_PORT=r.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=T.map(async t=>{let n=process.env[t.portEnvVar];if(!n){s.error(`\u26A0\uFE0F [${t.name}] Port environment variable ${t.portEnvVar} not set.`),s.log(`\u{1F4A1} Please set ${t.portEnvVar} in your .env file or use --port parameter.`),s.log(` Example: ${t.portEnvVar}=3000 or --port 3000`);return}let a=Number(n);if(isNaN(a)||a<=0){s.error(`\u274C [${t.name}] Invalid port number "${n}" defined in ${t.portEnvVar}.`);return}try{let l=new C(t.name,t.tools);s.log(`\u{1F527} [${t.name}] Initializing MCP Server in HTTP mode on port ${a}...`),await l.startHttpServer(a),s.log(`\u2705 [${t.name}] MCP Server started successfully!`),s.log(` \u{1F310} Endpoint: http://localhost:${a}/mcp`),s.log(` \u{1F4DA} Tools: ${t.tools.length} available`)}catch(l){s.error(`\u274C [${t.name}] Failed to start MCP Server on port ${a}:`,l)}});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 H=["geocode","reverse-geocode","search-nearby","search-places","place-details","directions","distance-matrix","elevation"];async function De(r,e,o){let t=new i(o);switch(r){case"geocode":case"maps_geocode":return t.geocode(e.address);case"reverse-geocode":case"maps_reverse_geocode":return t.reverseGeocode(e.latitude,e.longitude);case"search-nearby":case"search_nearby":return t.searchNearby(e);case"search-places":case"maps_search_places":return t.searchText({query:e.query,locationBias:e.locationBias,openNow:e.openNow,minRating:e.minRating,includedType:e.includedType});case"place-details":case"get_place_details":return t.getPlaceDetails(e.placeId);case"directions":case"maps_directions":return t.getDirections(e.origin,e.destination,e.mode,e.departure_time,e.arrival_time);case"distance-matrix":case"maps_distance_matrix":return t.calculateDistanceMatrix(e.origins,e.destinations,e.mode);case"elevation":case"maps_elevation":return t.getElevation(e.locations);default:throw new Error(`Unknown tool: ${r}. Available: ${H.join(", ")}`)}}var Ge=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")),He=import.meta.url===`file://${process.argv[1]}`;if(Ge||He){let r="0.0.0";try{let e=_(G,"../package.json");r=JSON.parse(Re(e,"utf-8")).version}catch{r="0.0.0"}Te(Ie(process.argv)).command("exec <tool> [params]","Execute a tool directly and output JSON",e=>e.positional("tool",{type:"string",describe:`Tool name: ${H.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=>{e.apikey||(console.error(JSON.stringify({error:"GOOGLE_MAPS_API_KEY not set. Use --apikey or set GOOGLE_MAPS_API_KEY environment variable."},null,2)),process.exit(1));try{let o=e.params?JSON.parse(e.params):{},t=await De(e.tool,o,e.apikey);console.log(JSON.stringify(t,null,2)),process.exit(0)}catch(o){console.error(JSON.stringify({error:o.message},null,2)),process.exit(1)}}).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("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 --stdio","Start in stdio mode (for Claude Desktop, Cursor, etc.)"]]),async e=>{e.apikey&&(process.env.GOOGLE_MAPS_API_KEY=e.apikey),e.stdio?await new C(T[0].name,T[0].tools).startStdio():(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("")),$e(e.port,e.apikey).catch(o=>{s.error("\u274C Failed to start server:",o),process.exit(1)}))}).version(r).alias("version","v").help().parse()}export{$e as startServer};
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cablate/mcp-google-map",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.0.26",
|
|
4
|
+
"mcpName": "io.github.cablate/google-map",
|
|
5
|
+
"description": "Google Maps tools for AI agents — 8 tools (geocode, search, directions, elevation) via MCP server or standalone Agent Skill CLI",
|
|
5
6
|
"type": "module",
|
|
6
7
|
"main": "dist/index.js",
|
|
7
8
|
"bin": {
|
|
@@ -10,7 +11,8 @@
|
|
|
10
11
|
"files": [
|
|
11
12
|
"dist",
|
|
12
13
|
"dist/**/*.map",
|
|
13
|
-
"README.md"
|
|
14
|
+
"README.md",
|
|
15
|
+
"skills"
|
|
14
16
|
],
|
|
15
17
|
"scripts": {
|
|
16
18
|
"build": "tsup --dts",
|
|
@@ -28,16 +30,21 @@
|
|
|
28
30
|
},
|
|
29
31
|
"keywords": [
|
|
30
32
|
"google",
|
|
33
|
+
"google-maps",
|
|
31
34
|
"map",
|
|
32
|
-
"api",
|
|
33
|
-
"llm",
|
|
34
|
-
"typescript",
|
|
35
35
|
"mcp",
|
|
36
|
-
"server",
|
|
37
|
-
"
|
|
38
|
-
"
|
|
36
|
+
"mcp-server",
|
|
37
|
+
"model-context-protocol",
|
|
38
|
+
"agent-skill",
|
|
39
|
+
"ai-agent",
|
|
40
|
+
"geospatial",
|
|
39
41
|
"geocoding",
|
|
40
|
-
"
|
|
42
|
+
"places",
|
|
43
|
+
"directions",
|
|
44
|
+
"navigation",
|
|
45
|
+
"location",
|
|
46
|
+
"streamable-http",
|
|
47
|
+
"typescript"
|
|
41
48
|
],
|
|
42
49
|
"author": "CabLate",
|
|
43
50
|
"license": "MIT",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: google-maps
|
|
3
|
+
description: Geospatial query capabilities — geocoding, nearby search, routing, place details, elevation. Trigger when the user mentions locations, addresses, coordinates, navigation, "what's nearby", "how to get there", distance/duration, or any question that inherently involves geographic information — even if they don't explicitly say "map". Update when new tools are added or tool parameters change.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Google Maps - Geospatial Query Capabilities
|
|
7
|
+
|
|
8
|
+
## Overview
|
|
9
|
+
|
|
10
|
+
Gives an AI Agent the ability to reason about physical space — understand locations, distances, routes, and elevation, and naturally weave that information into conversation.
|
|
11
|
+
|
|
12
|
+
Without this Skill, the agent can only guess or refuse when asked "how do I get from Taipei 101 to the National Palace Museum?". With it, the agent returns exact coordinates, step-by-step routes, and travel times.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Core Principles
|
|
17
|
+
|
|
18
|
+
| Principle | Explanation |
|
|
19
|
+
|-----------|-------------|
|
|
20
|
+
| Chain over single-shot | Most geo questions require chaining: geocode → search-nearby → place-details. Think about the full pipeline when planning queries. |
|
|
21
|
+
| Precise input saves trouble | Use coordinates over address strings when available. Use place_id over name search. More precise input = more reliable output. |
|
|
22
|
+
| Output is structured | Every tool returns JSON. Use it directly for downstream computation or comparison — no extra parsing needed. |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Tool Map
|
|
27
|
+
|
|
28
|
+
8 tools in three categories — pick by scenario:
|
|
29
|
+
|
|
30
|
+
### Place Discovery
|
|
31
|
+
| Tool | When to use | Example |
|
|
32
|
+
|------|-------------|---------|
|
|
33
|
+
| `geocode` | Have an address/landmark, need coordinates | "What are the coordinates of Tokyo Tower?" |
|
|
34
|
+
| `reverse-geocode` | Have coordinates, need an address | "What's at 35.65, 139.74?" |
|
|
35
|
+
| `search-nearby` | Know a location, find nearby places by type | "Coffee shops near my hotel" |
|
|
36
|
+
| `search-places` | Natural language place search | "Best ramen in Tokyo" |
|
|
37
|
+
| `place-details` | Have a place_id, need full info | "Opening hours and reviews for this restaurant?" |
|
|
38
|
+
|
|
39
|
+
### Routing & Distance
|
|
40
|
+
| Tool | When to use | Example |
|
|
41
|
+
|------|-------------|---------|
|
|
42
|
+
| `directions` | How to get from A to B | "Route from Taipei Main Station to the airport" |
|
|
43
|
+
| `distance-matrix` | Compare distances across multiple points | "Which of these 3 hotels is closest to the airport?" |
|
|
44
|
+
|
|
45
|
+
### Terrain
|
|
46
|
+
| Tool | When to use | Example |
|
|
47
|
+
|------|-------------|---------|
|
|
48
|
+
| `elevation` | Query altitude | "Elevation profile along this hiking trail" |
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Invocation
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
npx @cablate/mcp-google-map exec <tool> '<json_params>' [-k API_KEY]
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
- **API Key**: `-k` flag or `GOOGLE_MAPS_API_KEY` environment variable
|
|
59
|
+
- **Output**: JSON to stdout, errors to stderr
|
|
60
|
+
- **Stateless**: each call is independent
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## When to Update This Skill
|
|
65
|
+
|
|
66
|
+
| Trigger | What to update |
|
|
67
|
+
|---------|----------------|
|
|
68
|
+
| New tool added to the package | Tool Map table + references/tools-api.md |
|
|
69
|
+
| Tool parameters changed | references/tools-api.md |
|
|
70
|
+
| New chaining pattern discovered in practice | references/tools-api.md chaining section |
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Reference
|
|
75
|
+
|
|
76
|
+
| File | Content | When to read |
|
|
77
|
+
|------|---------|--------------|
|
|
78
|
+
| `references/tools-api.md` | Full parameter specs, response formats, and chaining patterns for all 8 tools | When you need exact parameter names, types, response shapes, or multi-tool workflows |
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# Google Maps Tools - Parameter & Response Reference
|
|
2
|
+
|
|
3
|
+
## geocode
|
|
4
|
+
|
|
5
|
+
Convert an address or landmark name to GPS coordinates.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
exec geocode '{"address": "Tokyo Tower"}'
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
| Param | Type | Required | Description |
|
|
12
|
+
|-------|------|----------|-------------|
|
|
13
|
+
| address | string | yes | Address or landmark name |
|
|
14
|
+
|
|
15
|
+
Response:
|
|
16
|
+
```json
|
|
17
|
+
{
|
|
18
|
+
"success": true,
|
|
19
|
+
"data": {
|
|
20
|
+
"location": { "lat": 35.6585805, "lng": 139.7454329 },
|
|
21
|
+
"formatted_address": "4-chome-2-8 Shibakoen, Minato City, Tokyo 105-0011, Japan",
|
|
22
|
+
"place_id": "ChIJCewJkL2LGGAR3Qmk0vCTGkg"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## reverse-geocode
|
|
30
|
+
|
|
31
|
+
Convert GPS coordinates to a street address.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
exec reverse-geocode '{"latitude": 35.6586, "longitude": 139.7454}'
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
| Param | Type | Required | Description |
|
|
38
|
+
|-------|------|----------|-------------|
|
|
39
|
+
| latitude | number | yes | Latitude |
|
|
40
|
+
| longitude | number | yes | Longitude |
|
|
41
|
+
|
|
42
|
+
Response:
|
|
43
|
+
```json
|
|
44
|
+
{
|
|
45
|
+
"success": true,
|
|
46
|
+
"data": {
|
|
47
|
+
"formatted_address": "...",
|
|
48
|
+
"place_id": "ChIJ...",
|
|
49
|
+
"address_components": [...]
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## search-nearby
|
|
57
|
+
|
|
58
|
+
Find places near a location by type.
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
exec search-nearby '{"center": {"value": "35.6586,139.7454", "isCoordinates": true}, "keyword": "restaurant", "radius": 500}'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
| Param | Type | Required | Description |
|
|
65
|
+
|-------|------|----------|-------------|
|
|
66
|
+
| center | object | yes | `{ value: string, isCoordinates: boolean }` — address or `lat,lng` |
|
|
67
|
+
| keyword | string | no | Place type (restaurant, cafe, hotel, gas_station, hospital, etc.) |
|
|
68
|
+
| radius | number | no | Search radius in meters (default: 1000) |
|
|
69
|
+
| openNow | boolean | no | Only show currently open places |
|
|
70
|
+
| minRating | number | no | Minimum rating (0-5) |
|
|
71
|
+
|
|
72
|
+
Response: `{ success, location, data: [{ name, place_id, formatted_address, geometry, rating, user_ratings_total, opening_hours }] }`
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## search-places
|
|
77
|
+
|
|
78
|
+
Free-text place search. More flexible than search-nearby.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
exec search-places '{"query": "ramen in Tokyo"}'
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
| Param | Type | Required | Description |
|
|
85
|
+
|-------|------|----------|-------------|
|
|
86
|
+
| query | string | yes | Natural language search query |
|
|
87
|
+
| locationBias | object | no | `{ latitude, longitude, radius? }` to bias results toward |
|
|
88
|
+
| openNow | boolean | no | Only show currently open places |
|
|
89
|
+
| minRating | number | no | Minimum rating (1.0-5.0) |
|
|
90
|
+
| includedType | string | no | Place type filter |
|
|
91
|
+
|
|
92
|
+
Response: `{ success, data: [{ name, place_id, address, location, rating, total_ratings, open_now }] }`
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## place-details
|
|
97
|
+
|
|
98
|
+
Get full details for a place by its place_id (from search results). Returns reviews, phone, website, hours, photos.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
exec place-details '{"placeId": "ChIJCewJkL2LGGAR3Qmk0vCTGkg"}'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
| Param | Type | Required | Description |
|
|
105
|
+
|-------|------|----------|-------------|
|
|
106
|
+
| placeId | string | yes | Google Maps place ID (from search results) |
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## directions
|
|
111
|
+
|
|
112
|
+
Get step-by-step navigation between two points.
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
exec directions '{"origin": "Tokyo Tower", "destination": "Shibuya Station", "mode": "transit"}'
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Param | Type | Required | Description |
|
|
119
|
+
|-------|------|----------|-------------|
|
|
120
|
+
| origin | string | yes | Starting point (address or landmark) |
|
|
121
|
+
| destination | string | yes | End point (address or landmark) |
|
|
122
|
+
| mode | string | no | Travel mode: driving, walking, bicycling, transit |
|
|
123
|
+
| departure_time | string | no | Departure time (ISO 8601 or "now") |
|
|
124
|
+
| arrival_time | string | no | Desired arrival time (transit only) |
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## distance-matrix
|
|
129
|
+
|
|
130
|
+
Calculate travel distances and times between multiple origins and destinations.
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
exec distance-matrix '{"origins": ["Tokyo Tower"], "destinations": ["Shibuya Station", "Shinjuku Station"], "mode": "driving"}'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
| Param | Type | Required | Description |
|
|
137
|
+
|-------|------|----------|-------------|
|
|
138
|
+
| origins | string[] | yes | List of origin addresses |
|
|
139
|
+
| destinations | string[] | yes | List of destination addresses |
|
|
140
|
+
| mode | string | no | Travel mode: driving, walking, bicycling, transit |
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## elevation
|
|
145
|
+
|
|
146
|
+
Get elevation data for geographic coordinates.
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
exec elevation '{"locations": [{"latitude": 35.6586, "longitude": 139.7454}]}'
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
| Param | Type | Required | Description |
|
|
153
|
+
|-------|------|----------|-------------|
|
|
154
|
+
| locations | object[] | yes | Array of `{ latitude, longitude }` |
|
|
155
|
+
|
|
156
|
+
Response:
|
|
157
|
+
```json
|
|
158
|
+
[{ "elevation": 17.23, "location": { "lat": 35.6586, "lng": 139.7454 }, "resolution": 610.81 }]
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Common Chaining Patterns
|
|
164
|
+
|
|
165
|
+
**Search → Details**
|
|
166
|
+
```bash
|
|
167
|
+
exec search-places '{"query":"Michelin restaurants in Taipei"}'
|
|
168
|
+
exec place-details '{"placeId":"ChIJ..."}' # use place_id from results
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
**Geocode → Nearby Search**
|
|
172
|
+
```bash
|
|
173
|
+
exec geocode '{"address":"Taipei 101"}'
|
|
174
|
+
exec search-nearby '{"center":{"value":"25.033,121.564","isCoordinates":true},"keyword":"cafe","radius":500}'
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
**Multi-point Comparison**
|
|
178
|
+
```bash
|
|
179
|
+
exec distance-matrix '{"origins":["Taipei Main Station","Banqiao Station"],"destinations":["Taoyuan Airport","Songshan Airport"],"mode":"driving"}'
|
|
180
|
+
```
|