maps_search
Search Google Maps for businesses by type and location. Returns each business with a Lead Quality Score (0–100) and outreach hints to prioritize prospects.
Instructions
Search Google Maps businesses by type and location. Returns each business with a Lead Quality Score (0–100) and outreach hints.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Business type or search query, e.g. 'coffee shops' | |
| location | Yes | City or area, e.g. 'Austin TX' | |
| max | No | Max results, up to 60 (default: 20) | |
| google_key | Yes | Your Google Places API key |
Implementation Reference
- src/mcp-stdio.ts:111-140 (registration)MCP tool registration for 'maps_search' in the ListToolsRequestSchema handler. Defines name, description, and inputSchema (query, location, max, google_key).
{ name: "maps_search", description: "Search Google Maps businesses by type and location. Returns each business with a Lead Quality Score (0–100) and outreach hints.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Business type or search query, e.g. 'coffee shops'", }, location: { type: "string", description: "City or area, e.g. 'Austin TX'", }, max: { type: "number", description: "Max results, up to 60 (default: 20)", }, google_key: { type: "string", description: "Your Google Places API key", }, }, required: ["query", "location", "google_key"], }, }, { name: "maps_leads", description: - src/mcp-stdio.ts:207-215 (handler)MCP call handler that dispatches 'maps_search' to searchMapsBusinesses function, extracting query, location, max, google_key args.
case "maps_search": { const { query, location, max, google_key } = args as { query: string; location: string; max?: number; google_key: string; }; result = await searchMapsBusinesses(query, location, google_key, max); break; - src/tools/maps.ts:1-24 (schema)Type definitions: PlaceBusiness interface and MapsSearchResult interface used as the return type for searchMapsBusinesses.
export interface PlaceBusiness { place_id: string; name: string; address: string; phone: string | null; website: string | null; rating: number | null; user_ratings_total: number | null; types: string[]; lat: number; lng: number; business_status: string; price_level: number | null; lead_quality_score: number; outreach_hints: string[]; } export interface MapsSearchResult { query: string; location: string; total_results: number; businesses: PlaceBusiness[]; searched_at: string; } - src/tools/maps.ts:216-269 (handler)Core handler function 'searchMapsBusinesses' — calls Google Places Text Search API paginated, normalizes results, enriches via Place Details, scores leads, and returns sorted MapsSearchResult.
export async function searchMapsBusinesses( query: string, location: string, apiKey: string, max = 20 ): Promise<MapsSearchResult> { const fullQuery = `${query} ${location}`.trim(); const clampedMax = Math.min(Math.max(1, max), 60); const businesses: PlaceBusiness[] = []; let pageToken: string | undefined; while (businesses.length < clampedMax) { const { results, next_page_token } = await textSearch( fullQuery, apiKey, pageToken ); for (const r of results) { if (businesses.length >= clampedMax) break; businesses.push(normalizeBusiness(r)); } if (!next_page_token || results.length === 0) break; pageToken = next_page_token; // Google requires a short delay before using page token await new Promise((r) => setTimeout(r, 2000)); } // Enrich top results with place details (phone, website) const enrichLimit = Math.min(businesses.length, 10); await Promise.allSettled( businesses.slice(0, enrichLimit).map(async (b, i) => { try { const details = await getPlaceDetails(b.place_id, apiKey); businesses[i] = normalizeBusiness({ ...b, ...details }); } catch { // Keep partial data on details failure } }) ); businesses.sort((a, b) => b.lead_quality_score - a.lead_quality_score); return { query, location, total_results: businesses.length, businesses, searched_at: new Date().toISOString(), }; } - src/tools/maps.ts:37-95 (helper)Helper functions: textSearch (Google Places API), getPlaceDetails (enrichment), calculateLeadQualityScore (0-100 scoring), generateOutreachHints, and normalizeBusiness.
async function textSearch( query: string, apiKey: string, pageToken?: string ): Promise<{ results: unknown[]; next_page_token?: string }> { const params = new URLSearchParams({ query, key: apiKey, ...(pageToken && { pagetoken: pageToken }), }); const res = await fetch(`${PLACES_BASE}/textsearch/json?${params}`); if (!res.ok) { throw new Error(`Google Places API error: HTTP ${res.status}`); } const data = (await res.json()) as { status: string; error_message?: string; results: unknown[]; next_page_token?: string; }; if (data.status !== "OK" && data.status !== "ZERO_RESULTS") { throw new Error( `Google Places API status: ${data.status}${data.error_message ? ` — ${data.error_message}` : ""}` ); } return { results: data.results ?? [], next_page_token: data.next_page_token }; } async function getPlaceDetails( placeId: string, apiKey: string ): Promise<Record<string, unknown>> { const params = new URLSearchParams({ place_id: placeId, fields: "name,formatted_address,formatted_phone_number,website,rating,user_ratings_total,types,geometry,business_status,price_level", key: apiKey, }); const res = await fetch(`${PLACES_BASE}/details/json?${params}`); if (!res.ok) { throw new Error(`Google Places Details API error: HTTP ${res.status}`); } const data = (await res.json()) as { status: string; result?: Record<string, unknown>; }; if (data.status !== "OK") { throw new Error(`Google Places Details API status: ${data.status}`); } return data.result ?? {}; }