# cpm-planner [Health: Active]

**Category:** 📋 Product Management  
**Repository:** https://github.com/praxec/cpm-planner  
**GitHub Stars:** 0  
**npm Downloads (last month):** 1479  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/cpm-planner

## Description
Critical Path Method scheduling and lock-aware parallel cohorts for MCP agents.

## Tools
Capabilities this server exposes over MCP:

- **plan.release.compile** — Compile version-1 bounded release metadata into an ordinary lint-clean PlanGraph. Expands protected PR/check/approval/merge hops, producer retries, retained receipts and central acceptance. Every collection declares cardinality or a bounded retained source. Pure offline operation: never executes, accepts, submits or baselines work.
- **plan.release.diff** — Compare a retained compiled graph with current version-1 workflow/promotion policy. Reports policy_changed, added/removed/changed ids and stale; includes retained-history estimate drift. Read-only: apply accepted changes through normal plan.sync or plan.revise.
- **plan.submit** — Submit a plan graph and receive a plan_id. Idempotent: identical graphs return the same plan_id. With `name` (and optional `project`/`variant`, variant defaults to "main"), registers a named variant instead: project defaults to the discovered project root.
- **plan.acquire_cohort** — Acquire up to max_count ready deliverables with no conflicting file claims atomically. Returns the cohort plus per-deliverable locks. A deliverable explicitly marked failed 3 times is circuit-broken to failed instead of re-leased; leases lost environmentally (TTL lapse, no terminal mark) never trip that breaker but are bounded separately (LAPSE_LIMIT at 10). Optional ttl_seconds sets the lease TTL (clamped to the server maximum). Optional ids targets specific deliverables and filter.metadata narrows by metadata equality; deliverables with metadata.kind = "manual" are never leased. ids that do not match filter.metadata are ignored (not reported in blocked). The response carries blocked [{id, code, reason}], blocked_count, and needs_operator (true when any blocked code is LAPSE_LIMIT: clear with plan.force_release {reset_counters: true}). Select the plan by plan_id, plan line name, or active: true.
- **plan.heartbeat** — Refresh the TTL on a held lock; LOCK_NOT_HELD or LOCK_EXPIRED on failure. Optional ttl_seconds sets the new TTL (clamped to the server maximum); without it the lease is never shortened. Select the plan by plan_id, plan line name, or active: true.
- **plan.mark_status** — Set a deliverable's status. Complete/Failed releases the lock; caller_id mismatch yields LOCK_NOT_HELD. Without a lock, Complete, Ready or InProgress requires all prerequisites complete (PREREQUISITES_INCOMPLETE) and is audited; other lockless marks are audited too, and an already-complete deliverable cannot be changed (LOCK_NOT_HELD). A lockless in_progress persists across restarts and is not leasable; hand it back with a lockless ready (or other status) mark. plan.force_release does not affect it. Optional earned-value progress: earned_pct (0..100, only with in_progress; ignored with complete), actual_effort_hours (total so far, 0..1000000; replaces leased hours as actual cost) and evidence (<= 2048 chars, appended to a list of at most 100); violations are INVALID_ACTUALS. Every lease that ends adds its hours to the deliverable's leased hours. Select the plan by plan_id, plan line name, or active: true.
- **plan.status** — Read-only snapshot: per-deliverable [id, status, attempt_count, failure_count, lapse_count] rows, critical_path (one real chain, always from __start__ to __finish__), critical_ids, per-deliverable schedule (es/ef/ls/lf/float, hours; synthetic __start__/__finish__ rows have synthetic=true), the ready set in float-ascending order (smallest float first, then smallest latest start, then id), plan_complete, milestones (per milestone: id, critical_path from __start__, hours, complete), and held locks. Select the plan by plan_id, plan line name, or active: true.
- **plan.get** — Return the stored PlanGraph (deliverables, estimates, files, metadata) for a plan selected by plan_id, plan line name, or active: true.
- **plan.force_release** — Operator escape hatch — release a lock regardless of caller. Emits an audit event carrying `reason`. Select the plan by plan_id, plan line name, or active: true.
- **plan.accept** — Manager/owner acceptance: mark a deliverable Complete without holding its lease. Requires all prerequisites Complete and evidence; audited. A live lease held by someone else is refused (LOCK_HELD) unless override_lock is true. Select the plan by plan_id, plan line name, or active: true.
- **plan.lint** — Lint a graph, stored plan, or plan file without creating one: cycles (with the loop), redundant edges, edges without rationale, interface edges not targeting a contract, deliverables feeding no milestone, and unordered file overlaps. Linting by plan_id also adds NO_BASELINE (warning) when the plan is executable and has recorded progress but no earned-value baseline. Provide exactly one of graph, plan_id, or path.
- **plan.schedule** — Level a graph or stored plan against resource capacities (`metadata.owner` by default; metadata.resources can claim several pools concurrently): makespan, per-deliverable start/finish, per-resource load, the driving chain (dependency vs named resource waits), and project/feeding buffers. A deliverable with duration_hours plus metadata.wall_clock_only:true advances elapsed time but consumes no capacity. Provide exactly one of graph or plan_id. capacities is required: every resource that carries work needs at least 1 unit (otherwise INVALID_CAPACITIES: lists the missing resources). plan.schedule rejects what plan.submit rejects (INVALID_GRAPH:).
- **plan.simulate** — Read-only what-if for a graph, stored plan, or plan file (nothing is persisted): lint, critical path, schedule, milestones, optional resource schedule and Monte Carlo, and the scorecard. Provide exactly one of graph, plan_id, or path. schedule takes the plan.schedule inputs (capacities required; INVALID_CAPACITIES: when a working resource has none). monte_carlo takes iterations (1..50000, default 2000) and seed (default 0xC0FFEE); iterations × (deliverables + prerequisite edges) must not exceed 200000000. plan.simulate rejects what plan.submit rejects; lint errors or an invalid graph are INVALID_GRAPH:.
- **plan.sync** — Register or update one variant of a named plan line from a plan file (`path`) or an inline `graph`. A path is read from .cpm-planner/plans/<name>/<variant>.json and tracked by content hash for drift detection. An inline graph requires `name`; `variant` defaults to "main". `project` defaults to the discovered project root. `force` releases live locks of removed deliverables.
- **plan.list** — List every plan line of `project` (default: the discovered project root), sorted by name with each line's variants sorted by variant. Archived lines and variants are omitted unless include_archived. With active: true, return only the project's active lines.
- **plan.export** — Write the head graph of `plan_id` under the project root and return its root-relative path: to `path` when given (confined to .cpm-planner/plans/<name>/<variant>.json), else to its own variant file. Refuses (INVALID_PATH) another variant's tracked plan file, or the variant's own file while it holds local edits never synced, unless force. The written file is not synced; exporting to the variant's own tracked file records its hash, so drift is false.
- **plan.revise** — Replace a plan's graph in place, carrying progress over: unchanged deliverables keep status and counters; changed or reopened ones are re-derived. Removed deliverables holding live locks are refused (LOCK_HELD) unless force. Returns the new revision and the diff.
- **plan.fork** — Copy the head graph of a named variant, apply `edits` in order, and register the result as a new draft (not selected) variant of the same line. Fails if the variant already exists.
- **plan.select** — Make the named variant owning `plan_id` its line's selected (the only executable) variant. Progress carries over from the previously selected variant. Live locks on the previous variant refuse selection with LOCK_HELD unless force releases them (audited).
- **plan.archive** — Archive (archived defaults to true) or unarchive a whole plan line (`variant` omitted) or one variant. Archived variants stay readable but are hidden from plan.list unless include_archived, and refuse sync, selection and execution (ARCHIVE_REFUSED).
- **plan.compare** — Compare stored plans on the scorecard, returning the Pareto front, weighted rank and a recommended plan. Give exactly one of `plan_ids` (2 to 16 distinct plans) or `plan` (a line name in `project`: every non-archived variant, at most 16). Read-only. Weights must be finite and >= 0. With monte_carlo, iterations × (deliverables + prerequisite edges) summed over all variants must not exceed 200000000.
- **plan.baseline** — Freeze the plan's current CPM schedule (earliest start/finish) and budgets (effort basis x metadata.cost_rate) as its next numbered baseline for earned value. The first baseline is number 1; re-baselining needs a non-blank `reason` (INVALID_GRAPH otherwise) and keeps actuals and snapshots. Without `calendar`, a re-baseline keeps the previous baseline's calendar (the first baseline counts wall-clock hours). `backfill_from` (RFC 3339) sets the baseline's `start` retroactively and marks it `backfilled: true` (reported by plan.ev); it must not be in the future or before the plan's creation time (INVALID_VALUE). Execution-side: the plan must be its line's selected, unarchived variant (VARIANT_NOT_SELECTED / ARCHIVE_REFUSED). Audited as plan.ev.baselined. Select the plan by plan_id, plan line name, or active: true.
- **plan.ev** — Earned-value report against the plan's latest baseline as of `as_of` (default now). `as_of` is the PV status date; EV and AC reflect progress and actuals recorded up to the moment the call runs. Returns BAC, PV, EV, AC, SV, CV, SPI, CPI, EAC, ETC, VAC, TCPI, per-deliverable rows (a baselined deliverable later removed by plan.revise has status `removed` and keeps the percent it had earned: 100 if complete), critical float consumed, SPI_BELOW_0_9 / CPI_BELOW_0_9 alerts from the two latest stored non-backfilled snapshots (by as_of) of the current baseline, and deliverables added since the baseline. AC counts every recorded hour of the plan, including removed and unbaselined deliverables (rate 1 when the baseline has no row). A ratio whose denominator is 0 is null and explained in `undefined`. `backfilled` is true when the baseline was taken with `backfill_from`. Read-only; works on any variant. NOT_BASELINED before plan.baseline. Select the plan by plan_id, plan line name, or active: true.
- **plan.snapshot** — Compute the earned-value report (as plan.ev) and append it as a snapshot. `as_of` is the PV status date; EV and AC are the progress recorded when the call runs, so a snapshot whose as_of is more than an hour before its taken_at is marked `backfilled: true` and ignored by alerts. Returns the snapshot summary (alerts consider this snapshot and the latest earlier non-backfilled one by as_of of the current baseline) and an export of the newest 100 snapshots by as_of, oldest first (an older backfill is counted but not listed): a list of summaries (format json, default) or a Markdown table with columns date, PV, EV, AC, SPI, CPI, EAC (format markdown). Execution-side like plan.baseline. NOT_BASELINED before plan.baseline. Select the plan by plan_id, plan line name, or active: true.
- **plan.review** — Optional AI review of a graph, stored plan, or plan file (read-only; provide exactly one of graph, plan_id, or path). Lint always runs; lint errors return status invalid_graph without calling the judge. Otherwise one batched call to Jev (calibrated judgments, via OpenRouter) asks up to max_questions (1..64, default 64) questions about likely false or missing dependencies, split and interface-split candidates and crash options; findings carry advisory probabilities. Proposals are plan.fork edit lists, each verified by simulate (lint clean, makespan shorter) and ranked by hours_saved / max(cost, 1). Without an OpenRouter key (or with an unusable LLM configuration) status is review_unavailable with a reason, plus lint. The plan graph (ids, descriptions, metadata) is sent to OpenRouter.

## Claude Desktop Quick Installation
Install path detected from listing signals. Uses `npx` (confidence: high):

```json
"mcpServers": {
  "cpm-planner": {
    "command": "npx",
    "args": ["-y","@matthew-cochran/cpm"],
    "env": {
      "CPM_PLANNER_DB": ""
    }
  }
}
```

**Requires environment variables:** `CPM_PLANNER_DB` — the values above are empty placeholders; fill in real credentials before running (see the repository for what each one is for).

## Documentation

## What cpm-planner does

cpm-planner exposes project planning and execution controls through MCP over standard input and output. An agent submits a directed task graph, and the server calculates earliest and latest dates, slack, the critical path, bottleneck tasks, and ready deliverables. Plans can also be represented as JSON files under `.cpm-planner/plans/<name>/<variant>.json`, allowing plan definitions to be tracked and registered from a repository.

The execution model is designed for multiple workers. `plan.acquire_cohort` leases up to a requested number of ready deliverables whose file claims do not conflict. Append claims can be co-leased on a shared path, and prerequisite ordering can permit otherwise repeated ownership. Heartbeats extend leases, status updates complete or fail work, and force-release provides an operator recovery path. Lockless manual progress and acceptance are audited.

## How it works

Use `plan.submit` for an in-memory task graph, or `plan.sync` to register a named file-backed plan variant. `plan.status` reports schedule, critical-path, ready-set, and lock information; `plan.get` reads the stored graph. A plan can be revised in place while carrying forward compatible progress, forked into a draft variant, selected as the executable variant, compared with alternatives, or archived.

Read-only analysis includes graph linting, resource-capacity leveling, schedule simulation, and Monte Carlo risk calculations. Release workflow metadata can be compiled into ordinary plans and compared for policy or estimate drift. Baselines freeze schedule and budget values for earned-value reporting, and `plan.ev` and `plan.snapshot` calculate measures including PV, EV, AC, SPI, CPI, schedule variance, and cost variance. Optional review can inspect a graph, stored plan, or plan file after linting.

## Setup and configuration

Prebuilt binaries are available for Linux, macOS, and Windows on x86_64 and ARM64. The latest-release installer verifies SHA-256 checksums before installation. Other supported routes include the npm launcher, a tagged Cargo build, and a Docker image. Node.js 18 or later is required for the npm route; building from source requires Rust 1.99 or newer.

Register the installed executable as an MCP server using its stdio transport. Docker requires an interactive stdin (`-i`) and can persist state in a mounted `/data` volume. The Docker image uses `CPM_PLANNER_DB=/data/cpm-planner.db`; set `CPM_PLANNER_DB` to override the database location. Restart the MCP client after upgrading the executable.

## Tools and capabilities

- Submit, read, revise, synchronize, export, fork, select, archive, compare, and list plans and variants.
- Compute CPM schedules, critical paths, slack, ready sets, resource loads, driving chains, and buffers.
- Acquire lock-aware cohorts with file ownership, append sharing, TTL heartbeats, and release controls.
- Lint graphs for cycles, redundant or unsupported edges, unproductive deliverables, and unordered file overlaps.
- Simulate schedules with capacities and Monte Carlo iterations, including what-if analysis without persistence.
- Freeze baselines and produce earned-value reports, snapshots, alerts, and per-deliverable progress data.

## Limitations and notes

The server uses stdio rather than a network transport in the documented setup. It is standalone and does not require praxec, although praxec workflows can drive it. A lockless `in_progress` status has no lease and is not automatically available for cohort acquisition; it must be handed back through a lockless status update. Archived variants remain readable but reject synchronization, selection, and execution. Plan files are tracked by content hash, so changes can be surfaced as definition drift.

_Full upstream README: https://allmcps.com/mcp/cpm-planner/readme_

