pmcontrols-mcp 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Atakan Arikan
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: pmcontrols-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for pmcontrols: validated project scheduling and earned value tools for AI agents.
5
+ Author: Atakan Arikan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/arikanatakan/pmcontrols-mcp
8
+ Project-URL: Repository, https://github.com/arikanatakan/pmcontrols-mcp
9
+ Project-URL: Issues, https://github.com/arikanatakan/pmcontrols-mcp/issues
10
+ Keywords: mcp,model-context-protocol,ai-agents,project-management,critical-path,cpm,pert,earned-value,earned-schedule,project-controls
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Office/Business :: Scheduling
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pmcontrols>=0.1.0
24
+ Requires-Dist: mcp>=1.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Requires-Dist: build; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ <!-- mcp-name: io.github.arikanatakan/pmcontrols-mcp -->
32
+
33
+ # pmcontrols-mcp
34
+
35
+ [![CI](https://github.com/arikanatakan/pmcontrols-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/pmcontrols-mcp/actions/workflows/ci.yml)
36
+ [![License: MIT](https://img.shields.io/github/license/arikanatakan/pmcontrols-mcp)](LICENSE)
37
+
38
+ An MCP server that exposes [pmcontrols](https://github.com/arikanatakan/pmcontrols),
39
+ the validated project scheduling and earned value library for Python, as tools
40
+ for AI agents.
41
+
42
+ Agents asked to plan a project or report its status tend to generate the
43
+ arithmetic themselves: a backward pass done by eye, an earned-value index
44
+ inverted, an earned schedule mistaken for schedule variance. Generated project
45
+ metrics fail silently. The calculation belongs in a deterministic, versioned,
46
+ validated library that the agent calls, which leaves the agent to choose the
47
+ analysis and explain the result.
48
+
49
+ ## Tools
50
+
51
+ | Tool | Purpose |
52
+ | ---- | ------- |
53
+ | `critical_path` | CPM forward and backward pass: ES, EF, LS, LF, slack, critical path |
54
+ | `schedule_risk` | PERT three-point analysis with a Monte Carlo completion distribution and criticality indices |
55
+ | `crash_schedule` | minimum-cost schedule compression to a deadline, solved as a linear program |
56
+ | `earned_value` | the full EVM indicator set with Lipke earned schedule, against a planned-value baseline |
57
+ | `earned_schedule` | the earned schedule for a given earned value |
58
+
59
+ Each tool returns the library's structured payload: named statistics, a tidy
60
+ table, structured alerts, and provenance (library version, input hash,
61
+ timestamp).
62
+
63
+ ## Installation
64
+
65
+ ```
66
+ pip install pmcontrols-mcp
67
+ ```
68
+
69
+ Or run it without installing, with [uv](https://docs.astral.sh/uv/):
70
+
71
+ ```
72
+ uvx pmcontrols-mcp
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ Add the server to your MCP client's configuration:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "pmcontrols": {
83
+ "command": "pmcontrols-mcp"
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ The server communicates over stdio and works with any MCP-compatible client.
90
+
91
+ ## Example
92
+
93
+ Calling `critical_path` with a list of activities returns a structured
94
+ result the agent reads directly, instead of computing the schedule itself:
95
+
96
+ ```json
97
+ {
98
+ "method": "cpm",
99
+ "stats": {"project_duration": 15.0, "n_activities": 8.0, "n_critical": 5.0},
100
+ "meta": {
101
+ "critical_activities": ["A", "C", "E", "G", "H"],
102
+ "version": "0.1.0",
103
+ "input_hash": "sha256:...",
104
+ "computed_at": "2026-06-15T09:14:02+00:00"
105
+ },
106
+ "table": {"activity": ["A", "B", "..."], "slack": [0.0, 1.0, "..."]}
107
+ }
108
+ ```
109
+
110
+ Every result carries provenance (library version, input hash, timestamp), so
111
+ a figure an agent reports can be recomputed and audited later.
112
+
113
+ ## Design
114
+
115
+ The reasoning behind routing project-control arithmetic through a validated
116
+ tool, rather than letting a model generate it, is set out in
117
+ [Project control is not a language task](https://arikanatakan.github.io/pmcontrols/agents/).
118
+
119
+ ## License
120
+
121
+ MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
122
+ MSc Student at Tsinghua University and Politecnico di Milano.
@@ -0,0 +1,92 @@
1
+ <!-- mcp-name: io.github.arikanatakan/pmcontrols-mcp -->
2
+
3
+ # pmcontrols-mcp
4
+
5
+ [![CI](https://github.com/arikanatakan/pmcontrols-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/pmcontrols-mcp/actions/workflows/ci.yml)
6
+ [![License: MIT](https://img.shields.io/github/license/arikanatakan/pmcontrols-mcp)](LICENSE)
7
+
8
+ An MCP server that exposes [pmcontrols](https://github.com/arikanatakan/pmcontrols),
9
+ the validated project scheduling and earned value library for Python, as tools
10
+ for AI agents.
11
+
12
+ Agents asked to plan a project or report its status tend to generate the
13
+ arithmetic themselves: a backward pass done by eye, an earned-value index
14
+ inverted, an earned schedule mistaken for schedule variance. Generated project
15
+ metrics fail silently. The calculation belongs in a deterministic, versioned,
16
+ validated library that the agent calls, which leaves the agent to choose the
17
+ analysis and explain the result.
18
+
19
+ ## Tools
20
+
21
+ | Tool | Purpose |
22
+ | ---- | ------- |
23
+ | `critical_path` | CPM forward and backward pass: ES, EF, LS, LF, slack, critical path |
24
+ | `schedule_risk` | PERT three-point analysis with a Monte Carlo completion distribution and criticality indices |
25
+ | `crash_schedule` | minimum-cost schedule compression to a deadline, solved as a linear program |
26
+ | `earned_value` | the full EVM indicator set with Lipke earned schedule, against a planned-value baseline |
27
+ | `earned_schedule` | the earned schedule for a given earned value |
28
+
29
+ Each tool returns the library's structured payload: named statistics, a tidy
30
+ table, structured alerts, and provenance (library version, input hash,
31
+ timestamp).
32
+
33
+ ## Installation
34
+
35
+ ```
36
+ pip install pmcontrols-mcp
37
+ ```
38
+
39
+ Or run it without installing, with [uv](https://docs.astral.sh/uv/):
40
+
41
+ ```
42
+ uvx pmcontrols-mcp
43
+ ```
44
+
45
+ ## Configuration
46
+
47
+ Add the server to your MCP client's configuration:
48
+
49
+ ```json
50
+ {
51
+ "mcpServers": {
52
+ "pmcontrols": {
53
+ "command": "pmcontrols-mcp"
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ The server communicates over stdio and works with any MCP-compatible client.
60
+
61
+ ## Example
62
+
63
+ Calling `critical_path` with a list of activities returns a structured
64
+ result the agent reads directly, instead of computing the schedule itself:
65
+
66
+ ```json
67
+ {
68
+ "method": "cpm",
69
+ "stats": {"project_duration": 15.0, "n_activities": 8.0, "n_critical": 5.0},
70
+ "meta": {
71
+ "critical_activities": ["A", "C", "E", "G", "H"],
72
+ "version": "0.1.0",
73
+ "input_hash": "sha256:...",
74
+ "computed_at": "2026-06-15T09:14:02+00:00"
75
+ },
76
+ "table": {"activity": ["A", "B", "..."], "slack": [0.0, 1.0, "..."]}
77
+ }
78
+ ```
79
+
80
+ Every result carries provenance (library version, input hash, timestamp), so
81
+ a figure an agent reports can be recomputed and audited later.
82
+
83
+ ## Design
84
+
85
+ The reasoning behind routing project-control arithmetic through a validated
86
+ tool, rather than letting a model generate it, is set out in
87
+ [Project control is not a language task](https://arikanatakan.github.io/pmcontrols/agents/).
88
+
89
+ ## License
90
+
91
+ MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
92
+ MSc Student at Tsinghua University and Politecnico di Milano.
@@ -0,0 +1,8 @@
1
+ """pmcontrols-mcp: project scheduling and earned value tools for AI agents.
2
+
3
+ A Model Context Protocol server that exposes pmcontrols (critical path,
4
+ PERT, schedule crashing, earned value, earned schedule) as validated tools,
5
+ so an agent calls a checked function instead of doing the math in tokens.
6
+ """
7
+
8
+ __version__ = "0.1.0"
@@ -0,0 +1,4 @@
1
+ from .server import main
2
+
3
+ if __name__ == "__main__":
4
+ main()
@@ -0,0 +1,118 @@
1
+ """Tool functions and their input schemas, kept free of the MCP SDK so they
2
+ can be tested directly.
3
+
4
+ Each activity type is a typed model, so the server advertises exactly which
5
+ fields an agent must supply. Each tool calls pmcontrols and returns the
6
+ analysis as a JSON-safe dict (``Result.to_dict()``) with stats, a tidy
7
+ table, structured alerts, and provenance.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import pmcontrols as pm
13
+ from pydantic import BaseModel, Field
14
+
15
+
16
+ class CpmActivity(BaseModel):
17
+ """One activity in a deterministic schedule network."""
18
+
19
+ id: str = Field(description="Unique activity identifier.")
20
+ predecessors: list[str] = Field(
21
+ default_factory=list,
22
+ description="Ids of the immediate predecessor activities; empty to start.",
23
+ )
24
+ duration: float = Field(description="Activity duration in consistent time units.")
25
+
26
+
27
+ class PertActivity(BaseModel):
28
+ """One activity with a three-point (optimistic/likely/pessimistic) estimate."""
29
+
30
+ id: str = Field(description="Unique activity identifier.")
31
+ predecessors: list[str] = Field(
32
+ default_factory=list,
33
+ description="Ids of the immediate predecessor activities; empty to start.",
34
+ )
35
+ a: float = Field(description="Optimistic duration.")
36
+ m: float = Field(description="Most likely duration.")
37
+ b: float = Field(description="Pessimistic duration.")
38
+
39
+
40
+ class CrashActivity(BaseModel):
41
+ """One activity with normal and fully-crashed durations and a crash cost."""
42
+
43
+ id: str = Field(description="Unique activity identifier.")
44
+ predecessors: list[str] = Field(
45
+ default_factory=list,
46
+ description="Ids of the immediate predecessor activities; empty to start.",
47
+ )
48
+ duration: float = Field(description="Normal duration.")
49
+ crash_duration: float = Field(description="Fully-crashed (minimum) duration.")
50
+ crash_cost: float = Field(description="Crash cost per period of compression.")
51
+
52
+
53
+ def _as_dicts(items: list) -> list[dict]:
54
+ """Accept either pydantic models (from the MCP layer) or plain dicts."""
55
+ return [i if isinstance(i, dict) else i.model_dump() for i in items]
56
+
57
+
58
+ def critical_path(activities: list[CpmActivity]) -> dict:
59
+ """Critical path analysis (CPM) of an activity-on-node network.
60
+
61
+ Runs the forward and backward pass and returns earliest/latest start and
62
+ finish, total slack, and the zero-float critical path.
63
+ """
64
+ return pm.cpm(_as_dicts(activities)).to_dict()
65
+
66
+
67
+ def schedule_risk(
68
+ activities: list[PertActivity],
69
+ n_sim: int = 20000,
70
+ seed: int | None = 0,
71
+ ) -> dict:
72
+ """PERT three-point analysis with a Monte Carlo schedule-risk simulation.
73
+
74
+ Returns the analytic expected duration and standard deviation, the
75
+ simulated completion distribution (p50/p80/p95), and a per-activity
76
+ criticality index. Pass ``seed=null`` for a fresh random draw.
77
+ """
78
+ return pm.pert(_as_dicts(activities), n_sim=n_sim, seed=seed).to_dict()
79
+
80
+
81
+ def crash_schedule(activities: list[CrashActivity], target: float) -> dict:
82
+ """Minimum-cost schedule compression to a target completion time.
83
+
84
+ Returns the globally optimal crash amount per activity, the resulting
85
+ schedule, and the total crash cost, solved as a linear program rather
86
+ than by greedy marginal-cost crashing.
87
+ """
88
+ return pm.crash(_as_dicts(activities), target=target).to_dict()
89
+
90
+
91
+ def earned_value(
92
+ periods: list[float],
93
+ pv: list[float],
94
+ ev: float,
95
+ ac: float,
96
+ at: float,
97
+ thresholds: dict[str, float] | None = None,
98
+ ) -> dict:
99
+ """Earned value status against a time-phased planned-value baseline.
100
+
101
+ ``periods`` and ``pv`` define the cumulative planned-value curve. Given
102
+ earned value ``ev``, actual cost ``ac`` and actual time ``at``, returns
103
+ the full indicator set (CV, SV, CPI, SPI, the EAC family, TCPI, VAC) plus
104
+ Lipke's earned schedule (ES, SPI(t), IEAC(t)) and any threshold alerts.
105
+ """
106
+ pmb = pm.plan(periods, pv)
107
+ return pm.evm(pmb, ev=ev, ac=ac, at=at, thresholds=thresholds).to_dict()
108
+
109
+
110
+ def earned_schedule(periods: list[float], pv: list[float], ev: float) -> dict:
111
+ """Lipke earned schedule (ES) for a given earned value.
112
+
113
+ ``periods`` and ``pv`` define the cumulative planned-value curve. Returns
114
+ the time at which the plan had earned what has now been earned, by linear
115
+ interpolation on the curve.
116
+ """
117
+ pmb = pm.plan(periods, pv)
118
+ return {"es": pm.earned_schedule(pmb, ev)}
@@ -0,0 +1,43 @@
1
+ """The MCP server: registers the pmcontrols tools and runs over stdio.
2
+
3
+ All tools are pure, read-only computations, marked with annotations so a
4
+ client can present and auto-run them safely.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from mcp.server.fastmcp import FastMCP
10
+ from mcp.types import ToolAnnotations
11
+
12
+ from . import _tools
13
+
14
+ mcp = FastMCP("pmcontrols")
15
+
16
+
17
+ def _annotations(title: str, idempotent: bool = True) -> ToolAnnotations:
18
+ return ToolAnnotations(
19
+ title=title,
20
+ readOnlyHint=True,
21
+ idempotentHint=idempotent,
22
+ openWorldHint=False,
23
+ )
24
+
25
+
26
+ mcp.tool(annotations=_annotations("Critical path (CPM)"))(_tools.critical_path)
27
+ mcp.tool(
28
+ annotations=_annotations("Schedule risk (PERT + Monte Carlo)", idempotent=False)
29
+ )(_tools.schedule_risk)
30
+ mcp.tool(annotations=_annotations("Schedule crashing (minimum-cost LP)"))(
31
+ _tools.crash_schedule
32
+ )
33
+ mcp.tool(annotations=_annotations("Earned value status"))(_tools.earned_value)
34
+ mcp.tool(annotations=_annotations("Earned schedule"))(_tools.earned_schedule)
35
+
36
+
37
+ def main() -> None:
38
+ """Console-script entry point: run the server on stdio."""
39
+ mcp.run()
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -0,0 +1,122 @@
1
+ Metadata-Version: 2.4
2
+ Name: pmcontrols-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for pmcontrols: validated project scheduling and earned value tools for AI agents.
5
+ Author: Atakan Arikan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/arikanatakan/pmcontrols-mcp
8
+ Project-URL: Repository, https://github.com/arikanatakan/pmcontrols-mcp
9
+ Project-URL: Issues, https://github.com/arikanatakan/pmcontrols-mcp/issues
10
+ Keywords: mcp,model-context-protocol,ai-agents,project-management,critical-path,cpm,pert,earned-value,earned-schedule,project-controls
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Science/Research
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering
19
+ Classifier: Topic :: Office/Business :: Scheduling
20
+ Requires-Python: >=3.10
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: pmcontrols>=0.1.0
24
+ Requires-Dist: mcp>=1.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7; extra == "dev"
27
+ Requires-Dist: ruff; extra == "dev"
28
+ Requires-Dist: build; extra == "dev"
29
+ Dynamic: license-file
30
+
31
+ <!-- mcp-name: io.github.arikanatakan/pmcontrols-mcp -->
32
+
33
+ # pmcontrols-mcp
34
+
35
+ [![CI](https://github.com/arikanatakan/pmcontrols-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/pmcontrols-mcp/actions/workflows/ci.yml)
36
+ [![License: MIT](https://img.shields.io/github/license/arikanatakan/pmcontrols-mcp)](LICENSE)
37
+
38
+ An MCP server that exposes [pmcontrols](https://github.com/arikanatakan/pmcontrols),
39
+ the validated project scheduling and earned value library for Python, as tools
40
+ for AI agents.
41
+
42
+ Agents asked to plan a project or report its status tend to generate the
43
+ arithmetic themselves: a backward pass done by eye, an earned-value index
44
+ inverted, an earned schedule mistaken for schedule variance. Generated project
45
+ metrics fail silently. The calculation belongs in a deterministic, versioned,
46
+ validated library that the agent calls, which leaves the agent to choose the
47
+ analysis and explain the result.
48
+
49
+ ## Tools
50
+
51
+ | Tool | Purpose |
52
+ | ---- | ------- |
53
+ | `critical_path` | CPM forward and backward pass: ES, EF, LS, LF, slack, critical path |
54
+ | `schedule_risk` | PERT three-point analysis with a Monte Carlo completion distribution and criticality indices |
55
+ | `crash_schedule` | minimum-cost schedule compression to a deadline, solved as a linear program |
56
+ | `earned_value` | the full EVM indicator set with Lipke earned schedule, against a planned-value baseline |
57
+ | `earned_schedule` | the earned schedule for a given earned value |
58
+
59
+ Each tool returns the library's structured payload: named statistics, a tidy
60
+ table, structured alerts, and provenance (library version, input hash,
61
+ timestamp).
62
+
63
+ ## Installation
64
+
65
+ ```
66
+ pip install pmcontrols-mcp
67
+ ```
68
+
69
+ Or run it without installing, with [uv](https://docs.astral.sh/uv/):
70
+
71
+ ```
72
+ uvx pmcontrols-mcp
73
+ ```
74
+
75
+ ## Configuration
76
+
77
+ Add the server to your MCP client's configuration:
78
+
79
+ ```json
80
+ {
81
+ "mcpServers": {
82
+ "pmcontrols": {
83
+ "command": "pmcontrols-mcp"
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ The server communicates over stdio and works with any MCP-compatible client.
90
+
91
+ ## Example
92
+
93
+ Calling `critical_path` with a list of activities returns a structured
94
+ result the agent reads directly, instead of computing the schedule itself:
95
+
96
+ ```json
97
+ {
98
+ "method": "cpm",
99
+ "stats": {"project_duration": 15.0, "n_activities": 8.0, "n_critical": 5.0},
100
+ "meta": {
101
+ "critical_activities": ["A", "C", "E", "G", "H"],
102
+ "version": "0.1.0",
103
+ "input_hash": "sha256:...",
104
+ "computed_at": "2026-06-15T09:14:02+00:00"
105
+ },
106
+ "table": {"activity": ["A", "B", "..."], "slack": [0.0, 1.0, "..."]}
107
+ }
108
+ ```
109
+
110
+ Every result carries provenance (library version, input hash, timestamp), so
111
+ a figure an agent reports can be recomputed and audited later.
112
+
113
+ ## Design
114
+
115
+ The reasoning behind routing project-control arithmetic through a validated
116
+ tool, rather than letting a model generate it, is set out in
117
+ [Project control is not a language task](https://arikanatakan.github.io/pmcontrols/agents/).
118
+
119
+ ## License
120
+
121
+ MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
122
+ MSc Student at Tsinghua University and Politecnico di Milano.
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ pmcontrols_mcp/__init__.py
5
+ pmcontrols_mcp/__main__.py
6
+ pmcontrols_mcp/_tools.py
7
+ pmcontrols_mcp/server.py
8
+ pmcontrols_mcp.egg-info/PKG-INFO
9
+ pmcontrols_mcp.egg-info/SOURCES.txt
10
+ pmcontrols_mcp.egg-info/dependency_links.txt
11
+ pmcontrols_mcp.egg-info/entry_points.txt
12
+ pmcontrols_mcp.egg-info/requires.txt
13
+ pmcontrols_mcp.egg-info/top_level.txt
14
+ tests/test_tools.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ pmcontrols-mcp = pmcontrols_mcp.server:main
@@ -0,0 +1,7 @@
1
+ pmcontrols>=0.1.0
2
+ mcp>=1.0
3
+
4
+ [dev]
5
+ pytest>=7
6
+ ruff
7
+ build
@@ -0,0 +1 @@
1
+ pmcontrols_mcp
@@ -0,0 +1,49 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pmcontrols-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for pmcontrols: validated project scheduling and earned value tools for AI agents."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Atakan Arikan" }]
12
+ requires-python = ">=3.10"
13
+ dependencies = ["pmcontrols>=0.1.0", "mcp>=1.0"]
14
+ keywords = [
15
+ "mcp", "model-context-protocol", "ai-agents", "project-management",
16
+ "critical-path", "cpm", "pert", "earned-value", "earned-schedule",
17
+ "project-controls",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Science/Research",
22
+ "License :: OSI Approved :: MIT License",
23
+ "Programming Language :: Python :: 3",
24
+ "Programming Language :: Python :: 3.10",
25
+ "Programming Language :: Python :: 3.11",
26
+ "Programming Language :: Python :: 3.12",
27
+ "Topic :: Scientific/Engineering",
28
+ "Topic :: Office/Business :: Scheduling",
29
+ ]
30
+
31
+ [project.scripts]
32
+ pmcontrols-mcp = "pmcontrols_mcp.server:main"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=7", "ruff", "build"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/arikanatakan/pmcontrols-mcp"
39
+ Repository = "https://github.com/arikanatakan/pmcontrols-mcp"
40
+ Issues = "https://github.com/arikanatakan/pmcontrols-mcp/issues"
41
+
42
+ [tool.setuptools.packages.find]
43
+ include = ["pmcontrols_mcp*"]
44
+
45
+ [tool.ruff]
46
+ line-length = 88
47
+
48
+ [tool.pytest.ini_options]
49
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,69 @@
1
+ """Tests for the tool functions (no MCP client needed).
2
+
3
+ The numbers are the General Foundry network and a linear PMB, the same
4
+ reference cases pmcontrols itself validates against.
5
+ """
6
+
7
+ import pmcontrols_mcp._tools as t
8
+
9
+ FOUNDRY = [
10
+ {"id": "A", "predecessors": [], "duration": 2},
11
+ {"id": "B", "predecessors": [], "duration": 3},
12
+ {"id": "C", "predecessors": ["A"], "duration": 2},
13
+ {"id": "D", "predecessors": ["B"], "duration": 4},
14
+ {"id": "E", "predecessors": ["C"], "duration": 4},
15
+ {"id": "F", "predecessors": ["C"], "duration": 3},
16
+ {"id": "G", "predecessors": ["D", "E"], "duration": 5},
17
+ {"id": "H", "predecessors": ["F", "G"], "duration": 2},
18
+ ]
19
+
20
+ CRASH = [
21
+ {"id": "A", "predecessors": [], "duration": 2, "crash_duration": 1, "crash_cost": 1000},
22
+ {"id": "B", "predecessors": [], "duration": 3, "crash_duration": 1, "crash_cost": 2000},
23
+ {"id": "C", "predecessors": ["A"], "duration": 2, "crash_duration": 1, "crash_cost": 1000},
24
+ {"id": "D", "predecessors": ["B"], "duration": 4, "crash_duration": 3, "crash_cost": 1000},
25
+ {"id": "E", "predecessors": ["C"], "duration": 4, "crash_duration": 2, "crash_cost": 1000},
26
+ {"id": "F", "predecessors": ["C"], "duration": 3, "crash_duration": 2, "crash_cost": 500},
27
+ {"id": "G", "predecessors": ["D", "E"], "duration": 5, "crash_duration": 2, "crash_cost": 2000},
28
+ {"id": "H", "predecessors": ["F", "G"], "duration": 2, "crash_duration": 1, "crash_cost": 3000},
29
+ ]
30
+
31
+ PERIODS = list(range(11))
32
+ PV = [i * 10000 for i in PERIODS]
33
+
34
+
35
+ def test_critical_path():
36
+ d = t.critical_path(FOUNDRY)
37
+ assert d["method"] == "cpm"
38
+ assert d["stats"]["project_duration"] == 15.0
39
+ assert d["meta"]["critical_activities"] == ["A", "C", "E", "G", "H"]
40
+
41
+
42
+ def test_schedule_risk():
43
+ tp = [
44
+ {"id": a["id"], "predecessors": a["predecessors"],
45
+ "a": a["duration"] * 0.8, "m": a["duration"], "b": a["duration"] * 1.3}
46
+ for a in FOUNDRY
47
+ ]
48
+ d = t.schedule_risk(tp, n_sim=2000, seed=1)
49
+ assert d["method"] == "pert"
50
+ assert "mc_p80" in d["stats"]
51
+ assert "criticality_index" in d["table"]
52
+
53
+
54
+ def test_crash_schedule():
55
+ d = t.crash_schedule(CRASH, target=13)
56
+ assert d["method"] == "crash"
57
+ assert d["stats"]["total_crash_cost"] == 3000.0
58
+
59
+
60
+ def test_earned_value():
61
+ d = t.earned_value(PERIODS, PV, ev=30000, ac=35000, at=4)
62
+ assert d["method"] == "evm"
63
+ assert round(d["stats"]["cpi"], 4) == 0.8571
64
+ assert {a["indicator"] for a in d["alerts"]} == {"cpi", "spi_t"}
65
+
66
+
67
+ def test_earned_schedule():
68
+ d = t.earned_schedule(PERIODS, PV, ev=34000)
69
+ assert d["es"] == 3.4