places-text-search.ts•3.97 kB
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { PlacesTextSearchParams } from "../lib/schema.js";
import { GooglePlacesHttpError, performPlacesTextSearch } from "../lib/helper.js";
import { z } from "zod";
// Define proper types for better type safety
type PlacesTextSearchParamsType = z.infer<typeof PlacesTextSearchParams>;
type SearchOptions = Omit<PlacesTextSearchParamsType, 'fields'>;
export function registerPlacesTextSearchTools(server: McpServer) {
server.tool(
"places_text_search",
"Search for places using Google Places API with text queries. Supports location bias, restrictions, filtering by type, price level, rating, and more. Returns 5 results by default with optimized fields including photos.",
PlacesTextSearchParams.shape,
async (args, extra) => {
// Log execution for debugging (only in development)
if (process.env.NODE_ENV === 'development') {
console.error("Executing places_text_search with args:", JSON.stringify(args, null, 2));
}
// Extract fields from args, rest goes to searchOptions
const { fields, ...searchOptions } = args;
try {
// Validate required environment variable
if (!process.env.GOOGLE_MAPS_API_KEY) {
throw new Error("GOOGLE_MAPS_API_KEY environment variable is required");
}
// Type-safe search options handling
const data = await performPlacesTextSearch(searchOptions as SearchOptions, fields);
if (!data) {
return {
content: [
{
type: "text",
text: "No places data returned from the Google Places API",
},
],
};
}
// Check if places were found
const placesCount = data.places?.length || 0;
// Log successful search for debugging
if (process.env.NODE_ENV === 'development') {
console.error(`Places search successful. Found ${placesCount} places`);
}
// Format the response nicely
let responseText = JSON.stringify(data, null, 2);
// Add a summary at the beginning if places were found
if (placesCount > 0) {
const summary = `Found ${placesCount} place${placesCount > 1 ? 's' : ''} matching your search:\n\n`;
responseText = summary + responseText;
} else {
responseText = "No places found matching your search criteria.";
}
return {
content: [
{
type: "text",
text: responseText,
},
],
};
} catch (error) {
console.error("Tool execution error:", error);
let errorMessage = "Unknown error occurred during places search";
if (error instanceof GooglePlacesHttpError) {
errorMessage = `Google Places API error (${error.statusCode}): ${error.message}`;
// Add helpful suggestions for common errors
if (error.statusCode === 403) {
errorMessage += "\n\nSuggestions:\n- Check that your API key is valid\n- Ensure Places API is enabled in Google Cloud Console\n- Verify API key has proper permissions";
} else if (error.statusCode === 429) {
errorMessage += "\n\nSuggestion: You may have exceeded your API quota or rate limit";
} else if (error.statusCode === 400) {
errorMessage += "\n\nSuggestion: Check your search parameters for validity";
}
} else if (error instanceof Error) {
errorMessage = `Error: ${error.message}`;
}
return {
content: [
{
type: "text",
text: errorMessage,
},
],
isError: true,
};
}
}
);
console.error("✅ Registered places_text_search tool with optimized defaults (5 results, includes photos)");
}