pmo.run MCP server — classic PMO decision tools (PERT, Monte Carlo, TCO, EVM) over the Model Context Protocol.
Project description
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.
Status: v0.1. Four classic PMO tools, stdio transport, structured errors. Published to PyPI as
pmorun-mcp— install it or run it from a source checkout (see Install). The hosted lane (Streamable HTTP + auth) and the calibration-driven tools are parked for v0.2 — 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.1.1 # pinned to this release (recommended)
The pinned form (@0.1.1) 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.1.1" # or: pip install "pmorun-mcp==0.1.1"
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.1.1"]
}
}
}
Pinning the args to pmorun-mcp@0.1.1 is recommended; use ["pmorun-mcp"] to
track the latest release instead. Restart Claude Desktop; the four tools appear
under the pmo-logic server.
For Claude Code:
claude mcp add pmo-logic -- uvx pmorun-mcp@0.1.1 # or drop @0.1.1 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.
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, each leading with a decision
question, each exposing input and output schemas), one worked example per tool,
seed-42 determinism on the Monte Carlo tool, and the structured-error contract.
Implementation-grade maths sweeps live in tests/{pert,montecarlo,tco,evm}/
already — we do not duplicate them here.
Out of scope (v0.2+)
- Streamable HTTP transport, OAuth, hosting, rate limiting, audit logging — the paid hosted lane. v0.1 is stdio-only and local-trust.
- Calibration-driven and stochastic-mix tools —
estimate_from_history(two-layer calibration) is parked intools.py; the Bayesian and Dirichlet-drift tools are parked too. All wait on field data to ground their calibration before they meet the v0.1 quality bar.
Licence
MIT — same as the rest of the logic repo.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pmorun_mcp-0.1.1.tar.gz.
File metadata
- Download URL: pmorun_mcp-0.1.1.tar.gz
- Upload date:
- Size: 55.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cdb232e077d33848d6a267243747bb6171bc920f3b9ad96f70ce5f3325526e54
|
|
| MD5 |
e5a52c7bac9ef9063351ff9a0f0bfc5c
|
|
| BLAKE2b-256 |
ada786b86a05498e85927966dc6e1f472a6480b4c3e498618ee98fc4d17fb5c2
|
Provenance
The following attestation bundles were made for pmorun_mcp-0.1.1.tar.gz:
Publisher:
release.yml on lemur47/logic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pmorun_mcp-0.1.1.tar.gz -
Subject digest:
cdb232e077d33848d6a267243747bb6171bc920f3b9ad96f70ce5f3325526e54 - Sigstore transparency entry: 1923373579
- Sigstore integration time:
-
Permalink:
lemur47/logic@66bae4f3557acea293aa735a921d1765492c5462 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/lemur47
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66bae4f3557acea293aa735a921d1765492c5462 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pmorun_mcp-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pmorun_mcp-0.1.1-py3-none-any.whl
- Upload date:
- Size: 61.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7db0133ba4a4820755224842522248c0909442461d56271c4b2347589fbddb1
|
|
| MD5 |
67b48f022d65d24a02eec89a03766ecc
|
|
| BLAKE2b-256 |
280caaa7c276c26b4c8260869f3c7cfa6d76a631683b7f5766a91e3b67e87b6a
|
Provenance
The following attestation bundles were made for pmorun_mcp-0.1.1-py3-none-any.whl:
Publisher:
release.yml on lemur47/logic
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pmorun_mcp-0.1.1-py3-none-any.whl -
Subject digest:
e7db0133ba4a4820755224842522248c0909442461d56271c4b2347589fbddb1 - Sigstore transparency entry: 1923373706
- Sigstore integration time:
-
Permalink:
lemur47/logic@66bae4f3557acea293aa735a921d1765492c5462 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/lemur47
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@66bae4f3557acea293aa735a921d1765492c5462 -
Trigger Event:
push
-
Statement type: