openconstraint-mcp
A local-first Model Context Protocol server for
constraint programming and optimization. openconstraint-mcp gives an MCP client a
deterministic way to compile-check and solve MiniZinc
models on a managed solver runtime, exposing open-source solvers (OR-Tools CP-SAT
by default, Chuffed as an optional verifier) over MCP stdio.
Constraint problems — scheduling, rostering, assignment, routing, production planning, inventory — are exactly where a language model is most likely to produce an answer that looks right but is subtly infeasible. The division of labor here is LLM proposes, server verifies: the client's LLM drafts a MiniZinc model, and the local runtime compiles and solves it to produce a checked result. The server runs the solver; it never drafts a model of its own and never calls an LLM.
Everything runs on your machine. No telemetry, no background network calls, and
nothing leaves your machine unless you opt in — the only network access in the entire
package is the runtime download you trigger explicitly with install-runtime.
Design principles
- Local-first. Solving, validation, and result inspection all run on your machine. There are no remote solving backends and no upload of your models or data.
- Managed runtime. Solver execution always goes through a MiniZinc runtime this
project resolves and controls, never an arbitrary
$PATHbinary — so a run does not depend on whatever MiniZinc happens to be installed on the host. - LLM proposes, server verifies. Natural-language → model translation, critique, and repair belong in the MCP client's LLM. The server owns the deterministic half: compile-check, solve, and report the runtime's verbatim output. It holds no LLM credentials and never invokes a generative model.
- No hidden network calls. Validation, solving, and result inspection are all
offline. The only sanctioned network call is the runtime download, and only when you
run
install-runtime— never on import, on server boot, or as a "convenience". - No telemetry. Not implemented. Any future telemetry would be opt-in and documented.
Stage 2 readiness
The LLM-verification loop is complete for both backends. See Workflow coverage.
Installation
Requires Python 3.12+. This project is uv-managed end-to-end; install uv
first if you don't already have it.
All three paths below give you the same openconstraint-mcp command and the same
tools. They differ only in where the command lives and whether it is tied to a
checkout.
Install it (recommended)
uv tool install openconstraint-mcp
openconstraint-mcp is now on your PATH and works from any directory. Upgrade
later with uv tool upgrade openconstraint-mcp.
Run it without installing
uvx openconstraint-mcp --help
uvx fetches the package into a disposable cached environment and runs it,
putting nothing on your PATH and re-resolving to the latest published version
on each run.
Develop on it
git clone https://github.com/Openconstraint/openconstraint-mcp.git
cd openconstraint-mcp
uv sync --all-groups
The command is then uv run openconstraint-mcp … (or just cli …, which wraps
the same thing), and it works only from inside the checkout.
Quick start (MCP users)
These commands assume uv tool install put openconstraint-mcp on your PATH;
prefix them with uvx or uv run if you chose one of the other installation
paths above.
-
Set up MiniZinc — optional, and one of:
openconstraint-mcp install-runtimeto fetch and install the managed bundle (Linux x86_64, macOS arm64, Windows x86_64). Roughly 200 MB, once per machine.openconstraint-mcp configure-runtime --runtime-dir <path>to point the package at an existing MiniZinc install (a directory containingbin/minizinc).
This step is optional because every installation path already includes the OR-Tools CP-SAT Python library, so the whole CP-SAT tool family works immediately; only the MiniZinc tools need the runtime. Note that the MiniZinc bundle ships its own native CP-SAT backend (
fzn-cp-sat, used when a MiniZinc model selects thecp-satsolver) — it is a separate copy from the Pythonortoolspackage, and neither install implies the other.The runtime is stored per user, outside any virtualenv (see Managed runtime), so you install it once and every installation path above finds it — including a
uvxenvironment created after the fact. -
Verify:
openconstraint-mcp check-runtimeandopenconstraint-mcp list-solvers. -
Wire into your MCP client. MCP standardizes the wire protocol, not how a client is told to launch a server, so each client has its own file name and schema. The executable and arguments are identical in all of them.
Claude Code —
.mcp.jsonin your project:{ "mcpServers": { "openconstraint": { "type": "stdio", "command": "openconstraint-mcp", "args": ["stdio", "--toolset", "full"] } } }
opencode —
opencode.jsonin your project, or~/.config/opencode/opencode.json:{ "$schema": "https://opencode.ai/config.json", "mcp": { "openconstraint": { "type": "local", "command": ["openconstraint-mcp", "stdio", "--toolset", "full"], "enabled": true } } }
Codex —
.codex/config.tomlin your project, or~/.codex/config.toml:[mcp_servers.openconstraint] command = "openconstraint-mcp" args = ["stdio", "--toolset", "full"] tool_timeout_sec = 900
All three assume
uv tool installputopenconstraint-mcpon yourPATH. To use the no-install path instead, make the commanduvxand prependopenconstraint-mcpto the arguments — for example"command": "uvx", "args": ["openconstraint-mcp", "stdio", "--toolset", "full"].Raise your client's per-tool timeout, as the Codex block does. A checked CP-SAT call runs two capped child processes, so its worst case is
(timeout_ms + 8000) + (checker_timeout_ms + 8000)milliseconds. If your client gives up sooner than that, you get a client-side timeout instead of a result while the server is still solving. For solves longer than any synchronous timeout allows, use the background job tools instead.Restart your MCP client; the
check_runtimeandlist_available_solverstools should appear.This repository's own
.mcp.json,opencode.json, and.codex/config.tomlare development configs, not templates. They launchuv run …against the checkout's virtualenv and, for Codex, pincwdto an absolute path, so they only work inside this clone. Use the blocks above, which need no checkout.
Once connected, the server's MCP instructions tell the client to route constraint
and optimization tasks here before running solver code directly — no client-side
setup (prompt files, AGENTS.md/CLAUDE.md entries) needed beyond the connection
above. Tool selection stays host/model-controlled, so this improves routing but
doesn't guarantee the client calls a tool.
CLI
The package exposes five commands:
-
openconstraint-mcp stdio— run the MCP server over stdio. This is the entry point an MCP client (e.g. Claude Desktop, Claude Code) launches.Flags:
-
--toolset core|full— which MCP toolset to advertise (default:core). The core profile exposes eight essential tools for a materially smallertools/listpayload and a less ambiguous default choice set:check_runtime,list_available_solvers,check_minizinc_model,solve_minizinc_model,check_minizinc_files,solve_minizinc_files,run_cpsat_python, andrun_cpsat_python_file— plus one MCP prompt,solve_constraint_problem, the backend-neutral workflow you invoke by hand in a client that exposes MCP prompts. The full profile (--toolset full) additionally exposes the three detailed MCP prompts and every advanced tool — background solve/CP-SAT jobs, solver portfolios, explicit CP-SAT experiments, checker-verified CP-SAT file runs (run_cpsat_python_file_checked), verified saving, model-interface inspection, unsat-core diagnostics, and tabular (Excel/CSV) I/O. Use--toolset fullwhen you need any of those; existing users who relied on an advanced tool from barestdiomust now pass--toolset full.This is an advertised-contract change only: the smaller payload is a server-level guarantee. Whether a smaller default set reduces the tokens your model sees is host-dependent — the MCP host decides how it caches, filters, or forwards discovered tool metadata.
-
-
openconstraint-mcp install-runtime— fetch and install the managed MiniZinc bundle (Linux x86_64, macOS arm64, and Windows x86_64 in v0). Streams the pinned upstream asset from the MiniZinc GitHub release (a.tgzon Linux, a.dmgon macOS, the NSISsetup-win64.exeon Windows — run silently), verifies its SHA256, installs it into the chosen target, smoke-checks the resultingbin/minizinc(bin\minizinc.exeon Windows), and remembers the install location socheck-runtimeandlist-solversfind it without further configuration. This is the only command in the package that touches the network.Flags:
-
--runtime-dir <path>— explicit install location. OverridesOPENCONSTRAINT_MCP_RUNTIME_DIR, the persisted install config, and the platformdirs default, and suppresses the interactive path prompt. Recommended when you want to be certain where the install lands. -
--yes/-y— non-interactive: skip the path prompt and skip the overwrite-confirmation prompt only for a prior managed install.--yesis required for non-TTY (CI / scripted) runs.--yesdoes not force overwrite of an unmanaged non-empty directory. Pointing--runtime-dirat$HOME,/tmp, a project checkout, or any directory the installer did not previously write to is refused regardless of--yes. The marker file.openconstraint-runtime.jsonwritten into the runtime root is what makes a directory eligible for overwrite —--yesonly authorises replacing the installer's own prior output.
When stdin is a TTY and neither
--runtime-dirnor--yesis given, the command prompts for the install location (Enter accepts the default). -
-
openconstraint-mcp configure-runtime --runtime-dir <path>— point the package at an existing MiniZinc install (e.g. a system install, package-manager install, or one you built yourself) without settingOPENCONSTRAINT_MCP_RUNTIME_DIR. Validates that<path>/bin/minizincexists and is executable, then persists the path to the install config. Does not download anything and does not claim ownership of the directory — use this when you already have MiniZinc on disk and just wantopenconstraint-mcpto find it. -
openconstraint-mcp check-runtime— report whether the managed MiniZinc runtime is installed. Prints the expected runtime path and exits 0 when present, exits 1 otherwise. -
openconstraint-mcp list-solvers— list solvers exposed by the managed MiniZinc runtime. Requires the runtime to be installed; exits 1 with a clear error otherwise.
Structured diagnostics
Every solve, check, inspect, unsat-core, save, job, portfolio, checker, and
experiment result carries an optional diagnostic field with a stable category
a client can branch on; diagnostic: null is the clean-success signal. See
docs/mcp-tools.md.
MCP tools
The full-profile tool catalog — every MiniZinc and CP-SAT tool the server can expose, each in an inline-source and a path-based form, plus background solve jobs, solver portfolios, registry bounds, and progress notifications. See docs/mcp-tools.md.
CP-SAT Python execution path
The second solving path: the client's LLM writes complete OR-Tools CP-SAT Python and the server runs it in a bounded local child process. Covers the script contract, the tools, explicit experiments, saved artifacts, background jobs, and the security posture. See docs/cpsat-python.md.
Tabular data I/O (Excel/CSV)
Two backend-agnostic tools move scalars between local .xlsx/.csv files and
MCP, feeding either solving path. Covers the cell contract, pagination, formula
safety, XLSX round-trip hazards, and the overwrite contract. See
docs/tabular-data.md.
MCP prompts
The four MCP prompts for client-side LLMs — solve_constraint_problem in both
profiles, three more under --toolset full — and how a user-controlled prompt
differs from a model-controlled tool. See
docs/mcp-prompts.md.
Example models
Note:
examples/is no longer tracked in this repository (see git history for the last tracked snapshot). The paths below describe the shape of the example models; real industrial examples will replace them here.
The examples/ directory holds small, self-contained MiniZinc models you can
point the path-based file tools at (or run by hand through the managed
runtime). Each is a model.mzn — usually with a matching data.dzn, and one
also ships a model.mzc.mzn solution checker:
examples/knapsack— bounded knapsack: choose how many of each item type to pack to maximize total value without exceeding the weightcapacity(solve maximize).examples/balanced_assignment— assign jobs to workers to minimize the most-loaded worker's total duration, i.e. balance the load (solve minimize).examples/social_golfers— the Social Golfer Problem: schedulen_groupsgroups ofgroup_sizegolfers overn_weeksweeks so no pair ever shares a group twice (solve satisfy). The shipped data is Kirkman's fifteen schoolgirls — 15 golfers in 5 groups of 3 over 7 weeks, which uses every one ofC(15,2) = 105pairs exactly once and is the most weeks a 5-3 schedule can reach. See Diagnosing and repairing infeasibility below for what happens past that maximum. The CP-SAT examples undercpsat/(afeasible, not independently checked, incumbent) andcpsat_best/(achecked-level saved artifact with a checker and a replay config — see Reproducing a saved CP-SAT artifact) are specialized Python constructions for the tougher 7-3-10 boundary instance.examples/golomb_ruler— find an optimal order-5 Golomb ruler: 5 marks on a ruler with all pairwise differences distinct, minimizing the ruler's length (solve minimize). Asave_verified_minizinc_modelartifact (seesave_verified_minizinc_model) —problem.mdandsolve-result.jsonare the saved provenance, and.openconstraint-model.jsonis the manifest naming the recorded solve controls, so it also demonstrates reproducing a saved MiniZinc result viasolve_minizinc_files.examples/golomb_ruler/cpsat_python/is the CP-SAT Python analogue of the same problem, scaled up to order 12 and saved at thecheckedverification level with both an expectation gate and a checker.examples/nonogram— a 5x5 nonogram puzzle (solve satisfy): shade cells so each row/column matches its block clues. Anothersave_verified_minizinc_modelartifact, reinforcing the reproducibility workflow with a satisfaction (not optimization)solvemethod.examples/australia_map_coloring— colour Australia's seven states/territories with three colours so no two bordering regions share one (solve satisfy). Its data (nc = 3) is inline, so there is nodata.dzn; instead it ships amodel.mzc.mznsolution checker, so it doubles as a demonstration of the checker feature (see below).
For instance, to solve the knapsack example end to end:
// solve_minizinc_files
{
"model_path": "examples/knapsack/model.mzn",
"data_path": "examples/knapsack/data.dzn"
}
(prefer absolute paths in real MCP calls — see Path-based file tools above).
To run the Australia example with its solution checker, point checker_path
at the shipped .mzc.mzn:
// solve_minizinc_files
{
"model_path": "examples/australia_map_coloring/model.mzn",
"checker_path": "examples/australia_map_coloring/model.mzc.mzn"
}
The resulting SolveResult.checker report then carries the checker's
per-solution verdict (here, CORRECT).
The social-golfers model is parameterized through its data.dzn, which enables
two workflows beyond a single solve:
- Longest schedule. "As many weeks as possible" is the same model re-solved
with
n_weeksraised until the search stops finding schedules. For the shipped instance,n_weeks = 7solves and uses every one ofC(15,2) = 105golfer pairs exactly once;n_weeks = 8would need 120 distinct pairs, which do not exist, so 7 is the true maximum — see the diagnosis-and-repair walkthrough right below for what solving past it actually looks like. - Multiple schedules. To enumerate several distinct schedules, lower
n_weeks(e.g. to 5) and request more than one solution with a solver that supports it —num_solutionsworks withorg.gecode.gecodeororg.chuffed.chuffed, not the defaultcp-sat.
Diagnosing and repairing infeasibility
Pushing n_weeks past the shipped instance's maximum of 7 is a convenient way
to walk through the repair loop end to end, reusing the shipped model rather
than a one-off toy — and it demonstrates the loop's least convenient case
honestly, rather than a tidy "unsatisfiable" verdict:
- Solve past the maximum. Solve
examples/social_golfers/model.mznwithn_weeksoverridden to8(either inlinedatatext or a scratch.dzn) viasolve_minizinc_files/solve_minizinc_model. Even though this instance genuinely has no solution — every pair of the 15 golfers is already used once by week 7, so an 8th week cannot avoid a repeat — cp-sat's search-basedsolvedoes not necessarily prove that quickly: with a short budget,statuscomes back"unknown", not a clean"unsatisfiable". This is the realistic trigger for reaching for a dedicated diagnostic rather than only a tidy failure case. - Localize the conflict. Call
find_unsat_core(orfind_unsat_core_files) with the samen_weeks = 8data.find_unsat_coreruns findMUS, a different algorithm from cp-sat's search, so it is worth trying even after an inconclusivesolve. This model encodes the pigeonhole argument through a singlesum(...) <= 1constraint per golfer pair rather than many small named constraints, so a real run typically comes back"no_core"quickly — findMUS completing without isolating a MUS is a normal, documented outcome (see thefind_unsat_coreConservativeno_corecaveat above), not a tool failure and not proof the instance is satisfiable either. - Repair. Neither
solvenorfind_unsat_coreresolved this instance cleanly, so acting on it means also reasoning about the domain, not pattern-matching on a single field: the infeasibility here is a genuine counting bound (8 * n_groups * C(group_size, 2) = 120required pair meetings>C(n_golfers, 2) = 105total pairs), so the fix is relaxing the instance, not hunting for a modeling bug or waiting out a longer search. Dropn_weeksback to the shipped7(or fewer) and re-solve —statusreturns to"satisfied".
This is the general MiniZinc infeasibility-repair loop — solve ->
inconclusive/"unsatisfiable" -> find_unsat_core -> read core/stdout ->
relax or fix the data or model -> solve again — applied to a case where
the "fix" is adjusting a parameter rather than editing constraints, and
neither diagnostic tool hands back a clean verdict on its own; see
find_unsat_core under MCP tools for the tool's full contract,
including the no-core and model-only-core caveats this walkthrough relies
on. tests/test_examples_integration.py::test_social_golfers_diagnose_and_repair_infeasibility
exercises this exact sequence against the real managed binary.
Example inventory
A compact map from each retained example to the roadmap domain it covers, the required workflow(s) it demonstrates (see the coverage list in this project's closeout plan), the tool surface exercised, its test coverage, and any known gap. Treat this as a coverage snapshot, not a completion gate: an empty "Gap" cell means the example is not known to be missing anything for the workflow listed, not that it is exhaustive.
| Example | Roadmap domain | Workflow(s) | Tools | Tests | Known gap |
|---|---|---|---|---|---|
examples/knapsack |
packing/knapsack | basic solve | solve_minizinc_files |
test_examples_integration.py::test_knapsack_files_solve_to_a_feasible_selection |
none |
examples/balanced_assignment |
assignment/allocation | basic solve | solve_minizinc_files |
test_examples_integration.py::test_balanced_assignment_files_solve_to_a_feasible_assignment |
no checker/portfolio demo on this example |
examples/social_golfers |
scheduling/rostering | infeasibility repair | solve_minizinc_files, find_unsat_core_files |
test_examples_integration.py::test_social_golfers_* |
the multiple-schedules (num_solutions) workflow described above has no dedicated test |
examples/australia_map_coloring |
assignment/allocation | checker-backed solve (acceptance) | solve_minizinc_files(checker_path=...) |
test_examples_integration.py::test_australia_map_coloring_with_shipped_checker_completes_correct |
the shipped checker only demonstrates acceptance; see the CP-SAT checkers below for a violation demo |
examples/golomb_ruler |
general CSP (no single roadmap domain) | reproducibility (save + file replay) | save_verified_minizinc_model, solve_minizinc_files |
test_examples_integration.py::test_golomb_ruler_files_reproduce_the_saved_optimum (integration only, not part of default just check) |
the .openconstraint-model.json manifest was dropped when examples/ was untracked, so test_examples_manifest.py no longer covers this example |
examples/nonogram |
general CSP | reproducibility (satisfaction variant) | save_verified_minizinc_model, solve_minizinc_files |
none | no automated coverage: the manifest fixture was dropped when examples/ was untracked and no integration test exists for this example |
examples/nonogram/python |
general CSP | checked CP-SAT save for the same puzzle | save_verified_cpsat_python |
none | no automated coverage: the manifest fixture was dropped when examples/ was untracked, and there is no live CP-SAT replay integration test |
examples/cpsat_python/assignment.py |
assignment/allocation | CP-SAT direct solve | run_cpsat_python |
tests/pyexec/test_core_integration.py::test_run_cpsat_python_solves_assignment_example |
none |
examples/cpsat_python/scheduling.py |
scheduling/rostering | CP-SAT direct solve | run_cpsat_python |
tests/pyexec/test_core_integration.py::test_run_cpsat_python_solves_scheduling_example |
none |
examples/cpsat_python/graph_coloring.py + graph_coloring_checker.py |
assignment/allocation | checker-backed solve, incl. a violation | run_cpsat_python, checker protocol |
tests/test_cpsat_python_examples.py::test_graph_coloring_checker_* |
none |
examples/cpsat_python/clinic_roster_checker.py |
scheduling/rostering | checker rejecting a plausible-looking wrong answer | checker protocol | tests/test_cpsat_python_examples.py::test_clinic_roster_checker_* |
exercised standalone against synthetic payloads; no paired model.py producing a live solve |
examples/online_printing_shop |
scheduling/optimization | checker-backed CP-SAT solve with resumable operations, machine calendars, and sequence-dependent setups | run_cpsat_python_file, run_cpsat_python_file_checked |
tests/examples/test_online_printing_shop.py |
only data_sops1.json proves optimality quickly (<1s) on the default single worker; data_mops1.json and data_lops1.json benefit from config={"solver_time_limit_seconds": N} (set well below script_timeout_ms, leaving room for parsing, model building, and serialization — nothing validates the two against each other) so CP-SAT stops itself and returns a clean status="feasible"/"optimal" with return_code=0 — without it the child is killed at script_timeout_ms, but the checker now grades a recovered status="timeout" incumbent too (the checker's solver_status gate admits optimal/feasible/timeout, mirroring pyexec/eligibility.py), so a plain timeout kill still reaches an accepted verdict as long as a well-formed schedule was recovered. data_lops1.json additionally needs num_workers above the default 1, read from that same config: on one worker it spends a 150s limit and still reports status="unknown" with no incumbent (which the checker still refuses, since unknown carries no claimed solution), while config={"solver_time_limit_seconds": 150, "num_workers": 8} reaches an accepted feasible schedule. The callback stays installed regardless of solver_time_limit_seconds, because that limit cannot prove the script finishes before script_timeout_ms, but caps intermediate envelopes at 512 KiB total. Search continues after that budget is exhausted; a later kill recovers the last envelope that fit, while a clean run still prints its final best result |
examples/nurse_rostering |
scheduling/rostering | checker-backed CP-SAT solve against a published benchmark optimum, with an independent scorer as the checker | run_cpsat_python_file, run_cpsat_python_file_checked |
tests/examples/test_nurse_rostering.py |
the real hospital instance (QMC-2 from schedulingbenchmarks.org, 19 nurses x 28 days, all 121 rules soft) reaches the published optimum of 29 but needs --time-limit 300; verify_model.py's third gate re-solves it on four seeds and runs for several minutes, so only its first two gates are in just check |
examples/guillotine_cutting |
packing/knapsack | checker-backed CP-SAT solve of two-dimensional guillotine cutting (fixed orientation, maximum quantities, unlimited stages) whose model decides the cut tree, plus a shelf-packing comparison graded by the same checker | run_cpsat_python_file, run_cpsat_python_file_checked |
tests/test_guillotine_cutting_checker.py, tests/test_guillotine_cutting_model.py, tests/test_guillotine_cutting_parse.py, tests/test_guillotine_cutting_integration.py (integration only) |
the model proves the authored polarizing_film.json optimal in under a second, but does not yet reproduce the published optima of the three shipped 2DPackLib instances (CGCUT1 244 found but unproven; OF1/OF2 below 2737/2690 after 60 s), so no test asserts them |
examples/assembly_line_balancing |
scheduling/assembly | checker-backed CP-SAT solve of simple assembly line balancing, type 1 (fixed cycle time, minimize stations) with the Bukchin & Raviv (2018) CP model in three selectable formulations (base, station-window bounds, pairwise distances) plus a total-work bound on the station count (the default), raced with run_cpsat_python_experiment |
run_cpsat_python_file_checked, run_cpsat_python_experiment |
tests/test_assembly_line_balancing_checker.py, tests/test_assembly_line_balancing_model.py, tests/test_assembly_line_balancing_parse.py, tests/test_assembly_line_balancing_integration.py (integration only) |
ships seven Scholl instances across three sizes; within 30 s the small ones prove their published optima, the medium and large ones do not all (problem.txt); the upper bound comes from one deterministic greedy run instead of the paper's 100 randomized ones; no formulation dominates on larger Scholl graphs (see problem.txt) |
examples/golomb_ruler/cpsat_python |
general CSP | checked save (expectation + checker gates) | save_verified_cpsat_python |
none (manually re-verified live during this closeout, see problem.txt) |
the saved objective is not exactly reproducible run to run (documented in problem.txt) — an expected CP-SAT property, not a bug; the manifest fixture was also dropped when examples/ was untracked, so test_examples_manifest.py no longer covers this example |
examples/social_golfers/cpsat + cpsat_best |
scheduling/rostering | CP-SAT background job, saved artifact, and file-based replay | submit_cpsat_python_file_job, get_cpsat_python_job, save_verified_cpsat_python |
tests/pyexec/test_jobs_integration.py::test_submit_file_with_real_checker_reaches_optimal_and_accepted |
cpsat/ (reported gate only, no checker) is superseded by cpsat_best; kept only for the reported-vs-checked contrast. cpsat_best/replay-config.json came from a config-only save, not an attached experiment_result, so this directory has no experiment-log.json — RESULT.md is a hand-written substitute for that provenance, not the generated artifact; see the explicit-experiment row below |
examples/social_golfers/cpsat_24 |
scheduling/rostering | CP-SAT saved artifact for the 8-3-11 boundary instance | save_verified_cpsat_python |
none | reported gate only; no checker or live replay integration test; the manifest fixture was also dropped when examples/ was untracked, so test_examples_manifest.py no longer covers this example |
| (no dedicated example file) | — | CP-SAT explicit experiment (run_cpsat_python_experiment) with a durable experiment-log.json |
run_cpsat_python_experiment, save_verified_cpsat_python(experiment_result=...) |
tests/pyexec/test_experiment_integration.py; tests/pyexec/test_save.py::test_save_with_matching_experiment_result_writes_experiment_log |
no shipped example directory pairs a real examples/ script with a saved experiment-log.json — the integration test is a small self-contained fixture and the save-path test uses synthetic fixtures too; this is the one required workflow this closeout leaves undemonstrated on a real example rather than papering over |
Managed runtime
The default managed runtime location is <platformdirs user_data_dir>/minizinc,
where user_data_dir comes from PlatformDirs("openconstraint-mcp", "openconstraint-mcp").
Concretely:
| Platform | Default runtime root |
|---|---|
| Linux | ~/.local/share/openconstraint-mcp/minizinc |
| macOS | ~/Library/Application Support/openconstraint-mcp/minizinc |
| Windows | %LOCALAPPDATA%\openconstraint-mcp\openconstraint-mcp\minizinc |
The doubled
openconstraint-mcp\openconstraint-mcp\…segment on Windows is aplatformdirsconvention (appauthor and appname), not a path-computation bug.
The minizinc binary itself is expected at <runtime>/bin/minizinc (or
<runtime>\bin\minizinc.exe on Windows).
Overriding the runtime path
Set the environment variable OPENCONSTRAINT_MCP_RUNTIME_DIR to override the
runtime root directory — not the path to the binary itself. The runtime
layer always appends bin/minizinc (or bin\minizinc.exe) underneath whatever
the env var points at.
For example, if your MiniZinc binary lives at $HOME/minizinc-bundle/bin/minizinc,
the correct override is:
export OPENCONSTRAINT_MCP_RUNTIME_DIR="$HOME/minizinc-bundle"
Setting OPENCONSTRAINT_MCP_RUNTIME_DIR=/path/to/minizinc directly (pointing at
the binary) will not work — the layer will look for …/minizinc/bin/minizinc
underneath it.
Installing the managed runtime
openconstraint-mcp install-runtime is the supported way to put a managed
MiniZinc bundle on disk. The first invocation:
- Resolves the install location. Precedence is
--runtime-dir> the env var > the persisted install config > the platformdirs default (<platformdirs user_data_dir>/minizinc). - Streams the pinned MiniZinc bundle for your platform from the official
MiniZinc GitHub release — the Linux x86_64
.tgz; the macOS.dmgon Apple Silicon (mounted read-only viahdiutiland reshaped into the samebin/lib/sharelayout); or, on Windows x86_64, the NSISsetup-win64.exerun silently (setup.exe /S /D=<runtime>) into the managed runtime directory — verifies its SHA256, installs it safely, and smoke-checks the resultingbin/minizinc(bin\minizinc.exeon Windows). On macOS the bundled Gecode is the Qt-linked build, so the installer vendors its Qt frameworks into<runtime>/Frameworksand relinks the solver to load them headlessly (no GUI is ever launched). That relink step uses the Xcode Command Line Tools, so runxcode-select --installfirst ifinstall-runtimereportsinstall_name_toolis missing. - Writes a small JSON config (
<platformdirs user_config_dir>/install.json, typically~/.config/openconstraint-mcp/install.jsonon Linux) recording the chosen path.
On Windows, the NSIS installer requests administrator rights, so the first
install-runtime shows a one-time Windows UAC elevation prompt — confirm it to
let the silent install finish.
Once that config is written, subsequent check-runtime and list-solvers calls
find the runtime automatically — no env-var fiddling. To reset, delete the
config file, or set OPENCONSTRAINT_MCP_RUNTIME_DIR (the env var always wins).
If the config file is present but corrupt (e.g. hand-edited into invalid JSON),
check-runtime and list-solvers print a warning to stderr and fall back to the
default location rather than failing silently.
If you pass --runtime-dir <path> again on a later install, the new path
replaces the old one in the config. The previous runtime directory is not
touched and can be deleted manually.
A successful install also writes a .openconstraint-runtime.json marker into
the runtime directory itself. Future install-runtime invocations check that
marker before overwriting: an unmanaged non-empty directory is refused
regardless of --yes, which makes --runtime-dir $HOME --yes (or similar)
safe — your home directory cannot be wiped by a fat-finger.
Startup diagnostic
On startup the MCP server prints a short three-line diagnostic to stderr:
the server version, the resolved runtime directory, and whether the managed
runtime is installed (with an install-runtime hint when it is absent). This
banner is stderr-only by design — over the stdio transport, stdout is the
JSON-RPC protocol channel, so the diagnostic never touches it. The banner only
reads the already-resolved runtime status; it never downloads or installs
anything. MCP clients that hide server stderr simply will not show it.
The server also advertises its project Homepage to MCP clients via the
website_url field, sourced from the package metadata (single source of truth:
[project.urls] in pyproject.toml).
v0 limitations
This is an early release; the focus is "easy install, reliable solving, clear errors" rather than feature breadth. In particular:
- The automated installer covers Linux x86_64, macOS arm64 (Apple Silicon),
and Windows x86_64. Windows ARM, Linux ARM, and macOS x86_64 (Intel) bundles
are tracked separately. On those platforms,
install-runtimeexits 1 with a clear message — useconfigure-runtime --runtime-dir <path>or pointOPENCONSTRAINT_MCP_RUNTIME_DIRat an existing MiniZinc install (a directory containingbin/minizinc) in the meantime. - No telemetry, ever, unless and until you explicitly opt in to a clearly labelled future feature.
- The only code path that touches the network is the
install-runtimeCLI command. The package does not phone home;httpxis only imported wheninstall-runtimeruns (enforced by a regression test).
Licensing & upstream sources
openconstraint-mcp is licensed under the Apache License 2.0; see LICENSE.
The MiniZinc runtime it wraps is
fetched from the official MiniZincIDE GitHub release at install time — the
Linux x86_64 .tgz, the macOS .dmg, or the Windows x86_64 NSIS
setup-win64.exe, depending on your platform — this repository does not
redistribute MiniZinc or its bundled solvers.
The upstream bundle includes:
- MiniZinc itself (the constraint modelling language and its compiler).
- Gecode, Chuffed, OR-Tools CP-SAT, COIN-BC, and other solvers shipped with the MiniZincIDE bundle. Their licenses are surfaced upstream — see minizinc.org for the license index, or the per-solver entries on the MiniZincIDE release page.
After install-runtime, each bundled component's license file lives inside the
installed runtime tree (typically under <runtime_dir>/share/minizinc/... and
adjacent directories) and is left untouched by the installer. For a single
authoritative document, the MiniZincIDE release page is the recommended source.
Releasing (maintainers)
Trusted Publishing setup, TestPyPI rehearsal, and the tagged PyPI release. See docs/releasing.md.
Metadata
Release files for openconstraint-mcp 0.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| openconstraint_mcp-0.3.0.tar.gz | 276.4 kB | With provenance | Details for openconstraint_mcp-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| openconstraint_mcp-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for openconstraint_mcp-0.3.0-py3-none-any.whl |
Total release size: 581.2 kB
Release files / openconstraint_mcp-0.3.0.tar.gz
| Download URL | openconstraint_mcp-0.3.0.tar.gz |
|---|---|
| Size | 276.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f8b98a8fbb1465320d55280d7f461b2d2efdc55ef20f0558d24d222dfba7d246
|
|
BLAKE2b-256 checksum How to use checksums |
97c8384a6b53993a33cdfd7a4faf54ab27bf38034d3656a8328afcf7b2796c6c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logRelease files / openconstraint_mcp-0.3.0-py3-none-any.whl
| Download URL | openconstraint_mcp-0.3.0-py3-none-any.whl |
|---|---|
| Size | 304.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
349912dd699c095e81579e2a89645f4c7c62e6df4f28bc6288c8a27e0274e6b6
|
|
BLAKE2b-256 checksum How to use checksums |
a2c0829869dff834f3e39b9a269cd06925022ba49a5f52f374ad3adcd637462a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logSecurity for openconstraint-mcp 0.3.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for openconstraint_mcp-0.3.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 19, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.