Skip to content

Commit a279050

Browse files
authored
refactor!: security hardening, standards alignment, and unified API (v0.9.0) (#32)
refactor!: security hardening, standards alignment, and unified API (v0.9.0)
2 parents 1e1c5dd + 0752dcd commit a279050

117 files changed

Lines changed: 16955 additions & 14267 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"name": "predictive-maintenance",
99
"source": "./plugin",
1010
"description": "Complete predictive maintenance toolkit for vibration analysis, bearing/gear fault diagnosis, anomaly detection, and ISO standards compliance.",
11-
"version": "1.0.0"
11+
"version": "1.1.0"
1212
}
1313
]
1414
}

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,4 +76,7 @@ rag_result.txt
7676

7777
# Project-internal docs (local only)
7878
CLAUDE.md
79-
PRD_INDUSTRIAL_TRANSFORMATION.md
79+
PRD_INDUSTRIAL_TRANSFORMATION.md
80+
81+
# Claude Code per-agent worktrees
82+
.claude/worktrees/

.vscode/mcp.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"type": "stdio",
55
"command": "${workspaceFolder}/.venv/Scripts/python.exe",
66
"args": [
7-
"${workspaceFolder}/src/machinery_diagnostics_server.py"
7+
"-m",
8+
"predictive_maintenance_mcp"
89
],
910
"env": {}
1011
}

CHANGELOG.md

Lines changed: 183 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,192 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.9.0] - 2026-07-13
11+
12+
Consolidation release: one credible diagnostic engine behind one unified API.
13+
The endpoint surface shrinks from 54 endpoints (46 tools + 4 resources + 4
14+
prompts) to **36 endpoints (33 tools + 0 resources + 3 prompts)**, every
15+
analysis flows through a single `signal_id` handle, and every number in every
16+
output is either measured, computed, or absent — never invented. **This is a
17+
breaking release** (pre-1.0 semver): see the migration table below.
18+
1019
### Added
11-
- Prognostics tools exposed as MCP endpoints (RUL estimation, trend analysis, degradation onset detection)
12-
- Decision support tools exposed as MCP endpoints (vibration alerts, maintenance recommendations)
13-
- Advanced prognostic models: Weibull degradation and Kalman filter-based RUL estimation
14-
- Block 5 (Prognostics) of ISO 13374 architecture now fully implemented
20+
- Prognostics MCP endpoints: `analyze_signal_trend` (within-recording
21+
screening with degradation-onset detection and a truncated feature series)
22+
and `estimate_rul` (Remaining Useful Life from a *multi-measurement* series —
23+
explicit `feature_values`+`timestamps` or multiple `signal_ids`). ISO 13374
24+
Block 5.
25+
- Decision support endpoint `generate_maintenance_recommendations` with a
26+
closed, canonical `fault_types` vocabulary (`outer_race`, `inner_race`,
27+
`ball`, `cage`, ...) that raises on unknown values instead of silently
28+
dropping them. ISO 13374 Block 6.
29+
- `load_signal` accepts a list of file paths for batch loading (fail-fast
30+
atomic: on the first invalid entry nothing is loaded), plus
31+
`signal_unit: "g" | "m/s2" | "mm/s" | "m/s"` and `overwrite` parameters.
32+
- `generate_test_signal` writes companion metadata (`sampling_rate`,
33+
`signal_unit`), auto-registers the signal in the repository and returns a
34+
`StoredSignalInfo` — immediately analyzable and ISO-assessable.
35+
- Bearing catalog entries carry a mandatory `source` citation (CWRU / XJTU-SY
36+
provenance), echoed in bearing-check outputs; a geometry-validation test
37+
suite guards every entry (bore < pitch < OD, ball fit, BPFO/fr + BPFI/fr ≈ Z).
38+
- CI drift guards: every documented tool call in the plugin and MCP prompts is
39+
validated against the introspected server inventory
40+
(`tests/test_documented_calls.py`); version strings and endpoint counts are
41+
pinned across `pyproject.toml`, `src/__init__.py`, `server.json`,
42+
`CITATION.cff` and the READMEs (`tests/test_version_alignment.py`); a
43+
surface-parity test maps all 54 v0.8.x endpoints to their destinations.
1544

1645
### Changed
17-
- Documentation updated to reflect implemented status of prognostics and RUL estimation
18-
- Endpoint terminology clarified: "MCP endpoints" used consistently for total count (tools + resources + prompts)
46+
- **One severity engine.** `assess_severity` replaces `evaluate_iso_20816`,
47+
`assess_vibration_severity`, `check_vibration_alert` and
48+
`check_custom_vibration_alert`. Input is `signal_id` XOR `rms_velocity_mm_s`
49+
(portable-instrument route), with native ISO vocabulary
50+
(`machine_group: 1|2`, `support_type: "rigid"|"flexible"`), optional custom
51+
`thresholds` and optional `machine_power_kw` (declared power < 15 kW →
52+
explicit scope refusal). Zone boundaries come from a single table (values
53+
from ISO 10816-3:2009, 4-zone scheme; outputs note that ISO 20816-3:2022
54+
merges zones A/B). The invented `machine_class I-IV` mapping is gone.
55+
- **ISO verdicts require a declared unit.** Severity is computed only when the
56+
signal unit is declared via `load_signal(signal_unit=...)` or companion
57+
`_metadata.json` — never guessed from amplitude. `diagnose_vibration`
58+
degrades honestly: the ISO block becomes a structured refusal
59+
(`status: "refused"` + `reason` + `remedy`) while spectral, bearing and
60+
anomaly blocks still run. Same discipline for `sampling_rate`: explicit >
61+
metadata > structured error (no more silent 1 kHz / 10 kHz defaults).
62+
- **One envelope tool.** `analyze_envelope` absorbs
63+
`compute_envelope_spectrum_tool`: default band 500–5000 Hz, invalid band vs
64+
Nyquist raises (never a silent clamp), detrend + window before the envelope
65+
FFT (no more DC skirt over the FTF zone), band echoed in the output.
66+
- **One bearing-fault tool.** `check_bearing_faults` absorbs
67+
`check_bearing_fault_peak_tool`, `check_bearing_faults_direct` and
68+
`lookup_bearing_and_compute_tool`: input `bearing_id` XOR
69+
`frequencies: {label: Hz}` XOR explicit geometry — the frequencies route
70+
covers gearbox GMF checks and out-of-catalog bearings. Outputs expose the
71+
canonical fault vocabulary (`fault_type_canonical`).
72+
- **`signal_id` is the universal handle.** Every analysis, diagnostics,
73+
prognostics and report tool takes `signal_id`; filename parameters are gone.
74+
Default ids derive from the path relative to the data directory
75+
(`real_train/baseline_1.csv``real_train_baseline_1`), so same-named files
76+
in different folders no longer collide; reloading an existing id errors
77+
unless `overwrite=True`.
78+
- Honest field names in prognostics and diagnosis: `fit_r_squared` (was
79+
`confidence` on RUL fits), `evidence_strength` (categorical, derived from
80+
corroborating evidence — a quiet machine can no longer score "high"
81+
confidence from severity alone), `precision_heuristic` (Kalman, explicitly
82+
labeled heuristic). No tool accepts a `confidence` input anymore.
83+
- Analysis segments are deterministic by default; random sampling is opt-in
84+
via an explicit `random_seed` parameter.
85+
- `predict_anomalies` returns bounded summaries (counts, score percentiles,
86+
worst segments) instead of per-segment arrays; its not-found error lists the
87+
models actually on disk.
88+
- Report filenames are timestamped (consecutive runs no longer overwrite);
89+
`list_html_reports(file_name=...)` returns per-report metadata (absorbs
90+
`get_report_info`).
91+
- Parameter naming unified: `rpm` (note: `generate_fft_report`'s old
92+
`rotation_freq` was in **Hz**; the new `rpm` parameter is in RPM),
93+
`file_name`, `bearing_id`, `sampling_rate`, `signal_id` — one name per
94+
concept across the whole surface.
95+
- Error contract unified: misuse and failures raise (surfaced as MCP errors)
96+
with "problem — actionable remedy" messages; legitimate negative outcomes
97+
(bearing not in catalog, no degradation trend) are typed results. No more
98+
error-shaped dicts returned as success.
99+
- All tools are module-level importable functions
100+
(`from predictive_maintenance_mcp.mcp_tools.analysis_tools import analyze_fft`).
101+
- Kinematic bearing formulas now cite Randall & Antoni (2011) instead of the
102+
incorrect "ISO 15243" attribution.
103+
104+
### Removed
105+
- **The legacy monolith `machinery_diagnostics_server.py` and the root import
106+
shims** (`bearing_analyzer`, `iso10816`, `spectral`, `diagnosis_pipeline`,
107+
`bearing_catalog`). The package ships only the modular server; the entry
108+
point (`predictive-maintenance-mcp` / `python -m predictive_maintenance_mcp`)
109+
is unchanged, so existing Claude Desktop configs keep working.
110+
*Why now instead of the promised v1.0.0*: after the 0.8.1 security patch the
111+
monolith remained a second, divergent copy of every analysis path — the same
112+
class of risk that let the path-traversal fix miss half the code in the
113+
first place. Keeping an unmaintained twin alive for one more minor version
114+
was a standing security and drift liability; pre-1.0, the deprecation
115+
promise is superseded by the safety argument.
116+
- The 4 MCP resources (`signal://list`, `signal://read`, `manual://list`,
117+
`manual://read`) — duplicates of `list_signals`, `get_signal_info`,
118+
`list_machine_manuals`, `read_manual_excerpt`.
119+
- The Weibull RUL estimator (physically unjustified on vibration features) and
120+
single-recording RUL extrapolation: `estimate_rul` now refuses anything less
121+
than 3 timestamped measurements and points to `analyze_signal_trend` for
122+
within-recording screening.
123+
- The amplitude-based unit-guessing heuristic (RMS > 0.5 → "g"), the
124+
"HYPOTHESIS/PROCEEDING" flow and the "PLEASE CONFIRM" log walls.
125+
- The hardcoded 81.13 Hz BPFO "example @ 1500 RPM" block that injected
126+
fictitious reference frequencies into envelope outputs.
127+
- 19 bearing-catalog entries with fabricated internal geometry (the old 6205
128+
pitch diameter was contaminated from a different bearing); only
129+
source-verifiable entries remain (6205, 6203 from CWRU; UER204 from XJTU-SY).
130+
- The ASCII-art ISO diagnostic prompt.
131+
132+
### Fixed
133+
- Same reading → same zone: `check_alert_thresholds` and the severity engine
134+
shared drifted threshold tables (3.0 mm/s, group 2, rigid gave zone C on one
135+
path and B on the other). One table now feeds every path.
136+
- ISO evaluation refuses when Nyquist < 1 kHz and reports the *real*
137+
integration band (e.g. "10-950 Hz" at fs = 2 kHz).
138+
- Kalman RUL variance includes the previously missing covariance cross-term.
139+
- Trend direction is gated on the computed p-value (not R² > 0.3); onset
140+
detection can no longer fire inside its own baseline window.
141+
- Absolute paths outside the data directory load *that* file (previously a
142+
same-named file inside the data directory could silently win).
143+
- Repository arrays are read-only views — tools can no longer corrupt the
144+
signal cache in place.
145+
- Envelope band-pass validation (including `generate_envelope_report`, which
146+
previously crashed with a raw scipy error when the band hit Nyquist).
147+
148+
### Migration table (v0.8.x → v0.9.0)
149+
150+
| v0.8.x endpoint | v0.9.0 destination |
151+
|---|---|
152+
| `evaluate_iso_20816`, `assess_vibration_severity`, `check_vibration_alert`, `check_custom_vibration_alert` | `assess_severity` |
153+
| `compute_envelope_spectrum_tool` | `analyze_envelope` |
154+
| `check_bearing_fault_peak_tool`, `check_bearing_faults_direct`, `lookup_bearing_and_compute_tool` | `check_bearing_faults` |
155+
| `detect_signal_degradation_onset` | `analyze_signal_trend` (onset fields in output) |
156+
| `diagnose_vibration_tool` | `diagnose_vibration` (renamed) |
157+
| `list_stored_signals` | `list_signals(scope="memory")` |
158+
| `clear_signal`, `clear_all_signals` | `clear_signals(signal_id=None)` |
159+
| `get_report_info` | `list_html_reports(file_name=...)` |
160+
| `plot_spectrum` | `generate_fft_report` |
161+
| `plot_envelope` | `generate_envelope_report` |
162+
| `plot_iso_20816_chart` | `generate_iso_report` |
163+
| `signal://list`, `signal://read` (resources) | `list_signals(scope="disk")`, `get_signal_info` |
164+
| `manual://list`, `manual://read` (resources) | `list_machine_manuals`, `read_manual_excerpt` |
165+
| `generate_iso_diagnostic_report` (prompt) | dropped |
166+
| params `filename` / `signal_file` / `signal_path` | `signal_id` (via `load_signal`) |
167+
| params `shaft_speed_rpm` / `operating_speed_rpm` / `rotation_freq` (Hz) | `rpm` |
168+
| param `manual_filename` | `file_name` |
169+
| param `bearing_designation` | `bearing_id` |
170+
| output `confidence` | `fit_r_squared` / `evidence_strength` / `precision_heuristic` |
171+
172+
Scripts that assumed `signal_id == file stem` must switch to the relative-path
173+
derivation (`folder/file.csv``folder_file`) or pass an explicit
174+
`signal_id=` to `load_signal`.
175+
176+
## [0.8.1] - 2026-07-10
177+
178+
Security-only patch release. No new features or API changes.
179+
180+
### Security
181+
- **Path traversal fixed across every model and report file path** (all sites, in
182+
both the modular server and the legacy monolith). `train_anomaly_model` built its
183+
pickle output path from an unvalidated `model_name` — an arbitrary-file-write
184+
primitive reachable from any MCP client — and the model-load and
185+
`read_report_metadata` read paths were likewise unvalidated. Every user-supplied
186+
filesystem path now flows through a single canonical `path_safety` helper that
187+
uses `Path.is_relative_to` for containment (closing the sibling-directory bypass
188+
a `str.startswith` check would miss) and validates model names before any I/O.
189+
Unsafe names are rejected with a clear error and no file is written or read.
190+
- **Signal read path contained.** `load_signal_data` (the shared read sink behind
191+
`analyze_fft`, `predict_anomalies`, and every signal tool) now resolves the
192+
user-supplied filename inside the data directory, closing an arbitrary
193+
file-content read reachable from any MCP client. Broader signal-path hardening
194+
(companion-metadata resolution and per-tool existence checks) is tracked as a
195+
follow-up.
19196

20197
## [0.8.0] - 2026-03-29
21198

CITATION.cff

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
cff-version: 1.2.0
22
message: "If you use this software, please cite it as below."
33
title: "Predictive Maintenance MCP Server: An open-source framework for integrating Large Language Models with predictive maintenance and fault diagnosis workflows"
4-
version: 0.7.1
5-
date-released: 2025-07-15
4+
version: 0.9.0
5+
date-released: 2026-07-13
66
authors:
77
- family-names: Di Maggio
88
given-names: Luigi Gianpio
@@ -52,7 +52,7 @@ preferred-citation:
5252
- family-names: Di Maggio
5353
given-names: Luigi Gianpio
5454
year: 2026
55-
version: 0.7.1
55+
version: 0.9.0
5656
repository-code: "https://github.com/LGDiMaggio/predictive-maintenance-mcp"
5757
license: MIT
5858
doi: "10.5281/zenodo.17611542"

CONTRIBUTING.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -190,14 +190,14 @@ We need anonymized vibration data from real machinery:
190190
You know what matters in the field. Open a discussion or issue describing:
191191
- What diagnostic procedure do you follow daily?
192192
- What information do you need that the current tools don't provide?
193-
- What standards or guidelines should we support (VDI 3832, ISO 10816, etc.)?
193+
- What standards or guidelines should we support (VDI 3832, ISO 20816, etc.)?
194194

195195
### Review the Bearing Catalog
196196

197-
The file `resources/bearing_catalogs/common_bearings_catalog.json` contains ~20 bearings. You can:
198-
- Verify the geometry data is correct
199-
- Suggest additional common bearing models that should be included
200-
- Provide corrections for contact angles, ball counts, or pitch diameters
197+
The file `resources/bearing_catalogs/common_bearings_catalog.json` contains a small set of bearings whose geometry is traceable to a public source — every entry carries a mandatory `source` citation, and `tests/test_bearing_catalog_validation.py` enforces physical validity. You can:
198+
- Verify the geometry data against the cited sources
199+
- Suggest additional bearing models **with a verifiable public source** (manufacturer datasheet, dataset documentation, or peer-reviewed paper)
200+
- Provide corrections for contact angles, ball counts, or pitch diameters (with source)
201201

202202
This is a JSON file — you can edit it directly or provide the data in any format and we'll add it.
203203

@@ -243,7 +243,7 @@ Closes #42"
243243
See the complete template in the [Developer Quickstart](docs/QUICKSTART_DEVELOPER.md#step-4-create-your-first-tool).
244244

245245
Summary:
246-
1. Add a `@mcp.tool()` decorated function in `machinery_diagnostics_server.py`
246+
1. Add a module-level tool function in the relevant `src/mcp_tools/*.py` module and register it in that module's `register()`
247247
2. Write comprehensive docstring (the LLM reads this!)
248248
3. Add tests in `tests/`
249249
4. Update `EXAMPLES.md` if the tool adds a new workflow
@@ -268,7 +268,7 @@ For bigger features, **open a discussion first** to align on approach:
268268
|------|--------|
269269
| Add a **video walkthrough** of the engineer quickstart | Reduces barrier for non-technical users |
270270
| Write a **case study** using real (anonymized) data | Shows the tool's value to decision-makers |
271-
| Improve **inline code comments** in `machinery_diagnostics_server.py` | Helps new developers understand the codebase |
271+
| Improve **inline code comments** in `src/mcp_tools/` | Helps new developers understand the codebase |
272272
| **Translate** the engineer quickstart to other languages | Expands reach globally |
273273
| Create a **glossary** of vibration analysis terms used in the project | Bridges the gap between domains |
274274

Dockerfile

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,12 @@ ENV MCP_PORT=8000
4343

4444
EXPOSE 8000
4545

46-
# Health check (verify imports work)
47-
RUN python -c "from src.machinery_diagnostics_server import mcp; print('Server imports OK')"
46+
# Build-time check: the installed package must import cleanly
47+
RUN python -c "from predictive_maintenance_mcp.server import mcp; print('Server imports OK')"
48+
49+
# Runtime health check: the SSE/HTTP port must accept connections
50+
# (shell form so $MCP_PORT is expanded; irrelevant for stdio deployments)
51+
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
52+
CMD python -c "import os, socket; socket.create_connection(('127.0.0.1', int(os.environ.get('MCP_PORT', '8000'))), timeout=3)" || exit 1
4853

4954
ENTRYPOINT ["predictive-maintenance-mcp"]

INSTALL.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ pip install -e .
219219
"mcpServers": {
220220
"predictive-maintenance": {
221221
"command": "C:/full/path/to/.venv/Scripts/python.exe",
222-
"args": ["C:/full/path/to/src/machinery_diagnostics_server.py"]
222+
"args": ["-m", "predictive_maintenance_mcp"]
223223
}
224224
}
225225
}
@@ -232,9 +232,8 @@ pip install -e .
232232
-`"command": "C:/path/.venv/Scripts/python.exe"` (Windows)
233233
-`"command": "/path/.venv/bin/python"` (macOS/Linux)
234234

235-
4. **Module import**: If installed via `pip install -e .`, you can also use:
236-
-`"args": ["-m", "predictive_maintenance_mcp"]`
237-
-`"args": ["C:/path/src/machinery_diagnostics_server.py"]`
235+
4. **Module import**: The package must be installed (`pip install -e .`) so that
236+
`"args": ["-m", "predictive_maintenance_mcp"]` resolves
238237

239238
5. **Restart Claude Desktop completely** after config changes
240239

0 commit comments

Comments
 (0)