Skip to main content
Glama
maps.ts13.2 kB
import { Client } from "@googlemaps/google-maps-services-js"; import { z } from "zod"; const googleMapsClient = new Client({}); // Markdown formatting helpers function formatLocationToMarkdown(title: string, address: string, lat: number, lng: number, placeId?: string): string { let markdown = `# ${title}\n\n`; markdown += `Address: ${address} \n`; markdown += `Coordinates: ${lat}, ${lng} \n`; if (placeId) markdown += `Place ID: \`${placeId}\` \n`; markdown += `Google Maps: [View on Maps](https://maps.google.com/?q=${lat},${lng})`; return markdown; } function formatPlacesToMarkdown(places: any[]): string { if (!places.length) return "No places found."; let markdown = `# Places Search Results (${places.length})\n\n`; places.forEach((place, index) => { markdown += `## ${index + 1}. ${place.name}\n`; markdown += `Address: ${place.formatted_address} \n`; if (place.rating) { markdown += `Rating: ${place.rating}⭐ \n`; } if (place.latitude && place.longitude) { markdown += `Location: ${place.latitude}, ${place.longitude} \n`; markdown += `Maps: [View](https://maps.google.com/?q=${place.latitude},${place.longitude}) \n`; } if (place.place_id) { markdown += `Place ID: \`${place.place_id}\` \n`; } markdown += `\n`; }); return markdown; } function formatDirectionsToMarkdown(route: any): string { let markdown = `# Directions: ${route.start_address} → ${route.end_address}\n\n`; markdown += `Distance: ${route.distance} \n`; markdown += `Duration: ${route.duration} \n\n`; if (route.steps && route.steps.length) { markdown += `## Step-by-Step Directions\n\n`; route.steps.forEach((step: any, index: number) => { markdown += `${index + 1}. ${step.instruction} *(${step.distance}, ${step.duration})*\n`; }); } return markdown; } function formatDistanceMatrixToMarkdown(results: any[]): string { let markdown = `# Distance Matrix Results\n\n`; results.forEach((result, index) => { markdown += `## ${index + 1}. ${result.origin} → ${result.destination}\n`; markdown += `Distance: ${result.distance} \n`; markdown += `Duration: ${result.duration} \n`; markdown += `Status: ${result.status} \n\n`; }); return markdown; } function formatPlaceDetailsToMarkdown(place: any): string { let markdown = `# ${place.name}\n\n`; if (place.formatted_address) markdown += `Address: ${place.formatted_address} \n`; if (place.phone_number) markdown += `Phone: ${place.phone_number} \n`; if (place.website) markdown += `Website: [Visit](${place.website}) \n`; if (place.rating) { markdown += `Rating: ${place.rating}⭐`; if (place.user_ratings_total) markdown += ` (${place.user_ratings_total} reviews)`; markdown += ` \n`; } if (place.price_level !== undefined) { const priceSymbols = '$'.repeat(place.price_level + 1); markdown += `Price Level: ${priceSymbols} \n`; } if (place.latitude && place.longitude) { markdown += `Location: ${place.latitude}, ${place.longitude} \n`; markdown += `Maps: [View](https://maps.google.com/?q=${place.latitude},${place.longitude}) \n`; } if (place.opening_hours && place.opening_hours.length) { markdown += `\n## Hours\n`; place.opening_hours.forEach((hours: string) => { markdown += `- ${hours}\n`; }); } if (place.types && place.types.length) { markdown += `\nCategories: ${place.types.join(', ')}`; } return markdown; } export const geocodeSchema = z.object({ address: z.string().describe("The address to geocode"), }); export const reverseGeocodeSchema = z.object({ latitude: z.number().describe("The latitude"), longitude: z.number().describe("The longitude"), }); export const placesSearchSchema = z.object({ query: z.string().describe("The search query"), location: z .string() .optional() .describe("Bias results around this location (e.g., 'lat,lng')"), radius: z.number().optional().describe("Search radius in meters"), }); export const directionsSchema = z.object({ origin: z.string().describe("Starting location (address or lat,lng)"), destination: z.string().describe("Ending location (address or lat,lng)"), mode: z .enum(["driving", "walking", "bicycling", "transit"]) .optional() .describe("Travel mode"), }); export const distanceMatrixSchema = z.object({ origins: z.array(z.string()).describe("Array of origin locations"), destinations: z.array(z.string()).describe("Array of destination locations"), mode: z .enum(["driving", "walking", "bicycling", "transit"]) .optional() .describe("Travel mode"), }); export const placeDetailsSchema = z.object({ place_id: z.string().describe("The Google Place ID"), }); export async function geocode( params: z.infer<typeof geocodeSchema>, extra?: any ) { const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new Error("GOOGLE_MAPS_API_KEY is required"); } try { const response = await googleMapsClient.geocode({ params: { address: params.address, key: apiKey, }, }); const results = response.data.results; if (results.length === 0) { return { content: [ { type: "text" as const, text: "No results found for the given address.", }, ], }; } const location = results[0]; return { content: [ { type: "text" as const, text: formatLocationToMarkdown( "Geocoded Location", location.formatted_address, location.geometry.location.lat, location.geometry.location.lng, location.place_id ), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error geocoding address: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } export async function reverseGeocode( params: z.infer<typeof reverseGeocodeSchema>, extra?: any ) { const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new Error("GOOGLE_MAPS_API_KEY is required"); } try { const response = await googleMapsClient.reverseGeocode({ params: { latlng: [params.latitude, params.longitude], key: apiKey, }, }); const results = response.data.results; if (results.length === 0) { return { content: [ { type: "text" as const, text: "No results found for the given coordinates.", }, ], }; } const location = results[0]; return { content: [ { type: "text" as const, text: formatLocationToMarkdown( "Reverse Geocoded Location", location.formatted_address, params.latitude, params.longitude, location.place_id ), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error reverse geocoding coordinates: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } export async function placesSearch( params: z.infer<typeof placesSearchSchema>, extra?: any ) { const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new Error("GOOGLE_MAPS_API_KEY is required"); } try { const requestParams: any = { query: params.query, key: apiKey, }; if (params.location) { requestParams.location = params.location; } if (params.radius) { requestParams.radius = params.radius; } const response = await googleMapsClient.textSearch({ params: requestParams, }); const results = response.data.results; if (results.length === 0) { return { content: [ { type: "text" as const, text: "No places found for the given query.", }, ], }; } const places = results.slice(0, 5).map((place) => ({ name: place.name, formatted_address: place.formatted_address, latitude: place.geometry?.location.lat, longitude: place.geometry?.location.lng, place_id: place.place_id, rating: place.rating, types: place.types, })); return { content: [ { type: "text" as const, text: formatPlacesToMarkdown(places), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error searching places: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } export async function getDirections( params: z.infer<typeof directionsSchema>, extra?: any ) { const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new Error("GOOGLE_MAPS_API_KEY is required"); } try { const response = await googleMapsClient.directions({ params: { origin: params.origin, destination: params.destination, mode: (params.mode || "driving") as any, key: apiKey, }, }); const routes = response.data.routes; if (routes.length === 0) { return { content: [ { type: "text" as const, text: "No routes found between the given locations.", }, ], }; } const route = routes[0]; const leg = route.legs[0]; const routeData = { distance: leg.distance.text, duration: leg.duration.text, start_address: leg.start_address, end_address: leg.end_address, steps: leg.steps.map((step) => ({ instruction: step.html_instructions.replace(/<[^>]*>/g, ""), distance: step.distance.text, duration: step.duration.text, })), }; return { content: [ { type: "text" as const, text: formatDirectionsToMarkdown(routeData), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error getting directions: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } export async function distanceMatrix( params: z.infer<typeof distanceMatrixSchema>, extra?: any ) { const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new Error("GOOGLE_MAPS_API_KEY is required"); } try { const response = await googleMapsClient.distancematrix({ params: { origins: params.origins, destinations: params.destinations, mode: (params.mode || "driving") as any, key: apiKey, }, }); const rows = response.data.rows; const results = []; for (let i = 0; i < params.origins.length; i++) { for (let j = 0; j < params.destinations.length; j++) { const element = rows[i].elements[j]; results.push({ origin: params.origins[i], destination: params.destinations[j], distance: element.distance?.text || "N/A", duration: element.duration?.text || "N/A", status: element.status, }); } } return { content: [ { type: "text" as const, text: formatDistanceMatrixToMarkdown(results), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error calculating distance matrix: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } } export async function placeDetails( params: z.infer<typeof placeDetailsSchema>, extra?: any ) { const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new Error("GOOGLE_MAPS_API_KEY is required"); } try { const response = await googleMapsClient.placeDetails({ params: { place_id: params.place_id, key: apiKey, }, }); const place = response.data.result; const placeData = { name: place.name, formatted_address: place.formatted_address, phone_number: place.formatted_phone_number, website: place.website, rating: place.rating, user_ratings_total: place.user_ratings_total, price_level: place.price_level, opening_hours: place.opening_hours?.weekday_text, types: place.types, latitude: place.geometry?.location.lat, longitude: place.geometry?.location.lng, }; return { content: [ { type: "text" as const, text: formatPlaceDetailsToMarkdown(placeData), }, ], }; } catch (error) { return { content: [ { type: "text" as const, text: `Error getting place details: ${ error instanceof Error ? error.message : String(error) }`, }, ], }; } }

Implementation Reference

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/CaptainCrouton89/maps-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server