Skip to main content
Glama

MCP Google Map Server

toolclass.ts12.1 kB
import { Client, Language, TravelMode } from "@googlemaps/google-maps-services-js"; import dotenv from "dotenv"; import { Logger } from "../index.js"; dotenv.config(); interface SearchParams { location: { lat: number; lng: number }; radius?: number; keyword?: string; openNow?: boolean; minRating?: number; } interface PlaceResult { name: string; place_id: string; formatted_address?: string; geometry: { location: { lat: number; lng: number; }; }; rating?: number; user_ratings_total?: number; opening_hours?: { open_now?: boolean; }; } interface GeocodeResult { lat: number; lng: number; formatted_address?: string; place_id?: string; } /** * Extracts a meaningful error message from various error types * Prioritizes Google Maps API error messages when available */ function extractErrorMessage(error: any): string { // Extract Google API error message if available const apiError = error?.response?.data?.error_message; const statusCode = error?.response?.status; if (apiError) { return `${apiError} (HTTP ${statusCode})`; } // Fallback to standard error message return error instanceof Error ? error.message : String(error); } export class GoogleMapsTools { private client: Client; private readonly defaultLanguage: Language = Language.en; private apiKey: string; constructor(apiKey?: string) { this.client = new Client({}); // Use provided API key, or fall back to environment variable this.apiKey = apiKey || process.env.GOOGLE_MAPS_API_KEY || ""; if (!this.apiKey) { throw new Error("Google Maps API Key is required"); } } async searchNearbyPlaces(params: SearchParams): Promise<PlaceResult[]> { const searchParams = { location: params.location, radius: params.radius || 1000, keyword: params.keyword, opennow: params.openNow, language: this.defaultLanguage, key: this.apiKey, }; try { const response = await this.client.placesNearby({ params: searchParams, }); let results = response.data.results; if (params.minRating) { results = results.filter((place) => (place.rating || 0) >= (params.minRating || 0)); } return results as PlaceResult[]; } catch (error: any) { Logger.error("Error in searchNearbyPlaces:", error); throw new Error(`Failed to search nearby places: ${extractErrorMessage(error)}`); } } async getPlaceDetails(placeId: string) { try { const response = await this.client.placeDetails({ params: { place_id: placeId, fields: ["name", "rating", "formatted_address", "opening_hours", "reviews", "geometry", "formatted_phone_number", "website", "price_level", "photos"], language: this.defaultLanguage, key: this.apiKey, }, }); return response.data.result; } catch (error: any) { Logger.error("Error in getPlaceDetails:", error); throw new Error(`Failed to get place details for ${placeId}: ${extractErrorMessage(error)}`); } } private async geocodeAddress(address: string): Promise<GeocodeResult> { try { const response = await this.client.geocode({ params: { address: address, key: this.apiKey, language: this.defaultLanguage, }, }); if (response.data.results.length === 0) { throw new Error(`No location found for address: "${address}"`); } const result = response.data.results[0]; const location = result.geometry.location; return { lat: location.lat, lng: location.lng, formatted_address: result.formatted_address, place_id: result.place_id, }; } catch (error: any) { Logger.error("Error in geocodeAddress:", error); throw new Error(`Failed to geocode address "${address}": ${extractErrorMessage(error)}`); } } private parseCoordinates(coordString: string): GeocodeResult { const coords = coordString.split(",").map((c) => parseFloat(c.trim())); if (coords.length !== 2 || isNaN(coords[0]) || isNaN(coords[1])) { throw new Error(`Invalid coordinate format: "${coordString}". Please use "latitude,longitude" format (e.g., "25.033,121.564"`); } return { lat: coords[0], lng: coords[1] }; } async getLocation(center: { value: string; isCoordinates: boolean }): Promise<GeocodeResult> { if (center.isCoordinates) { return this.parseCoordinates(center.value); } return this.geocodeAddress(center.value); } async geocode(address: string): Promise<{ location: { lat: number; lng: number }; formatted_address: string; place_id: string; }> { try { const result = await this.geocodeAddress(address); return { location: { lat: result.lat, lng: result.lng }, formatted_address: result.formatted_address || "", place_id: result.place_id || "", }; } catch (error: any) { Logger.error("Error in geocode:", error); throw new Error(`Failed to geocode address "${address}": ${extractErrorMessage(error)}`); } } async reverseGeocode( latitude: number, longitude: number ): Promise<{ formatted_address: string; place_id: string; address_components: any[]; }> { try { const response = await this.client.reverseGeocode({ params: { latlng: { lat: latitude, lng: longitude }, language: this.defaultLanguage, key: this.apiKey, }, }); if (response.data.results.length === 0) { throw new Error(`No address found for coordinates: (${latitude}, ${longitude})`); } const result = response.data.results[0]; return { formatted_address: result.formatted_address, place_id: result.place_id, address_components: result.address_components, }; } catch (error: any) { Logger.error("Error in reverseGeocode:", error); throw new Error(`Failed to reverse geocode coordinates (${latitude}, ${longitude}): ${extractErrorMessage(error)}`); } } async calculateDistanceMatrix( origins: string[], destinations: string[], mode: "driving" | "walking" | "bicycling" | "transit" = "driving" ): Promise<{ distances: any[][]; durations: any[][]; origin_addresses: string[]; destination_addresses: string[]; }> { try { const response = await this.client.distancematrix({ params: { origins: origins, destinations: destinations, mode: mode as TravelMode, language: this.defaultLanguage, key: this.apiKey, }, }); const result = response.data; if (result.status !== "OK") { throw new Error(`Distance matrix calculation failed with status: ${result.status}`); } const distances: any[][] = []; const durations: any[][] = []; result.rows.forEach((row: any) => { const distanceRow: any[] = []; const durationRow: any[] = []; row.elements.forEach((element: any) => { if (element.status === "OK") { distanceRow.push({ value: element.distance.value, text: element.distance.text, }); durationRow.push({ value: element.duration.value, text: element.duration.text, }); } else { distanceRow.push(null); durationRow.push(null); } }); distances.push(distanceRow); durations.push(durationRow); }); return { distances: distances, durations: durations, origin_addresses: result.origin_addresses, destination_addresses: result.destination_addresses, }; } catch (error: any) { Logger.error("Error in calculateDistanceMatrix:", error); throw new Error(`Failed to calculate distance matrix: ${extractErrorMessage(error)}`); } } async getDirections( origin: string, destination: string, mode: "driving" | "walking" | "bicycling" | "transit" = "driving", departure_time?: Date, arrival_time?: Date ): Promise<{ routes: any[]; summary: string; total_distance: { value: number; text: string }; total_duration: { value: number; text: string }; arrival_time: string; departure_time: string; }> { try { let apiArrivalTime: number | undefined = undefined; if (arrival_time) { apiArrivalTime = Math.floor(arrival_time.getTime() / 1000); } let apiDepartureTime: number | "now" | undefined = undefined; if (!apiArrivalTime) { if (departure_time instanceof Date) { apiDepartureTime = Math.floor(departure_time.getTime() / 1000); } else if (departure_time) { apiDepartureTime = departure_time as unknown as "now"; } else { apiDepartureTime = "now"; } } const response = await this.client.directions({ params: { origin: origin, destination: destination, mode: mode as TravelMode, language: this.defaultLanguage, key: this.apiKey, arrival_time: apiArrivalTime, departure_time: apiDepartureTime, }, }); const result = response.data; if (result.status !== "OK") { throw new Error(`Failed to get directions with status: ${result.status} (arrival_time: ${apiArrivalTime}, departure_time: ${apiDepartureTime}`); } if (result.routes.length === 0) { throw new Error(`No route found from "${origin}" to "${destination}" with mode: ${mode}`); } const route = result.routes[0]; const legs = route.legs[0]; const formatTime = (timeInfo: any) => { if (!timeInfo || typeof timeInfo.value !== "number") return ""; const date = new Date(timeInfo.value * 1000); const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }; if (timeInfo.time_zone && typeof timeInfo.time_zone === "string") { options.timeZone = timeInfo.time_zone; } return date.toLocaleString(this.defaultLanguage.toString(), options); }; return { routes: result.routes, summary: route.summary, total_distance: { value: legs.distance.value, text: legs.distance.text, }, total_duration: { value: legs.duration.value, text: legs.duration.text, }, arrival_time: formatTime(legs.arrival_time), departure_time: formatTime(legs.departure_time), }; } catch (error: any) { Logger.error("Error in getDirections:", error); throw new Error(`Failed to get directions from "${origin}" to "${destination}": ${extractErrorMessage(error)}`); } } async getElevation(locations: Array<{ latitude: number; longitude: number }>): Promise<Array<{ elevation: number; location: { lat: number; lng: number } }>> { try { const formattedLocations = locations.map((loc) => ({ lat: loc.latitude, lng: loc.longitude, })); const response = await this.client.elevation({ params: { locations: formattedLocations, key: this.apiKey, }, }); const result = response.data; if (result.status !== "OK") { throw new Error(`Failed to get elevation data with status: ${result.status}`); } return result.results.map((item: any, index: number) => ({ elevation: item.elevation, location: formattedLocations[index], })); } catch (error: any) { Logger.error("Error in getElevation:", error); throw new Error(`Failed to get elevation data for ${locations.length} location(s): ${extractErrorMessage(error)}`); } } }

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/cablate/mcp-google-map'

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