Skip to main content
Glama
jagan-shanmugam

OpenStreetMap MCP Server

find_nearby_places

Search for points of interest and amenities near a geographic location to support location-based recommendations and proximity analysis.

Instructions

Discover points of interest and amenities near a specific location.

This tool performs a comprehensive search around a geographic point to identify nearby establishments, amenities, and points of interest. Results are organized by category and subcategory, making it easy to find specific types of places. Essential for location-based recommendations, neighborhood analysis, and proximity-based decision making.

Args: latitude: Center point latitude (decimal degrees) longitude: Center point longitude (decimal degrees) radius: Search radius in meters (defaults to 1000m/1km) categories: List of OSM categories to search for (e.g., ["amenity", "shop", "tourism"]). If omitted, searches common categories. limit: Maximum number of total results to return

Returns: Structured dictionary containing: - Original query parameters - Total count of places found - Results grouped by category and subcategory - Each place includes name, coordinates, and associated tags

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
latitudeYes
longitudeYes
radiusNo
categoriesNo
limitNo

Implementation Reference

  • The primary handler function for the 'find_nearby_places' tool. It orchestrates the search for nearby points of interest using the OSMClient, groups results by category/subcategory, and formats the response.
    @mcp.tool()
    async def find_nearby_places(
        latitude: float,
        longitude: float,
        ctx: Context,
        radius: float = 1000,  # meters
        categories: List[str] = None,
        limit: int = 20
    ) -> Dict[str, Any]:
        """
        Discover points of interest and amenities near a specific location.
        
        This tool performs a comprehensive search around a geographic point to identify
        nearby establishments, amenities, and points of interest. Results are organized by
        category and subcategory, making it easy to find specific types of places. Essential
        for location-based recommendations, neighborhood analysis, and proximity-based decision making.
        
        Args:
            latitude: Center point latitude (decimal degrees)
            longitude: Center point longitude (decimal degrees)
            radius: Search radius in meters (defaults to 1000m/1km)
            categories: List of OSM categories to search for (e.g., ["amenity", "shop", "tourism"]).
                       If omitted, searches common categories.
            limit: Maximum number of total results to return
            
        Returns:
            Structured dictionary containing:
            - Original query parameters
            - Total count of places found
            - Results grouped by category and subcategory
            - Each place includes name, coordinates, and associated tags
        """
        osm_client = ctx.request_context.lifespan_context.osm_client
        
        # Set default categories if not provided
        if not categories:
            categories = ["amenity", "shop", "tourism", "leisure"]
        
        ctx.info(f"Searching for places within {radius}m of ({latitude}, {longitude})")
        places = await osm_client.get_nearby_pois(latitude, longitude, radius, categories)
        
        # Group results by category
        results_by_category = {}
        
        for place in places[:limit]:
            tags = place.get("tags", {})
            
            # Find the matching category
            for category in categories:
                if category in tags:
                    subcategory = tags[category]
                    if category not in results_by_category:
                        results_by_category[category] = {}
                    
                    if subcategory not in results_by_category[category]:
                        results_by_category[category][subcategory] = []
                    
                    # Add place to appropriate category and subcategory
                    place_info = {
                        "id": place.get("id"),
                        "name": tags.get("name", "Unnamed"),
                        "latitude": place.get("lat"),
                        "longitude": place.get("lon"),
                        "tags": tags
                    }
                    
                    results_by_category[category][subcategory].append(place_info)
        
        # Calculate total count
        total_count = sum(
            len(places)
            for category_data in results_by_category.values()
            for places in category_data.values()
        )
        
        return {
            "query": {
                "latitude": latitude,
                "longitude": longitude,
                "radius": radius
            },
            "categories": results_by_category,
            "total_count": total_count
        }
  • Supporting utility method in the OSMClient class that queries the Overpass API to retrieve raw points of interest data near the given coordinates, used by the find_nearby_places handler.
    async def get_nearby_pois(self, 
                             lat: float, 
                             lon: float, 
                             radius: float = 1000,
                             categories: List[str] = None) -> List[Dict]:
        """Get points of interest near a location"""
        if not self.session:
            raise RuntimeError("OSM client not connected")
        
        # Convert radius to bounding box (approximate)
        # 1 degree latitude ~= 111km
        # 1 degree longitude ~= 111km * cos(latitude)
        lat_delta = radius / 111000
        lon_delta = radius / (111000 * math.cos(math.radians(lat)))
        
        bbox = (
            lon - lon_delta,
            lat - lat_delta,
            lon + lon_delta,
            lat + lat_delta
        )
        
        # Build Overpass query
        overpass_url = "https://overpass-api.de/api/interpreter"
        
        # Default to common POI types if none specified
        if not categories:
            categories = ["amenity", "shop", "tourism", "leisure"]
        
        # Build tag filters
        tag_filters = []
        for category in categories:
            tag_filters.append(f'node["{category}"]({{bbox}});')
        
        query = f"""
        [out:json];
        (
            {" ".join(tag_filters)}
        );
        out body;
        """
        
        query = query.replace("{bbox}", f"{bbox[1]},{bbox[0]},{bbox[3]},{bbox[2]}")
        
        async with self.session.post(overpass_url, data={"data": query}) as response:
            if response.status == 200:
                data = await response.json()
                return data.get("elements", [])
            else:
                raise Exception(f"Failed to get nearby POIs: {response.status}")
Behavior3/5

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

With no annotations provided, the description carries full burden and discloses some behavioral traits: it performs a 'comprehensive search', organizes results 'by category and subcategory', and returns structured data. However, it lacks details on rate limits, authentication needs, or potential limitations like data source constraints.

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

Conciseness4/5

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

The description is appropriately sized and front-loaded with the core purpose in the first sentence. The Args and Returns sections are well-structured, though some sentences in the middle paragraph could be more concise (e.g., 'Essential for...' could be integrated more tightly).

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

Completeness4/5

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

Given 5 parameters with 0% schema coverage and no output schema, the description does a good job explaining both inputs and return structure. However, for a tool with no annotations and multiple siblings, it could better address when to use alternatives and provide more behavioral context like data freshness or source attribution.

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?

Schema description coverage is 0%, so the description must compensate. It provides clear semantic meaning for all 5 parameters: latitude/longitude as 'center point', radius as 'search radius in meters', categories as 'OSM categories', and limit as 'maximum number of total results'. This adds significant value beyond the bare schema.

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

Purpose5/5

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

The description clearly states the tool's purpose with specific verbs ('discover', 'search') and resources ('points of interest', 'amenities', 'establishments'), and distinguishes it from siblings by emphasizing comprehensive geographic search rather than specific categories like schools or EV stations.

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 for 'location-based recommendations, neighborhood analysis, and proximity-based decision making', but does not explicitly state when to use this tool versus alternatives like 'search_category' or 'explore_area'. No exclusions or clear alternatives are provided.

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/jagan-shanmugam/open-streetmap-mcp'

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