A decision engine that can't lie about the constraints - an MCP / function-calling tool that returns verified, constraint-satisfying answers for AI agents
Project description
OptiMCP — a decision engine that can't lie about the constraints
Give any AI agent (Claude, GPT, LangChain, …) a tool that turns "here's a decision under these constraints" into a verified, constraint-satisfying answer — instead of an LLM confidently blowing a budget or double-booking a resource.
LLM agents are good at proposing decisions and bad at guaranteeing them. Ask one to pick projects under a budget, staff shifts without leaving a gap, or build a portfolio under a diversification rule, and it will hand you an answer that sometimes quietly violates the very constraint you told it about. OptiMCP is a tool you plug into the agent: it takes the decision as structured data, solves it deterministically, and then independently re-verifies the answer against those constraints before returning it.
There is no LLM inside OptiMCP. The agent fills a structured schema; the mapping from that schema to a checked answer is deterministic. The optimization engine underneath is invisible plumbing — you never need any special vocabulary to use the tool.
flowchart TB
subgraph agent [Your agent]
LLM[LLM decides it needs a verified answer] --> Spec[Fills structured DecisionSpec]
end
subgraph OptiMCP [OptiMCP tool]
Spec --> Build[Deterministic builder]
Build --> Engine[Optimization engine]
Build --> Classical[Classical exact / heuristic]
Engine --> Verify[Independent verifier]
Classical --> Verify
Verify --> Best[Best verified answer + certificate]
end
Best --> LLM2[Agent gets a constraint-satisfying answer]
Why agents use OptiMCP
| You want… | OptiMCP gives you… |
|---|---|
| An answer that actually respects the constraints | solve_decision(spec) → independently verified assignment |
| To check the agent's own proposed answer | verify_solution(spec, assignment) → per-constraint certificate |
| Zero quantum knowledge required | Quantum stays invisible; the schema is plain variables/objective/constraints |
| Reliability even when the solver struggles | Classical exact/heuristic fallback; best verified answer wins |
| Deterministic, auditable tool behavior | No LLM inside; same spec → same answer |
| To wire it into any stack | MCP server, OpenAI/Anthropic function schema, LangChain tool |
OptiMCP is not a general planner or an LLM wrapper. It is a constraint-solving tool with an independent verification layer. The guarantee is constraint satisfaction (independently verified) — not global optimality (except on the exact tier, below).
Table of contents
- Install
- 60-second quickstart
- Add it to your agent
- The decision spec
- The result payload
- Verify an answer the agent already has
- How it works
- Guarantees, reliability tiers & limits
- What "guaranteed" means (honestly)
- Does it actually help? (benchmark)
- Examples
- Troubleshooting
- Repository layout
- License
Install
Requirements
- Python 3.10+
- No GPU, no CUDA, no WSL. The default optimization engine runs on CPU on Windows/macOS/Linux; if it isn't present, an exact/heuristic classical solver is used instead.
PyPI
pip install optimcp
Extras
pip install "optimcp[qokit]" # accelerated optimization engine (recommended; CPU, cross-platform)
pip install "optimcp[langchain]" # LangChain StructuredTool adapter
pip install "optimcp[dev]" # pytest for the test suite
Source checkout
git clone https://github.com/ProfessionalQwerty/OptiMCP.git
cd OptiMCP
pip install -e ".[dev]"
This installs:
| Command / module | Purpose |
|---|---|
optimcp |
Launches the MCP stdio server (solve_decision, verify_solution, capabilities) |
import optimcp |
solve_decision, verify_assignment, spec/result models |
optimcp.schemas |
OpenAI / Anthropic function-tool JSON schema export |
optimcp.adapters.langchain |
LangChain StructuredTool wrapper |
60-second quickstart
1. As an MCP server (Claude Desktop, Cursor, any MCP client). The optimcp command speaks MCP over stdio. Add it to your client config (see examples/mcp_config.json):
{
"mcpServers": {
"optimcp": { "command": "optimcp", "args": [] }
}
}
Your agent now has three tools: solve_decision, verify_solution, capabilities.
2. Or call it directly in Python:
from optimcp.solve import solve_decision
from optimcp.spec import DecisionSpec
spec = DecisionSpec.model_validate({
"variables": [{"name": "a"}, {"name": "b"}, {"name": "c"}],
"objective": {"sense": "maximize", "terms": [
{"vars": ["a"], "coeff": 60}, {"vars": ["b"], "coeff": 40}, {"vars": ["c"], "coeff": 35},
]},
"constraints": [{"name": "budget", "op": "<=", "rhs": 100, "terms": [
{"vars": ["a"], "coeff": 55}, {"vars": ["b"], "coeff": 45}, {"vars": ["c"], "coeff": 50},
]}],
})
result = solve_decision(spec)
print(result.status) # "solved"
print(result.assignment) # {"a": 1, "b": 1, "c": 0}
print(result.objective_value) # 100.0
print(result.verification.constraint_checks[0].detail) # "budget: 100 <= 100: satisfied"
status == "solved" means the assignment was independently re-checked against the budget before it was returned.
Add it to your agent
MCP (Claude Desktop / Cursor / local agents)
Use the config above. The server exposes the tools over stdio; no ports, no keys. Optional HTTP transports:
optimcp --transport streamable-http # or: sse
OpenAI / Anthropic function calling
from optimcp.schemas import openai_tool, anthropic_tool
from optimcp.solve import solve_decision
from optimcp.spec import DecisionSpec
tools = [openai_tool()] # or [anthropic_tool()]
def dispatch(name, arguments): # call this from your tool-call loop
if name == "solve_decision":
return solve_decision(DecisionSpec.model_validate(arguments)).model_dump()
Full, runnable example (a live model calls the tool to avoid overspending): examples/openai_function_calling.py.
LangChain / LangGraph
from optimcp.adapters.langchain import build_langchain_tool
tool = build_langchain_tool() # a StructuredTool; pass to your agent's tools=[...]
Full example (a live model calls the tool on a schedule-coverage decision): examples/langchain_agent.py. Requires pip install "optimcp[langchain]".
The decision spec
A decision has the shape:
maximize / minimize sum(coeff · x_i [· x_j]) (objective)
subject to sum(coeff · x_i [· x_j]) <op> rhs (each constraint)
over binary or bounded-integer variables. Terms are degree ≤ 2 (linear or quadratic).
Variables (VariableSpec)
| Field | Type | Notes |
|---|---|---|
name |
str | Unique, case-sensitive identifier used by terms |
kind |
"binary" | "integer" |
Binary = 0/1; integer = bounded count |
lb, ub |
int | Required for integer variables; lb ≤ ub |
description |
str? | Optional human-readable meaning |
{"name": "fund_alpha", "kind": "binary"}
{"name": "trucks", "kind": "integer", "lb": 0, "ub": 5}
Terms (Term)
| Field | Type | Notes |
|---|---|---|
vars |
list[str] | 1 name (linear) or 2 names (quadratic) |
coeff |
float | Coefficient; defaults to 1.0 |
{"vars": ["a"], "coeff": 60} # 60·a
{"vars": ["a", "b"], "coeff": -5} # -5·a·b (quadratic)
Objective (ObjectiveSpec)
| Field | Type | Notes |
|---|---|---|
sense |
"maximize" | "minimize" |
Direction |
terms |
list[Term] | Empty ⇒ "any feasible answer" |
Constraints (ConstraintSpec)
| Field | Type | Notes |
|---|---|---|
terms |
list[Term] | Left-hand side (a sum of terms) |
op |
one of <= >= == != < > |
Comparison |
rhs |
float | Right-hand side constant |
name |
str? | Echoed back in the certificate |
description |
str? | Optional meaning |
Model
==deliberately. "Exactly one per shift" / "hold exactly 3 assets" is an equality (==), not<=. OptiMCP verifies the operator you write — see What "guaranteed" means.
Validation & limits
The spec is validated up front; bad specs are rejected, never silently coerced:
- unique variable names; every term references a declared variable;
- integer variables must carry
lb/ubwithlb ≤ ub; - term degree ≤ 2 (cubic+ rejected);
- at most 64 variables (larger decisions should be decomposed).
The result payload
solve_decision returns a DecisionResult:
| Field | Type | Meaning |
|---|---|---|
status |
"solved" | "infeasible" | "no_feasible_found" |
Outcome |
feasible |
bool | True iff a verified constraint-satisfying answer is returned |
assignment |
dict[str, int] | Chosen values by variable name (empty if not solved) |
objective_value |
float? | Objective at the assignment |
objective_sense |
str | "maximize" / "minimize" |
verification |
object | Independent certificate (below) |
message |
str | Human-readable summary |
diagnostics |
dict? | Internals (winning source, timings); off by default |
The verification certificate:
| Field | Type | Meaning |
|---|---|---|
all_satisfied |
bool | Every constraint + domain holds |
objective_value |
float | Recomputed independently |
constraint_checks |
list | {name, satisfied, detail} per constraint |
domain_valid |
bool | All values within their declared domains |
issues |
list[str] | Domain / unknown-variable / missing-variable problems |
{
"status": "solved",
"feasible": true,
"assignment": {"a": 1, "b": 1, "c": 0},
"objective_value": 100.0,
"verification": {
"all_satisfied": true,
"constraint_checks": [{"name": "budget", "satisfied": true, "detail": "budget: 100 <= 100: satisfied"}],
"domain_valid": true, "issues": []
}
}
Statuses:
solved— a verified feasible answer is returned (optimal on the exact tier).infeasible— no assignment can satisfy all constraints (proven by exhaustive search on small problems).no_feasible_found— the heuristic did not find a feasible answer within budget on a large problem (one may still exist).
Verify an answer the agent already has
Sometimes the agent has already proposed an answer in text. Have it check itself before committing — this is the "can't lie" story in one call:
from optimcp.verify import verify_assignment
cert = verify_assignment(spec, {"a": 1, "b": 1, "c": 1})
print(cert.all_satisfied) # False — spends 150 > 100
print(cert.constraint_checks[0].detail) # "budget: 150 <= 100: VIOLATED"
The verifier is adversarially hardened: a miscased or missing variable is reported (never silently accepted, never a crash), fractional values on binary/integer variables are flagged, and unknown keys are surfaced as spelling/casing mistakes.
How it works
flowchart LR
Spec[DecisionSpec] --> Build[Deterministic builder]
Build --> Engine["Optimization engine\n(invisible)"]
Build --> Classical["Classical solver\nexact ≤ 2M states · heuristic above"]
Engine --> V[Independent verifier]
Classical --> V
V --> Pick["Best VERIFIED answer\nby objective"]
Pick --> Out[DecisionResult + certificate]
- The structured spec is compiled deterministically into an internal optimization problem.
- Two solvers run: the built-in optimization engine and a classical solver (exact enumeration when the state space is ≤ 2,000,000, a heuristic otherwise).
- Every candidate is independently re-verified against the raw spec.
- The best verified answer by objective value is returned. On small problems this guarantees the exact optimum rather than whatever feasible sample the engine happened to produce; on large problems it returns the best verified answer found.
You only ever see step 3's verdict: a feasible, verified answer — or an honest infeasible / no_feasible_found status.
Two engines, one best answer (worked example)
Most solvers give you one algorithm's answer and trust it. OptiMCP runs two — a fast optimization engine and a classical exact/heuristic solver — independently verifies both, and returns the better one. Turn on diagnostics and you can watch it happen:
from optimcp.solve import solve_decision
from optimcp.spec import DecisionSpec
# Pick the most valuable items that fit within a weight budget of 40.
knapsack = DecisionSpec.model_validate({
"variables": [{"name": "a"}, {"name": "b"}, {"name": "c"}, {"name": "d"}],
"objective": {"sense": "maximize", "terms": [
{"vars": ["a"], "coeff": 60}, {"vars": ["b"], "coeff": 100},
{"vars": ["c"], "coeff": 120}, {"vars": ["d"], "coeff": 40},
]},
"constraints": [{"name": "weight", "op": "<=", "rhs": 40, "terms": [
{"vars": ["a"], "coeff": 10}, {"vars": ["b"], "coeff": 20},
{"vars": ["c"], "coeff": 30}, {"vars": ["d"], "coeff": 15},
]}],
})
result = solve_decision(knapsack, include_diagnostics=True)
print(result.objective_value) # 180.0
print(result.diagnostics["winning_source"]) # 'exact_enumeration'
print(result.diagnostics["candidates"])
# [{'source': 'engine', 'objective': 100.0},
# {'source': 'exact_enumeration', 'objective': 180.0}]
Read that candidates list carefully — it's the whole pitch in three lines:
- The fast engine, on its own, returned a valid but suboptimal answer worth 100. It respects the weight limit, so nothing looks wrong: a tool built on that one engine would hand you 100 and call it solved.
- The classical exact tier found the true optimum worth 180.
- Because OptiMCP verifies both and keeps the better one, you get 180 — the single-engine answer would have quietly left 80 points of value on the table.
It cuts the other way too — knowing when there is no answer:
# "Choose at least 2" and "choose at most 1" cannot both hold.
impossible = DecisionSpec.model_validate({
"variables": [{"name": "x"}, {"name": "y"}],
"objective": {"sense": "maximize",
"terms": [{"vars": ["x"], "coeff": 1}, {"vars": ["y"], "coeff": 1}]},
"constraints": [
{"name": "at_least_2", "op": ">=", "rhs": 2,
"terms": [{"vars": ["x"], "coeff": 1}, {"vars": ["y"], "coeff": 1}]},
{"name": "at_most_1", "op": "<=", "rhs": 1,
"terms": [{"vars": ["x"], "coeff": 1}, {"vars": ["y"], "coeff": 1}]},
],
})
result = solve_decision(impossible)
print(result.status) # 'infeasible'
print(result.message) # 'No assignment can satisfy all constraints (proven by exhaustive search).'
A sampler-style engine never says "impossible" — it always emits some answer, so a single-engine tool would confidently return a constraint-violating result. OptiMCP's exact tier proves infeasibility instead of guessing.
Net effect: you are never worse off than the better of the two engines — the true optimum (or a proof that none exists) on small problems, and the best verified answer found on large ones.
Guarantees, reliability tiers & limits
Machine-readable via the capabilities() tool. In words:
| Tier | When | Promise |
|---|---|---|
| Verified feasible | Always, on status="solved" |
Answer independently re-checked to satisfy every constraint & domain |
| Exact optimum | Joint state space ≤ 2,000,000 assignments | Exhaustive search returns the true optimum and can prove infeasibility |
| Heuristic | Larger problems | Best-effort; may return no_feasible_found even if a feasible answer exists |
| Supported | Values |
|---|---|
| Variable kinds | binary, integer (bounded) |
| Objective senses | maximize, minimize |
| Constraint operators | <= >= == != < > |
| Term degree | ≤ 2 (linear / quadratic) |
| Max variables | 64 (rejected above the limit at validation time) |
What "guaranteed" means (honestly)
- Guaranteed: the returned answer satisfies every constraint and variable domain as encoded in the spec, checked by an independent verifier — a separate code path from the one that produced the answer.
- Scope of the guarantee: it covers the constraints the agent wrote down, not the ones it meant. If a model encodes "exactly one" as
<=instead of==, OptiMCP faithfully verifies the weaker rule. The certificate echoes every constraint back (e.g.coverage: 1 <= 1) precisely so a human or agent can confirm the encoding matches intent. Useverify_solutionand the echoed certificate as the checkpoint. - Not claimed: global optimality in general. OptiMCP returns a good, feasible answer; on the exact tier it is the true optimum, but for large problems "the single best" is not promised.
- Quantum: invisible plumbing. Whether the answer came from the quantum engine or the classical solver, it is the same verified result; the default response contains no quantum vocabulary (
include_diagnostics=Trueexposes internals).
Does it actually help? (benchmark)
We measured whether the tool actually changes outcomes, rather than assuming it. A capable-but-imperfect model (gpt-4o-mini, N=8, temperature=0.7) runs each scenario with the tool absent (reason alone) and with the tool present, scored against ground truth (recomputed by exact enumeration) for constraint violations. The battery spans budget / scheduling / portfolio decisions and includes deliberate traps.
The result is nuanced and reported honestly — the tool is not a blanket win. The percentages below are provisional (N=8); the patterns are robust but the exact numbers should be re-measured at N≥20 before being treated as final:
| Scenario | Trap | No-tool feasible | With-tool feasible | Verdict |
|---|---|---|---|---|
| budget_easy / scheduling_easy / portfolio_easy | – | 100% | 100% | No effect — the model already nails easy cases |
scheduling_trap (== coverage) |
✓ | 100% | 100% | No effect here |
portfolio_trap (== 3 cardinality) |
✓ | 87.5% | 100% | Tool helps — enforces a constraint the model drops |
| budget_trap | ✓ | 100% | 25% | Tool hurts — model mis-serializes units ($k vs $) into the spec |
Three honest takeaways:
- On simple decisions a capable model already gets right, the tool adds nothing. The value is not "matters on any budget question" — it's "matters as the constraint structure gets harder to hold in your head."
- Where the model tends to forget a constraint (cardinality/coverage), the tool helps by enforcing it by construction.
- Structuring a problem for the tool can introduce errors free-form reasoning avoids — here, the model wrote costs in "$k" but the budget in full dollars, so the tool faithfully solved the wrong (unbounded) budget. The guarantee is only as good as the encoding; use
verify_solutionand read the echoed certificate.
Full methodology, per-scenario numbers, and root-cause analysis:
benchmarks/AGENT_TOOL_BENCHMARK.md.
Reproduce it yourself:
pip install "optimcp[langchain]" openai
setx OPENROUTER_API_KEY sk-or-... # or OPENAI_API_KEY
python benchmarks/agent_benchmark.py --n 8
Full methodology, per-scenario numbers, and an honest reading (including the boring/negative findings) are in
benchmarks/AGENT_TOOL_BENCHMARK.md. Harness:
benchmarks/agent_benchmark.py; scenarios:
benchmarks/scenarios.py.
Examples
| File | Scenario | Shows |
|---|---|---|
examples/mcp_config.json |
MCP registration | One-line client config |
examples/openai_function_calling.py |
Budget allocation | A live model calls the tool to avoid overspending |
examples/langchain_agent.py |
Shift coverage (==) |
A live LangChain model gets a verified answer only correct under == |
The runnable examples work with either an OPENAI_API_KEY or an OPENROUTER_API_KEY (OpenRouter is OpenAI-compatible); set OPTIMCP_MODEL to override the model.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
status="no_feasible_found" on a big problem |
Heuristic tier couldn't find a feasible point | Shrink/decompose the problem so it hits the exact tier (≤ 2M states) |
status="infeasible" unexpectedly |
Constraints genuinely conflict (proven) | Re-check the ==/<= operators and rhs values |
all_satisfied=False with unknown variable 'X' |
Miscased/typo'd name in a proposed assignment | Use exact, case-sensitive variable names |
| Validation error: "too many variables" | > 64 variables | Decompose into sub-decisions |
| Validation error: integer "requires both lb and ub" | Integer variable missing bounds | Add lb/ub |
| Answer feasible but not what you meant | Spec encodes the wrong rule (e.g. <= vs ==) |
Read the echoed certificate; fix the operator in the spec |
| MCP client shows no tools | Server not launched / wrong command | Ensure optimcp is on PATH; test with optimcp --help |
| LLM never calls the tool | Weak system prompt | Instruct the model to use solve_decision for hard numeric constraints |
Repository layout
OptiMCP/
pyproject.toml Package metadata; optimcp console entry
LICENSE Business Source License 1.1
README.md This file
src/optimcp/
spec.py DecisionSpec + validation (the whole input contract)
builder.py Deterministic DecisionSpec -> optimization problem
verify.py Independent constraint/domain verifier (trust anchor)
classical.py Exact enumeration + heuristic reliability backbone
solve.py Orchestrator: engine + classical, verify, best wins
result.py DecisionResult / VerificationCertificate models
server.py FastMCP server (solve_decision, verify_solution, capabilities)
schemas.py OpenAI / Anthropic function-tool schema export
adapters/langchain.py LangChain StructuredTool wrapper
examples/ MCP config + runnable OpenAI / LangChain agents
benchmarks/ Agent tool benchmark (harness, scenarios, results)
tests/ Spec / builder / verify / solve / server tests
OptiMCP wraps a fast optimization engine (installed automatically from PyPI) with a structured decision spec and an independent verification layer.
License
Business Source License 1.1 — see LICENSE. On the Change Date it converts to Apache 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file optimcp-0.1.0.tar.gz.
File metadata
- Download URL: optimcp-0.1.0.tar.gz
- Upload date:
- Size: 34.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07a28158780cf26c6774ea8f75c3d3a5907d2eb20aff28c39598fc16ebfa602e
|
|
| MD5 |
81e1bbe64db5f1f90eb467411daef373
|
|
| BLAKE2b-256 |
a10a97514eb178eada8cec6ba777efae676dcf84047fbb064d66ff9697a7e7cd
|
Provenance
The following attestation bundles were made for optimcp-0.1.0.tar.gz:
Publisher:
publish.yml on ProfessionalQwerty/OptiMCP
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optimcp-0.1.0.tar.gz -
Subject digest:
07a28158780cf26c6774ea8f75c3d3a5907d2eb20aff28c39598fc16ebfa602e - Sigstore transparency entry: 2212433939
- Sigstore integration time:
-
Permalink:
ProfessionalQwerty/OptiMCP@947f983f05a9408fad52b893cff104dd3311c8d2 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ProfessionalQwerty
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@947f983f05a9408fad52b893cff104dd3311c8d2 -
Trigger Event:
release
-
Statement type:
File details
Details for the file optimcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: optimcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 26.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e6df7a4fd902a6fd98498534df64adb0cadfaa20a468b56436b8d2b5537cdf4a
|
|
| MD5 |
367fe8ec8c5f86cffd5986c6bcd39ea6
|
|
| BLAKE2b-256 |
ad41c536097cc341d089a9b3030111d7b9797cdad5b80aaff654f428379812ca
|
Provenance
The following attestation bundles were made for optimcp-0.1.0-py3-none-any.whl:
Publisher:
publish.yml on ProfessionalQwerty/OptiMCP
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
optimcp-0.1.0-py3-none-any.whl -
Subject digest:
e6df7a4fd902a6fd98498534df64adb0cadfaa20a468b56436b8d2b5537cdf4a - Sigstore transparency entry: 2212433956
- Sigstore integration time:
-
Permalink:
ProfessionalQwerty/OptiMCP@947f983f05a9408fad52b893cff104dd3311c8d2 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/ProfessionalQwerty
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@947f983f05a9408fad52b893cff104dd3311c8d2 -
Trigger Event:
release
-
Statement type: