oee-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.
oee_mcp-0.1.0/LICENSE ADDED
@@ -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.
oee_mcp-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: oee-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for oee: Overall Equipment Effectiveness, the six big losses and charts for AI agents.
5
+ Author: Atakan Arikan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/arikanatakan/oee-mcp
8
+ Project-URL: Repository, https://github.com/arikanatakan/oee-mcp
9
+ Project-URL: Issues, https://github.com/arikanatakan/oee-mcp/issues
10
+ Project-URL: Library, https://github.com/arikanatakan/oee
11
+ Keywords: mcp,model-context-protocol,ai-agents,oee,overall-equipment-effectiveness,manufacturing,tpm,teep,six-big-losses,production
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Manufacturing
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: oee[plot]>=0.1.2
25
+ Requires-Dist: mcp>=1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Requires-Dist: ruff; extra == "dev"
29
+ Requires-Dist: build; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ <!-- mcp-name: io.github.arikanatakan/oee-mcp -->
33
+
34
+ # oee-mcp
35
+
36
+ [![CI](https://github.com/arikanatakan/oee-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/oee-mcp/actions/workflows/ci.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/oee-mcp?v=2)](https://pypi.org/project/oee-mcp/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
+
40
+ An MCP server that exposes [oee](https://github.com/arikanatakan/oee), the
41
+ Overall Equipment Effectiveness library for Python, as tools for AI agents: give
42
+ it machine times and piece counts and it returns OEE, the time waterfall, the
43
+ six big losses, TEEP, and ready-to-show charts.
44
+
45
+ Agents asked to compute or report OEE tend to do the arithmetic themselves: a
46
+ performance figure inverted, schedule loss left out, or - the usual mistake -
47
+ OEE figures averaged across machines, which is wrong. Generated OEE fails
48
+ silently. The calculation belongs in a deterministic, versioned, validated
49
+ library that the agent calls, which leaves the agent to choose the analysis and
50
+ explain the result.
51
+
52
+ ## Tools
53
+
54
+ **Analysis tools** return the library's payload: the factors, the time
55
+ waterfall, the six big losses, TEEP, alerts and provenance.
56
+
57
+ | Tool | Purpose |
58
+ | ---- | ------- |
59
+ | `compute_oee` | OEE, the waterfall and the six big losses from times and counts |
60
+ | `oee_from_log` | OEE from an event log of production runs and downtime events |
61
+ | `oee_from_factors` | OEE from availability, performance and quality directly |
62
+ | `aggregate_oee` | roll OEE up across machines or shifts correctly (sums the buckets, never averages) |
63
+ | `describe_inputs` | the input fields, units and OEE definitions |
64
+
65
+ **Chart tools** return a PNG image.
66
+
67
+ | Tool | Purpose |
68
+ | ---- | ------- |
69
+ | `waterfall_chart` | the OEE time waterfall |
70
+ | `loss_pareto_chart` | a Pareto of the six big losses |
71
+ | `trend_chart` | OEE and the factors over a sequence of shifts |
72
+
73
+ All tools are read-only.
74
+
75
+ ## Installation
76
+
77
+ Run it with [uv](https://docs.astral.sh/uv/) (no install needed):
78
+
79
+ ```
80
+ uvx oee-mcp
81
+ ```
82
+
83
+ or install from PyPI:
84
+
85
+ ```
86
+ pip install oee-mcp
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ Add it to your MCP client. For example:
92
+
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "oee": {
97
+ "command": "uvx",
98
+ "args": ["oee-mcp"]
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ If you installed with pip, use `"command": "oee-mcp"` with no args.
105
+
106
+ ## Example
107
+
108
+ ```
109
+ compute_oee(machine={
110
+ "planned_production_time": 420, "downtime": 47, "ideal_rate": 60,
111
+ "total_count": 19271, "reject_count": 423, "all_time": 480
112
+ })
113
+ -> { "factors": { "availability": 0.888, "performance": 0.861,
114
+ "quality": 0.978, "oee": 0.748, "teep": 0.654 },
115
+ "summary": "oee - ...\n OEE 74.8% ..." }
116
+ ```
117
+
118
+ ## Design
119
+
120
+ The server is a thin, stateless wrapper. All of the arithmetic lives in the oee
121
+ library, which computes OEE from the standard definitions and is validated
122
+ against published worked examples (Vorne, TeepTrak) and the Nakajima world-class
123
+ benchmark. The server adds the tool schema, read-only annotations and an
124
+ input-schema helper so an agent can format the input and act on the result.
125
+
126
+ ## Related
127
+
128
+ - [oee](https://github.com/arikanatakan/oee): the library this server wraps.
129
+
130
+ ## License
131
+
132
+ MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
133
+ MSc Student at Tsinghua University and Politecnico di Milano.
@@ -0,0 +1,102 @@
1
+ <!-- mcp-name: io.github.arikanatakan/oee-mcp -->
2
+
3
+ # oee-mcp
4
+
5
+ [![CI](https://github.com/arikanatakan/oee-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/oee-mcp/actions/workflows/ci.yml)
6
+ [![PyPI](https://img.shields.io/pypi/v/oee-mcp?v=2)](https://pypi.org/project/oee-mcp/)
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
8
+
9
+ An MCP server that exposes [oee](https://github.com/arikanatakan/oee), the
10
+ Overall Equipment Effectiveness library for Python, as tools for AI agents: give
11
+ it machine times and piece counts and it returns OEE, the time waterfall, the
12
+ six big losses, TEEP, and ready-to-show charts.
13
+
14
+ Agents asked to compute or report OEE tend to do the arithmetic themselves: a
15
+ performance figure inverted, schedule loss left out, or - the usual mistake -
16
+ OEE figures averaged across machines, which is wrong. Generated OEE fails
17
+ silently. The calculation belongs in a deterministic, versioned, validated
18
+ library that the agent calls, which leaves the agent to choose the analysis and
19
+ explain the result.
20
+
21
+ ## Tools
22
+
23
+ **Analysis tools** return the library's payload: the factors, the time
24
+ waterfall, the six big losses, TEEP, alerts and provenance.
25
+
26
+ | Tool | Purpose |
27
+ | ---- | ------- |
28
+ | `compute_oee` | OEE, the waterfall and the six big losses from times and counts |
29
+ | `oee_from_log` | OEE from an event log of production runs and downtime events |
30
+ | `oee_from_factors` | OEE from availability, performance and quality directly |
31
+ | `aggregate_oee` | roll OEE up across machines or shifts correctly (sums the buckets, never averages) |
32
+ | `describe_inputs` | the input fields, units and OEE definitions |
33
+
34
+ **Chart tools** return a PNG image.
35
+
36
+ | Tool | Purpose |
37
+ | ---- | ------- |
38
+ | `waterfall_chart` | the OEE time waterfall |
39
+ | `loss_pareto_chart` | a Pareto of the six big losses |
40
+ | `trend_chart` | OEE and the factors over a sequence of shifts |
41
+
42
+ All tools are read-only.
43
+
44
+ ## Installation
45
+
46
+ Run it with [uv](https://docs.astral.sh/uv/) (no install needed):
47
+
48
+ ```
49
+ uvx oee-mcp
50
+ ```
51
+
52
+ or install from PyPI:
53
+
54
+ ```
55
+ pip install oee-mcp
56
+ ```
57
+
58
+ ## Configuration
59
+
60
+ Add it to your MCP client. For example:
61
+
62
+ ```json
63
+ {
64
+ "mcpServers": {
65
+ "oee": {
66
+ "command": "uvx",
67
+ "args": ["oee-mcp"]
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ If you installed with pip, use `"command": "oee-mcp"` with no args.
74
+
75
+ ## Example
76
+
77
+ ```
78
+ compute_oee(machine={
79
+ "planned_production_time": 420, "downtime": 47, "ideal_rate": 60,
80
+ "total_count": 19271, "reject_count": 423, "all_time": 480
81
+ })
82
+ -> { "factors": { "availability": 0.888, "performance": 0.861,
83
+ "quality": 0.978, "oee": 0.748, "teep": 0.654 },
84
+ "summary": "oee - ...\n OEE 74.8% ..." }
85
+ ```
86
+
87
+ ## Design
88
+
89
+ The server is a thin, stateless wrapper. All of the arithmetic lives in the oee
90
+ library, which computes OEE from the standard definitions and is validated
91
+ against published worked examples (Vorne, TeepTrak) and the Nakajima world-class
92
+ benchmark. The server adds the tool schema, read-only annotations and an
93
+ input-schema helper so an agent can format the input and act on the result.
94
+
95
+ ## Related
96
+
97
+ - [oee](https://github.com/arikanatakan/oee): the library this server wraps.
98
+
99
+ ## License
100
+
101
+ MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
102
+ MSc Student at Tsinghua University and Politecnico di Milano.
@@ -0,0 +1,5 @@
1
+ """oee-mcp: the oee Overall Equipment Effectiveness library exposed as MCP tools."""
2
+
3
+ from ._version import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,3 @@
1
+ from .server import main
2
+
3
+ main()
@@ -0,0 +1,176 @@
1
+ """Tool logic, kept free of the MCP SDK so it can be tested directly.
2
+
3
+ Each analysis tool calls the oee library and returns its JSON-safe payload
4
+ (factors, time waterfall, six big losses, provenance) plus a plain-language
5
+ summary. Chart helpers render a PNG.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import io
11
+
12
+ import matplotlib
13
+
14
+ matplotlib.use("Agg")
15
+
16
+ import oee # noqa: E402
17
+ from pydantic import BaseModel, Field # noqa: E402
18
+
19
+ SIGN_AND_UNITS = (
20
+ "All times must be in the same unit; ideal_cycle_time is that unit per piece "
21
+ "(or pass ideal_rate in pieces per that unit). Performance above 100% is "
22
+ "capped and flagged."
23
+ )
24
+
25
+ DEFINITIONS = [
26
+ "Availability = run time / planned production time",
27
+ "Performance = (ideal cycle time x total count) / run time",
28
+ "Quality = good count / total count",
29
+ "OEE = Availability x Performance x Quality",
30
+ "TEEP = OEE x utilization, with utilization = planned production time / all time",
31
+ "World-class OEE >= 85% (availability >= 90%, performance >= 95%, "
32
+ "quality >= 99.9%) [Nakajima, 1988]",
33
+ ]
34
+
35
+ SIX_BIG_LOSSES = [
36
+ "Availability: breakdowns, setup and adjustments",
37
+ "Performance: minor stops, reduced speed (reported combined)",
38
+ "Quality: process defects, reduced yield (startup rejects)",
39
+ ]
40
+
41
+
42
+ class MachineInput(BaseModel):
43
+ """One machine or shift, by times and piece counts."""
44
+
45
+ planned_production_time: float = Field(
46
+ description="Scheduled production time (any unit, used consistently).")
47
+ total_count: float = Field(description="Total pieces produced (good and bad).")
48
+ run_time: float | None = Field(
49
+ default=None, description="Actual run time; give this or downtime.")
50
+ downtime: float | None = Field(default=None, description="Stop time within planned time.")
51
+ ideal_cycle_time: float | None = Field(
52
+ default=None, description="Time per piece at max speed; give this or ideal_rate.")
53
+ ideal_rate: float | None = Field(default=None, description="Pieces per time unit at max speed.")
54
+ good_count: float | None = Field(
55
+ default=None, description="Good pieces; give this or reject_count.")
56
+ reject_count: float | None = Field(default=None, description="Rejected pieces.")
57
+ all_time: float | None = Field(
58
+ default=None, description="Total calendar time, for TEEP and utilization.")
59
+ setup_time: float | None = Field(
60
+ default=None,
61
+ description="Planned-stop time within downtime; splits availability into "
62
+ "setup-and-adjustments and breakdowns.")
63
+ startup_rejects: float | None = Field(
64
+ default=None,
65
+ description="Rejects at startup; splits quality into reduced yield and "
66
+ "process defects.")
67
+ name: str | None = Field(default=None, description="Optional label.")
68
+
69
+
70
+ class ProductionRun(BaseModel):
71
+ """One production run in an event log."""
72
+
73
+ count: float = Field(description="Pieces produced in this run.")
74
+ good: float | None = Field(default=None, description="Good pieces; give this or reject.")
75
+ reject: float | None = Field(default=None, description="Rejected pieces.")
76
+ ideal_cycle_time: float | None = Field(default=None, description="Time per piece at max speed.")
77
+ ideal_rate: float | None = Field(default=None, description="Pieces per time unit at max speed.")
78
+
79
+
80
+ class DowntimeEvent(BaseModel):
81
+ """One downtime event in an event log."""
82
+
83
+ reason: str = Field(description="Reason for the stop.")
84
+ duration: float = Field(description="Stop duration (same time unit as the runs).")
85
+ planned: bool = Field(default=False, description="True for planned stops (setup, changeover).")
86
+
87
+
88
+ def _payload(result) -> dict:
89
+ out = result.to_dict()
90
+ out["summary"] = result.summary()
91
+ return out
92
+
93
+
94
+ def _result(machine: MachineInput):
95
+ return oee.oee(**machine.model_dump(exclude_none=True))
96
+
97
+
98
+ def _png(ax) -> bytes:
99
+ import matplotlib.pyplot as plt
100
+
101
+ buf = io.BytesIO()
102
+ ax.figure.savefig(buf, format="png", dpi=150, bbox_inches="tight",
103
+ facecolor="white")
104
+ plt.close(ax.figure)
105
+ return buf.getvalue()
106
+
107
+
108
+ def compute_oee(machine: MachineInput) -> dict:
109
+ """OEE, the time waterfall and the six big losses from times and counts."""
110
+ try:
111
+ return _payload(_result(machine))
112
+ except (ValueError, TypeError) as exc:
113
+ return {"error": str(exc), "hint": "call describe_inputs for the fields and units"}
114
+
115
+
116
+ def oee_from_log(planned_production_time: float, runs: list[ProductionRun],
117
+ downtime_events: list[DowntimeEvent] | None = None,
118
+ all_time: float | None = None, startup_rejects: float | None = None,
119
+ target_oee: float = 0.85, name: str | None = None) -> dict:
120
+ """OEE from an event log of production runs and downtime events."""
121
+ try:
122
+ result = oee.from_log(
123
+ planned_production_time,
124
+ runs=[r.model_dump(exclude_none=True) for r in runs],
125
+ downtime_events=[e.model_dump() for e in downtime_events]
126
+ if downtime_events else None,
127
+ all_time=all_time, startup_rejects=startup_rejects,
128
+ target_oee=target_oee, name=name)
129
+ return _payload(result)
130
+ except (ValueError, TypeError, KeyError) as exc:
131
+ return {"error": str(exc), "hint": "call describe_inputs for the fields and units"}
132
+
133
+
134
+ def oee_from_factors(availability: float, performance: float, quality: float,
135
+ target_oee: float = 0.85, name: str | None = None) -> dict:
136
+ """OEE from the three factors directly (each between 0 and 1)."""
137
+ try:
138
+ return _payload(oee.oee_from_factors(availability, performance, quality,
139
+ target_oee=target_oee, name=name))
140
+ except (ValueError, TypeError) as exc:
141
+ return {"error": str(exc)}
142
+
143
+
144
+ def aggregate_oee(machines: list[MachineInput]) -> dict:
145
+ """Roll OEE up across machines or shifts correctly (sums the buckets)."""
146
+ try:
147
+ results = [_result(m) for m in machines]
148
+ rolled = oee.aggregate(results)
149
+ out = _payload(rolled)
150
+ out["machines"] = [{"name": r.name, "oee": r.oee} for r in results]
151
+ return out
152
+ except (ValueError, TypeError) as exc:
153
+ return {"error": str(exc), "hint": "call describe_inputs for the fields and units"}
154
+
155
+
156
+ def describe_inputs() -> dict:
157
+ """The input fields, units and OEE definitions, to format input correctly."""
158
+ return {
159
+ "sign_and_units": SIGN_AND_UNITS,
160
+ "machine_fields": {n: f.description
161
+ for n, f in MachineInput.model_fields.items()},
162
+ "definitions": DEFINITIONS,
163
+ "six_big_losses": SIX_BIG_LOSSES,
164
+ }
165
+
166
+
167
+ def waterfall_png(machine: MachineInput) -> bytes:
168
+ return _png(oee.waterfall(_result(machine)))
169
+
170
+
171
+ def loss_pareto_png(machine: MachineInput) -> bytes:
172
+ return _png(oee.losses_pareto(_result(machine)))
173
+
174
+
175
+ def trend_png(shifts: list[MachineInput]) -> bytes:
176
+ return _png(oee.trend([_result(m) for m in shifts], factors=True))
@@ -0,0 +1 @@
1
+ __version__ = "0.1.0"
@@ -0,0 +1,58 @@
1
+ """The MCP server: registers the oee tools and runs over stdio.
2
+
3
+ All tools are pure, read-only computations, marked with annotations so a client
4
+ can present and auto-run them safely.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from mcp.server.fastmcp import FastMCP, Image
10
+ from mcp.types import ToolAnnotations
11
+
12
+ from . import _tools
13
+ from ._tools import MachineInput
14
+
15
+ mcp = FastMCP("oee")
16
+
17
+
18
+ def _annotations(title: str) -> ToolAnnotations:
19
+ return ToolAnnotations(
20
+ title=title,
21
+ readOnlyHint=True,
22
+ idempotentHint=True,
23
+ openWorldHint=False,
24
+ )
25
+
26
+
27
+ mcp.tool(annotations=_annotations("Compute OEE"))(_tools.compute_oee)
28
+ mcp.tool(annotations=_annotations("OEE from an event log"))(_tools.oee_from_log)
29
+ mcp.tool(annotations=_annotations("OEE from the three factors"))(_tools.oee_from_factors)
30
+ mcp.tool(annotations=_annotations("Aggregate OEE across machines"))(_tools.aggregate_oee)
31
+ mcp.tool(annotations=_annotations("Describe the inputs"))(_tools.describe_inputs)
32
+
33
+
34
+ @mcp.tool(annotations=_annotations("OEE waterfall chart (PNG)"))
35
+ def waterfall_chart(machine: MachineInput) -> Image:
36
+ """Render the OEE time waterfall (planned to fully productive) as a PNG."""
37
+ return Image(data=_tools.waterfall_png(machine), format="png")
38
+
39
+
40
+ @mcp.tool(annotations=_annotations("Loss Pareto chart (PNG)"))
41
+ def loss_pareto_chart(machine: MachineInput) -> Image:
42
+ """Render a Pareto of the six big losses as a PNG image."""
43
+ return Image(data=_tools.loss_pareto_png(machine), format="png")
44
+
45
+
46
+ @mcp.tool(annotations=_annotations("OEE trend chart (PNG)"))
47
+ def trend_chart(shifts: list[MachineInput]) -> Image:
48
+ """Render the OEE and A/P/Q trend over a sequence of results as a PNG."""
49
+ return Image(data=_tools.trend_png(shifts), format="png")
50
+
51
+
52
+ def main() -> None:
53
+ """Console-script entry point: run the server on stdio."""
54
+ mcp.run()
55
+
56
+
57
+ if __name__ == "__main__":
58
+ main()
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: oee-mcp
3
+ Version: 0.1.0
4
+ Summary: MCP server for oee: Overall Equipment Effectiveness, the six big losses and charts for AI agents.
5
+ Author: Atakan Arikan
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/arikanatakan/oee-mcp
8
+ Project-URL: Repository, https://github.com/arikanatakan/oee-mcp
9
+ Project-URL: Issues, https://github.com/arikanatakan/oee-mcp/issues
10
+ Project-URL: Library, https://github.com/arikanatakan/oee
11
+ Keywords: mcp,model-context-protocol,ai-agents,oee,overall-equipment-effectiveness,manufacturing,tpm,teep,six-big-losses,production
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Intended Audience :: Manufacturing
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Topic :: Scientific/Engineering
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: oee[plot]>=0.1.2
25
+ Requires-Dist: mcp>=1.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7; extra == "dev"
28
+ Requires-Dist: ruff; extra == "dev"
29
+ Requires-Dist: build; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ <!-- mcp-name: io.github.arikanatakan/oee-mcp -->
33
+
34
+ # oee-mcp
35
+
36
+ [![CI](https://github.com/arikanatakan/oee-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/arikanatakan/oee-mcp/actions/workflows/ci.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/oee-mcp?v=2)](https://pypi.org/project/oee-mcp/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
+
40
+ An MCP server that exposes [oee](https://github.com/arikanatakan/oee), the
41
+ Overall Equipment Effectiveness library for Python, as tools for AI agents: give
42
+ it machine times and piece counts and it returns OEE, the time waterfall, the
43
+ six big losses, TEEP, and ready-to-show charts.
44
+
45
+ Agents asked to compute or report OEE tend to do the arithmetic themselves: a
46
+ performance figure inverted, schedule loss left out, or - the usual mistake -
47
+ OEE figures averaged across machines, which is wrong. Generated OEE fails
48
+ silently. The calculation belongs in a deterministic, versioned, validated
49
+ library that the agent calls, which leaves the agent to choose the analysis and
50
+ explain the result.
51
+
52
+ ## Tools
53
+
54
+ **Analysis tools** return the library's payload: the factors, the time
55
+ waterfall, the six big losses, TEEP, alerts and provenance.
56
+
57
+ | Tool | Purpose |
58
+ | ---- | ------- |
59
+ | `compute_oee` | OEE, the waterfall and the six big losses from times and counts |
60
+ | `oee_from_log` | OEE from an event log of production runs and downtime events |
61
+ | `oee_from_factors` | OEE from availability, performance and quality directly |
62
+ | `aggregate_oee` | roll OEE up across machines or shifts correctly (sums the buckets, never averages) |
63
+ | `describe_inputs` | the input fields, units and OEE definitions |
64
+
65
+ **Chart tools** return a PNG image.
66
+
67
+ | Tool | Purpose |
68
+ | ---- | ------- |
69
+ | `waterfall_chart` | the OEE time waterfall |
70
+ | `loss_pareto_chart` | a Pareto of the six big losses |
71
+ | `trend_chart` | OEE and the factors over a sequence of shifts |
72
+
73
+ All tools are read-only.
74
+
75
+ ## Installation
76
+
77
+ Run it with [uv](https://docs.astral.sh/uv/) (no install needed):
78
+
79
+ ```
80
+ uvx oee-mcp
81
+ ```
82
+
83
+ or install from PyPI:
84
+
85
+ ```
86
+ pip install oee-mcp
87
+ ```
88
+
89
+ ## Configuration
90
+
91
+ Add it to your MCP client. For example:
92
+
93
+ ```json
94
+ {
95
+ "mcpServers": {
96
+ "oee": {
97
+ "command": "uvx",
98
+ "args": ["oee-mcp"]
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ If you installed with pip, use `"command": "oee-mcp"` with no args.
105
+
106
+ ## Example
107
+
108
+ ```
109
+ compute_oee(machine={
110
+ "planned_production_time": 420, "downtime": 47, "ideal_rate": 60,
111
+ "total_count": 19271, "reject_count": 423, "all_time": 480
112
+ })
113
+ -> { "factors": { "availability": 0.888, "performance": 0.861,
114
+ "quality": 0.978, "oee": 0.748, "teep": 0.654 },
115
+ "summary": "oee - ...\n OEE 74.8% ..." }
116
+ ```
117
+
118
+ ## Design
119
+
120
+ The server is a thin, stateless wrapper. All of the arithmetic lives in the oee
121
+ library, which computes OEE from the standard definitions and is validated
122
+ against published worked examples (Vorne, TeepTrak) and the Nakajima world-class
123
+ benchmark. The server adds the tool schema, read-only annotations and an
124
+ input-schema helper so an agent can format the input and act on the result.
125
+
126
+ ## Related
127
+
128
+ - [oee](https://github.com/arikanatakan/oee): the library this server wraps.
129
+
130
+ ## License
131
+
132
+ MIT. Written and maintained by [Atakan Arikan](https://github.com/arikanatakan),
133
+ MSc Student at Tsinghua University and Politecnico di Milano.
@@ -0,0 +1,16 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ oee_mcp/__init__.py
5
+ oee_mcp/__main__.py
6
+ oee_mcp/_tools.py
7
+ oee_mcp/_version.py
8
+ oee_mcp/server.py
9
+ oee_mcp.egg-info/PKG-INFO
10
+ oee_mcp.egg-info/SOURCES.txt
11
+ oee_mcp.egg-info/dependency_links.txt
12
+ oee_mcp.egg-info/entry_points.txt
13
+ oee_mcp.egg-info/requires.txt
14
+ oee_mcp.egg-info/top_level.txt
15
+ tests/test_server.py
16
+ tests/test_tools.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ oee-mcp = oee_mcp.server:main
@@ -0,0 +1,7 @@
1
+ oee[plot]>=0.1.2
2
+ mcp>=1.0
3
+
4
+ [dev]
5
+ pytest>=7
6
+ ruff
7
+ build
@@ -0,0 +1 @@
1
+ oee_mcp
@@ -0,0 +1,50 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "oee-mcp"
7
+ version = "0.1.0"
8
+ description = "MCP server for oee: Overall Equipment Effectiveness, the six big losses and charts for AI agents."
9
+ readme = "README.md"
10
+ license = { text = "MIT" }
11
+ authors = [{ name = "Atakan Arikan" }]
12
+ requires-python = ">=3.10"
13
+ dependencies = ["oee[plot]>=0.1.2", "mcp>=1.0"]
14
+ keywords = [
15
+ "mcp", "model-context-protocol", "ai-agents", "oee",
16
+ "overall-equipment-effectiveness", "manufacturing", "tpm", "teep",
17
+ "six-big-losses", "production",
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Manufacturing",
22
+ "Intended Audience :: Developers",
23
+ "License :: OSI Approved :: MIT License",
24
+ "Programming Language :: Python :: 3",
25
+ "Programming Language :: Python :: 3.10",
26
+ "Programming Language :: Python :: 3.11",
27
+ "Programming Language :: Python :: 3.12",
28
+ "Topic :: Scientific/Engineering",
29
+ ]
30
+
31
+ [project.scripts]
32
+ oee-mcp = "oee_mcp.server:main"
33
+
34
+ [project.optional-dependencies]
35
+ dev = ["pytest>=7", "ruff", "build"]
36
+
37
+ [project.urls]
38
+ Homepage = "https://github.com/arikanatakan/oee-mcp"
39
+ Repository = "https://github.com/arikanatakan/oee-mcp"
40
+ Issues = "https://github.com/arikanatakan/oee-mcp/issues"
41
+ Library = "https://github.com/arikanatakan/oee"
42
+
43
+ [tool.setuptools.packages.find]
44
+ include = ["oee_mcp*"]
45
+
46
+ [tool.ruff]
47
+ line-length = 88
48
+
49
+ [tool.pytest.ini_options]
50
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,18 @@
1
+ def test_server_imports_and_wires():
2
+ from oee_mcp import server
3
+
4
+ assert server.mcp is not None
5
+ assert callable(server.main)
6
+
7
+
8
+ def test_all_tools_registered():
9
+ import asyncio
10
+
11
+ from oee_mcp import server
12
+
13
+ names = {tool.name for tool in asyncio.run(server.mcp.list_tools())}
14
+ expected = {
15
+ "compute_oee", "oee_from_log", "oee_from_factors", "aggregate_oee",
16
+ "describe_inputs", "waterfall_chart", "loss_pareto_chart", "trend_chart",
17
+ }
18
+ assert expected <= names
@@ -0,0 +1,68 @@
1
+ import json
2
+
3
+ import pytest
4
+
5
+ from oee_mcp import _tools as t
6
+ from oee_mcp._tools import DowntimeEvent, MachineInput, ProductionRun
7
+
8
+
9
+ def _vorne():
10
+ return MachineInput(planned_production_time=420, downtime=47, ideal_rate=60,
11
+ total_count=19271, reject_count=423, all_time=480)
12
+
13
+
14
+ def test_compute_oee():
15
+ r = t.compute_oee(_vorne())
16
+ assert r["factors"]["oee"] == pytest.approx(0.748, abs=0.001)
17
+ assert r["factors"]["teep"] == pytest.approx(0.654, abs=0.001)
18
+ assert "summary" in r and "OEE" in r["summary"]
19
+
20
+
21
+ def test_compute_oee_error_has_hint():
22
+ r = t.compute_oee(MachineInput(planned_production_time=420, downtime=47,
23
+ ideal_rate=60, total_count=100))
24
+ assert "error" in r
25
+ assert "describe_inputs" in r["hint"]
26
+
27
+
28
+ def test_oee_from_log():
29
+ r = t.oee_from_log(
30
+ 420, runs=[ProductionRun(count=19271, good=18848, ideal_rate=60)],
31
+ downtime_events=[DowntimeEvent(reason="jam", duration=47)])
32
+ assert r["factors"]["oee"] == pytest.approx(0.748, abs=0.001)
33
+ assert r["downtime_reasons"] == {"jam": 47}
34
+
35
+
36
+ def test_oee_from_factors():
37
+ r = t.oee_from_factors(0.90, 0.95, 0.999)
38
+ assert r["factors"]["oee"] == pytest.approx(0.854, abs=0.001)
39
+
40
+
41
+ def test_aggregate_is_not_the_average():
42
+ m1 = MachineInput(planned_production_time=100, run_time=90, ideal_cycle_time=1,
43
+ total_count=80, good_count=80, name="M1")
44
+ m2 = MachineInput(planned_production_time=300, run_time=150, ideal_cycle_time=1,
45
+ total_count=150, good_count=135, name="M2")
46
+ r = t.aggregate_oee([m1, m2])
47
+ assert r["factors"]["oee"] == pytest.approx(0.5375, abs=1e-3)
48
+ assert len(r["machines"]) == 2
49
+
50
+
51
+ def test_describe_inputs():
52
+ d = t.describe_inputs()
53
+ assert "planned_production_time" in d["machine_fields"]
54
+ assert d["definitions"]
55
+ assert d["six_big_losses"]
56
+
57
+
58
+ def test_payload_is_json_serializable():
59
+ json.dumps(t.compute_oee(_vorne()))
60
+
61
+
62
+ def test_charts_return_png_bytes():
63
+ m = MachineInput(planned_production_time=480, downtime=80, ideal_cycle_time=0.5,
64
+ total_count=700, reject_count=100, setup_time=30,
65
+ startup_rejects=40)
66
+ for png in (t.waterfall_png(m), t.loss_pareto_png(m), t.trend_png([m, m])):
67
+ assert isinstance(png, bytes)
68
+ assert png[:4] == b"\x89PNG"