# io.github.praxec/cpm-planner (oci · ghcr.io/praxec/cpm-planner)

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

- Trust score: 41/100 (low)
- Change this week: +4
- Registry status: active
- Liveness: live
- Owner verified: no
- Last scored: 2026-10-23

## Components

- oci · `ghcr.io/praxec/cpm-planner`: 41/100 (this document), [markdown](https://verifymcp.io/servers/praxec-cpm-planner/ghcr-io-praxec-cpm-planner.md), [page](https://verifymcp.io/servers/praxec-cpm-planner/ghcr-io-praxec-cpm-planner)

## Channel facts

- Registry: `oci`
- Package: `ghcr.io/praxec/cpm-planner`
- Version: `0.4.2`
- Transport: `stdio`

## Trust breakdown

How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. Scores are 0–100 per category. Scoring method: https://verifymcp.io/docs/scoring (what has changed: https://verifymcp.io/docs/scoring/changelog)

Scored 2026-10-23.

- **Supply Chain Security**: 0/100
  - No malware scan is available for this kind of package: the supply-chain vendors we use do not cover it. This is a permanent gap in our coverage, not a finding about the package.
  - Known CVEs could not be checked: this artifact ships no SBOM or dependency manifest, so there is no dependency list to read.
  - Install-script risk not yet assessed.
  - Dependency health could not be checked: this artifact ships no SBOM or dependency manifest, so there is no dependency list to read.
- **Provenance & Transparency**: 48/100
  - Source repository is publicly reachable at the declared URL.
  - Provenance check failed: no build-provenance attestation is published.
  - Clear OSI-approved license (Apache-2.0).
  - Actively maintained (last published 0 days ago).
  - Publishes a security disclosure policy (SECURITY.md).
- **Schema Quality & AI Usability**: 53/100
  - AI-judged instruction clarity (good).
  - Context-footprint check failed: tool/resource definitions use about 9146 tokens (~397/item across 23 items; 23 tools + 0 resources), over budget; trim descriptions and params.
  - Usage-examples check failed: none of the tools include examples.
- **Stability & Change Management**: 30/100
  - Stability observed for 9 of 30 days with no destabilising changes; credit accrues until the full window elapses.
- **Tool Coverage**: 94/100
  - 100% of tools have a non-trivial description (not blank, and not just the tool's name).
  - 82% of tool parameters carry a description.
- **Tool Safety**: 100/100
  - No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.
  - We read all 23 captured tool definition(s), and no name or description among them implies an irreversible operation.
  - An AI judge read all 24 captured unit(s) of tool text and found none that tries to manipulate the model reading it.
- **Capabilities**: 100/100
  - Implements a current MCP spec version (2026-07-28).

**Unverified: 1 category.** A category scored 0 because we could not verify it: a data source with nothing on this package, evidence we could not reach, or a check we could not run. We only credit what we can confirm.

## Install

### How do I install the io.github.praxec/cpm-planner MCP server?

io.github.praxec/cpm-planner runs locally as a container image, launched with docker run --rm -i ghcr.io/praxec/cpm-planner:0.4.2. Ready-made configuration for Claude, Cursor, VS Code, Codex and 3 more is on this page, copied from each client's own documentation.

### Claude

```bash
claude mcp add praxec-cpm-planner -- docker run --rm -i ghcr.io/praxec/cpm-planner:0.4.2
```

### Cursor

```json
{
  "mcpServers": {
    "praxec-cpm-planner": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/praxec/cpm-planner:0.4.2"
      ]
    }
  }
}
```

### VS Code

```json
{
  "servers": {
    "praxec-cpm-planner": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/praxec/cpm-planner:0.4.2"
      ]
    }
  }
}
```

### Codex

```bash
codex mcp add praxec-cpm-planner -- docker run --rm -i ghcr.io/praxec/cpm-planner:0.4.2
```

### opencode

```json
{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "praxec-cpm-planner": {
      "type": "local",
      "command": [
        "docker",
        "run",
        "--rm",
        "-i",
        "ghcr.io/praxec/cpm-planner:0.4.2"
      ],
      "enabled": true
    }
  }
}
```

### Hermes

```yaml
mcp_servers:
  praxec-cpm-planner:
    command: "docker"
    args: ["run", "--rm", "-i", "ghcr.io/praxec/cpm-planner:0.4.2"]
```

### Netclaw

```json
{
  "McpServers": {
    "praxec-cpm-planner": {
      "Transport": "stdio",
      "Command": "docker",
      "Arguments": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/praxec/cpm-planner:0.4.2"
      ]
    }
  }
}
```

### Other

```json
{
  "mcpServers": {
    "praxec-cpm-planner": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "ghcr.io/praxec/cpm-planner:0.4.2"
      ]
    }
  }
}
```

## Changelog

Every change recorded for this component, newest first. Days that predate change tracking, or that we cannot explain, say so: "we were watching and nothing happened" and "we were not watching" are different claims.

### 2026-10-23 (score 41, +1)

No change was recorded against any check on this day. Stability & Change Management went from 27 to 30. That category is still filling its 30-day observation window: 8 days of observed history at the previous scan, 9 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-22 (score 40, 0)

- [security regression] Stability: 0.23 → unverified
- [security regression] Tool safety: pass → unverified
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional] Package version: 0.4.1 → 0.4.2

### 2026-10-21 (score 40, +1)

No change was recorded against any check on this day. Stability & Change Management went from 20 to 23. That category is still filling its 30-day observation window: 6 days of observed history at the previous scan, 7 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-20 (score 39, 0)

- [functional] Package version: 0.4.0 → 0.4.1

### 2026-10-19 (score 39, +1)

- [security regression] Stability: 0.13 → unverified
- [security regression] Tool safety: pass → unverified
- [functional regression] Schema quality: 346 → 397
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional improvement] Tool coverage: 65% → 82%
- [functional] Package version: 0.3.0 → 0.4.0

### 2026-10-18 (score 38, +1)

No change was recorded against any check on this day. Stability & Change Management went from 10 to 13. That category is still filling its 30-day observation window: 3 days of observed history at the previous scan, 4 at this one. The score rises as the window fills, whether or not the server changes.

### 2026-10-16 (score 37, +1)

- [security regression] Tool safety: pass → unverified
- [security] Stability: Stability not yet verified: we do not have a sandbox capture of the MCP schema this version of the package serves yet.
- [functional regression] Capabilities: pass → unverified
- [functional regression] Tool coverage: 100 → unverified
- [functional improvement] Stability: unverified → 0.07
- [functional] Package version: 0.2.2 → 0.3.0

### 2026-10-14 (score 36, +26)

- [security improvement] Injection markers: unverified → pass
- [security] First check of Judged manipulation: pass
- [security] Stability: Stability not yet verified: not enough scan history yet (needs a 30-day window).
- [functional improvement] Tool coverage: unverified → 100
- [functional improvement] MCP protocol: unverified → pass
- [functional] First check of Schema quality: fail
- [functional] First check of Tool coverage: 65
- [functional] First check of Destructive annotations: pass
- [functional] First check of Schema quality: fail
- [functional] First check of Schema quality: good

## MCP tools (23)

### `plan.submit` (~130 tokens)

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.

Input parameters:

- `graph` (object, required)
- `name` (string): Plan line name; when present the graph is synced as a named variant.
- `project` (string): Project key; required with `name` when no project root is discovered.
- `variant` (string): Variant name of a named plan (default "main").

### `plan.acquire_cohort` (~343 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `caller_id` (string, required)
- `filter` (object)
- `ids` (array)
- `max_count` (integer, required)
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.
- `ttl_seconds` (integer)

### `plan.heartbeat` (~198 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `caller_id` (string, required)
- `deliverable_id` (string, required)
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.
- `ttl_seconds` (integer)

### `plan.mark_status` (~465 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `actual_effort_hours` (number): Total effort spent so far, in hours; replaces the stored value and takes precedence over leased hours for actual cost.
- `caller_id` (string, required)
- `deliverable_id` (string, required)
- `earned_pct` (integer): Percent complete (used by the weighted earning rule). Only with status in_progress; accepted and ignored with complete.
- `evidence` (string): One evidence note, appended to the deliverable's evidence list (at most 100 entries).
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.
- `status` (object, required): Internally-tagged: {"status":"pending|ready|in_progress|complete"} or {"status":"failed","reason":"..."}

### `plan.status` (~249 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.

### `plan.get` (~146 tokens)

Return the stored PlanGraph (deliverables, estimates, files, metadata) for a plan selected by plan_id, plan line name, or active: true.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.

### `plan.force_release` (~190 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `deliverable_id` (string, required)
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.
- `reason` (string, required)
- `reset_counters` (boolean): Also clear lapse and failure counters (revives a circuit-broken deliverable).

### `plan.accept` (~215 tokens)

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.

Input parameters:

- `accepted_by` (string, required)
- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `deliverable_id` (string, required)
- `evidence` (string, required)
- `override_lock` (boolean): Take over a live lease held by another caller.
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.

### `plan.lint` (~204 tokens)

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.

Input parameters:

- `graph` (object): An inline plan graph to analyse. Provide exactly one of graph, plan_id, or path.
- `path` (string): A plan file path of the form .cpm-planner/plans/<name>/<variant>.json, relative to the project root. Provide exactly one of graph, plan_id, or path.
- `plan_id` (string): A stored plan (from plan.submit/plan.sync) to analyse instead of an inline graph. Provide exactly one of graph, plan_id, or path.

### `plan.schedule` (~274 tokens)

Level a graph or stored plan against resource capacities (`metadata.owner` by default): makespan, per-deliverable start/finish, per-resource load, the driving chain (dependency vs resource waits), and project/feeding buffers. 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:).

Input parameters:

- `capacities` (object, required): Units available per resource name. Every resource that carries work (a deliverable with nonzero scheduled length) needs a count of at least 1; a missing or zero entry for such a resource is INVALID_C…
- `graph` (object): An inline plan graph to analyse. Provide exactly one of graph, plan_id, or path.
- `plan_id` (string): A stored plan (from plan.submit/plan.sync) to analyse instead of an inline graph. Provide exactly one of graph, plan_id, or path.
- `project_buffer_pct` (number): Project buffer as a percentage of the driving chain (default 25).
- `resource_key` (string): Metadata key naming a deliverable's resource (default "owner").

### `plan.simulate` (~307 tokens)

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

Input parameters:

- `graph` (object): An inline plan graph to analyse. Provide exactly one of graph, plan_id, or path.
- `monte_carlo` (object): Seeded Monte Carlo over three-point estimates. iterations × (deliverables + prerequisite edges) must not exceed 200000000.
- `path` (string): A plan file path of the form .cpm-planner/plans/<name>/<variant>.json, relative to the project root. Provide exactly one of graph, plan_id, or path.
- `plan_id` (string): A stored plan (from plan.submit/plan.sync) to analyse instead of an inline graph. Provide exactly one of graph, plan_id, or path.
- `schedule` (object): Level the plan (same inputs as plan.schedule).

### `plan.sync` (~213 tokens)

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.

Input parameters:

- `force` (boolean): Release live locks of removed deliverables instead of refusing.
- `graph` (object)
- `name` (string): Plan line name (required with an inline graph).
- `path` (string): A plan file path of the form .cpm-planner/plans/<name>/<variant>.json, relative to the project root. Provide exactly one of graph, plan_id, or path.
- `project` (string): Project key; defaults to the discovered project root.
- `variant` (string): Variant name (default "main" for an inline graph).

### `plan.list` (~118 tokens)

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.

Input parameters:

- `active` (boolean): Return only the project's active lines (unarchived, incomplete); mutually exclusive with include_archived.
- `include_archived` (boolean): Include archived lines and variants (default false).
- `project` (string): Project key; defaults to the discovered project root.

### `plan.export` (~186 tokens)

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.

Input parameters:

- `force` (boolean): Overwrite another variant's tracked file or unsynced local edits.
- `path` (string): A plan file path of the form .cpm-planner/plans/<name>/<variant>.json, relative to the project root. Provide exactly one of graph, plan_id, or path.
- `plan_id` (string, required)

### `plan.revise` (~93 tokens)

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.

Input parameters:

- `force` (boolean): Release live locks of removed deliverables instead of refusing.
- `graph` (object, required)
- `plan_id` (string, required)

### `plan.fork` (~164 tokens)

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.

Input parameters:

- `edits` (array): Structured graph edits applied in order. Each edit is an object tagged by `op`: remove_edge {from,to}, add_edge {from,to,consumes?}, set_effort {id,hours}, set_duration {id,hours?}, set_estimate {id,…
- `plan_id` (string, required): The named variant to copy.
- `variant` (string, required): Name of the new draft variant.

### `plan.select` (~80 tokens)

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

Input parameters:

- `force` (boolean): Release live locks on the previously selected variant.
- `plan_id` (string, required)

### `plan.archive` (~133 tokens)

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

Input parameters:

- `archived` (boolean): False unarchives (default true).
- `force` (boolean): Release live locks on the selected variant when archiving a line.
- `name` (string, required): Plan line name.
- `project` (string): Project key; defaults to the discovered project root.
- `variant` (string): One variant; omitted archives the whole line.

### `plan.compare` (~216 tokens)

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.

Input parameters:

- `monte_carlo` (object): Run Monte Carlo on every variant (P80 then comes from it).
- `plan` (string): Plan line name; compares every live variant.
- `plan_ids` (array)
- `project` (string): Project key of `plan`; defaults to the discovered project root.
- `schedule` (object): Level every variant (same inputs as plan.schedule).
- `weights` (object): Per-criterion weights for the combined score (lower is better). Each must be finite and >= 0.

### `plan.baseline` (~433 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `backfill_from` (string): RFC 3339 instant to use as the baseline's start, marking it backfilled; must not be in the future or before the plan's creation time.
- `calendar` (object): Working-time calendar: only working hours advance the PV clock. Omitted: wall-clock hours. A workday contributes hours_per_day hours starting at 00:00 local time.
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.
- `reason` (string): Why the plan is re-baselined; required when a baseline exists.
- `start` (string): RFC 3339 instant the PV clock starts from; defaults to now.

### `plan.ev` (~396 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `as_of` (string): RFC 3339 PV status date; defaults to now. EV and AC are always as recorded when the call runs.
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.

### `plan.snapshot` (~341 tokens)

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.

Input parameters:

- `active` (boolean): Resolve to the project's one unarchived, incomplete line (NO_ACTIVE_PLAN / AMBIGUOUS_ACTIVE_PLAN otherwise). Use instead of plan_id.
- `as_of` (string): RFC 3339 PV status date; defaults to now. EV and AC are always as recorded when the call runs.
- `format` (string): Export format (default json).
- `plan` (string): Plan line name; resolves to its selected variant. Use instead of plan_id.
- `plan_id` (string): A stored plan id (from plan.submit/plan.sync). Provide exactly one of plan_id, plan, or active.
- `project` (string): Project for plan or active; default the discovered root.

### `plan.review` (~389 tokens)

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.

Input parameters:

- `capacities` (object): Optional: level the plan so makespans account for resources (same rules as plan.schedule capacities).
- `graph` (object): An inline plan graph to analyse. Provide exactly one of graph, plan_id, or path.
- `max_questions` (integer): Question cap for the single Jev call (default 64); extra candidates are dropped and truncated is true.
- `path` (string): A plan file path of the form .cpm-planner/plans/<name>/<variant>.json, relative to the project root. Provide exactly one of graph, plan_id, or path.
- `plan_id` (string): A stored plan (from plan.submit/plan.sync) to analyse instead of an inline graph. Provide exactly one of graph, plan_id, or path.
- `project_buffer_pct` (number): Project buffer percentage (default 25); requires capacities.
- `resource_key` (string): Metadata key naming a deliverable's resource (default "owner"); requires capacities.

## Diagnostics

Captured diagnostic sections: Provenance. The full working is on the page: https://verifymcp.io/servers/praxec-cpm-planner/ghcr-io-praxec-cpm-planner#diagnostics

## Score history

- 2026-10-23: 41
- 2026-10-22: 40
- 2026-10-21: 40
- 2026-10-20: 39
- 2026-10-19: 39
- 2026-10-18: 38
- 2026-10-17: 37
- 2026-10-16: 37
- 2026-10-15: 36
- 2026-10-14: 36
- 2026-10-13: 10
- 2026-10-12: 9

## Common questions

### What is the io.github.praxec/cpm-planner MCP server?

io.github.praxec/cpm-planner is an MCP server listed in the public MCP registry as io.github.praxec/cpm-planner. Critical Path Method scheduling and lock-aware parallel cohorts for MCP agents. This page covers its container image (ghcr.io/praxec/cpm-planner).

### Is the io.github.praxec/cpm-planner MCP server safe to use?

io.github.praxec/cpm-planner scores 41 out of 100 on VerifyMCP. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.

### What tools does the io.github.praxec/cpm-planner MCP server expose?

io.github.praxec/cpm-planner exposes 23 tools: plan.submit, plan.acquire_cohort, plan.heartbeat, plan.mark_status, plan.status, and 18 more. Their descriptions and schemas cost roughly 5,483 tokens of context every time the server is loaded.

### Is the io.github.praxec/cpm-planner MCP server still maintained?

io.github.praxec/cpm-planner is still listed as active in the MCP registry. We last reached this channel on 23 October 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.

### What licence is the io.github.praxec/cpm-planner MCP server under?

io.github.praxec/cpm-planner declares the Apache-2.0 licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.

## Links

- GitHub Container Registry: https://ghcr.io/praxec/cpm-planner
- Repository: https://github.com/praxec/cpm-planner
- Changelog RSS feed: https://verifymcp.io/servers/praxec-cpm-planner/ghcr-io-praxec-cpm-planner.xml
- Changelog JSON feed: https://verifymcp.io/servers/praxec-cpm-planner/ghcr-io-praxec-cpm-planner.json
- HTML version of this page: https://verifymcp.io/servers/praxec-cpm-planner/ghcr-io-praxec-cpm-planner
