Skip to main content

openconstraint-mcp

CI PyPI

OpenConstraint website · Worked case studies

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 $PATH binary — 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.

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.

  1. Set up MiniZinc — optional, and one of:

    • openconstraint-mcp install-runtime to 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 containing bin/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 the cp-sat solver) — it is a separate copy from the Python ortools package, 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 uvx environment created after the fact.

  2. Verify: openconstraint-mcp check-runtime and openconstraint-mcp list-solvers.

  3. 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.json in your project:

    {
      "mcpServers": {
        "openconstraint": {
          "type": "stdio",
          "command": "openconstraint-mcp",
          "args": ["stdio", "--toolset", "full"]
        }
      }
    }
    

    opencode — opencode.json in 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.toml in 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 install put openconstraint-mcp on your PATH. To use the no-install path instead, make the command uvx and prepend openconstraint-mcp to 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_runtime and list_available_solvers tools should appear.

    This repository's own .mcp.json, opencode.json, and .codex/config.toml are development configs, not templates. They launch uv run … against the checkout's virtualenv and, for Codex, pin cwd to 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 smaller tools/list payload 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, and run_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 full when you need any of those; existing users who relied on an advanced tool from bare stdio must 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 .tgz on Linux, a .dmg on macOS, the NSIS setup-win64.exe on Windows — run silently), verifies its SHA256, installs it into the chosen target, smoke-checks the resulting bin/minizinc (bin\minizinc.exe on Windows), and remembers the install location so check-runtime and list-solvers find it without further configuration. This is the only command in the package that touches the network.

    Flags:

    • --runtime-dir <path> — explicit install location. Overrides OPENCONSTRAINT_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. --yes is required for non-TTY (CI / scripted) runs.

      --yes does not force overwrite of an unmanaged non-empty directory. Pointing --runtime-dir at $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.json written into the runtime root is what makes a directory eligible for overwrite — --yes only authorises replacing the installer's own prior output.

    When stdin is a TTY and neither --runtime-dir nor --yes is 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 setting OPENCONSTRAINT_MCP_RUNTIME_DIR. Validates that <path>/bin/minizinc exists 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 want openconstraint-mcp to 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 weight capacity (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: schedule n_groups groups of group_size golfers over n_weeks weeks 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 of C(15,2) = 105 pairs 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 under cpsat/ (a feasible, not independently checked, incumbent) and cpsat_best/ (a checked-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). A save_verified_minizinc_model artifact (see save_verified_minizinc_model) — problem.md and solve-result.json are the saved provenance, and .openconstraint-model.json is the manifest naming the recorded solve controls, so it also demonstrates reproducing a saved MiniZinc result via solve_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 the checked verification 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. Another save_verified_minizinc_model artifact, reinforcing the reproducibility workflow with a satisfaction (not optimization) solve method.
  • 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 no data.dzn; instead it ships a model.mzc.mzn solution 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_weeks raised until the search stops finding schedules. For the shipped instance, n_weeks = 7 solves and uses every one of C(15,2) = 105 golfer pairs exactly once; n_weeks = 8 would 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_solutions works with org.gecode.gecode or org.chuffed.chuffed, not the default cp-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:

  1. Solve past the maximum. Solve examples/social_golfers/model.mzn with n_weeks overridden to 8 (either inline data text or a scratch .dzn) via solve_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-based solve does not necessarily prove that quickly: with a short budget, status comes back "unknown", not a clean "unsatisfiable". This is the realistic trigger for reaching for a dedicated diagnostic rather than only a tidy failure case.
  2. Localize the conflict. Call find_unsat_core (or find_unsat_core_files) with the same n_weeks = 8 data. find_unsat_core runs findMUS, a different algorithm from cp-sat's search, so it is worth trying even after an inconclusive solve. This model encodes the pigeonhole argument through a single sum(...) <= 1 constraint 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 the find_unsat_core Conservative no_core caveat above), not a tool failure and not proof the instance is satisfiable either.
  3. Repair. Neither solve nor find_unsat_core resolved 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) = 120 required pair meetings > C(n_golfers, 2) = 105 total pairs), so the fix is relaxing the instance, not hunting for a modeling bug or waiting out a longer search. Drop n_weeks back to the shipped 7 (or fewer) and re-solve — status returns 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, plus a largest-candidate rule comparison graded by the same checker 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_largest_candidate.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 a platformdirs convention (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:

  1. Resolves the install location. Precedence is --runtime-dir > the env var > the persisted install config > the platformdirs default (<platformdirs user_data_dir>/minizinc).
  2. Streams the pinned MiniZinc bundle for your platform from the official MiniZinc GitHub release — the Linux x86_64 .tgz; the macOS .dmg on Apple Silicon (mounted read-only via hdiutil and reshaped into the same bin/lib/share layout); or, on Windows x86_64, the NSIS setup-win64.exe run silently (setup.exe /S /D=<runtime>) into the managed runtime directory — verifies its SHA256, installs it safely, and smoke-checks the resulting bin/minizinc (bin\minizinc.exe on Windows). On macOS the bundled Gecode is the Qt-linked build, so the installer vendors its Qt frameworks into <runtime>/Frameworks and relinks the solver to load them headlessly (no GUI is ever launched). That relink step uses the Xcode Command Line Tools, so run xcode-select --install first if install-runtime reports install_name_tool is missing.
  3. Writes a small JSON config (<platformdirs user_config_dir>/install.json, typically ~/.config/openconstraint-mcp/install.json on 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-runtime exits 1 with a clear message — use configure-runtime --runtime-dir <path> or point OPENCONSTRAINT_MCP_RUNTIME_DIR at an existing MiniZinc install (a directory containing bin/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-runtime CLI command. The package does not phone home; httpx is only imported when install-runtime runs (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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for openconstraint-mcp 0.3.1
File Size Uploaded Provenance Actions
openconstraint_mcp-0.3.1.tar.gz 282.3 kB With provenance Details for openconstraint_mcp-0.3.1.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for openconstraint-mcp 0.3.1
File Interpreter ABI Platform Actions
openconstraint_mcp-0.3.1-py3-none-any.whl Python 3 none any Details for openconstraint_mcp-0.3.1-py3-none-any.whl

Total release size: 587.2 kB

Release files / openconstraint_mcp-0.3.1.tar.gz

Download URL openconstraint_mcp-0.3.1.tar.gz
Size 282.3 kB
Tags Source
SHA-256 checksum
How to use checksums
8f311e69e6028a79e9fb48918bd0fba7e28cdec6ef6bc6a85e25b087a7a4a959
BLAKE2b-256 checksum
How to use checksums
ad3579e35f502d38ed5c1289a6be3c48baa449bc7c809e31fdaf015d35da3ac5
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 Oct 15, 2026.

Transparency log

Release files / openconstraint_mcp-0.3.1-py3-none-any.whl

Download URL openconstraint_mcp-0.3.1-py3-none-any.whl
Size 304.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3d89fa4fb59ff4518070f2e78d943a37c5d1578a48f29c5cf7f0acdd6482e660
BLAKE2b-256 checksum
How to use checksums
6215734a299afbf6ca90f3151425036f7529eee111a82afa975d5fee105d283b
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 Oct 15, 2026.

Transparency log

Security for openconstraint-mcp 0.3.1

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.

Select from 2 files

Attestation for openconstraint_mcp-0.3.1.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 Oct 15, 2026.

Transparency log

A 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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page