Skip to main content
Glama
krunal16-c

Government of Canada Open Data MCP Servers

by krunal16-c
server.py11.1 kB
""" GOV CA TRANSPORTATION INFRASTRUCTURE MCP Server Specialized MCP for Canadian transportation infrastructure data including bridges, tunnels, ports, airports, and railways. Supports both stdio and SSE transports: - stdio: python -m gov_ca_transportation.server - SSE: python -m gov_ca_transportation.server --sse --port 8001 """ import argparse import logging from typing import Any # Parse args early to set port before FastMCP initialization def _parse_args(): parser = argparse.ArgumentParser(description="GOV CA Transportation MCP Server") parser.add_argument("--sse", action="store_true", help="Run with SSE transport (HTTP)") parser.add_argument("--host", default="0.0.0.0", help="Host to bind to (default: 0.0.0.0)") parser.add_argument("--port", type=int, default=8001, help="Port to listen on (default: 8001)") args, _ = parser.parse_known_args() return args _args = _parse_args() from mcp.server.fastmcp import FastMCP from gov_ca_transportation.api_client import TransportationAPIClient from gov_ca_transportation.http_client import RetryConfig # Configure logging logging.basicConfig( level=logging.INFO, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" ) logger = logging.getLogger(__name__) # Create the MCP server using FastMCP with SSE config mcp = FastMCP( "gov_ca_transportation", instructions="GOV CA TRANSPORTATION INFRASTRUCTURE MCP - Specialized MCP for Canadian transportation infrastructure including bridges, tunnels, ports, airports, and railways.", host=_args.host, port=_args.port, ) # Initialize API client api_client = TransportationAPIClient(retry_config=RetryConfig(max_retries=3)) @mcp.tool() def query_bridges( province: str | None = None, city: str | None = None, condition: str | None = None, capacity_min: float | None = None, built_after: int | None = None, limit: int = 100, ) -> dict[str, Any]: """ Search and filter bridge infrastructure. Args: province: Filter by province (e.g., 'Ontario', 'British Columbia', 'Alberta') city: Filter by city name condition: Filter by condition rating (good, fair, poor, critical) capacity_min: Minimum capacity in tonnes built_after: Filter bridges built after this year limit: Maximum records to return (default 100) Returns: Bridge records with GeoJSON and metadata """ try: result = api_client.query_bridges( province=province, city=city, condition=condition, capacity_min=capacity_min, built_after=built_after, limit=limit, ) return result except Exception as e: logger.error(f"Error in query_bridges: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def query_road_conditions( province: str | None = None, highway: str | None = None, condition: str | None = None, pci_min: float | None = None, pci_max: float | None = None, limit: int = 100, ) -> dict[str, Any]: """ Query road/pavement condition data across Canada. Args: province: Filter by province (e.g., 'Ontario'). Currently Ontario has detailed data. highway: Filter by highway number/name (e.g., '401', '17', '11') condition: Filter by condition rating (good, fair, poor, critical) pci_min: Minimum Pavement Condition Index (0-100) pci_max: Maximum Pavement Condition Index (0-100) limit: Maximum records to return (default 100) Returns: Road condition records with PCI, DMI, IRI metrics and location data. PCI thresholds: >=80 good, 60-79 fair, 40-59 poor, <40 critical """ try: result = api_client.query_road_conditions( province=province, highway=highway, condition=condition, pci_min=pci_min, pci_max=pci_max, limit=limit, ) return result except Exception as e: logger.error(f"Error in query_road_conditions: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def query_tunnels( province: str | None = None, city: str | None = None, length_min: float | None = None, tunnel_type: str | None = None, limit: int = 100, ) -> dict[str, Any]: """ Search and filter tunnel infrastructure. Args: province: Filter by province city: Filter by city length_min: Minimum length in meters tunnel_type: Type of tunnel (road, rail, pedestrian, utility) limit: Maximum records to return (default 100) Returns: Tunnel records with GeoJSON and metadata """ try: result = api_client.query_tunnels( province=province, city=city, length_min=length_min, tunnel_type=tunnel_type, limit=limit, ) return result except Exception as e: logger.error(f"Error in query_tunnels: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def query_ports_airports( facility_type: str, province: str | None = None, capacity: str | None = None, services: list[str] | None = None, limit: int = 100, ) -> dict[str, Any]: """ Query ports, marinas, and airports. Args: facility_type: Type of facility - 'port', 'marina', 'airport', or 'heliport' (required) province: Filter by province capacity: Capacity filter (e.g., 'large', 'medium', 'small') services: List of required services (e.g., ['fuel', 'customs', 'repair']) limit: Maximum records to return (default 100) Returns: Port/Airport records with GeoJSON and metadata """ try: result = api_client.query_ports_airports( facility_type=facility_type, province=province, capacity=capacity, services=services, limit=limit, ) return result except Exception as e: logger.error(f"Error in query_ports_airports: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def query_railways( operator: str | None = None, region: str | None = None, rail_type: str | None = None, limit: int = 100, ) -> dict[str, Any]: """ Query railway lines and stations. Args: operator: Railway operator (e.g., 'CN', 'CP', 'VIA Rail') region: Region/province filter rail_type: Type of railway (freight, passenger, commuter, industrial) limit: Maximum records to return (default 100) Returns: Railway infrastructure with GeoJSON """ try: result = api_client.query_railways( operator=operator, region=region, rail_type=rail_type, limit=limit, ) return result except Exception as e: logger.error(f"Error in query_railways: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def analyze_bridge_conditions( region: str, group_by: str | None = None, limit: int = 100, ) -> dict[str, Any]: """ Aggregate bridge condition data for analysis. Args: region: Region/province to analyze (required) group_by: Group results by field (city, structure_type, age, condition) limit: Maximum records to return (default 100) Returns: Statistical analysis of bridge conditions """ try: result = api_client.analyze_bridge_conditions( region=region, group_by=group_by, limit=limit, ) return result except Exception as e: logger.error(f"Error in analyze_bridge_conditions: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def get_infrastructure_costs( infrastructure_type: str, location: str, limit: int = 50, ) -> dict[str, Any]: """ Get actual infrastructure replacement cost data from Statistics Canada. Downloads and analyzes the Core Public Infrastructure Survey to provide detailed cost breakdowns by condition rating and owner type. Args: infrastructure_type: Type of infrastructure - 'bridge', 'road', or 'transit' location: Province name (e.g., 'Ontario', 'Quebec', 'British Columbia') or 'Canada' for national limit: Maximum records for fallback search (default 50) Returns: Detailed cost analysis including: - Total replacement value (in millions CAD) - Costs by condition (Good, Fair, Poor, Very Poor) - Costs by owner type (Provincial, Municipal, etc.) - Priority investment needed (Poor + Very Poor assets) - Data source and quality indicators """ try: result = api_client.get_infrastructure_costs( infrastructure_type=infrastructure_type, location=location, limit=limit, ) return result except Exception as e: logger.error(f"Error in get_infrastructure_costs: {e}", exc_info=True) return {"error": str(e)} @mcp.tool() def compare_across_regions( infrastructure_type: str, regions: list[str], metrics: list[str], limit: int = 50, ) -> dict[str, Any]: """ Compare infrastructure across multiple regions. Args: infrastructure_type: Type of infrastructure to compare (bridge, transit, railway, cycling) regions: List of regions to compare (e.g., ['Ontario', 'British Columbia', 'Quebec']) metrics: List of metrics to compare (e.g., ['count', 'condition', 'age', 'capacity']) limit: Maximum records per region (default 50) Returns: Comparative analysis data across regions """ try: result = api_client.compare_across_regions( infrastructure_type=infrastructure_type, regions=regions, metrics=metrics, limit=limit, ) return result except Exception as e: logger.error(f"Error in compare_across_regions: {e}", exc_info=True) return {"error": str(e)} def main(): """Entry point for the Transportation MCP server.""" if _args.sse: import uvicorn from starlette.middleware.cors import CORSMiddleware logger.info(f"Starting GOV CA TRANSPORTATION MCP Server with SSE on http://{_args.host}:{_args.port}") logger.info(f"SSE endpoint: http://{_args.host}:{_args.port}/sse") # Get the SSE app and add CORS middleware app = mcp.sse_app() app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Run with uvicorn directly uvicorn.run(app, host=_args.host, port=_args.port) else: logger.info("Starting GOV CA TRANSPORTATION MCP Server with stdio transport...") mcp.run() if __name__ == "__main__": main()

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/krunal16-c/gov-ca-mcp'

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