Skip to main content
Glama

Google Places MCP Server

by colakang
helper.ts6.28 kB
import axios from "axios"; import { PlacesTextSearchParams } from "./schema.js"; import { z } from "zod"; // Define the type from the schema type PlacesTextSearchParamsType = z.infer<typeof PlacesTextSearchParams>; export class HttpError extends Error { constructor(public statusCode: number, message: string) { super(message); this.name = "HttpError"; } } export class GooglePlacesHttpError extends Error { constructor(public statusCode: number, message: string) { super(message); this.name = "GooglePlacesHttpError"; } } // Define optimized default fields - focused on essential information with review summaries const DEFAULT_FIELDS = [ // Basic identification "places.id", "places.displayName", // Location and address "places.formattedAddress", "places.location", "places.googleMapsUri", // Business information "places.businessStatus", "places.primaryType", "places.primaryTypeDisplayName", "places.types", "places.photos", // Ratings and contact "places.rating", "places.userRatingCount", "places.priceLevel", "places.priceRange", "places.internationalPhoneNumber", "places.websiteUri", // Hours "places.regularOpeningHours", // Service capabilities "places.delivery", "places.dineIn", "places.takeout", "places.reservable", // Essential food services "places.servesVegetarianFood", "places.servesBreakfast", "places.servesLunch", "places.servesDinner", // Payment and accessibility "places.paymentOptions", "places.accessibilityOptions", // Review summary (more concise than full reviews) "places.reviewSummary" ].join(","); export async function performPlacesTextSearch( options: PlacesTextSearchParamsType, fields?: string[] ) { const url = new URL("https://places.googleapis.com/v1/places:searchText"); // Add region code as query parameter if provided if (options.regionCode) { url.searchParams.append("regionCode", options.regionCode); } const apiKey = process.env.GOOGLE_MAPS_API_KEY; if (!apiKey) { throw new GooglePlacesHttpError(500, "Google Maps API key not configured. Please set GOOGLE_MAPS_API_KEY environment variable."); } // Build request headers const headers: Record<string, string> = { "Content-Type": "application/json", "X-Goog-Api-Key": apiKey, }; // Add field mask if fields are specified, otherwise use optimized default fields const fieldMask = Array.isArray(fields) && fields.length > 0 ? fields.join(",") : DEFAULT_FIELDS; if (fieldMask) { headers["X-Goog-FieldMask"] = fieldMask; } // Prepare request body with default pageSize of 5 const { fields: _fields, regionCode: _regionCode, ...requestBody } = options; // Set default pageSize to 5 if not specified if (!requestBody.pageSize) { requestBody.pageSize = 5; } // Log request details in development mode if (process.env.NODE_ENV === 'development') { console.error("Making Google Places API request:", { url: url.toString(), body: JSON.stringify(requestBody, null, 2), fieldMask: fieldMask.length > 100 ? fieldMask.substring(0, 100) + "..." : fieldMask }); } try { const response = await axios.post(url.toString(), requestBody, { headers, timeout: 15000, // Increased timeout to 15 seconds validateStatus: (status) => status < 500, // Don't throw on 4xx errors, handle them gracefully }); if (process.env.NODE_ENV === 'development') { console.error("Google Places API response status:", response.status); console.error("Response data preview:", JSON.stringify(response.data, null, 2).substring(0, 500) + "..."); } // Handle successful responses if (response.status >= 200 && response.status < 300) { const data = response.data; // Post-process data to prevent truncation if (data && data.places) { data.places.forEach((place: any) => { // Limit photos to maximum 3 to reduce data size if (place.photos && place.photos.length > 3) { place.photos = place.photos.slice(0, 3); } // Remove verbose fields that might cause truncation delete place.contextualContents; delete place.currentOpeningHours; // Keep only regularOpeningHours if present }); } return data; } // Handle 4xx errors with specific messages const errorData = response.data; let message = "Google Places API error"; if (errorData?.error?.message) { message = errorData.error.message; } else if (errorData?.error) { message = JSON.stringify(errorData.error); } throw new GooglePlacesHttpError(response.status, message); } catch (error) { // Handle network errors and other exceptions if (error instanceof GooglePlacesHttpError) { throw error; // Re-throw our custom errors } if (axios.isAxiosError(error)) { const statusCode = error.response?.status || 500; const errorData = error.response?.data; // Try to extract a meaningful error message let message = "Google Places API error"; if (errorData?.error?.message) { message = errorData.error.message; } else if (errorData?.error) { message = JSON.stringify(errorData.error); } else if (error.message) { message = error.message; } // Log detailed error information in development if (process.env.NODE_ENV === 'development') { console.error("API Error Details:", { status: statusCode, statusText: error.response?.statusText, data: errorData, headers: error.response?.headers, requestConfig: { url: error.config?.url, method: error.config?.method, headers: error.config?.headers } }); } throw new GooglePlacesHttpError(statusCode, message); } // Handle other types of errors (network, timeout, etc.) const message = error instanceof Error ? `Network error: ${error.message}` : "Unknown error occurred while calling Google Places API"; throw new GooglePlacesHttpError(500, message); } }

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/colakang/google-places-mcp'

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