Skip to main content
Glama

optimize_production_plan_tool

Optimize multi-period production planning to maximize profit or minimize costs by balancing product demand, resource capacity, and inventory constraints.

Instructions

Optimize multi-period production planning to maximize profit or minimize costs.

    Args:
        products: List of product dictionaries with costs and resource requirements
        resources: List of resource dictionaries with capacity constraints
        periods: Number of planning periods
        demand: List of demand requirements per product per period
        objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
        inventory_costs: Optional inventory holding costs per product
        setup_costs: Optional setup costs per product
        solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
        time_limit_seconds: Maximum solving time in seconds (default: 30.0)

    Returns:
        Optimization result with optimal production plan
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
productsYes
resourcesYes
periodsYes
demandYes
objectiveNomaximize_profit
inventory_costsNo
setup_costsNo
solver_nameNoCBC
time_limit_secondsNo

Implementation Reference

  • The handler function for 'optimize_production_plan_tool', decorated with @mcp.tool(). It constructs input data from parameters and calls the core solver solve_production_planning.
    @mcp.tool()
    def optimize_production_plan_tool(
        products: list[dict[str, Any]],
        resources: list[dict[str, Any]],
        periods: int,
        demand: list[dict[str, Any]],
        objective: str = "maximize_profit",
        inventory_costs: dict[str, float] | None = None,
        setup_costs: dict[str, float] | None = None,
        solver_name: str = "CBC",
        time_limit_seconds: float = 30.0,
    ) -> dict[str, Any]:
        """Optimize multi-period production planning to maximize profit or minimize costs.
    
        Args:
            products: List of product dictionaries with costs and resource requirements
            resources: List of resource dictionaries with capacity constraints
            periods: Number of planning periods
            demand: List of demand requirements per product per period
            objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
            inventory_costs: Optional inventory holding costs per product
            setup_costs: Optional setup costs per product
            solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
            time_limit_seconds: Maximum solving time in seconds (default: 30.0)
    
        Returns:
            Optimization result with optimal production plan
        """
        input_data = {
            "products": products,
            "resources": resources,
            "periods": periods,
            "demand": demand,
            "objective": objective,
            "inventory_costs": inventory_costs,
            "setup_costs": setup_costs,
            "solver_name": solver_name,
            "time_limit_seconds": time_limit_seconds,
        }
    
        result = solve_production_planning(input_data)
        result_dict: dict[str, Any] = result.model_dump()
        return result_dict
  • Core helper function that implements the mathematical optimization model using PuLP for production planning, handling multi-period, inventory, setup costs, etc.
    @with_resource_limits(timeout_seconds=120.0, estimated_memory_mb=150.0)
    def solve_production_planning(input_data: dict[str, Any]) -> OptimizationResult:
        """Solve Production Planning Problem using PuLP.
    
        Args:
            input_data: Production planning problem specification
    
        Returns:
            OptimizationResult with optimal production plan
        """
        start_time = time.time()
    
        try:
            # Parse and validate input
            planning_input = ProductionPlanningInput(**input_data)
            products = planning_input.products
            resources = planning_input.resources
            demand_constraints = planning_input.demand_constraints
            horizon = planning_input.planning_horizon
    
            # Create optimization problem
            if planning_input.objective == "maximize_profit":
                prob = pulp.LpProblem("Production_Planning", pulp.LpMaximize)
            else:
                prob = pulp.LpProblem("Production_Planning", pulp.LpMinimize)
    
            # Decision variables: production quantities for each product in each period
            production_vars: dict[int, dict[str, Any]] = {}
            setup_vars: dict[int, dict[str, Any]] = {}  # Binary variables for setup decisions
            inventory_vars: dict[int, dict[str, Any]] = {}  # Inventory levels
    
            for period in range(horizon):
                production_vars[period] = {}
                setup_vars[period] = {}
                inventory_vars[period] = {}
    
                for product in products:
                    # Production quantity
                    production_vars[period][product.name] = pulp.LpVariable(
                        f"production_{product.name}_period_{period}",
                        lowBound=0,
                        cat="Continuous",
                    )
    
                    # Setup decision (binary)
                    if product.setup_cost > 0:
                        setup_vars[period][product.name] = pulp.LpVariable(
                            f"setup_{product.name}_period_{period}", cat="Binary"
                        )
    
                    # Inventory level
                    inventory_vars[period][product.name] = pulp.LpVariable(
                        f"inventory_{product.name}_period_{period}",
                        lowBound=0,
                        cat="Continuous",
                    )
    
            # Resource capacity constraints
            for period in range(horizon):
                for resource_name, resource in resources.items():
                    resource_usage = pulp.lpSum(
                        production_vars[period][product.name] * product.resources.get(resource_name, 0)
                        for product in products
                    )
                    prob += (
                        resource_usage <= resource.available,
                        f"Resource_{resource_name}_Period_{period}",
                    )
    
            # Production constraints
            for period in range(horizon):
                for product in products:
                    prod_var = production_vars[period][product.name]
    
                    # Minimum production
                    if product.min_production > 0:
                        prob += (
                            prod_var >= product.min_production,
                            f"Min_Production_{product.name}_Period_{period}",
                        )
    
                    # Maximum production
                    if product.max_production is not None:
                        prob += (
                            prod_var <= product.max_production,
                            f"Max_Production_{product.name}_Period_{period}",
                        )
    
                    # Setup constraints
                    if product.setup_cost > 0:
                        setup_var = setup_vars[period][product.name]
                        # If producing, must setup
                        prob += (
                            prod_var <= (product.max_production or 1000000) * setup_var,
                            f"Setup_{product.name}_Period_{period}",
                        )
    
            # Demand constraints
            demand_by_product_period = {}
            for constraint in demand_constraints:
                period = constraint.period if constraint.period is not None else 0
                if period < horizon:
                    key = (constraint.product, period)
                    demand_by_product_period[key] = constraint
    
            # Inventory balance constraints
            for period in range(horizon):
                for product in products:
                    prod_var = production_vars[period][product.name]
                    inv_var = inventory_vars[period][product.name]
    
                    # Get demand for this product in this period
                    demand_constraint = demand_by_product_period.get((product.name, period))
                    demand = demand_constraint.min_demand if demand_constraint else 0
    
                    if period == 0:
                        # First period: production + initial_inventory = demand + ending_inventory
                        prob += (
                            prod_var == demand + inv_var,
                            f"Inventory_Balance_{product.name}_Period_{period}",
                        )
                    else:
                        # Other periods: production + previous_inventory = demand + ending_inventory
                        prev_inv_var = inventory_vars[period - 1][product.name]
                        prob += (
                            prod_var + prev_inv_var == demand + inv_var,
                            f"Inventory_Balance_{product.name}_Period_{period}",
                        )
    
                    # Maximum demand constraints
                    if demand_constraint and demand_constraint.max_demand is not None:
                        prob += (
                            prod_var + (inventory_vars[period - 1][product.name] if period > 0 else 0)
                            <= demand_constraint.max_demand + inv_var,
                            f"Max_Demand_{product.name}_Period_{period}",
                        )
    
            # Objective function
            if planning_input.objective == "maximize_profit":
                # Maximize profit = revenue - production costs - setup costs - inventory costs
                total_profit = 0.0
    
                for period in range(horizon):
                    # Production profit
                    period_profit = pulp.lpSum(
                        production_vars[period][product.name] * product.profit for product in products
                    )
                    total_profit += period_profit
    
                    # Setup costs
                    if any(product.setup_cost > 0 for product in products):
                        setup_costs = pulp.lpSum(
                            setup_vars[period][product.name] * product.setup_cost
                            for product in products
                            if product.setup_cost > 0
                        )
                        total_profit -= setup_costs
    
                    # Inventory costs
                    if planning_input.inventory_cost > 0:
                        inventory_costs = pulp.lpSum(
                            inventory_vars[period][product.name] * planning_input.inventory_cost
                            for product in products
                        )
                        total_profit -= inventory_costs
    
                prob += total_profit, "Total_Profit"
    
            elif planning_input.objective == "minimize_cost":
                # Minimize total production and setup costs
                total_cost = 0.0
    
                for period in range(horizon):
                    # Production costs (negative profit)
                    production_costs = pulp.lpSum(
                        production_vars[period][product.name] * (-product.profit)
                        for product in products
                    )
                    total_cost += production_costs
    
                    # Setup costs
                    if any(product.setup_cost > 0 for product in products):
                        setup_costs = pulp.lpSum(
                            setup_vars[period][product.name] * product.setup_cost
                            for product in products
                            if product.setup_cost > 0
                        )
                        total_cost += setup_costs
    
                prob += total_cost, "Total_Cost"
    
            elif planning_input.objective == "minimize_time":
                # Minimize total production time
                total_time = pulp.lpSum(
                    production_vars[period][product.name] * product.production_time
                    for period in range(horizon)
                    for product in products
                )
                prob += total_time, "Total_Time"
    
            # Solve
            prob.solve(pulp.PULP_CBC_CMD(msg=0))
    
            # Process results
            status = pulp.LpStatus[prob.status]
            execution_time = time.time() - start_time
    
            if prob.status == pulp.LpStatusOptimal:
                # Extract solution
                production_plan: list[dict[str, Any]] = []
                total_profit = 0.0
                total_cost = 0.0
                total_time = 0.0
                resource_utilization: dict[str, list[float]] = {}
    
                for period in range(horizon):
                    period_plan: dict[str, Any] = {
                        "period": period,
                        "products": [],
                        "resource_usage": {},
                        "period_profit": 0.0,
                        "period_cost": 0.0,
                    }
    
                    for product in products:
                        production_qty = production_vars[period][product.name].varValue or 0
                        inventory_qty = inventory_vars[period][product.name].varValue or 0
                        setup_decision = (
                            setup_vars[period][product.name].varValue if product.setup_cost > 0 else 0
                        )
    
                        product_profit = production_qty * product.profit
                        product_cost = production_qty * (-product.profit) if product.profit < 0 else 0
                        product_time = production_qty * product.production_time
    
                        period_plan["products"].append(
                            {
                                "name": product.name,
                                "production_quantity": production_qty,
                                "inventory_level": inventory_qty,
                                "setup_required": bool(setup_decision),
                                "profit": product_profit,
                                "production_time": product_time,
                            }
                        )
    
                        period_plan["period_profit"] = float(period_plan["period_profit"]) + float(
                            product_profit
                        )
                        period_plan["period_cost"] = float(period_plan["period_cost"]) + float(
                            product_cost
                        )
    
                        total_profit += float(product_profit)
                        total_cost += float(product_cost)
                        total_time += float(product_time)
    
                    # Calculate resource usage for this period
                    for resource_name, resource in resources.items():
                        usage = sum(
                            production_vars[period][product.name].varValue
                            * product.resources.get(resource_name, 0)
                            for product in products
                        )
                        period_plan["resource_usage"][resource_name] = {
                            "used": usage,
                            "available": resource.available,
                            "utilization": usage / resource.available if resource.available > 0 else 0,
                        }
    
                        if resource_name not in resource_utilization:
                            resource_utilization[resource_name] = []
                        resource_list = resource_utilization[resource_name]
                        resource_list.append(float(usage))
    
                    production_plan.append(period_plan)
    
                # Calculate summary statistics
                resource_utilization_summary: dict[str, dict[str, float]] = {}
                for resource_name, usage_list in resource_utilization.items():
                    resource = resources[resource_name]
                    resource_utilization_summary[resource_name] = {
                        "total_usage": sum(usage_list),
                        "average_utilization": sum(u / resource.available for u in usage_list)
                        / len(usage_list)
                        if resource.available > 0
                        else 0,
                        "peak_utilization": max(u / resource.available for u in usage_list)
                        if resource.available > 0
                        else 0,
                    }
    
                summary = {
                    "total_profit": total_profit,
                    "total_cost": total_cost,
                    "total_production_time": total_time,
                    "planning_horizon": horizon,
                    "resource_utilization_summary": resource_utilization_summary,
                }
    
                return OptimizationResult(
                    status=OptimizationStatus.OPTIMAL,
                    objective_value=pulp.value(prob.objective),
                    variables={"production_plan": production_plan, "summary": summary},
                    execution_time=execution_time,
                    solver_info={
                        "solver_name": "PuLP CBC",
                        "objective": planning_input.objective,
                        "num_products": len(products),
                        "num_resources": len(resources),
                        "planning_horizon": horizon,
                    },
                )
    
            elif prob.status == pulp.LpStatusInfeasible:
                return OptimizationResult(
                    status=OptimizationStatus.INFEASIBLE,
                    error_message="Production planning problem is infeasible. Check resource constraints and demand requirements.",
                    execution_time=execution_time,
                )
    
            elif prob.status == pulp.LpStatusUnbounded:
                return OptimizationResult(
                    status=OptimizationStatus.UNBOUNDED,
                    error_message="Production planning problem is unbounded.",
                    execution_time=execution_time,
                )
    
            else:
                return OptimizationResult(
                    status=OptimizationStatus.ERROR,
                    error_message=f"Solver failed with status: {status}",
                    execution_time=execution_time,
                )
    
        except Exception as e:
            return OptimizationResult(
                status=OptimizationStatus.ERROR,
                error_message=f"Production planning error: {str(e)}",
                execution_time=time.time() - start_time,
            )
  • The registration function that defines and registers the tool using the MCP decorator.
    def register_production_tools(mcp: FastMCP[Any]) -> None:
        """Register production planning optimization tools with MCP server."""
    
        @mcp.tool()
        def optimize_production_plan_tool(
            products: list[dict[str, Any]],
            resources: list[dict[str, Any]],
            periods: int,
            demand: list[dict[str, Any]],
            objective: str = "maximize_profit",
            inventory_costs: dict[str, float] | None = None,
            setup_costs: dict[str, float] | None = None,
            solver_name: str = "CBC",
            time_limit_seconds: float = 30.0,
        ) -> dict[str, Any]:
            """Optimize multi-period production planning to maximize profit or minimize costs.
    
            Args:
                products: List of product dictionaries with costs and resource requirements
                resources: List of resource dictionaries with capacity constraints
                periods: Number of planning periods
                demand: List of demand requirements per product per period
                objective: Optimization objective ("maximize_profit", "minimize_cost", "minimize_time")
                inventory_costs: Optional inventory holding costs per product
                setup_costs: Optional setup costs per product
                solver_name: Solver to use ("CBC", "GLPK", "GUROBI", "CPLEX")
                time_limit_seconds: Maximum solving time in seconds (default: 30.0)
    
            Returns:
                Optimization result with optimal production plan
            """
            input_data = {
                "products": products,
                "resources": resources,
                "periods": periods,
                "demand": demand,
                "objective": objective,
                "inventory_costs": inventory_costs,
                "setup_costs": setup_costs,
                "solver_name": solver_name,
                "time_limit_seconds": time_limit_seconds,
            }
    
            result = solve_production_planning(input_data)
            result_dict: dict[str, Any] = result.model_dump()
            return result_dict
  • The call site where register_production_tools is invoked during MCP server creation to register the production tools.
    register_production_tools(mcp)
  • Pydantic schema used for validating the input data to the solver function.
    class ProductionPlanningInput(BaseModel):
        """Input schema for Production Planning."""
    
        products: list[Product]
        resources: dict[str, Resource]
        demand_constraints: list[DemandConstraint] = Field(default_factory=list)
        planning_horizon: int = Field(default=1, ge=1)
        objective: str = Field(
            default="maximize_profit",
            pattern="^(maximize_profit|minimize_cost|minimize_time)$",
        )
        allow_backorders: bool = Field(default=False)
        inventory_cost: float = Field(default=0.0, ge=0)
    
        @field_validator("products")
        @classmethod
        def validate_products(cls, v: list[Product]) -> list[Product]:
            if not v:
                raise ValueError("Must have at least one product")
            return v
    
        @field_validator("resources")
        @classmethod
        def validate_resources(cls, v: dict[str, Resource]) -> dict[str, Resource]:
            if not v:
                raise ValueError("Must have at least one resource")
            return v
    
        @field_validator("demand_constraints")
        @classmethod
        def validate_demand_constraints(cls, v: list[DemandConstraint]) -> list[DemandConstraint]:
            if not v:
                raise ValueError("Must have at least one demand constraint")
            return v
Behavior2/5

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

With no annotations provided, the description carries the full burden of behavioral disclosure. It mentions the optimization objective and solver options but doesn't cover critical aspects like whether this is a read-only or destructive operation, error handling, performance characteristics, or what happens if optimization fails. For a complex tool with 9 parameters, this is a significant gap.

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 parameter details and return information. It's appropriately sized for a complex tool, though the parameter documentation is quite detailed (which is necessary given the schema coverage gap), making it slightly less concise than ideal.

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 9 parameters, no annotations, and no output schema, the description provides good parameter semantics but lacks behavioral context and usage guidance. The return statement is vague ('Optimization result with optimal production plan'), leaving uncertainty about output format. This is adequate but has clear gaps.

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?

The description provides detailed parameter information in the Args section, including data types, purposes, and default values for all 9 parameters. With 0% schema description coverage, this fully compensates by explaining what each parameter means beyond just their names, which is essential for proper tool invocation.

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's purpose as 'Optimize multi-period production planning to maximize profit or minimize costs,' which is specific about the action (optimize) and domain (production planning). However, it doesn't explicitly differentiate from sibling tools like 'solve_linear_program_tool' or 'solve_mixed_integer_program' that might also handle optimization problems, so it doesn't reach the highest score.

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?

The description provides no guidance on when to use this tool versus the many sibling optimization tools (e.g., 'solve_linear_program_tool', 'solve_integer_program_tool'). It lacks context about prerequisites, alternatives, or exclusions, leaving the agent to guess based on the tool name alone.

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