Skip to main content
Glama
jezweb

Australian Postcodes MCP Server

location_tools.py16.1 kB
"""Location-based tools for LGA, radius, and neighboring suburb queries.""" from typing import Dict, Any, Optional, List import logging from database import get_database from utils.config import Config logger = logging.getLogger(__name__) async def list_suburbs_in_lga( lga_name: str, state: Optional[str] = None ) -> Dict[str, Any]: """ List all suburbs in a Local Government Area (city/council area). Args: lga_name: Name of the LGA (e.g., "Newcastle", "Sydney") state: Optional state filter Returns: Dictionary with all suburbs and postcodes in the LGA """ try: lga_name = lga_name.strip() # Validate state if provided if state: state = state.strip().upper() if state not in Config.STATES: return { "status": "error", "error": f"Invalid state: {state}", "suggestion": f"Valid states are: {', '.join(Config.STATES.keys())}" } db = await get_database() results = await db.search_by_lga(lga_name, state) if not results: return { "status": "error", "error": f"No LGA found matching '{lga_name}'", "suggestion": "Please check the LGA name or try a partial match" } # Group results by suburb suburbs_dict = {} for result in results: suburb = result['locality'] if suburb not in suburbs_dict: suburbs_dict[suburb] = { "suburb": suburb, "postcodes": set(), "state": result['state'], "lga_name": result['lga_name'] } suburbs_dict[suburb]["postcodes"].add(result['postcode']) # Convert sets to sorted lists suburbs_list = [] for suburb_data in suburbs_dict.values(): suburbs_list.append({ "suburb": suburb_data["suburb"], "postcodes": sorted(list(suburb_data["postcodes"])), "state": suburb_data["state"], "lga_name": suburb_data["lga_name"] }) # Sort by suburb name suburbs_list.sort(key=lambda x: x["suburb"]) # Extract unique LGA names found unique_lgas = list(set(r['lga_name'] for r in results if r['lga_name'])) return { "status": "success", "query": lga_name, "lga_names_found": unique_lgas, "suburbs": suburbs_list, "suburb_count": len(suburbs_list), "total_postcodes": len(set(r['postcode'] for r in results)), "validation_notes": [ f"Found {len(suburbs_list)} suburb(s) in LGA matching '{lga_name}'" ] } except Exception as e: logger.error(f"Error listing suburbs in LGA: {e}") return { "status": "error", "error": str(e) } async def find_lga_for_suburb( suburb: str, state: Optional[str] = None ) -> Dict[str, Any]: """ Find the Local Government Area (city/council) for a suburb. Args: suburb: Suburb name state: Optional state filter Returns: LGA information for the suburb """ try: suburb = suburb.strip() # Validate state if provided if state: state = state.strip().upper() if state not in Config.STATES: return { "status": "error", "error": f"Invalid state: {state}", "suggestion": f"Valid states are: {', '.join(Config.STATES.keys())}" } db = await get_database() results = await db.search_by_suburb(suburb, state) if not results: return { "status": "error", "error": f"Suburb '{suburb}' not found", "suggestion": "Please check the spelling or use the fuzzy search tool" } # Extract unique LGAs lgas = {} for result in results: if result.get('lga_name'): lga_key = (result['lga_name'], result.get('lga_code'), result['state']) if lga_key not in lgas: lgas[lga_key] = { "lga_name": result['lga_name'], "lga_code": result.get('lga_code'), "state": result['state'], "postcodes": set() } lgas[lga_key]["postcodes"].add(result['postcode']) # Convert to list and sort postcodes lga_list = [] for lga_data in lgas.values(): lga_data["postcodes"] = sorted(list(lga_data["postcodes"])) lga_list.append(lga_data) if not lga_list: return { "status": "success", "suburb": suburb, "lga": None, "message": f"No LGA information available for '{suburb}'", "validation_notes": ["Some suburbs may not have LGA data"] } # Primary result is first LGA (or filtered by state) primary_lga = lga_list[0] if lga_list else None response = { "status": "success", "suburb": suburb, "primary_lga": primary_lga, "all_lgas": lga_list } if len(lga_list) > 1: response["multiple_lgas"] = True response["suggestion"] = f"Suburb '{suburb}' spans multiple LGAs" response["validation_notes"] = [ f"Found LGA information for '{suburb}'" ] return response except Exception as e: logger.error(f"Error finding LGA for suburb: {e}") return { "status": "error", "error": str(e) } async def list_suburbs_in_radius( postcode_or_suburb: str, radius_km: float, state: Optional[str] = None ) -> Dict[str, Any]: """ Find all suburbs within a radius of a location. Args: postcode_or_suburb: Center point (postcode or suburb name) radius_km: Search radius in kilometers state: Optional state filter Returns: Suburbs within the specified radius """ try: center = postcode_or_suburb.strip() # Validate radius if radius_km <= 0 or radius_km > 500: return { "status": "error", "error": f"Invalid radius: {radius_km}km", "suggestion": "Radius must be between 1 and 500 kilometers" } # Validate state if provided if state: state = state.strip().upper() if state not in Config.STATES: return { "status": "error", "error": f"Invalid state: {state}" } db = await get_database() # Find center coordinates center_coords = None center_info = {} # Check if it's a postcode if center.isdigit() and len(center) == 4: results = await db.search_by_postcode(center) if results: # Use first result with coordinates for r in results: if r.get('latitude') and r.get('longitude'): center_coords = (r['latitude'], r['longitude']) center_info = { "type": "postcode", "postcode": center, "suburb": r['locality'], "state": r['state'] } break else: # It's a suburb name results = await db.search_by_suburb(center, state) if results: # Use first result with coordinates for r in results: if r.get('latitude') and r.get('longitude'): center_coords = (r['latitude'], r['longitude']) center_info = { "type": "suburb", "suburb": r['locality'], "postcode": r['postcode'], "state": r['state'] } break if not center_coords: return { "status": "error", "error": f"Location '{center}' not found or has no coordinates", "suggestion": "Please check the location name or postcode" } # Search within radius results = await db.search_by_radius( center_coords[0], center_coords[1], radius_km ) # Group by suburb to avoid duplicates suburbs_dict = {} for result in results: suburb_key = (result['locality'], result['state']) if suburb_key not in suburbs_dict: suburbs_dict[suburb_key] = { "suburb": result['locality'], "state": result['state'], "postcodes": set(), "distance_km": result.get('distance_km', 0), "lga_name": result.get('lga_name') } suburbs_dict[suburb_key]["postcodes"].add(result['postcode']) # Keep minimum distance if result.get('distance_km', 0) < suburbs_dict[suburb_key]["distance_km"]: suburbs_dict[suburb_key]["distance_km"] = result.get('distance_km', 0) # Convert to list nearby_suburbs = [] for suburb_data in suburbs_dict.values(): nearby_suburbs.append({ "suburb": suburb_data["suburb"], "state": suburb_data["state"], "postcodes": sorted(list(suburb_data["postcodes"])), "distance_km": round(suburb_data["distance_km"], 1), "lga_name": suburb_data["lga_name"] }) # Sort by distance nearby_suburbs.sort(key=lambda x: x["distance_km"]) return { "status": "success", "center": center_info, "center_coordinates": { "latitude": center_coords[0], "longitude": center_coords[1] }, "radius_km": radius_km, "suburbs": nearby_suburbs, "suburb_count": len(nearby_suburbs), "validation_notes": [ f"Found {len(nearby_suburbs)} suburb(s) within {radius_km}km of {center}" ] } except Exception as e: logger.error(f"Error searching suburbs in radius: {e}") return { "status": "error", "error": str(e) } async def get_neighboring_suburbs( suburb: str, state: Optional[str] = None, max_neighbors: int = 10 ) -> Dict[str, Any]: """ Find neighboring suburbs (adjacent areas). Args: suburb: Suburb name state: Optional state filter max_neighbors: Maximum number of neighbors to return Returns: List of neighboring suburbs """ try: suburb = suburb.strip() # Validate state if provided if state: state = state.strip().upper() if state not in Config.STATES: return { "status": "error", "error": f"Invalid state: {state}" } # Validate max_neighbors if max_neighbors < 1 or max_neighbors > 50: max_neighbors = 10 db = await get_database() # Find the suburb's coordinates results = await db.search_by_suburb(suburb, state) if not results: return { "status": "error", "error": f"Suburb '{suburb}' not found", "suggestion": "Please check the spelling or use the fuzzy search tool" } # Get coordinates from first result with location data center_coords = None center_info = None for result in results: if result.get('latitude') and result.get('longitude'): center_coords = (result['latitude'], result['longitude']) center_info = { "suburb": result['locality'], "postcode": result['postcode'], "state": result['state'], "lga_name": result.get('lga_name') } break if not center_coords: return { "status": "error", "error": f"No coordinates available for '{suburb}'", "suggestion": "Cannot find neighbors without location data" } # Search within small radius (typically 5-10km for neighbors) # Adjust based on typical suburb size in Australia search_radius = 8.0 # km nearby_results = await db.search_by_radius( center_coords[0], center_coords[1], search_radius ) # Filter out the original suburb and group by suburb neighbors_dict = {} for result in nearby_results: # Skip the original suburb if result['locality'].lower() == suburb.lower(): continue suburb_key = (result['locality'], result['state']) if suburb_key not in neighbors_dict: neighbors_dict[suburb_key] = { "suburb": result['locality'], "state": result['state'], "postcodes": set(), "distance_km": result.get('distance_km', 0), "lga_name": result.get('lga_name'), "direction": None # Could calculate compass direction } neighbors_dict[suburb_key]["postcodes"].add(result['postcode']) # Keep minimum distance if result.get('distance_km', 0) < neighbors_dict[suburb_key]["distance_km"]: neighbors_dict[suburb_key]["distance_km"] = result.get('distance_km', 0) # Convert to list and sort by distance neighbors = [] for neighbor_data in neighbors_dict.values(): neighbors.append({ "suburb": neighbor_data["suburb"], "state": neighbor_data["state"], "postcodes": sorted(list(neighbor_data["postcodes"])), "distance_km": round(neighbor_data["distance_km"], 1), "lga_name": neighbor_data["lga_name"] }) # Sort by distance and limit neighbors.sort(key=lambda x: x["distance_km"]) neighbors = neighbors[:max_neighbors] # Check if same LGA (true neighbors often share LGA) if center_info.get('lga_name'): for neighbor in neighbors: if neighbor.get('lga_name') == center_info['lga_name']: neighbor['same_lga'] = True return { "status": "success", "query_suburb": center_info, "neighbors": neighbors, "neighbor_count": len(neighbors), "search_radius_km": search_radius, "validation_notes": [ f"Found {len(neighbors)} neighboring suburb(s) for '{suburb}'" ] } except Exception as e: logger.error(f"Error finding neighboring suburbs: {e}") return { "status": "error", "error": str(e) }

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/jezweb/australian-postcodes-mcp'

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