Skip to main content
Glama

solve_vehicle_routing_problem

Optimize routes for multiple vehicles to efficiently serve multiple locations with capacity constraints.

Instructions

Solve Vehicle Routing Problem (VRP) to optimize routes for multiple vehicles.

    Args:
        locations: List of location dictionaries with name, coordinates, and demand
        vehicles: List of vehicle dictionaries with capacity constraints
        distance_matrix: Optional pre-calculated distance matrix
        time_matrix: Optional pre-calculated time matrix
        depot: Index of depot location (default: 0)
        time_limit_seconds: Maximum solving time in seconds (default: 30.0)

    Returns:
        Optimization result with routes for all vehicles
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
locationsYes
vehiclesYes
distance_matrixNo
time_matrixNo
depotNo
time_limit_secondsNo

Implementation Reference

  • The @mcp.tool() decorated handler function for 'solve_vehicle_routing_problem' that validates input, calls the core VRP solver, and returns the result as a dictionary.
    def solve_vehicle_routing_problem(
        locations: list[dict[str, Any]],
        vehicles: list[dict[str, Any]],
        distance_matrix: list[list[float]] | None = None,
        time_matrix: list[list[int]] | None = None,
        depot: int = 0,
        time_limit_seconds: float = 30.0,
    ) -> dict[str, Any]:
        """Solve Vehicle Routing Problem (VRP) to optimize routes for multiple vehicles.
    
        Args:
            locations: List of location dictionaries with name, coordinates, and demand
            vehicles: List of vehicle dictionaries with capacity constraints
            distance_matrix: Optional pre-calculated distance matrix
            time_matrix: Optional pre-calculated time matrix
            depot: Index of depot location (default: 0)
            time_limit_seconds: Maximum solving time in seconds (default: 30.0)
    
        Returns:
            Optimization result with routes for all vehicles
        """
        input_data = {
            "locations": locations,
            "vehicles": vehicles,
            "distance_matrix": distance_matrix,
            "time_matrix": time_matrix,
            "depot": depot,
            "time_limit_seconds": time_limit_seconds,
        }
    
        result = solve_vehicle_routing(input_data)
        result_dict: dict[str, Any] = result.model_dump()
        return result_dict
  • Core helper function implementing the Vehicle Routing Problem solver using OR-Tools constraint programming solver. Handles model creation, constraints, solving, and result extraction.
    @with_resource_limits(timeout_seconds=180.0, estimated_memory_mb=150.0)
    def solve_vehicle_routing(input_data: dict[str, Any]) -> OptimizationResult:
        """Solve Vehicle Routing Problem using OR-Tools.
    
        Args:
            input_data: VRP problem specification
    
        Returns:
            OptimizationResult with routes for all vehicles
        """
        if not ORTOOLS_AVAILABLE:
            return OptimizationResult(
                status=OptimizationStatus.ERROR,
                objective_value=None,
                variables={},
                execution_time=0.0,
                error_message="OR-Tools is not available. Please install it with 'pip install ortools'",
            )
    
        start_time = time.time()
    
        try:
            # Parse and validate input
            vrp_input = VRPInput(**input_data)
            locations = vrp_input.locations
            vehicles = vrp_input.vehicles
            n_locations = len(locations)
            n_vehicles = len(vehicles)
    
            if n_locations < 2:
                return OptimizationResult(
                    status=OptimizationStatus.ERROR,
                    error_message="At least 2 locations required for VRP",
                    execution_time=time.time() - start_time,
                )
    
            if n_vehicles < 1:
                return OptimizationResult(
                    status=OptimizationStatus.ERROR,
                    error_message="At least 1 vehicle required for VRP",
                    execution_time=time.time() - start_time,
                )
    
            # Get or calculate distance matrix
            if vrp_input.distance_matrix:
                distance_matrix = vrp_input.distance_matrix
            else:
                distance_matrix = calculate_distance_matrix(locations)  # type: ignore
    
            # Create routing model
            manager = pywrapcp.RoutingIndexManager(n_locations, n_vehicles, vrp_input.depot)
            routing = pywrapcp.RoutingModel(manager)
    
            # Create distance callback
            def distance_callback(from_index: int, to_index: int) -> int:
                from_node = manager.IndexToNode(from_index)
                to_node = manager.IndexToNode(to_index)
                return int(distance_matrix[from_node][to_node] * 1000)
    
            transit_callback_index = routing.RegisterTransitCallback(distance_callback)
            routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
    
            # Add capacity constraints
            demands = [int(loc.demand) for loc in locations]
    
            def demand_callback(from_index: int) -> int:
                from_node = manager.IndexToNode(from_index)
                return int(demands[from_node])
    
            demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
    
            # Set vehicle capacities
            vehicle_capacities = [int(vehicle.capacity) for vehicle in vehicles]
            routing.AddDimensionWithVehicleCapacity(
                demand_callback_index,
                0,  # null capacity slack
                vehicle_capacities,  # vehicle maximum capacities
                True,  # start cumul to zero
                "Capacity",
            )
    
            # Set search parameters
            search_parameters = pywrapcp.DefaultRoutingSearchParameters()
            search_parameters.first_solution_strategy = (
                routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
            )
            search_parameters.time_limit.seconds = int(vrp_input.time_limit_seconds)
    
            # Solve
            solution = routing.SolveWithParameters(search_parameters)
    
            if solution:
                # Extract routes for all vehicles
                routes = []
                total_distance = 0.0
                total_load = 0.0
    
                for vehicle_id in range(n_vehicles):
                    route = []
                    route_distance = 0.0
                    route_load = 0.0
                    index = routing.Start(vehicle_id)
    
                    while not routing.IsEnd(index):
                        node = manager.IndexToNode(index)
                        route.append(
                            {
                                "location": locations[node].name,
                                "index": node,
                                "demand": locations[node].demand,
                                "coordinates": {
                                    "lat": locations[node].lat,
                                    "lng": locations[node].lng,
                                    "x": locations[node].x,
                                    "y": locations[node].y,
                                },
                            }
                        )
                        route_load += locations[node].demand
    
                        previous_index = index
                        index = solution.Value(routing.NextVar(index))
                        if not routing.IsEnd(index):
                            from_node = manager.IndexToNode(previous_index)
                            to_node = manager.IndexToNode(index)
                            route_distance += distance_matrix[from_node][to_node]
    
                    # Add return to depot
                    if route:
                        depot_node = manager.IndexToNode(routing.End(vehicle_id))
                        if route[-1]["index"] != depot_node:
                            route.append(
                                {
                                    "location": locations[depot_node].name,
                                    "index": depot_node,
                                    "demand": locations[depot_node].demand,
                                    "coordinates": {
                                        "lat": locations[depot_node].lat,
                                        "lng": locations[depot_node].lng,
                                        "x": locations[depot_node].x,
                                        "y": locations[depot_node].y,
                                    },
                                }
                            )
                            # Add distance back to depot
                            last_node = route[-2]["index"]
                            route_distance += distance_matrix[last_node][depot_node]
    
                    if len(route) > 1:  # Only include routes with actual stops
                        routes.append(
                            {
                                "vehicle_id": vehicle_id,
                                "route": route,
                                "distance": route_distance,
                                "load": route_load,
                                "capacity": vehicles[vehicle_id].capacity,
                                "utilization": route_load / vehicles[vehicle_id].capacity
                                if vehicles[vehicle_id].capacity > 0
                                else 0,
                            }
                        )
                        total_distance += route_distance
                        total_load += route_load
    
                execution_time = time.time() - start_time
    
                return OptimizationResult(
                    status=OptimizationStatus.OPTIMAL,
                    objective_value=total_distance,
                    variables={
                        "routes": routes,
                        "total_distance": total_distance,
                        "total_load": total_load,
                        "num_vehicles_used": len(routes),
                        "num_locations_served": sum(
                            len(route_data["route"]) - 1  # type: ignore[misc,arg-type]
                            for route_data in routes
                            if isinstance(route_data, dict) and "route" in route_data
                        ),  # Exclude depot returns
                    },
                    execution_time=execution_time,
                    solver_info={
                        "solver_name": "OR-Tools Routing",
                        "search_strategy": "PATH_CHEAPEST_ARC",
                        "vehicles_available": n_vehicles,
                    },
                )
            else:
                return OptimizationResult(
                    status=OptimizationStatus.INFEASIBLE,
                    error_message="No solution found within time limit",
                    execution_time=time.time() - start_time,
                )
    
        except Exception as e:
            return OptimizationResult(
                status=OptimizationStatus.ERROR,
                error_message=f"VRP solving error: {str(e)}",
                execution_time=time.time() - start_time,
            )
  • Pydantic BaseModel schema defining the input structure and validation for the VRP tool, including locations, vehicles, matrices, and depot validation.
    class VRPInput(BaseModel):
        """Input schema for Vehicle Routing Problem."""
    
        locations: list[Location]
        vehicles: list[Vehicle]
        distance_matrix: list[list[float]] | None = None
        time_matrix: list[list[int]] | None = None
        depot: int = Field(default=0, ge=0)
        time_limit_seconds: float = Field(default=30.0, ge=0)
    
        @field_validator("depot")
        @classmethod
        def validate_depot(cls, v: int, info: ValidationInfo) -> int:
            if info.data and "locations" in info.data and v >= len(info.data["locations"]):
                raise ValueError("depot must be a valid location index")
            return v
  • Supporting Pydantic schema for individual locations used in VRP input, with coordinates, demand, and time window validation.
    class Location(BaseModel):
        """Location with coordinates and optional properties."""
    
        name: str
        lat: float | None = None
        lng: float | None = None
        x: float | None = None
        y: float | None = None
        demand: float = Field(default=0.0, ge=0)
        time_window: tuple[int, int] | None = None
        service_time: int = Field(default=0, ge=0)
    
        @field_validator("time_window")
        @classmethod
        def validate_time_window(cls, v: tuple[int, int] | None) -> tuple[int, int] | None:
            if v is not None and v[0] >= v[1]:
                raise ValueError("Time window start must be before end")
            return v
  • Invocation of the register_routing_tools function during MCP server creation, which registers the solve_vehicle_routing_problem tool.
    register_routing_tools(mcp)

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/dmitryanchikov/mcp-optimizer'

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