1-day longest streak
solver-mcp solver-mcp is a local MCP server that gives AI agents a real Timefold Solver backend for assignment problems. The agent writes a declarative JSON spec with generic entities, values,…
solver-mcp
solver-mcp is a local MCP server that gives AI agents a real Timefold Solver backend for assignment problems. The agent writes a declarative JSON spec with generic entities, values, attributes, and constraint primitives; the server validates it, solves it, and returns assignments with a score breakdown. The LLM writes a declarative spec, never code; Timefold computes the answer; nothing is hallucinated.
Unofficial project powered by Timefold Solver Community (Apache 2.0).
Quickstart
Prerequisites:
- JDK 21
- A local MCP client that can start stdio servers
bash
./gradlew build
Run the server:
bash
java -jar build/libs/solver-mcp-1.0.0-all.jar
For MCP clients, use the jar command above. Do not launch the MCP server through ./gradlew run; Gradle writes progress output to stdout, and stdio MCP requires stdout to contain protocol frames only.
Or build and run the Docker image:
bash
docker build -t solver-mcp:1.0.0 .
docker run -i --rm solver-mcp:1.0.0
Docker MCP clients must keep stdin open with -i; the server speaks MCP over stdio.
Claude Code
From this repository:
bash
claude mcp add solver-mcp -- java -jar /absolute/path/to/solver-mcp/build/libs/solver-mcp-1.0.0-all.jar
Docker alternative:
bash
claude mcp add solver-mcp -- docker run -i --rm solver-mcp:1.0.0
Then start Claude Code and ask it to use solver-mcp to validate and solve a spec.
Claude Desktop
Add a stdio server entry to your Claude Desktop MCP config:
json
{
"mcpServers": {
"solver-mcp": {
"type": "stdio",
"command": "java",
"args": [
"-jar",
"/absolute/path/to/solver-mcp/build/libs/solver-mcp-1.0.0-all.jar"
]
}
}
}
Restart Claude Desktop after editing the config.
Docker alternative:
json
{
"mcpServers": {
"solver-mcp": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "solver-mcp:1.0.0"]
}
}
}
Tools
| Tool | Purpose |
| --- | --- |
| modeling_guide | Returns the DSL guide, expression syntax, primitive examples, full examples, and repair workflow. |
| validate_spec | Checks JSON shape, expression parsing, attribute references, primitive compatibility, and size warnings. |
| solve | Solves the ASSIGNMENT spec with a bounded Timefold run and returns the best-found assignment. |
| explain_solution | Rebuilds a supplied assignment and returns deterministic per-constraint totals and top matches. |
| diagnose_infeasible | Lists broken hard constraints and briefly re-solves with one hard constraint relaxed at a time. |
Spec
The v1.0 schema is published at [spec-schema.json](spec-schema.json). The current archetype is ASSIGNMENT: every entity gets one value from the values list.
json
{
"name": "exam timetabling",
"archetype": "ASSIGNMENT",
"entities": [
{ "id": "examA", "attrs": { "students": 120, "teacher": "kim" } }
],
"values": [
{ "id": "slot1_roomX", "attrs": { "day": 1, "slot": 1, "capacity": 150 } }
],
"constraints": [
{
"id": "capacity",
"type": "require",
"when": "entity.attrs.students <= value.attrs.capacity",
"level": "HARD"
}
]
}
Constraint weights use the canonical schema:
json
{ "level": "HARD" }
json
{ "level": "SOFT", "weight": 3 }
Expression Language
Expressions are parsed by a small hand-rolled parser and evaluated against the generic model. They support identifiers entity, value, a, b, and group; paths such as entity.attrs.load, value.id, and a.value.attrs.day; number, string, boolean, and null literals; arithmetic, comparisons, &&, ||, !, and parentheses.
There are no method calls, loops, reflection, or arbitrary code execution.
Primitive Reference
| Primitive | Level | Required fields | Meaning |
| --- | --- | --- | --- |
| require | HARD or SOFT | when | Penalizes each assignment where when is false. |
| forbid | HARD or SOFT | when | Penalizes each assignment where when is true. |
| all_different | HARD or SOFT | expr | Penalizes entity pairs with equal expr values. |
| pair_rule | HARD or SOFT | forbid or penalizeIf | Penalizes entity pairs selected by optional pairFilter when the pair condition is true. |
| group_count | HARD or SOFT | groupBy, min or max | Bounds the number of assigned entities per group. |
| group_sum | HARD or SOFT | groupBy, sumExpr, min or max | Bounds a numeric sum per group. |
| cardinality | HARD or SOFT | min or max | Bounds how many entities are assigned to each value, optionally limited by valueFilter. |
| balance | SOFT | groupBy | Penalizes (group count)^2 for each group. |
| objective | SOFT | expr | minimize penalizes the sum; maximize rewards the sum. |
Examples
The repository includes five complete specs:
- [N-queens 8](examples/n-queens-8.json)
- [Graph coloring](examples/graph-coloring.json)
- [Task to machine with capacities](examples/task-machine-capacity.json)
- [Wedding seating with incompatible pairs](examples/wedding-seating.json)
- [Exam timetabling](examples/exam-timetabling.json)
text
validate_spec -> solve -> explain_solution -> repair the spec if hard infeasible
Architecture
solver-mcp deliberately avoids domain classes such as employees, shifts, rooms, or jobs. The Timefold model has generic Element facts, generic planning entities, and constraint spec items stored on the planning solution as problem facts.
The ConstraintProvider contains always-present streams for every primitive and score level. Each stream starts from ConstraintSpecItem, filters by primitive and level, joins generic entities or values, and evaluates the cached expression AST. This keeps the solver data-driven without static registries or generated code.
solve returns the best-found solution within the requested time limit. explain_solution rebuilds the solution from { spec, assignments } and produces deterministic constraint totals and top matches so every score contribution is traceable.
Limitations
- v1.0 supports ASSIGNMENT only. SEQUENCE is planned for a future release.
- Timefold returns the best-found solution within the time limit unless configured otherwise; this project does not prove optimality.
- The expression language is intentionally small and bounded.
- The generic constraint streams are flexible, not a replacement for a hand-tuned domain solver on large production instances.
validate_specwarns above 500 entities or 50,000 estimated pairs.
Roadmap
- SEQUENCE archetype with list-variable shadow variables:
@IndexShadowVariable,@PreviousElementShadowVariable, and@CascadingUpdateShadowVariable. - CP-SAT backend for optimality proofs where the DSL can be compiled exactly.
- More primitive constraints and stronger static type checks.
- Packaged desktop extension distribution.
Development
Run the full suite:
bash
./gradlew build
Build the container:
bash
docker build -t solver-mcp:1.0.0 .
The tests cover expression parsing and evaluation, every assignment primitive via Timefold ConstraintVerifier, end-to-end N-queens and graph coloring, infeasible diagnosis, validation errors, stdout discipline, and all checked-in examples.
License
Apache License 2.0. See [LICENSE](LICENSE).
-
solver-mcp ★ PINNED
General-purpose Timefold-powered MCP solver server for AI agents
Kotlin ★ 4 22d agoExplain → -
vibe-swarm
Bash-based agent swarm for solo devs. Spawn Codex/Claude agents, auto-review PRs, auto-retry on failure.
Shell ★ 1 4mo agoExplain →
No repos match these filters.