Skip to main content

pmo.run MCP Server

An MCP server that exposes the pmo.run decision-logic modules — PERT, Monte Carlo, TCO and EVM — as tools an LLM can call. It is one half of the composition pattern that makes pmo.run useful: a data-source MCP (Airtable, GitHub) feeds records in; this server runs the maths; Claude orchestrates and narrates the result.

What this is. Four classic PMO tools, stdio transport, structured errors, plus an opt-in calibration memory (a local SQLite estimation log — see Calibration memory), which takes the surface from four tools to eight when you set PMORUN_DB. Published to PyPI as pmorun-mcp — install it or run it from a source checkout (see Install). Version history: CHANGELOG. The hosted lane (Streamable HTTP + auth) is a separate product, not a later version of this package — see Out of scope.

What's in the box

Each tool is a thin adapter over the corresponding app.{module}.core function, with inputs and outputs validated by the same Pydantic models as the FastAPI surface — one source of truth, no duplication.

Tool Decision question Wraps
estimate_task_duration "How long will this single task take, given a three-point estimate and known frictions?" app.pert.core.calculate_task
identify_schedule_risk "Across this task network, how long are we likely to take and which tasks drive the risk?" app.montecarlo.core.simulate_schedule
compare_investment_options "Of these vendor / platform / tool options, which is cheapest on real lifetime cost?" app.tco.core.compare_options
evaluate_project_health "Given PV / EV / AC / BAC, are we on track, at risk, or off track?" app.evm.core.evm_metrics + health_signal

Tools are named verb-noun (estimate_*, identify_*, compare_*, evaluate_*), not by acronym. LLMs pick tools by purpose, not by domain shorthand — so every description leads with a "Use when:" decision question, documents every parameter, and states the units of every output.

How it fits into the stack

┌─────────────────────────────────────────────────────────────────┐
│  Claude (orchestrator) — composes tools into a decision          │
└──────────────┬──────────────────────────┬───────────────────────┘
               │                          │
   ┌───────────▼───────────┐  ┌───────────▼───────────┐
   │  Data MCP (Airtable,  │  │  pmo-logic MCP        │
   │  GitHub, etc.)        │  │  (this server)        │
   └───────────────────────┘  └───────────┬───────────┘
                                          │ imports
                                          │ pure-function core
                              ┌───────────▼───────────┐
                              │  app.{module}.core    │
                              │  (MIT-licensed maths) │
                              └───────────────────────┘

The server imports only app.{module}.core — never the routers or the SQLite store directly — so it runs without uvicorn or a database, and stays portable to a future TypeScript port that mirrors the same surface.

Install

The package is on PyPI as pmorun-mcp. The quickest path is to run it without installing — uvx fetches it into a throwaway environment:

uvx pmorun-mcp           # latest release
uvx pmorun-mcp@0.2.0     # pinned to this release (recommended)

The pinned form (@0.2.0) resolves to an immutable PyPI artefact: a reproducible install that won't be auto-pulled onto a future top-level release. Drop the pin to always track the latest.

Or install it into an environment of your own:

uv pip install "pmorun-mcp==0.2.0"   # or: pip install "pmorun-mcp==0.2.0"
pmorun-mcp                           # console script — same entry point as python -m mcp_server.server

To hack on the server itself, run it from a source checkout instead:

git clone https://github.com/lemur47/logic.git
cd logic
uv run python -m mcp_server.server

Wire it into Claude Desktop

Add this to claude_desktop_config.json (on macOS, ~/Library/Application Support/Claude/claude_desktop_config.json; the path differs on Linux/Windows):

{
  "mcpServers": {
    "pmo-logic": {
      "command": "uvx",
      "args": ["pmorun-mcp@0.2.0"]
    }
  }
}

Pinning the args to pmorun-mcp@0.2.0 is recommended; use ["pmorun-mcp"] to track the latest release instead. Restart Claude Desktop; the four classic tools appear under the pmo-logic server — plus four more if you set PMORUN_DB, as described under Calibration memory below.

For Claude Code:

claude mcp add pmo-logic -- uvx pmorun-mcp@0.2.0   # or drop @0.2.0 to track latest

Running from a source checkout instead? Swap the command for uv --directory /absolute/path/to/logic run python -m mcp_server.server.

Worked examples

One representative call per tool. Inputs are the shared Pydantic models, so these shapes match the FastAPI request bodies exactly.

estimate_task_duration (PERT)

A task estimated at 2 / 5 / 14 (optimistic / most-likely / pessimistic) days:

{ "task": { "optimistic": 2, "most_likely": 5, "pessimistic": 14 } }

Returns the textbook PERT expected duration of 6.0 days ((2 + 4·5 + 14) / 6) with a standard deviation of 2.0 ((14 − 2) / 6). Add insight tags to widen the pessimistic tail for known frictions:

{
  "task": {
    "optimistic": 2, "most_likely": 5, "pessimistic": 14,
    "tags": [{ "name": "FRAGMENTED_COMMUNICATION", "severity": 0.5 }]
  }
}

identify_schedule_risk (Monte Carlo)

A three-task chain (Design → Build → Test), 2,000 iterations, seeded for reproducibility:

{
  "tasks": [
    { "name": "Design", "optimistic": 3, "most_likely": 5, "pessimistic": 10 },
    { "name": "Build", "optimistic": 8, "most_likely": 14, "pessimistic": 25, "depends_on": ["Design"] },
    { "name": "Test", "optimistic": 3, "most_likely": 5, "pessimistic": 10, "depends_on": ["Build"] }
  ],
  "config": { "num_simulations": 2000, "seed": 42 }
}

Returns P50 ≈ 25.5 and P85 ≈ 29.6 days, plus the per-task critical_path_frequency (in this strict chain, 1.0 for every task — they all always sit on the critical path). seed defaults to 42 when omitted, so runs are reproducible by default; pass a different integer to vary the draw.

compare_investment_options (TCO)

Cloud (low upfront, high running cost) versus on-prem (high upfront, low running cost), each over three years:

{
  "request": {
    "options": [
      { "name": "Cloud", "initial_price": 5000, "useful_life_years": 3, "annual_operating_cost": 12000 },
      { "name": "On-prem", "initial_price": 40000, "useful_life_years": 3, "annual_maintenance": 3000 }
    ]
  }
}

Returns the options ranked by annual cost (rank 1 = cheapest). Here best_option is Cloud (≈ 13,667/yr versus ≈ 16,333/yr for on-prem).

evaluate_project_health (EVM)

A project a little behind and over budget:

{ "evm": { "pv": 1000, "ev": 900, "ac": 1100, "bac": 5000 } }

Returns SPI 0.9, CPI ≈ 0.82, the forecast eac/etc/vac/tcpi, and a health verdict of off_track with the reasons spelled out.

Calibration memory (opt-in)

By default the server is fully stateless — nothing is written anywhere, and the four tools above are the whole surface. Set the PMORUN_DB environment variable to a writable file path and four more tools register, backed by a local SQLite estimation log:

// Claude Desktop / claude mcp add — opt in via env
{
  "command": "uvx",
  "args": ["pmorun-mcp"],
  "env": { "PMORUN_DB": "/path/to/calibration.db" }
}

The database is stored unencrypted. It is a plain local SQLite file, and the task names, categories and estimates you record are readable by anything that can read that path. Choose a location covered by your existing disk encryption and file permissions, and treat it as you would any other working file — do not point PMORUN_DB at a shared or synced directory if the task names themselves are sensitive.

Tool Decision question Wraps
record_estimate "Log this three-point estimate so we can learn from it later." app.pert.core.calculate_task + the log
record_actual "The task is done — what did it actually take, and how wrong were we?" the log
summarise_calibration "How biased are our estimates, and what should this new estimate really be?" app.bayesian.core.update_belief / adjust_estimate
estimate_from_history "Given what similar tasks actually took, how long will this one take?" the two-layer estimator in tools.py, grounded in the log

The loop: record_estimate when you commit to an estimate → record_actual when the task completes → summarise_calibration learns the systematic delay factor from every completed pair via conjugate Bayesian updating (prior N(1.0, 0.25)); pass it a fresh pert_expected and it returns the bias-adjusted estimate with credible intervals. estimate_from_history — parked since v0.1 pending exactly this data source — reads the recorded actuals per task category and derives a calibrated three-point estimate from them.

Estimates are unit-agnostic (record the unit; "sessions", "days" and "hours" are all fine — the log stores, it never converts). The schema is deliberately D1-portable (plain SQLite DDL, ISO-8601 text timestamps): the local log is the reference data model for the hosted calibration memory.

Privacy note: the log is a plain local file you own. Nothing leaves the machine; delete the file and the memory is gone.

Errors

Every tool returns structured, tagged errors — never a Python traceback. Errors carry a type tag the model can reason about:

  • [ValidationError] — inputs are individually valid but jointly inconsistent (e.g. an unknown insight tag), or rejected before computation.
  • [ComputationError] — the underlying maths rejected the inputs (e.g. an optimistic estimate larger than the most-likely one, a non-positive budget).
  • [InternalError] — an unexpected failure, reported generically so no internal state leaks.

Field-level constraints (a negative cost, fewer than two options to compare) are caught by the shared Pydantic models and surfaced as structured validation messages.

Development

Tests live in tests/mcp_server/:

pytest tests/mcp_server/

They cover registration (exactly the four tools by default; the calibration tools join only when PMORUN_DB is set), one worked example per tool, seed-42 determinism on the Monte Carlo tool, the calibration round-trip and its Bayesian summary maths, and the structured-error contract. Implementation-grade maths sweeps live in tests/{pert,montecarlo,tco,evm,bayesian}/ already — we do not duplicate them here.

Out of scope for this package

  • Streamable HTTP transport, OAuth, hosting, rate limiting, audit logging — the paid hosted lane, which is a separate product rather than a later version of this package. pmorun-mcp is stdio-only and local-trust by design, and no future release of it changes that.
  • Stochastic-mix tools — the Dirichlet-drift tools remain parked, waiting on field data to ground their calibration. (estimate_from_history and the Bayesian calibration summary shipped with the opt-in calibration memory — their data-source precondition is now met by the estimation log.)

Licence

MIT — same as the rest of the logic repo.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pmorun_mcp-0.2.0.tar.gz (80.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pmorun_mcp-0.2.0-py3-none-any.whl (85.1 kB view details)

Uploaded Python 3

File details

Details for the file pmorun_mcp-0.2.0.tar.gz.

File metadata

  • Download URL: pmorun_mcp-0.2.0.tar.gz
  • Upload date:
  • Size: 80.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pmorun_mcp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c50f9235451eca38c639e4744e6758ef6afa6cea273bef769421f54a62f7ab31
MD5 9cb266d768fc60788422f0d5455cf45e
BLAKE2b-256 6d2dcae597daa398b2a1bcf0d563aa4170d7e488a7fe5f83355a8441b07a5f21

See more details on using hashes here.

Provenance

The following attestation bundles were made for pmorun_mcp-0.2.0.tar.gz:

Publisher: release.yml on lemur47/logic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file pmorun_mcp-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: pmorun_mcp-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 85.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for pmorun_mcp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 839d86c414a0701c89d754f64b3648b6ba0e06d0286083d583866bb141fdab5d
MD5 68f792682c69f4e60c610ae5b5f45203
BLAKE2b-256 bd53a16f70c19481673dc5a9d4377d08c6d518c97f4482213cd7710ca3b7a3b1

See more details on using hashes here.

Provenance

The following attestation bundles were made for pmorun_mcp-0.2.0-py3-none-any.whl:

Publisher: release.yml on lemur47/logic

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 files

0.1.1

2 files

0.1.0

2 files

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