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)
Behavior2/5

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

With no annotations provided, the description carries full burden but provides minimal behavioral context. It mentions optimization and a time limit default, but doesn't disclose computational characteristics (deterministic/stochastic, approximation guarantees), what happens when constraints can't be satisfied, whether solutions are cached, or error handling for invalid inputs. For a complex optimization tool with 6 parameters, this is inadequate.

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 well-structured with a clear purpose statement followed by organized Args and Returns sections. It's appropriately sized for a complex tool, though the Returns section could be more informative given there's no output schema. Every sentence adds value.

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?

For a complex optimization tool with 6 parameters, no annotations, and no output schema, the description is moderately complete. It covers parameters well but lacks behavioral context, usage guidance, and return value details. The absence of output schema means the Returns statement should be more informative about what 'Optimization result with routes for all vehicles' actually contains.

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

Parameters4/5

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

With 0% schema description coverage, the description provides excellent parameter semantics that fully compensate. It clearly explains all 6 parameters including their purposes (locations with name/coordinates/demand, vehicles with capacity constraints, optional matrices, depot index, solving time limit) and default values. 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 'solves Vehicle Routing Problem (VRP) to optimize routes for multiple vehicles', which is a specific verb+resource combination. It distinguishes from siblings like 'solve_traveling_salesman_problem' by specifying multiple vehicles, but doesn't explicitly contrast with other optimization tools like 'solve_assignment_problem_tool' beyond the VRP domain.

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

Usage Guidelines2/5

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

No guidance is provided about when to use this tool versus alternatives. The description doesn't mention when VRP is appropriate compared to other routing or optimization tools in the sibling list, nor does it discuss prerequisites or typical use cases for vehicle routing problems.

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

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