Skip to main content
Glama

solve_assignment_problem_tool

Optimize task assignments between workers and tasks using the Hungarian algorithm to minimize or maximize assignment costs with optional constraints.

Instructions

    Solve assignment problem using OR-Tools Hungarian algorithm.

    Args:
        workers: List of worker names
        tasks: List of task names
        costs: 2D cost matrix where costs[i][j] is cost of assigning worker i to task j
        maximize: Whether to maximize instead of minimize (default: False)
        max_tasks_per_worker: Maximum tasks per worker (optional)
        min_tasks_per_worker: Minimum tasks per worker (optional)

    Returns:
        Dictionary with solution status, assignments, total cost, and execution time
    

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
workersYes
tasksYes
costsYes
maximizeNo
max_tasks_per_workerNo
min_tasks_per_workerNo

Implementation Reference

  • The primary handler function for the 'solve_assignment_problem_tool' MCP tool. It defines the tool interface, documentation (serving as schema), and delegates to the core solver function.
    @mcp.tool()
    def solve_assignment_problem_tool(
        workers: list[str],
        tasks: list[str],
        costs: list[list[float]],
        maximize: bool = False,
        max_tasks_per_worker: int | None = None,
        min_tasks_per_worker: int | None = None,
    ) -> dict[str, Any]:
        """
        Solve assignment problem using OR-Tools Hungarian algorithm.
    
        Args:
            workers: List of worker names
            tasks: List of task names
            costs: 2D cost matrix where costs[i][j] is cost of assigning worker i to task j
            maximize: Whether to maximize instead of minimize (default: False)
            max_tasks_per_worker: Maximum tasks per worker (optional)
            min_tasks_per_worker: Minimum tasks per worker (optional)
    
        Returns:
            Dictionary with solution status, assignments, total cost, and execution time
        """
        result = solve_assignment_problem(
            workers=workers,
            tasks=tasks,
            costs=costs,
            maximize=maximize,
            max_tasks_per_worker=max_tasks_per_worker,
            min_tasks_per_worker=min_tasks_per_worker,
        )
        result_dict: dict[str, Any] = result
        return result_dict
  • Core helper function implementing the assignment problem solving logic with input validation, OR-Tools solver invocation, and comprehensive error handling.
    @with_resource_limits(timeout_seconds=60.0, estimated_memory_mb=80.0)
    def solve_assignment_problem(
        workers: list[str],
        tasks: list[str],
        costs: list[list[float]],
        maximize: bool = False,
        max_tasks_per_worker: int | None = None,
        min_tasks_per_worker: int | None = None,
        objective: str | None = None,
        constraints: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        """Solve assignment problem using OR-Tools."""
        try:
            # Validate input
            if not workers:
                return {
                    "status": "error",
                    "total_cost": None,
                    "assignments": [],
                    "execution_time": 0.0,
                    "error_message": "No workers provided",
                }
    
            if not tasks:
                return {
                    "status": "error",
                    "total_cost": None,
                    "assignments": [],
                    "execution_time": 0.0,
                    "error_message": "No tasks provided",
                }
    
            if len(costs) != len(workers):
                return {
                    "status": "error",
                    "total_cost": None,
                    "assignments": [],
                    "execution_time": 0.0,
                    "error_message": f"Cost matrix dimensions: rows ({len(costs)}) must match workers count ({len(workers)})",
                }
    
            for i, row in enumerate(costs):
                if len(row) != len(tasks):
                    return {
                        "status": "error",
                        "total_cost": None,
                        "assignments": [],
                        "execution_time": 0.0,
                        "error_message": f"Cost matrix row {i} length ({len(row)}) must match tasks count ({len(tasks)})",
                    }
    
            # Handle objective parameter (convert to maximize flag)
            if objective is not None:
                maximize = objective.lower() == "maximize"
    
            # Handle constraints parameter
            if constraints is not None:
                # Check for unsupported constraint types
                forbidden_assignments = constraints.get("forbidden_assignments", [])
                task_requirements = constraints.get("task_requirements", {})
    
                if forbidden_assignments:
                    return {
                        "status": "error",
                        "total_cost": None,
                        "assignments": [],
                        "execution_time": 0.0,
                        "error_message": "Forbidden assignments constraints are not currently supported",
                    }
    
                if task_requirements:
                    return {
                        "status": "error",
                        "total_cost": None,
                        "assignments": [],
                        "execution_time": 0.0,
                        "error_message": "Task requirements constraints are not currently supported",
                    }
    
                # Extract worker limits if provided
                worker_limits = constraints.get("worker_limits", {})
                if worker_limits:
                    # Convert worker limits to max_tasks_per_worker if all workers have same limit
                    limit_values = list(worker_limits.values())
                    if len(set(limit_values)) == 1 and all(
                        worker in worker_limits for worker in workers
                    ):
                        max_tasks_per_worker = limit_values[0]
                        # If all workers have limit 0 and there are tasks, problem is infeasible
                        if max_tasks_per_worker == 0 and tasks:
                            return {
                                "status": "infeasible",
                                "total_cost": None,
                                "assignments": [],
                                "execution_time": 0.0,
                                "error_message": "No worker can be assigned any tasks due to constraints",
                            }
                    else:
                        return {
                            "status": "error",
                            "total_cost": None,
                            "assignments": [],
                            "execution_time": 0.0,
                            "error_message": "Individual worker limits with different values are not currently supported",
                        }
    
            # Create solver and solve
            from mcp_optimizer.solvers import ORToolsSolver
    
            solver = ORToolsSolver()
            result = solver.solve_assignment_problem(
                workers=workers,
                tasks=tasks,
                costs=costs,
                maximize=maximize,
                max_tasks_per_worker=max_tasks_per_worker,
                min_tasks_per_worker=min_tasks_per_worker,
            )
    
            # Add objective to result if specified
            if objective is not None:
                result["objective"] = objective
    
            logger.info(f"Assignment problem solved with status: {result.get('status')}")
            return result
    
        except Exception as e:
            logger.error(f"Error in solve_assignment_problem: {e}")
            return {
                "status": "error",
                "total_cost": None,
                "assignments": [],
                "execution_time": 0.0,
                "error_message": f"Failed to solve assignment problem: {str(e)}",
            }
  • Registration function that defines and registers the solve_assignment_problem_tool using the @mcp.tool() decorator.
    def register_assignment_tools(mcp: FastMCP[Any]) -> None:
        """Register assignment and transportation problem tools."""
    
        @mcp.tool()
        def solve_assignment_problem_tool(
            workers: list[str],
            tasks: list[str],
            costs: list[list[float]],
            maximize: bool = False,
            max_tasks_per_worker: int | None = None,
            min_tasks_per_worker: int | None = None,
        ) -> dict[str, Any]:
            """
            Solve assignment problem using OR-Tools Hungarian algorithm.
    
            Args:
                workers: List of worker names
                tasks: List of task names
                costs: 2D cost matrix where costs[i][j] is cost of assigning worker i to task j
                maximize: Whether to maximize instead of minimize (default: False)
                max_tasks_per_worker: Maximum tasks per worker (optional)
                min_tasks_per_worker: Minimum tasks per worker (optional)
    
            Returns:
                Dictionary with solution status, assignments, total cost, and execution time
            """
            result = solve_assignment_problem(
                workers=workers,
                tasks=tasks,
                costs=costs,
                maximize=maximize,
                max_tasks_per_worker=max_tasks_per_worker,
                min_tasks_per_worker=min_tasks_per_worker,
            )
            result_dict: dict[str, Any] = result
            return result_dict
    
        @mcp.tool()
        def solve_transportation_problem_tool(
            suppliers: list[dict[str, Any]],
            consumers: list[dict[str, Any]],
            costs: list[list[float]],
        ) -> dict[str, Any]:
            """
            Solve transportation problem using OR-Tools.
    
            Args:
                suppliers: List of supplier dictionaries with 'name' and 'supply' keys
                consumers: List of consumer dictionaries with 'name' and 'demand' keys
                costs: 2D cost matrix where costs[i][j] is cost of shipping from supplier i to consumer j
    
            Returns:
                Dictionary with solution status, flows, total cost, and execution time
            """
            return solve_transportation_problem(
                suppliers=suppliers,
                consumers=consumers,
                costs=costs,
            )
    
        logger.info("Registered assignment and transportation tools")
  • Top-level import and call to register_assignment_tools(mcp) in the main MCP server creation, making the tool available.
    from mcp_optimizer.tools.assignment import register_assignment_tools
    from mcp_optimizer.tools.financial import register_financial_tools
    from mcp_optimizer.tools.integer_programming import register_integer_programming_tools
    from mcp_optimizer.tools.knapsack import register_knapsack_tools
    from mcp_optimizer.tools.linear_programming import register_linear_programming_tools
    from mcp_optimizer.tools.production import register_production_tools
    from mcp_optimizer.tools.routing import register_routing_tools
    from mcp_optimizer.tools.scheduling import register_scheduling_tools
    from mcp_optimizer.tools.validation import register_validation_tools
    from mcp_optimizer.utils.resource_monitor import (
        get_resource_status,
        reset_resource_stats,
        resource_monitor,
    )
    
    logger = logging.getLogger(__name__)
    
    # Server start time for uptime calculation
    _server_start_time = time.time()
    
    
    def get_health() -> dict[str, Any]:
        """Get server health status and resource information."""
        try:
            resource_status = get_resource_status()
    
            status = "healthy"
            messages = []
    
            # Check memory usage
            current_memory = resource_status.get("current_memory_mb", 0)
            max_memory = resource_status.get("max_memory_mb", 1024)
            memory_usage_pct = (current_memory / max_memory) * 100 if max_memory > 0 else 0
    
            if memory_usage_pct > 90:
                status = "critical"
                messages.append(f"High memory usage: {memory_usage_pct:.1f}%")
            elif memory_usage_pct > 75:
                status = "warning"
                messages.append(f"Elevated memory usage: {memory_usage_pct:.1f}%")
    
            # Check active requests
            active_requests = resource_status.get("active_requests", 0)
            max_requests = resource_status.get("max_concurrent_requests", 10)
    
            if active_requests >= max_requests:
                status = "warning"
                messages.append("At maximum concurrent request limit")
    
            health_info = {
                "status": status,
                "version": get_version("mcp-optimizer"),
                "uptime": time.time() - _server_start_time,
                "requests_processed": resource_monitor.total_requests,
                "resource_stats": resource_monitor.get_stats(),
            }
    
            return {
                "status": status,
                "version": get_version("mcp-optimizer"),
                "uptime": time.time() - _server_start_time,
                "message": "; ".join(messages),
                "resource_status": resource_status,
                "health_info": health_info,
            }
        except ImportError as e:
            return {
                "status": "error",
                "message": f"Health check failed: {e}",
                "resource_status": {},
            }
    
    
    def get_resource_stats() -> dict[str, Any]:
        """Get detailed resource usage statistics."""
        return get_resource_status()
    
    
    def reset_resource_statistics() -> dict[str, str]:
        """Reset resource monitoring statistics."""
        reset_resource_stats()
        return {"status": "reset", "message": "Resource statistics have been reset"}
    
    
    def get_server_info() -> dict[str, Any]:
        """Get comprehensive server information."""
        return {
            "name": "MCP Optimizer",
            "version": get_version("mcp-optimizer"),
            "description": "Mathematical optimization server with multiple solvers",
            "uptime": time.time() - _server_start_time,
            "capabilities": {
                "linear_programming": True,
                "integer_programming": True,
                "mixed_integer_programming": True,
                "assignment_problems": True,
                "transportation_problems": True,
                "knapsack_problems": True,
                "routing_problems": True,
                "scheduling_problems": True,
                "portfolio_optimization": True,
                "production_planning": True,
                "input_validation": True,
            },
            "solvers": {
                "pulp": "Linear/Integer Programming",
                "ortools": "Routing, Scheduling, Assignment",
                "native": "Portfolio, Production Planning",
            },
            "configuration": {
                "max_solve_time": settings.max_solve_time,
                "max_memory_mb": settings.max_memory_mb,
                "max_concurrent_requests": settings.max_concurrent_requests,
                "log_level": settings.log_level.value,
                "debug": settings.debug,
            },
        }
    
    
    def create_mcp_server() -> FastMCP[dict[str, str]]:
        """Create and configure the MCP server with optimization tools."""
    
        # Create MCP server
        mcp: FastMCP[dict[str, str]] = FastMCP("MCP Optimizer")
    
        # Register all optimization tools
        register_linear_programming_tools(mcp)
        register_integer_programming_tools(mcp)
        register_assignment_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