Skip to main content
Glama
jayozer

Outscraper MCP Server

google_maps_search

Search Google Maps for businesses and places using Outscraper's data extraction. Retrieve detailed information including contact details, locations, and enriched data for market research and lead generation.

Instructions

Search for businesses and places on Google Maps using Outscraper

Args:
    query: Search query (e.g., 'restaurants brooklyn usa', 'hotels paris france')
    limit: Number of results to return (default: 20, max: 400)  
    language: Language code (default: 'en')
    region: Country/region code (e.g., 'US', 'GB', 'DE')
    drop_duplicates: Remove duplicate results (default: False)
    enrichment: Additional services to run (e.g., ['domains_service', 'emails_validator_service'])

Returns:
    Formatted search results with business information

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
queryYes
limitNo
languageNoen
regionNo
drop_duplicatesNo
enrichmentNo

Implementation Reference

  • Primary MCP tool handler for 'google_maps_search'. Decorated with @mcp.tool() for automatic registration. Validates parameters, calls OutscraperClient API method, handles asynchronous responses, formats results into readable markdown output.
    @mcp.tool()
    def google_maps_search(query: str, limit: int = 20, language: str = "en", 
                          region: Optional[str] = None, drop_duplicates: bool = False,
                          enrichment: Optional[List[str]] = None) -> str:
        """
        Search for businesses and places on Google Maps using Outscraper
        
        Args:
            query: Search query (e.g., 'restaurants brooklyn usa', 'hotels paris france')
            limit: Number of results to return (default: 20, max: 400)  
            language: Language code (default: 'en')
            region: Country/region code (e.g., 'US', 'GB', 'DE')
            drop_duplicates: Remove duplicate results (default: False)
            enrichment: Additional services to run (e.g., ['domains_service', 'emails_validator_service'])
        
        Returns:
            Formatted search results with business information
        """
        # Input validation
        if not query or not query.strip():
            return "Error: Search query cannot be empty."
        
        if limit < 1 or limit > MAX_SEARCH_LIMIT:
            return f"Error: Limit must be between 1 and {MAX_SEARCH_LIMIT}."
        
        if language and len(language) != 2:
            logger.warning(f"Unusual language code: {language}. Standard codes are 2 characters (e.g., 'en', 'es').")
        try:
            logger.info(f"Searching Google Maps for: {query}")
            
            results = client.google_maps_search(
                query=query,
                limit=limit,
                language=language,
                region=region,
                drop_duplicates=drop_duplicates,
                enrichment=enrichment
            )
            
            if not results:
                return "No results found for the given query."
            
            # Handle async response
            if isinstance(results, str) and "Request processing asynchronously" in results:
                return results
            
            # Format results for better readability
            formatted_results = []
            
            if isinstance(results, list) and len(results) > 0:
                places = results[0] if isinstance(results[0], list) else results
                
                # Ensure places is a list
                if not isinstance(places, list):
                    places = [places]
                
                for i, place in enumerate(places[:limit], 1):
                    if isinstance(place, dict):
                        name = place.get('name', 'Unknown')
                        formatted_place = f"**{i}. {name}**\n"
                        
                        # Address
                        address = place.get('full_address') or place.get('address', 'N/A')
                        formatted_place += f"   📍 Address: {address}\n"
                        
                        # Rating and reviews
                        rating = place.get('rating')
                        reviews = place.get('reviews', 0)
                        if rating:
                            formatted_place += f"   ⭐ Rating: {rating} ({reviews} reviews)\n"
                        else:
                            formatted_place += f"   ⭐ Rating: No ratings yet\n"
                        
                        # Contact info
                        phone = place.get('phone', 'N/A')
                        formatted_place += f"   📞 Phone: {phone}\n"
                        
                        website = place.get('site', 'N/A')
                        formatted_place += f"   🌐 Website: {website}\n"
                        
                        # Type/category
                        place_type = place.get('type') or place.get('main_category', 'N/A')
                        formatted_place += f"   🏷️ Type: {place_type}\n"
                        
                        if place.get('working_hours'):
                            formatted_place += f"   🕒 Hours: {place.get('working_hours_old_format', 'N/A')}\n"
                        
                        formatted_place += f"   🆔 Place ID: {place.get('place_id', 'N/A')}\n"
                        
                        # Include enrichment data if available
                        if place.get('emails'):
                            formatted_place += f"   📧 Emails: {', '.join([email.get('value') for email in place.get('emails', [])])}\n"
                        
                        formatted_place += "---\n"
                        formatted_results.append(formatted_place)
                
                return f"Found {len(places)} places for '{query}':\n\n" + "\n".join(formatted_results)
            else:
                return f"Search results for '{query}':\n\n" + str(results)
                
        except Exception as e:
            logger.error(f"Error in google_maps_search: {str(e)}")
            logger.error(f"Query: {query}, Limit: {limit}, Language: {language}")
            return f"Error searching Google Maps: {str(e)}"
  • Helper method in OutscraperClient class that constructs the API request to Outscraper's /maps/search-v3 endpoint, handles retries and timeouts, and processes the response using _handle_response.
    def google_maps_search(self, query: Union[List[str], str], limit: int = 20, 
                          language: str = 'en', region: str = None, 
                          drop_duplicates: bool = False, enrichment: List[str] = None) -> Union[List, Dict]:
        """Search Google Maps for places/businesses"""
        if isinstance(query, str):
            queries = [query]
        else:
            queries = query
            
        wait_async = len(queries) > 10 and limit > 1
        
        params = {
            'query': queries,
            'language': language,
            'organizationsPerQueryLimit': limit,
            'async': wait_async,
            'dropDuplicates': drop_duplicates
        }
        
        if region:
            params['region'] = region
        if enrichment:
            params['enrichment'] = enrichment
            
        try:
            response = self.session.get(
                f'{OUTSCRAPER_API_BASE}/maps/search-v3',
                params=params,
                headers=self.headers,
                timeout=60 if wait_async else 30
            )
            return self._handle_response(response, wait_async)
        except requests.exceptions.Timeout:
            logger.error(f"Request timeout during Google Maps search")
            raise Exception("Request timed out. Please try again with fewer queries or lower limit.")
        except requests.exceptions.ConnectionError:
            logger.error(f"Connection error during Google Maps search")
            raise Exception("Connection error. Please check your internet connection.")
        except requests.exceptions.RequestException as e:
            logger.error(f"Network error during Google Maps search: {e}")
            raise Exception(f"Network error: {str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error during Google Maps search: {e}")
            raise
  • Type annotations and comprehensive docstring defining the input schema (parameters with types, defaults, descriptions) and output format for the google_maps_search tool.
    def google_maps_search(query: str, limit: int = 20, language: str = "en", 
                          region: Optional[str] = None, drop_duplicates: bool = False,
                          enrichment: Optional[List[str]] = None) -> str:
        """
        Search for businesses and places on Google Maps using Outscraper
        
        Args:
            query: Search query (e.g., 'restaurants brooklyn usa', 'hotels paris france')
            limit: Number of results to return (default: 20, max: 400)  
            language: Language code (default: 'en')
            region: Country/region code (e.g., 'US', 'GB', 'DE')
            drop_duplicates: Remove duplicate results (default: False)
            enrichment: Additional services to run (e.g., ['domains_service', 'emails_validator_service'])
        
        Returns:
            Formatted search results with business information
        """
  • The @mcp.tool() decorator registers the google_maps_search function with the FastMCP server instance.
    @mcp.tool()
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. It mentions the tool uses Outscraper and returns formatted results, but lacks critical details like rate limits, authentication requirements, pagination behavior, error handling, or whether it's a read-only operation. For a search tool with 6 parameters, this is insufficient behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured with clear sections (Args, Returns), uses bullet-like formatting for parameters, and every sentence adds value. It's appropriately sized for a tool with 6 parameters and no annotations, with no redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's moderate complexity (6 parameters, no annotations, no output schema), the description covers purpose and parameters well but has significant gaps. It lacks behavioral context (rate limits, auth), doesn't explain the return format beyond 'formatted search results', and provides no error handling information. The parameter coverage is excellent, but other aspects are incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description fully compensates by providing clear explanations for all 6 parameters. Each parameter gets practical examples (e.g., query examples), default values, constraints (max limit), and usage context (e.g., what enrichment services do). This adds substantial meaning beyond the bare schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool searches for businesses and places on Google Maps using Outscraper, providing a specific verb ('search') and resource ('businesses and places on Google Maps'). It distinguishes from the sibling tool google_maps_reviews by focusing on search rather than reviews. However, it doesn't explicitly contrast with the sibling beyond the different function.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage through the purpose statement and parameter explanations, suggesting it's for finding businesses/places. However, it lacks explicit guidance on when to use this tool versus alternatives (like the sibling google_maps_reviews) or any prerequisites. The context is clear but not comprehensive.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/jayozer/outscraper-mcp'

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