cpm-planner
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
cpm-planner
cpm-planner is a Critical Path Method (CPM) planner for agent work, exposed as an MCP server. You submit a task graph and it computes the schedule: earliest and latest start and finish, slack, the critical path and the bottleneck tasks that gate completion. It then leases lock-aware cohorts of ready deliverables with disjoint file sets, so several workers can run in parallel without colliding. Plans can live in the repository as plan-as-code files with forkable, comparable variants; read-only tools lint, level against resource capacities and run Monte Carlo risk; earned-value tools track PV, EV, AC, SPI and CPI against a frozen baseline; and an optional AI review proposes verified schedule improvements. Any MCP client (Claude Code, Cursor, VS Code, Codex, a custom orchestrator, or a praxec workflow) drives it over stdio. It is a standalone tool with no dependency on praxec.
Contents
Related MCP server: LinkedRun
Install
Prebuilt binary (recommended)
Prebuilt packages are published for Linux, macOS and Windows on x86_64 and ARM64.
The installers resolve your OS and CPU and download the matching archive. They
verify its SHA-256 against the release's checksums.sha256 before installing,
then atomically replace the binary in a user-local directory. A checksum mismatch
aborts with a non-zero exit and installs nothing. Re-running the installer upgrades
in place.
# Linux / macOS -> ~/.local/bin/cpm-planner
curl -fsSL https://github.com/praxec/cpm-planner/releases/latest/download/install.sh | sh
# Windows (PowerShell) -> %LOCALAPPDATA%\Programs\cpm-planner\cpm-planner.exe
irm https://github.com/praxec/cpm-planner/releases/latest/download/install.ps1 | iexThe installer prints the absolute path it installed to. Use that path when you register the server with a GUI client.
If you would rather read the script before running it, download, inspect, then run
(see Pin a version for <version>):
# Linux / macOS
curl -fsSLO https://github.com/praxec/cpm-planner/releases/latest/download/install.sh
less install.sh
sh install.sh --version <version># Windows
irm https://github.com/praxec/cpm-planner/releases/latest/download/install.ps1 -OutFile install.ps1
Get-Content install.ps1
.\install.ps1 -Version <version>PATH. If the install directory is not on your PATH, the installer prints the
exact line to add. Pass --add-to-path (-AddToPath on Windows) to have it do that
for you, user-level only and never with sudo.
On Linux and macOS it appends the line only if it is not already there, to:
~/.zshrcfor zsh;~/.bash_profileif it exists, else~/.bashrc, for bash;~/.profilefor other shells.
For fish it prints fish_add_path <dir> for you to run instead of writing a file.
Downloads. The installers only accept https:// download URLs.
Upgrades. After upgrading, restart your MCP client so it launches the new
binary. On Windows the old exe is renamed to cpm-planner.exe.old and removed on
the next run.
Direct downloads. You can also download an archive yourself and check it
against checksums.sha256; a machine-readable release-manifest.json is published
too. All assets are at https://github.com/praxec/cpm-planner/releases/latest.
Platform | Asset |
Linux x86_64 |
|
Linux ARM64 |
|
macOS x86_64 |
|
macOS Apple Silicon |
|
Windows x86_64 |
|
Windows ARM64 |
|
Pin a version
In the commands in this README, <version> is a release tag such as v0.2.0. Pick
one from the releases page.
# Linux / macOS
curl -fsSL https://github.com/praxec/cpm-planner/releases/latest/download/install.sh | sh -s -- --version <version>
# Windows (PowerShell): the piped form cannot take parameters, so use the environment variable...
$env:PRAXEC_VERSION = '<version>'; irm https://github.com/praxec/cpm-planner/releases/latest/download/install.ps1 | iex
# ...or a script block
& ([scriptblock]::Create((irm https://github.com/praxec/cpm-planner/releases/latest/download/install.ps1))) -Version <version>npm
With Node 18 or later, install the cpm-planner command from npm:
npm install -g @matthew-cochran/cpm
cpm-planner --versionThe package is a small launcher. It downloads the release binary for your
platform, verifies its checksum and caches it, during install or, where npm
skips install scripts, on first run. The package
version is the release version, so npm install -g @matthew-cochran/cpm@0.2.0
pins one. To run it without installing, use npx -y @matthew-cochran/cpm.
For details and environment variables, see npm/README.md.
From source
Build and install a tagged release with Cargo (Rust 1.99 or newer):
cargo install --git https://github.com/praxec/cpm-planner --tag <version> --lockedThe crates.io listing lags behind the GitHub releases until the next
cargo publish, so cargo install cpm-planner does not give you the current
version yet. Install from the tag as above.
Docker
docker pull ghcr.io/praxec/cpm-planner
docker run -i --rm -v cpm-planner-data:/data ghcr.io/praxec/cpm-planner-i keeps stdin open for the stdio transport.
The image sets CPM_PLANNER_DB=/data/cpm-planner.db and declares /data as a
volume owned by the unprivileged app user, so a named volume persists plan state
across runs. For a bind mount (-v "$PWD:/data"), run as yourself so the directory
is writable: --user "$(id -u):$(id -g)". Pass -e CPM_PLANNER_DB=... only to
override the path.
To register the image with a client, put docker in command and the rest in
args. For example, in Claude Code:
claude mcp add cpm-planner --scope user -- docker run -i --rm -v cpm-planner-data:/data ghcr.io/praxec/cpm-plannerVerify the install
cpm-planner --versionIt prints cpm-planner <version>. To check the MCP side without a client, run the smoke
script from the repository. It performs the MCP initialize and tools/list handshake and
fails if plan.submit, plan.status or plan.get is missing:
node scripts/mcp-smoke.mjs /absolute/path/to/cpm-plannerQuickstart: plan as code
Write a plan as .cpm-planner/plans/<name>/<variant>.json (a PlanGraph):
plan.lint {path: ".cpm-planner/plans/checkout/main.json"}— static checks, no state written.plan.sync {path: ".cpm-planner/plans/checkout/main.json"}— register the variant and return itsplan_id.plan.status {plan_id}— schedule, critical path, ready set and locks.
plan.sync tracks the file by content hash; plan.status reports
definition_drift when the tracked file and the stored head graph disagree.
Quickstart: earned value
Freeze the baseline, record progress, then read the report (earned_pct is earned in
proportion under earning_rule: "weighted"; fifty_fifty credits 50% once a deliverable is in progress or has any
reported earned_pct, and the default zero_hundred
earns at completion):
plan.baseline {plan_id}— freeze the CPM schedule and budgets as baseline 1.plan.mark_status {plan_id, deliverable_id, caller_id, status: {"status": "in_progress"}, earned_pct: 50, actual_effort_hours: 4}— report progress and actual cost.plan.ev {plan_id}— PV, EV, AC, SV, CV, SPI, CPI, EAC and alerts.plan.snapshot {plan_id, format: "markdown"}— append the reading and export a Markdown table.
A ratio with a zero denominator is null and explained in undefined; alerts
(SPI_BELOW_0_9, CPI_BELOW_0_9) compare the two latest stored readings.
Use with AI agents
Any MCP client can run cpm-planner over stdio: register the cpm-planner
binary as the command, or, with Node 18 or later, npx -y @matthew-cochran/cpm.
For a GUI client, use the absolute path the installer printed.
Install the agent skills (/cpm-plan and friends) with
cpm-planner skills install --target <tool> --user (or --project <dir>), where
<tool> is claude, codex, cursor, copilot, gemini, all, or
agents-md (project only).
docs/AGENT-INSTALL.md has the per-client steps (Claude
Code, Claude Desktop, Codex, Cursor, VS Code, Gemini CLI and others), env
blocks, verification and troubleshooting. llms.txt indexes the docs
for LLMs.
MCP tools
Tool | Does |
| Submit a task graph; returns a plan id (idempotent on the graph + caller). A prerequisite is a deliverable id string or an object |
| Atomically acquire up to N ready deliverables with mutually disjoint file sets (an |
| Refresh the TTL on a held lock; optional |
| Mark a deliverable complete/failed; releases its lock. Without a lock, Complete requires complete prerequisites ( |
| Read-only snapshot of the plan and its locks. |
| Return the stored plan graph for a plan selected by |
| Operator escape hatch: release a lock regardless of holder/TTL; |
| Manager/owner acceptance: complete a deliverable without holding its lease (audited, with evidence: a note or an evidence record, checked as on |
| Static checks without submitting: cycles (with the loop), redundant edges, edges without rationale, interface edges not targeting a contract, deliverables feeding no milestone, and unordered file overlaps (#20). |
| Level a graph against resource capacities ( |
| Read-only what-if for a graph, stored plan or plan file (persists nothing): lint, critical path, schedule, milestones, optional resource schedule ( |
| Compile versioned, bounded workflow/promotion metadata into a lint-clean ordinary plan: protected PRs, parallel checks/joins, owner waits/approvals/merges, producer retry receipts, retained artifacts, native fixed-cost integration waves/evidence reuse, and central acceptance. Reports batched-versus-per-change gate economics and dominance warnings; runs offline and never executes or accepts work (release workflows). |
| Compare a retained generated |
| Register or update one variant of a named plan line from a plan file ( |
| List every plan line of |
| Write the head graph of |
| Replace a plan's graph in place, carrying progress over (unchanged deliverables keep status/counters; changed or reopened ones are re-derived). A deliverable can declare |
| Copy a named variant's head graph, apply ordered |
| Make a named variant its line's selected (only executable) variant, carrying progress over; |
| Archive ( |
| Compare stored plans on the scorecard ( |
| Freeze the plan's current CPM schedule (earliest start/finish per deliverable), or with |
| Earned-value report against the latest baseline as of |
| Compute the |
| Optional AI review of a graph, stored plan or plan file (read-only; exactly one of |
| Read-only, deterministic one-call snapshot of a plan at |
| Read-only, deterministic: for |
| Offline, deterministic optimization of a graph, stored plan or plan file (exactly one of |
plan.status, plan.get, plan.ev, plan.snapshot, plan.baseline,
plan.overview, plan.explain, plan.acquire_cohort, plan.heartbeat,
plan.mark_status, plan.accept and plan.force_release address a plan by exactly one of plan_id, plan (a plan line
name, resolved to its selected variant) or active: true (the project's single line that
is unarchived and whose selected variant is unarchived and not complete). plan and
active also take an optional project (default: the discovered project root); project
with plan_id is invalid params. No active line is NO_ACTIVE_PLAN; several are
AMBIGUOUS_ACTIVE_PLAN, naming them; an unknown line, or a line with no selected
variant, is PLAN_NOT_FOUND.
Recurring jobs and agents should look the plan up with active (or plan) rather than
pin a plan_id, so they follow the work to the next line. active matches only an
incomplete line, and it changes (or becomes ambiguous) when another line goes live:
resolve once (plan.status and plan.acquire_cohort return the plan_id), then keep that
plan_id for heartbeats, marks and the final plan.status/plan.ev of that plan. A pinned
id of an archived variant still reads through plan.status/plan.ev, which report
archived: true and active_plan naming where the live work is, while snapshots, baselines
and execution refuse with ARCHIVE_REFUSED.
plan.lint, plan.simulate, plan.optimize and plan.review take exactly one of an inline graph, a stored
plan_id, or a plan-file path; plan.schedule takes graph or plan_id;
plan.schedule and plan.simulate reject what plan.submit rejects, and plan.lint reports it as findings. Monte Carlo output is reproducible for a given seed on the same platform
and toolchain; bit-identical results across targets or compiler versions are
not guaranteed, because float math functions can differ.
Plan-as-code workflow
Author a plan as a file at .cpm-planner/plans/<name>/<variant>.json, check it
with plan.lint {path}, register it with plan.sync {path} and execute the
returned plan_id. A named line has many variants but exactly one selected:
plan.fork creates a draft from structured edits, plan.compare scores the
variants, and plan.select makes one executable. Keep definitions in tracked
files — never keep untracked scratch graphs.
Plan review (optional)
Run plan.optimize first: it is free, offline and deterministic, and it
catches the structural waste the graph shows by itself (false dependencies,
same-owner merges, redundant gates). plan.review stays the optional AI step
for the judgments the graph cannot make alone.
plan.review asks Jev (typesafe/jev-1.13), TypeSafe's
calibrated-judgment model, about a lint-clean plan through OpenRouter: one
batched call of at most 64 questions per review. It is off until you set
OPENROUTER_API_KEY (or CPM_OPENROUTER_KEY_FILE); without a key the tool
still answers, with review_unavailable and the lint report. An invalid LLM
setting never stops the server: it is logged at startup and plan.review
reports review_unavailable with a reason naming the setting.
Probabilities are advisory. Treat findings as a second opinion. Proposals are verified mechanically (the edited plan must lint clean and simulate to a shorter makespan); apply one with
plan.fork {edits}, thenplan.compare.Cost. Each
plan.reviewcall that reaches the judge is one billed OpenRouter request; Jev costs about $0.042 per million input tokens on OpenRouter. Each report carries the provider'susagewhen given. At most 2 reviews that call the judge run at once per server; further calls wait for a slot (reviews that end before the call, such as no key or lint errors, never wait).Privacy. This is everything sent to OpenRouter in that one request:
a fixed task sentence, the plan makespan and the critical path (ids);
the questions: for each, its kind, the deliverable ids it names, and a question sentence that quotes those ids and, depending on the kind, the prerequisite's
consumestext, the deliverable's scheduled hours, the plan's median scheduled hours, and the heuristic signals that raised a missing-dependency question (a description mentions the other, files in the same or nested directories, a sharedmetadata.owner);for every deliverable a question names: its id, scheduled hours, float hours,
criticalandmilestoneflags, owned file paths (sent in full, not truncated), prerequisites (id,consumes, kind) and itsdescription,artifactandownermetadata.description,artifact,ownerandconsumesare cut to 500 characters each.
Nothing else in the graph (other deliverables, other metadata keys, estimates) is sent. Do not review plans whose contents you may not share with OpenRouter.
Audit. The key is never logged or returned, and neither is a misplaced value of any LLM variable. Every
plan.reviewcall records oneplan.reviewaudit event, whatever its outcome (including a missing plan, a bad path or a failed review); the only unaudited case is a call rejected for its params (invalid_params). The event has exactly these fields:status:ok,review_unavailable,invalid_graph, orerror(the review failed:PLAN_NOT_FOUND,INVALID_PATH,INVALID_CAPACITIES, …);code: the error prefix forerror, else null;failure_class: for areview_unavailablecaused by a failed judge call, its class (unauthorized,rate_limited,timeout,upstream,decode,transport,invalid_request); null otherwise (including no key and an unusable LLM setting);plan_id: the stored plan reviewed, null for an inlinegraphorpath;question_count: questions sent (0 when the judge was not called);jev_called: whether the judge was called;prompt_hash: sha256 of the canonical request, null when the judge was not called;model: the model the provider reported, else the configured model when the call failed; null when the judge was not called;endpoint: the judge endpoint's host; null when no judge is configured, lint found errors, or the review ended inerror.
The prompt itself is never recorded.
Use as a library
The CPM kernel is also a plain Rust library, independent of MCP:
use cpm_planner::{CpmAlgorithm, Task, TaskKind};
let mut tasks = vec![
Task::new("design", "Design", TaskKind::Custom { description: "design".into() }, 4.0),
Task::new("build", "Build", TaskKind::Custom { description: "build".into() }, 8.0)
.depends_on("design"),
Task::new("test", "Test", TaskKind::Custom { description: "test".into() }, 2.0)
.depends_on("build"),
];
let result = CpmAlgorithm::calculate(&mut tasks);
println!("critical path: {:?}", result.critical_path); // ["design", "build", "test"]
// also: result.bottlenecks, result.optimal_duration_parallel,
// and per-task .float (slack) / .is_critical on each Task.The crate-level docs (cargo doc --open, or docs.rs
once a current version is published there) have a runnable example that drives the
planner through submit, lease and complete.
llm::jev::JevJudge (the plan.review judge) owns its HTTP client and
connection pool. A pooled connection is driven by the tokio runtime that
opened it, so use a judge (and its clones) from one runtime, and build a new
judge for another runtime.
Praxec
cpm-planner is fully standalone: it speaks plain MCP and has no code dependency on any particular client. Praxec packs use it as an MCP tool. To wire it into a praxec workflow, declare an MCP connection (protocol only, no shared code):
connections:
planner:
kind: mcp
command: cpm-plannerThe easiest way to get it, plus a workflow pack that uses it, up and running is the one-command setup:
curl -fsSL https://raw.githubusercontent.com/praxec/packs/main/setup.sh | bashSee the pack registry for this tool's provider coordinates (container image or release binary) and which packs depend on it.
Environment variables
Set these in a client's env block (or -e/--env flag, or docker run -e). All are optional.
Variable | Default | Meaning |
|
| SQLite path for durable, cross-process planner state; |
| unset | Leave unset for normal durable-state opening. If an existing database is empty or its sidecar receipt detects missing known plans, startup refuses to mutate it. Set to |
| nearest ancestor of cwd with | Repo root for plan-as-code files ( |
|
| Server-side ceiling for |
| unset | OpenRouter key for |
| unset | File holding the OpenRouter key (contents trimmed; at most 4096 bytes), used when |
|
| Who answers |
| unset | Absolute path of the review adapter for |
| unset | Comma-separated variable names to pass to the adapter, whose environment is otherwise cleared. It may not name the OpenRouter key variables. |
|
| Jev model id for |
|
| Jev endpoint URL: |
|
| Bound on each LLM call, integer seconds in 1..=300. |
|
| Generative (chat) model id: at most 128 characters of |
Leases default to 5 minutes. For long-running work pass ttl_seconds (≤ the server maximum) on acquire/heartbeat, and heartbeat at least every ttl/3.
Documentation
docs/README.md: index of the project documentation.
docs/AGENT-INSTALL.md: install, register and verify cpm-planner for your AI coding tool.
docs/architecture.md: modules, request flow, store schema and concurrency.
docs/releasing.md: the release runbook.
Contributing
Contributions are welcome. See CONTRIBUTING.md for the toolchain, the gates and the branch model. Everyone taking part agrees to the Code of Conduct.
License
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Draft-safe project planning: tasks, dependencies, resources, schedules and cost metrics.
Control plane for autonomous software labor. Agents claim objectives over MCP with audit trail.
A project planning board your AI reads and updates over MCP, so the plan stays one plan.
MCP server for generating rough-draft project plans from natural-language prompts.
Related MCP Servers
- AlicenseBqualityDmaintenanceA durable DAG-based task planner exposed as an MCP server that lets AI orchestrators break a goal into a dependency graph of tasks, execute them in parallel where possible, track state durably, and handle human-in-the-loop approval through 22 MCP tools.24MIT
- AlicenseAqualityCmaintenanceEnables agents to submit and manage persistent, dependency-aware task graphs with immutable artifacts, resource reservations, durable event streaming, and retryable process execution over MCP.12MIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP-compatible AI clients to query, analyze, and mutate Oracle Primavera P6 schedules from XER files or live P6 EPPM, with tools for critical path, DCMA compliance, earned value, resources, baselines, and exports.35 PyPI1MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI clients to build and calculate critical-path project plans through tools, including resource leveling, vacation and holiday calendars, what-if scenario comparison and Monte Carlo schedule risk simulation. Projects are read from and written as plain JSON files.MIT