Skip to main content
Glama

Constrained Optimization MCP Server

main.pyโ€ข25.2 kB
#!/usr/bin/env python3 """ Constrained Optimization MCP Server A general-purpose Model Context Protocol server for solving combinatorial optimization problems with logical and numerical constraints. This server provides tools for: - Constraint satisfaction problems (Z3) - Convex optimization (CVXPY) - Linear and mixed-integer programming (HiGHS) - Constraint programming (OR-Tools) - Financial optimization problems """ import json import sys from typing import Any, List from fastmcp import FastMCP from mcp.types import TextContent from returns.result import Failure, Success from ..models import ( Z3Problem, Z3Solution, Z3Variable, Z3Constraint, CVXPYProblem, CVXPYSolution, CVXPYVariable, CVXPYConstraint, HiGHSProblem, HiGHSSolution, HiGHSVariable, HiGHSConstraint, ORToolsProblem, ORToolsSolution, ORToolsVariable, ORToolsConstraint ) from ..solvers import ( solve_z3_problem, solve_cvxpy_problem, solve_highs_problem, solve_ortools_problem ) from ..core.problem_types import ProblemType, OptimizationSense, VariableType, ConstraintType app = FastMCP( name="constrained-opt-mcp", version="1.0.0", description="A general-purpose MCP server for solving combinatorial optimization problems with logical and numerical constraints", dependencies=[ "z3-solver>=4.14.1.0", "pydantic>=2.0.0", "returns>=0.20.0", "fastmcp>=0.1.0", "cvxpy>=1.6.0", "ortools<9.15.0", "highspy>=1.11.0", "numpy>=1.24.0", "scipy>=1.10.0", "pandas>=2.0.0", "matplotlib>=3.7.0", "seaborn>=0.12.0", "jupyter>=1.0.0", "ipywidgets>=8.0.0", ], ) @app.tool("solve_constraint_satisfaction") async def solve_constraint_satisfaction( variables: List[dict[str, str]], constraints: List[str], description: str = "", timeout: int = None, ) -> List[TextContent]: """ Solve constraint satisfaction problems using Z3 SMT solver. This tool is ideal for logical reasoning, puzzle solving, and constraint satisfaction problems where you need to find values that satisfy a set of logical constraints. Args: variables: List of variable definitions with 'name' and 'type' fields constraints: List of constraint expressions as strings description: Optional problem description timeout: Optional timeout in milliseconds Returns: Solution results including variable values and satisfiability status Example: variables = [ {"name": "x", "type": "integer"}, {"name": "y", "type": "integer"} ] constraints = [ "x + y == 10", "x - y == 2" ] """ try: # Convert to Z3 problem problem_variables = [] for var in variables: if "name" not in var or "type" not in var: return [ TextContent( type="text", text="Each variable must have 'name' and 'type' fields", ) ] try: from ..models.z3_models import Z3VariableType var_type = Z3VariableType(var["type"]) except ValueError: return [ TextContent( type="text", text=( f"Invalid variable type: {var['type']}. " f"Must be one of: integer, real, boolean, string" ), ) ] problem_variables.append(Z3Variable( name=var["name"], type=var_type, description=var.get("description", "") )) problem_constraints = [Z3Constraint(expression=expr) for expr in constraints] problem = Z3Problem( variables=problem_variables, constraints=problem_constraints, description=description, timeout=timeout, ) # Solve the problem result = solve_z3_problem(problem) match result: case Success(solution): return [ TextContent( type="text", text=json.dumps( { "values": solution.values, "is_satisfiable": solution.is_satisfiable, "status": solution.status, "solve_time": solution.solve_time, } ), ) ] case Failure(error): return [TextContent(type="text", text=f"Error solving problem: {error}")] except Exception as e: return [TextContent(type="text", text=f"Error in solve_constraint_satisfaction: {e!s}")] @app.tool("solve_convex_optimization") async def solve_convex_optimization( variables: List[dict[str, Any]], objective_type: str, objective_expr: str, constraints: List[str], parameters: dict[str, Any] = None, description: str = "", ) -> List[TextContent]: """ Solve convex optimization problems using CVXPY. This tool is ideal for mathematical optimization problems with convex objectives and constraints, including linear programming, quadratic programming, and semidefinite programming. Args: variables: List of variable definitions with 'name' and 'shape' objective_type: Either 'minimize' or 'maximize' objective_expr: The objective function expression as a string constraints: List of constraint expressions as strings parameters: Dictionary of parameter values (e.g., matrices A, b) description: Optional problem description Returns: Solution results including variable values and objective value Example: variables = [{"name": "x", "shape": 2}] objective_type = "minimize" objective_expr = "cp.sum_squares(x)" constraints = ["x >= 0", "cp.sum(x) == 1"] """ try: # Convert to CVXPY problem problem_variables = [] for var in variables: if "name" not in var or "shape" not in var: return [ TextContent( type="text", text="Each variable must have 'name' and 'shape' fields", ) ] problem_variables.append(CVXPYVariable(**var)) try: from ..models.cvxpy_models import ObjectiveType obj_type = ObjectiveType(objective_type) except ValueError: return [ TextContent( type="text", text=( f"Invalid objective type: {objective_type}. " f"Must be one of: minimize, maximize" ), ) ] from ..models.cvxpy_models import CVXPYObjective, CVXPYConstraint objective = CVXPYObjective(type=obj_type, expression=objective_expr) problem_constraints = [CVXPYConstraint(expression=expr) for expr in constraints] problem = CVXPYProblem( variables=problem_variables, objective=objective, constraints=problem_constraints, parameters=parameters or {}, description=description, ) # Solve the problem result = solve_cvxpy_problem(problem) match result: case Success(solution): return [ TextContent( type="text", text=json.dumps( { "values": { k: v.tolist() if hasattr(v, "tolist") else v for k, v in solution.values.items() }, "objective_value": solution.objective_value, "status": solution.status, "solve_time": solution.solve_time, "dual_values": { k: v.tolist() if hasattr(v, "tolist") else v for k, v in (solution.dual_values or {}).items() }, } ), ) ] case Failure(error): return [TextContent(type="text", text=f"Error solving problem: {error}")] except Exception as e: return [TextContent(type="text", text=f"Error in solve_convex_optimization: {e!s}")] @app.tool("solve_linear_programming") async def solve_linear_programming( sense: str, objective_coeffs: List[float], variables: List[dict[str, Any]], constraint_matrix: List[List[float]], constraint_senses: List[str], rhs_values: List[float], options: dict[str, Any] = None, description: str = "", ) -> List[TextContent]: """ Solve linear and mixed-integer programming problems using HiGHS. This tool is ideal for linear programming, mixed-integer linear programming, and large-scale optimization problems with linear constraints. Args: sense: Optimization sense, either "minimize" or "maximize" objective_coeffs: List of objective function coefficients variables: List of variable definitions with optional bounds and types constraint_matrix: 2D list representing the constraint matrix (dense format) constraint_senses: List of constraint directions ("<=", ">=", "=") rhs_values: List of right-hand side values for constraints options: Optional solver options dictionary description: Optional problem description Returns: Solution results including variable values and objective value Example: sense = "minimize" objective_coeffs = [1.0, 2.0, 3.0] variables = [ {"name": "x1", "lb": 0, "ub": 10, "type": "cont"}, {"name": "x2", "lb": 0, "ub": None, "type": "int"}, {"name": "x3", "lb": 0, "ub": 1, "type": "bin"} ] constraint_matrix = [[1, 1, 0], [0, 1, 1]] constraint_senses = ["<=", ">="] rhs_values = [5, 3] """ try: # Validate sense try: from ..models.highs_models import HiGHSSense problem_sense = HiGHSSense(sense) except ValueError: return [ TextContent( type="text", text=( f"Invalid sense: {sense}. " f"Must be one of: minimize, maximize" ), ) ] # Create objective from ..models.highs_models import HiGHSObjective objective = HiGHSObjective(linear=objective_coeffs) # Create variables problem_variables = [] for i, var in enumerate(variables): var_name = var.get("name", f"x{i+1}") var_lb = var.get("lb", 0.0) var_ub = var.get("ub", None) var_type_str = var.get("type", "cont") try: from ..models.highs_models import HiGHSVariableType var_type = HiGHSVariableType(var_type_str) except ValueError: return [ TextContent( type="text", text=( f"Invalid variable type: {var_type_str}. " f"Must be one of: cont, int, bin" ), ) ] from ..models.highs_models import HiGHSVariable problem_variables.append( HiGHSVariable(name=var_name, lb=var_lb, ub=var_ub, type=var_type) ) # Create constraints constraint_sense_enums = [] for sense_str in constraint_senses: try: from ..models.highs_models import HiGHSConstraintSense constraint_sense_enums.append(HiGHSConstraintSense(sense_str)) except ValueError: return [ TextContent( type="text", text=( f"Invalid constraint sense: {sense_str}. " f"Must be one of: <=, >=, =" ), ) ] from ..models.highs_models import HiGHSConstraints, HiGHSProblemSpec, HiGHSProblem, HiGHSOptions constraints = HiGHSConstraints( dense=constraint_matrix, sparse=None, sense=constraint_sense_enums, rhs=rhs_values, ) # Create problem specification problem_spec = HiGHSProblemSpec( sense=problem_sense, objective=objective, variables=problem_variables, constraints=constraints, ) # Create options if provided highs_options = None if options: highs_options = HiGHSOptions(**options) # Create full problem problem = HiGHSProblem(problem=problem_spec, options=highs_options) # Solve the problem result = solve_highs_problem(problem) match result: case Success(solution): return [ TextContent( type="text", text=json.dumps( { "values": solution.values, "objective_value": solution.objective_value, "status": solution.status.value, "solve_time": solution.solve_time, "dual_values": solution.dual_values, "reduced_costs": solution.reduced_costs, } ), ) ] case Failure(error): return [TextContent(type="text", text=f"Error solving problem: {error}")] except Exception as e: return [TextContent(type="text", text=f"Error in solve_linear_programming: {e!s}")] @app.tool("solve_constraint_programming") async def solve_constraint_programming( variables: List[dict[str, Any]], constraints: List[str], objective: dict[str, Any] = None, parameters: dict[str, Any] = None, description: str = "", ) -> List[TextContent]: """ Solve constraint programming problems using OR-Tools. This tool is ideal for combinatorial optimization problems, scheduling, assignment problems, and constraint satisfaction with discrete variables. Args: variables: List of variable definitions with 'name', 'type', and optional 'domain'/'shape' constraints: List of constraint expressions as strings objective: Optional objective definition with 'type' and 'expression' parameters: Dictionary of solver parameters description: Optional problem description Returns: Solution results including variable values and feasibility status Example: variables = [ {"name": "x", "type": "integer", "domain": [0, 10]}, {"name": "y", "type": "boolean"} ] constraints = [ "x + y >= 5", "x - y <= 3" ] objective = {"type": "minimize", "expression": "x + y"} """ try: # Convert to OR-Tools problem problem_variables = [] for var in variables: if "name" not in var or "type" not in var: return [ TextContent( type="text", text="Each variable must have 'name' and 'type' fields", ) ] try: from ..models.ortools_models import ORToolsVariableType var_type = ORToolsVariableType(var["type"]) except ValueError: return [ TextContent( type="text", text=( f"Invalid variable type: {var['type']}. " f"Must be one of: boolean, integer, interval" ), ) ] problem_variables.append(ORToolsVariable(**var)) problem_constraints = [ORToolsConstraint(expression=expr) for expr in constraints] # Create objective if provided problem_objective = None if objective: try: from ..models.ortools_models import ORToolsObjectiveType obj_type = ORToolsObjectiveType(objective["type"]) problem_objective = ORToolsObjective( type=obj_type, expression=objective.get("expression"), description=objective.get("description", "") ) except ValueError: return [ TextContent( type="text", text=( f"Invalid objective type: {objective['type']}. " f"Must be one of: minimize, maximize, feasibility" ), ) ] problem = ORToolsProblem( variables=problem_variables, constraints=problem_constraints, objective=problem_objective, parameters=parameters or {}, description=description, ) # Solve the problem result = solve_ortools_problem(problem) match result: case Success(solution): return [ TextContent( type="text", text=json.dumps( { "values": solution.values, "is_feasible": solution.is_feasible, "status": solution.status, "objective_value": solution.objective_value, "solve_time": solution.solve_time, "statistics": solution.statistics, } ), ) ] case Failure(error): return [TextContent(type="text", text=f"Error solving problem: {error}")] except Exception as e: return [TextContent(type="text", text=f"Error in solve_constraint_programming: {e!s}")] @app.tool("solve_portfolio_optimization") async def solve_portfolio_optimization( assets: List[str], expected_returns: List[float], risk_factors: List[float], correlation_matrix: List[List[float]], max_allocations: List[float] = None, risk_budget: float = None, description: str = "", ) -> List[TextContent]: """ Solve portfolio optimization problems using modern portfolio theory. This tool implements Markowitz mean-variance optimization to find optimal asset allocations that maximize expected return while constraining risk. Args: assets: List of asset names expected_returns: List of expected returns for each asset risk_factors: List of risk factors (standard deviations) for each asset correlation_matrix: Correlation matrix between assets max_allocations: Optional maximum allocation limits for each asset risk_budget: Optional maximum portfolio risk (variance) description: Optional problem description Returns: Optimal portfolio weights and performance metrics Example: assets = ["Bonds", "Stocks", "RealEstate", "Commodities"] expected_returns = [0.08, 0.12, 0.10, 0.15] risk_factors = [0.02, 0.15, 0.08, 0.20] correlation_matrix = [[1.0, 0.2, 0.3, 0.1], [0.2, 1.0, 0.6, 0.7], ...] max_allocations = [0.4, 0.6, 0.3, 0.2] risk_budget = 0.01 """ try: import numpy as np n_assets = len(assets) # Validate inputs if len(expected_returns) != n_assets: return [TextContent(type="text", text="Expected returns length must match number of assets")] if len(risk_factors) != n_assets: return [TextContent(type="text", text="Risk factors length must match number of assets")] if len(correlation_matrix) != n_assets or len(correlation_matrix[0]) != n_assets: return [TextContent(type="text", text="Correlation matrix must be square and match number of assets")] # Convert to numpy arrays expected_returns = np.array(expected_returns) risk_factors = np.array(risk_factors) correlation_matrix = np.array(correlation_matrix) # Create covariance matrix covariance_matrix = np.outer(risk_factors, risk_factors) * correlation_matrix # Set default max allocations if not provided if max_allocations is None: max_allocations = [1.0] * n_assets # Create CVXPY problem variables = [CVXPYVariable(name="weights", shape=n_assets)] # Create constraints constraint_exprs = [ "cp.sum(weights) == 1", # Budget constraint "weights >= 0", # Non-negativity ] # Add individual allocation limits for i, max_alloc in enumerate(max_allocations): constraint_exprs.append(f"weights[{i}] <= {max_alloc}") # Add risk constraint if specified if risk_budget is not None: constraint_exprs.append("cp.quad_form(weights, covariance_matrix) <= risk_budget") constraints = [CVXPYConstraint(expression=expr) for expr in constraint_exprs] # Create objective (maximize expected return) from ..models.cvxpy_models import CVXPYObjective, ObjectiveType objective = CVXPYObjective( type=ObjectiveType.MAXIMIZE, expression="expected_returns.T @ weights" ) problem = CVXPYProblem( variables=variables, objective=objective, constraints=constraints, parameters={ "expected_returns": expected_returns, "covariance_matrix": covariance_matrix, "risk_budget": risk_budget, }, description=description or "Portfolio optimization using modern portfolio theory", ) # Solve the problem result = solve_cvxpy_problem(problem) match result: case Success(solution): if solution.status == "optimal": weights = solution.values["weights"] expected_return = solution.objective_value # Calculate portfolio risk portfolio_risk = np.sqrt(weights.T @ covariance_matrix @ weights) # Calculate Sharpe ratio (assuming risk-free rate = 0) sharpe_ratio = expected_return / portfolio_risk if portfolio_risk > 0 else 0 return [ TextContent( type="text", text=json.dumps( { "status": "optimal", "weights": {assets[i]: float(weights[i]) for i in range(n_assets)}, "expected_return": float(expected_return), "portfolio_risk": float(portfolio_risk), "sharpe_ratio": float(sharpe_ratio), "solve_time": solution.solve_time, } ), ) ] else: return [TextContent(type="text", text=f"Optimization failed: {solution.status}")] case Failure(error): return [TextContent(type="text", text=f"Error solving portfolio optimization: {error}")] except Exception as e: return [TextContent(type="text", text=f"Error in solve_portfolio_optimization: {e!s}")] def main() -> None: """Main function to run the MCP server.""" print("Starting Constrained Optimization MCP server...", file=sys.stderr) app.run() if __name__ == "__main__": main()

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/Sharmarajnish/MCP-Constrained-Optimization'

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