node804-hpe-msa 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.
- node804_hpe_msa-0.1.0/.gitignore +21 -0
- node804_hpe_msa-0.1.0/.pre-commit-config.yaml +19 -0
- node804_hpe_msa-0.1.0/AGENT_PLAN.md +289 -0
- node804_hpe_msa-0.1.0/CHANGELOG.md +90 -0
- node804_hpe_msa-0.1.0/LICENSE +21 -0
- node804_hpe_msa-0.1.0/PKG-INFO +461 -0
- node804_hpe_msa-0.1.0/README.md +423 -0
- node804_hpe_msa-0.1.0/pyproject.toml +123 -0
- node804_hpe_msa-0.1.0/skills/README.md +68 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/SKILL.md +97 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/events-alerts.md +43 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/health-model.md +54 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/hosts-mapping.md +60 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/capacity-review.md +36 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/disk-failure-triage.md +42 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/event-triage.md +37 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/firmware-inventory.md +30 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/health-check.md +38 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/mapping-audit.md +39 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/playbooks/performance-review.md +41 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/statistics.md +55 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/storage-model.md +64 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/references/tool-map.md +86 -0
- node804_hpe_msa-0.1.0/skills/hpe-msa-engineer/scripts/README.md +5 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/__init__.py +14 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/__main__.py +20 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/_redact.py +75 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/client.py +395 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/config/__init__.py +0 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/config/arrays.py +591 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/py.typed +0 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/responses/__init__.py +0 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/responses/shapers.py +595 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/server.py +123 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/setup.py +781 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/__init__.py +0 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/_common.py +191 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/aggregations.py +152 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/events.py +106 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/hardware.py +160 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/hosts.py +138 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/statistics.py +88 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/status.py +76 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/storage.py +118 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/system.py +190 -0
- node804_hpe_msa-0.1.0/src/hpe_msa_mcp/tools/utility.py +143 -0
- node804_hpe_msa-0.1.0/tests/__init__.py +0 -0
- node804_hpe_msa-0.1.0/tests/test_client_auth.py +190 -0
- node804_hpe_msa-0.1.0/tests/test_config.py +315 -0
- node804_hpe_msa-0.1.0/tests/test_setup.py +258 -0
- node804_hpe_msa-0.1.0/tests/test_shapers.py +165 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*.egg-info/
|
|
4
|
+
.pytest_cache/
|
|
5
|
+
.mypy_cache/
|
|
6
|
+
.ruff_cache/
|
|
7
|
+
.venv/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
*.log
|
|
11
|
+
.coverage
|
|
12
|
+
htmlcov/
|
|
13
|
+
.env
|
|
14
|
+
arrays.json
|
|
15
|
+
audit*.log
|
|
16
|
+
|
|
17
|
+
# Local Claude Code settings (per-machine permissions, not shared)
|
|
18
|
+
.claude/settings.local.json
|
|
19
|
+
|
|
20
|
+
# Generated duplication/analysis reports (jscpd, etc.)
|
|
21
|
+
report/
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Pre-commit hooks for local development.
|
|
2
|
+
#
|
|
3
|
+
# pip install -e ".[dev]" # installs pre-commit + ruff
|
|
4
|
+
# pre-commit install # activates the git hook
|
|
5
|
+
#
|
|
6
|
+
# The ruff rev MUST match the pinned ruff in pyproject.toml [dev] so local,
|
|
7
|
+
# pre-commit, and CI all format identically. Bump both together.
|
|
8
|
+
repos:
|
|
9
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
10
|
+
rev: v0.15.13
|
|
11
|
+
hooks:
|
|
12
|
+
# Lint (mirrors CI's `ruff check .`); --fix so fixable issues are
|
|
13
|
+
# corrected in place before the commit.
|
|
14
|
+
- id: ruff
|
|
15
|
+
args: [--fix]
|
|
16
|
+
# Format check (mirrors CI's `ruff format --check .`). The hook
|
|
17
|
+
# reformats in place; a reformatted file blocks the commit so the
|
|
18
|
+
# change gets re-staged and reviewed.
|
|
19
|
+
- id: ruff-format
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# HPE MSA MCP — Agent Build Plan
|
|
2
|
+
|
|
3
|
+
> Working plan for building `node804-hpe-msa`, a token-efficient, read-only
|
|
4
|
+
> MCP server for HPE MSA (Modular Smart Array) storage. This document is the
|
|
5
|
+
> source of truth an implementing agent follows. Keep it updated as decisions
|
|
6
|
+
> change.
|
|
7
|
+
|
|
8
|
+
## 1. Goal & scope
|
|
9
|
+
|
|
10
|
+
Build an MCP server that exposes **read-only** visibility into HPE MSA storage
|
|
11
|
+
arrays through the array's REST management API, matching the conventions of the
|
|
12
|
+
existing suite (`panos-mcp-py`, `falcon-mcp`, etc.).
|
|
13
|
+
|
|
14
|
+
Confirmed decisions (from the requester):
|
|
15
|
+
|
|
16
|
+
| Decision | Choice |
|
|
17
|
+
|----------|--------|
|
|
18
|
+
| Tool scope | **Read-only** — only `show`-style tools; no provisioning/writes exposed. Matches the suite's default-deny posture. |
|
|
19
|
+
| Target hardware | **Gen6 (1060/2060/2062)** and **Gen5 (1050/2050/2052)** — REST API v3/v4, JSON-capable. |
|
|
20
|
+
| Build approach | Full build in one pass, matching `panos-mcp-py` structure. |
|
|
21
|
+
|
|
22
|
+
Explicitly **out of scope** (for now): any write/provisioning verb (create/delete/
|
|
23
|
+
map/set/restart), firmware operations, multi-array orchestration beyond a simple
|
|
24
|
+
target registry. Leave a gated `MSA_MODE` seam so writes *could* be added later
|
|
25
|
+
without re-architecting, but register nothing above `Mode.READ`.
|
|
26
|
+
|
|
27
|
+
## 2. Reference architecture (mirror `panos-mcp-py`)
|
|
28
|
+
|
|
29
|
+
The suite shares `node804-mcp-toolkit`, which provides everything cross-cutting.
|
|
30
|
+
Reuse it; do not reinvent:
|
|
31
|
+
|
|
32
|
+
- `Mode`, `ModeGate` — RBAC. Default-deny: unset `MSA_MODE` ⇒ `READ`. Tools are
|
|
33
|
+
registered via `gate.tool(mcp, required=Mode.READ, ...)`; denied tools are
|
|
34
|
+
never registered, so the client can't see them.
|
|
35
|
+
- `resolve_tls_config(env, prefix="MSA")` / `describe_tls` — verify-by-default TLS,
|
|
36
|
+
`MSA_TLS_VERIFY` / `MSA_TLS_CA` opt-outs.
|
|
37
|
+
- `open_sink(env_var="MSA_AUDIT_LOG")` — JSONL audit sink (no-op when unset).
|
|
38
|
+
- `VerboseFlag`, `FieldsList`, `Pagination`, `Pattern` — standard lean-response params.
|
|
39
|
+
- `whitelist`, `strip_keys`, `paginate`, `filter_by_pattern` — pure shaping helpers.
|
|
40
|
+
|
|
41
|
+
**The one real divergence from panos:** panos wraps `pan-os-python`; MSA has no
|
|
42
|
+
SDK. We talk to the REST API directly over `httpx`, and MSA auth is a
|
|
43
|
+
**session key** (username+password → hashed login → `sessionKey` header), not a
|
|
44
|
+
long-lived API key. So `client.py` owns login, session-key caching, and
|
|
45
|
+
re-login-on-expiry instead of delegating to an SDK.
|
|
46
|
+
|
|
47
|
+
## 3. Package layout
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
hpe-msa-mcp/
|
|
51
|
+
pyproject.toml # node804-hpe-msa, console script node804-hpe-msa
|
|
52
|
+
README.md # env table + tool catalog
|
|
53
|
+
LICENSE # MIT
|
|
54
|
+
.gitignore
|
|
55
|
+
AGENT_PLAN.md # this file
|
|
56
|
+
src/hpe_msa_mcp/
|
|
57
|
+
__init__.py # __version__ = "0.1.0"; module docstring w/ 3 principles
|
|
58
|
+
__main__.py # python -m hpe_msa_mcp → server.main()
|
|
59
|
+
_redact.py # strip password / sessionKey from errors & logs
|
|
60
|
+
client.py # MSAClient: login, session cache, request(), error surface
|
|
61
|
+
config/
|
|
62
|
+
__init__.py
|
|
63
|
+
arrays.py # per-array registry (name/host/username in JSON, password in keyring)
|
|
64
|
+
responses/
|
|
65
|
+
__init__.py
|
|
66
|
+
shapers.py # per-object lean whitelists + object-key extraction
|
|
67
|
+
tools/
|
|
68
|
+
__init__.py
|
|
69
|
+
_common.py # ToolContext, fetch() helper, shape wiring
|
|
70
|
+
status.py # server_status (local), check_connectivity
|
|
71
|
+
system.py # system, versions, controllers, enclosures, frus, license, ntp, network
|
|
72
|
+
hardware.py # disks, sensor-status, fans, power-supplies, expander-status, certificate
|
|
73
|
+
storage.py # pools, disk-groups, volumes, snapshots
|
|
74
|
+
hosts.py # host-groups, initiators, hosts, maps, ports
|
|
75
|
+
events.py # events, alerts
|
|
76
|
+
statistics.py # controller/volume/disk statistics
|
|
77
|
+
aggregations.py # get_health_summary (rollup), count helpers
|
|
78
|
+
utility.py # run_show_command (generic, show-verb-only), list_arrays, add_array
|
|
79
|
+
py.typed
|
|
80
|
+
tests/
|
|
81
|
+
__init__.py
|
|
82
|
+
test_shapers.py
|
|
83
|
+
test_config.py
|
|
84
|
+
test_client_auth.py # respx-mocked login + session reuse + re-login
|
|
85
|
+
server.py is src/hpe_msa_mcp/server.py (FastMCP wiring)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## 4. HPE MSA REST API notes (Gen5/Gen6)
|
|
89
|
+
|
|
90
|
+
Base URL: `https://<host>/api`. All requests set header `datatype: json`.
|
|
91
|
+
|
|
92
|
+
### Authentication
|
|
93
|
+
1. Compute `hash = SHA-256("<username>_<password>")` (hex). Gen5/Gen6 support
|
|
94
|
+
SHA-256; older firmware only MD5 — implement SHA-256 first, **fall back to
|
|
95
|
+
MD5** if login returns an auth error, so mixed/older arrays still work.
|
|
96
|
+
2. `GET /api/login/<hash>` with `datatype: json`.
|
|
97
|
+
3. Response: `{"status":[{"response-type":"Success","response":"<sessionKey>", "return-code":1, ...}]}`.
|
|
98
|
+
On failure `response-type` is `Error` and `return-code` is negative.
|
|
99
|
+
4. Subsequent calls: `GET /api/<command>/<args...>` with headers
|
|
100
|
+
`sessionKey: <key>` and `datatype: json`.
|
|
101
|
+
5. Session expiry (idle ~30 min, or `return-code` == `-10005` / HTTP 401) ⇒
|
|
102
|
+
re-login once and retry the request. Logout is `GET /api/exit` (best-effort
|
|
103
|
+
on shutdown; not required).
|
|
104
|
+
|
|
105
|
+
### Response envelope
|
|
106
|
+
Every response is a JSON object containing:
|
|
107
|
+
- one or more **object arrays** keyed by object type (see key table below), and
|
|
108
|
+
- a trailing `"status"` array describing the command result.
|
|
109
|
+
|
|
110
|
+
Surface an error dict when the trailing status `response-type == "Error"`.
|
|
111
|
+
|
|
112
|
+
### Object-key map (key varies by command — auto-detect as fallback)
|
|
113
|
+
| Command | JSON key |
|
|
114
|
+
|---------|----------|
|
|
115
|
+
| `show/system` | `system` |
|
|
116
|
+
| `show/controllers` | `controllers` |
|
|
117
|
+
| `show/disks` | `drives` |
|
|
118
|
+
| `show/disk-groups` | `disk-groups` |
|
|
119
|
+
| `show/pools` | `pools` |
|
|
120
|
+
| `show/volumes` | `volumes` |
|
|
121
|
+
| `show/enclosures` | `enclosures` |
|
|
122
|
+
| `show/ports` | `port` |
|
|
123
|
+
| `show/host-groups` | `host-group` |
|
|
124
|
+
| `show/initiators` | `initiator` |
|
|
125
|
+
| `show/maps` | `volume-view` |
|
|
126
|
+
| `show/sensor-status` | `sensors` |
|
|
127
|
+
| `show/fans` | `fan` |
|
|
128
|
+
| `show/power-supplies` | `power-supplies` |
|
|
129
|
+
| `show/events` | `events` |
|
|
130
|
+
| `show/alerts` | `alert` (Gen6) |
|
|
131
|
+
| `show/versions` | `versions` |
|
|
132
|
+
| `show/license` | `license` |
|
|
133
|
+
|
|
134
|
+
Because keys are inconsistent across firmware, `_extract_objects(payload,
|
|
135
|
+
preferred_key)` should try `preferred_key`, then fall back to "first key whose
|
|
136
|
+
value is a list and key != 'status'". Never assume a key exists.
|
|
137
|
+
|
|
138
|
+
**Always** check `status`/`timed_out`-style signals: a partial or errored
|
|
139
|
+
response is not an empty inventory. Do not report "nothing configured" on an
|
|
140
|
+
auth/transport error — surface the error string instead.
|
|
141
|
+
|
|
142
|
+
## 5. Config & credentials (`config/arrays.py`)
|
|
143
|
+
|
|
144
|
+
Mirror `panos config/firewalls.py`, adapted for username+password:
|
|
145
|
+
|
|
146
|
+
- File: `$MSA_ARRAYS_CONFIG` or `~/.config/hpe-msa-mcp/arrays.json`.
|
|
147
|
+
- Stored per entry (JSON, **never the password**): `{name, host, username}`.
|
|
148
|
+
- Password lives in the OS keychain: `keyring` service `"hpe-msa-mcp"`,
|
|
149
|
+
account = entry `name`. Auto-migrate any legacy plaintext `password` field to
|
|
150
|
+
keychain on load, then rewrite the file `0o600` without it.
|
|
151
|
+
- Single-array env fallback when no JSON file: `MSA_HOST`, `MSA_USERNAME`,
|
|
152
|
+
`MSA_PASSWORD`.
|
|
153
|
+
- `resolve_array(name=None)`: named entry → sole entry → env fallback → None.
|
|
154
|
+
Multi-array mode (>1 entry) requires every tool to take an explicit
|
|
155
|
+
`array: str` (no silent default).
|
|
156
|
+
- Host normalization: strip `https?://` and trailing slashes.
|
|
157
|
+
|
|
158
|
+
## 6. Client (`client.py`)
|
|
159
|
+
|
|
160
|
+
`MSAClient` per resolved array, cached for process lifetime (`_CLIENTS`):
|
|
161
|
+
- Fields: `name, host, username, password, tls, timeout, _session_key`.
|
|
162
|
+
- One `httpx.Client(verify=<from TlsConfig>, timeout=<MSA_API_TIMEOUT|30s>)`.
|
|
163
|
+
- verify: `False` when TLS off; `str(ca_path)` when a CA bundle is configured;
|
|
164
|
+
else `True`.
|
|
165
|
+
- `login()` → SHA-256 then MD5 fallback; store `_session_key`.
|
|
166
|
+
- `request(segments: list[str])` → ensure key, GET, parse JSON, detect expiry,
|
|
167
|
+
re-login once, retry. Return parsed dict **or** a `ResolveError`-style error.
|
|
168
|
+
- `ResolveError` dataclass mirrors panos: tools surface `.message`, never raise
|
|
169
|
+
a stack trace at the model.
|
|
170
|
+
- `probe_client()` for `server_status`: resolves config only, **no** network call.
|
|
171
|
+
- All error text passes through `_redact` (strip password + sessionKey).
|
|
172
|
+
|
|
173
|
+
Per-request timeout must be bounded (default 30–60s) so a stuck array fails fast
|
|
174
|
+
instead of hanging the whole MCP process — same rationale as the panos timeout cap.
|
|
175
|
+
|
|
176
|
+
## 7. Tools (all `Mode.READ`)
|
|
177
|
+
|
|
178
|
+
Every list tool takes `verbose / fields / pattern / pagination` and returns a
|
|
179
|
+
lean whitelist by default. Grouping:
|
|
180
|
+
|
|
181
|
+
- **status.py** — `server_status` (local: mode, tls, audit, configured arrays;
|
|
182
|
+
no network), `check_connectivity` (login + `show/system` round-trip).
|
|
183
|
+
- **system.py** — `get_system`, `get_versions`, `get_controllers`,
|
|
184
|
+
`get_enclosures`, `get_frus`, `get_license`, `get_ntp_status`, `get_network`.
|
|
185
|
+
- **hardware.py** — `get_disks`, `get_sensor_status`, `get_fans`,
|
|
186
|
+
`get_power_supplies`, `get_expander_status`, `get_certificate`.
|
|
187
|
+
- **storage.py** — `get_pools`, `get_disk_groups`, `get_volumes`, `get_snapshots`.
|
|
188
|
+
- **hosts.py** — `get_host_groups`, `get_initiators`, `get_hosts`, `get_maps`,
|
|
189
|
+
`get_ports`.
|
|
190
|
+
- **events.py** — `get_events` (args: severity + count, e.g. `last 100`),
|
|
191
|
+
`get_alerts`.
|
|
192
|
+
- **statistics.py** — `get_controller_statistics`, `get_volume_statistics`,
|
|
193
|
+
`get_disk_statistics`.
|
|
194
|
+
- **aggregations.py** — `get_health_summary`: one call that rolls up overall
|
|
195
|
+
system health + degraded/failed disks + active alerts + controller redundancy,
|
|
196
|
+
so the model can answer "is this array healthy?" cheaply.
|
|
197
|
+
- **utility.py** — `run_show_command(command, args)`: generic escape hatch,
|
|
198
|
+
**hard-restricted to the `show` verb** (reject anything else) so it can't be
|
|
199
|
+
used to mutate; `list_arrays`; `add_array` (writes config + keychain — this is
|
|
200
|
+
a *local config* write, not an array write, so it's acceptable under read-only,
|
|
201
|
+
but gate it behind `Mode.READ` and document clearly).
|
|
202
|
+
|
|
203
|
+
## 8. Server wiring (`server.py`)
|
|
204
|
+
|
|
205
|
+
- `open_sink(env_var="MSA_AUDIT_LOG")`, `resolve_tls_config(prefix="MSA")`,
|
|
206
|
+
`ModeGate.from_env(env_var="MSA_MODE", default=Mode.READ, audit_sink=...)`.
|
|
207
|
+
- `SERVER_INSTRUCTIONS`: tight operating guidance — MSA has two controllers
|
|
208
|
+
(A/B); health lives in `health`/`health-numeric` fields; prefer
|
|
209
|
+
`get_health_summary` before dumping full inventories; check the response
|
|
210
|
+
`status` before trusting an empty list; read-only server.
|
|
211
|
+
- `ctx = ToolContext(mcp, gate, tls, audit_sink)`, then `register_*_tools(ctx)`
|
|
212
|
+
for every module.
|
|
213
|
+
- `main()`: `load_array_config()`, print version + TLS + RBAC + audit lines to
|
|
214
|
+
stderr, `mcp.run()`.
|
|
215
|
+
|
|
216
|
+
## 9. Environment variables
|
|
217
|
+
|
|
218
|
+
| Var | Purpose | Default |
|
|
219
|
+
|-----|---------|---------|
|
|
220
|
+
| `MSA_MODE` | RBAC mode. Read-only surface only registers `read`. | `read` |
|
|
221
|
+
| `MSA_AUDIT_LOG` | Path to JSONL audit log. Unset = disabled. | unset |
|
|
222
|
+
| `MSA_TLS_VERIFY` | `true`/`false`. MSA controller certs are often self-signed. | `true` |
|
|
223
|
+
| `MSA_TLS_CA` | Path to a custom CA PEM bundle. Implies verify on. | unset |
|
|
224
|
+
| `MSA_ARRAYS_CONFIG` | Override path for `arrays.json`. | `~/.config/hpe-msa-mcp/arrays.json` |
|
|
225
|
+
| `MSA_HOST` | Single-array fallback hostname. | unset |
|
|
226
|
+
| `MSA_USERNAME` | Single-array fallback username. | unset |
|
|
227
|
+
| `MSA_PASSWORD` | Single-array fallback password. | unset |
|
|
228
|
+
| `MSA_API_TIMEOUT` | Per-request timeout (seconds). | `30` |
|
|
229
|
+
|
|
230
|
+
## 10. Security & correctness invariants
|
|
231
|
+
|
|
232
|
+
- **Read-only**: no tool above `Mode.READ` is registered. `run_show_command`
|
|
233
|
+
rejects any verb other than `show`.
|
|
234
|
+
- **Secret hygiene**: password never written to JSON once keychain is available;
|
|
235
|
+
every error/log line passes through `_redact` (password, `sessionKey`).
|
|
236
|
+
- **TLS**: verify on by default; disabling emits the toolkit's stderr warning.
|
|
237
|
+
- **No confident-empty**: on transport/auth/status errors, return the error, not
|
|
238
|
+
an empty inventory.
|
|
239
|
+
- **Bounded requests**: per-request timeout cap so one stuck controller can't
|
|
240
|
+
freeze the process.
|
|
241
|
+
|
|
242
|
+
## 11. Build order (task checklist)
|
|
243
|
+
|
|
244
|
+
1. Scaffold: `pyproject.toml`, `__init__.py`, `__main__.py`, `LICENSE`, `.gitignore`, `py.typed`.
|
|
245
|
+
2. `_redact.py` + `config/arrays.py` (+ unit test `test_config.py`).
|
|
246
|
+
3. `client.py` (+ `test_client_auth.py` with `respx`: login, session reuse, re-login).
|
|
247
|
+
4. `responses/shapers.py` + `tools/_common.py` (+ `test_shapers.py`).
|
|
248
|
+
5. Tool modules (status → system → hardware → storage → hosts → events →
|
|
249
|
+
statistics → aggregations → utility).
|
|
250
|
+
6. `server.py` wiring + `README.md`.
|
|
251
|
+
7. Verify: `python -c "import hpe_msa_mcp.server"`, `ruff check`, `pytest`.
|
|
252
|
+
|
|
253
|
+
## 12. Open items / to confirm before/while building
|
|
254
|
+
|
|
255
|
+
- [ ] Live array host + credentials to run the `integration`-marked smoke test
|
|
256
|
+
(`check_connectivity` against a real MSA). Build proceeds without it;
|
|
257
|
+
creds get configured via keyring/env later.
|
|
258
|
+
- [ ] Confirm exact JSON object keys against the target firmware (table in §4 is
|
|
259
|
+
the documented set; auto-detect covers drift, but verify on first live run).
|
|
260
|
+
- [x] Decide whether `add_array` belongs in the read-only surface or should be a
|
|
261
|
+
one-off CLI/setup step instead of an exposed tool.
|
|
262
|
+
**Decision (built):** exposed as a `Mode.READ` tool. It writes only *local
|
|
263
|
+
MCP config* (the array registry + keychain), never the storage array, so it
|
|
264
|
+
stays within the read-only posture toward the hardware. Documented as such
|
|
265
|
+
in the tool docstring and README.
|
|
266
|
+
|
|
267
|
+
## 13. Build status (as built)
|
|
268
|
+
|
|
269
|
+
Implemented in one pass per the plan. Verified: `python -c "import
|
|
270
|
+
hpe_msa_mcp.server"` OK (34 tools registered), `ruff check` clean, `ruff format`
|
|
271
|
+
clean, `mypy --strict src` clean, `pytest` 30 passed.
|
|
272
|
+
|
|
273
|
+
**Config model (extended post-plan):** one entry per *array* (not per
|
|
274
|
+
controller); an entry may carry a `hosts` failover list (VIP or both controller
|
|
275
|
+
IPs) that the client walks in order. Shared `defaults` (username in
|
|
276
|
+
`arrays.json`, password in a keychain default account) let every array inherit a
|
|
277
|
+
common monitoring credential, with per-array override. Resolution is
|
|
278
|
+
per-array → default → env. New tool `set_default_credentials`; `add_array` takes
|
|
279
|
+
`host`/`hosts` and optional username/password (inherits defaults when omitted).
|
|
280
|
+
|
|
281
|
+
Tool surface (all `Mode.READ`): `server_status`, `check_connectivity`;
|
|
282
|
+
`get_system`, `get_versions`, `get_controllers`, `get_enclosures`, `get_frus`,
|
|
283
|
+
`get_license`, `get_ntp_status`, `get_network`; `get_disks`, `get_sensor_status`,
|
|
284
|
+
`get_fans`, `get_power_supplies`, `get_expander_status`, `get_certificate`;
|
|
285
|
+
`get_pools`, `get_disk_groups`, `get_volumes`, `get_snapshots`;
|
|
286
|
+
`get_host_groups`, `get_hosts`, `get_initiators`, `get_maps`, `get_ports`;
|
|
287
|
+
`get_events`, `get_alerts`; `get_controller_statistics`, `get_volume_statistics`,
|
|
288
|
+
`get_disk_statistics`; `get_health_summary`; `run_show_command`, `list_arrays`,
|
|
289
|
+
`set_default_credentials`, `add_array`.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
Format based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/);
|
|
6
|
+
versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] — 2026-11-03
|
|
11
|
+
|
|
12
|
+
Initial release. A token-efficient, read-only MCP server for HPE MSA (Modular
|
|
13
|
+
Smart Array) storage — Gen5 (1050/2050/2052) and Gen6 (1060/2060/2062) — over the
|
|
14
|
+
array's REST management API.
|
|
15
|
+
|
|
16
|
+
### Added — tool surface
|
|
17
|
+
|
|
18
|
+
33 read-only tools, all `Mode.READ`. List tools return default-lean field
|
|
19
|
+
whitelists (`verbose=true` to opt out) with `fields` / `pattern` / `pagination`.
|
|
20
|
+
|
|
21
|
+
- **Diagnostics** — `server_status` (local; no network), `check_connectivity`.
|
|
22
|
+
- **System** — `get_system`, `get_versions`, `get_controllers`, `get_enclosures`,
|
|
23
|
+
`get_frus`, `get_license`, `get_ntp_status`, `get_network`.
|
|
24
|
+
- **Hardware** — `get_disks`, `get_sensor_status`, `get_fans`,
|
|
25
|
+
`get_power_supplies`, `get_expander_status`, `get_certificate`.
|
|
26
|
+
- **Storage** — `get_pools`, `get_disk_groups`, `get_volumes`, `get_snapshots`.
|
|
27
|
+
- **Hosts / SAN** — `get_host_groups`, `get_hosts`, `get_initiators`, `get_maps`,
|
|
28
|
+
`get_ports`.
|
|
29
|
+
- **Events** — `get_events`, `get_alerts`.
|
|
30
|
+
- **Statistics** — `get_controller_statistics`, `get_volume_statistics`,
|
|
31
|
+
`get_disk_statistics`.
|
|
32
|
+
- **Aggregation** — `get_health_summary`, a one-call rollup of system health,
|
|
33
|
+
controller redundancy, degraded/failed disks, pool capacity, and active alerts.
|
|
34
|
+
- **Utility** — `run_show_command` (hard-restricted to the `show` verb) and
|
|
35
|
+
`list_arrays`. Registering arrays and setting shared credentials are
|
|
36
|
+
intentionally **not** MCP tools (config/keychain mutation from a prompt is a
|
|
37
|
+
credential-exfil risk); those live only in the setup wizard CLI.
|
|
38
|
+
|
|
39
|
+
### Added — configuration & security
|
|
40
|
+
|
|
41
|
+
- Multi-array registry (`arrays.json`): one entry per array, with an optional
|
|
42
|
+
`hosts` failover list (VIP or both controller IPs) walked in order.
|
|
43
|
+
- OS-keychain credential storage (service `hpe-msa-mcp`); plaintext passwords in
|
|
44
|
+
the file are auto-migrated to the keychain and stripped on load.
|
|
45
|
+
- Shared default credentials (one monitoring user+password reused by arrays with
|
|
46
|
+
none of their own); resolution is per-array → default → env.
|
|
47
|
+
- Session-key authentication (SHA-256 with MD5 fallback) with transparent
|
|
48
|
+
re-login on expiry; login hashes, session keys, and passwords redacted from
|
|
49
|
+
every error and log line.
|
|
50
|
+
- Default-deny RBAC (`MSA_MODE`, read-only surface), verify-by-default TLS
|
|
51
|
+
(`MSA_TLS_VERIFY` / `MSA_TLS_CA`), and an optional JSONL audit log
|
|
52
|
+
(`MSA_AUDIT_LOG`).
|
|
53
|
+
|
|
54
|
+
### Added — `node804-hpe-msa-setup` install wizard
|
|
55
|
+
|
|
56
|
+
A bundled wizard that configures the server end to end, so a new install no
|
|
57
|
+
longer means hand-editing JSON. Run `node804-hpe-msa-setup` (or
|
|
58
|
+
`python -m hpe_msa_mcp.setup` if the console script isn't on `PATH`).
|
|
59
|
+
|
|
60
|
+
- Stores the controller password in the OS keychain (read with `getpass`,
|
|
61
|
+
confirmed by retype, never written to disk) and writes only non-secret fields
|
|
62
|
+
to `arrays.json`. Guards against terminals where `getpass` mis-reads (warns on
|
|
63
|
+
the insecure fallback and on a suspiciously short value); `--password-stdin`
|
|
64
|
+
offers a terminal-agnostic alternative.
|
|
65
|
+
- Supports a comma-separated failover host list and optional shared defaults.
|
|
66
|
+
- Wires a Claude Desktop server entry (host and password resolve from
|
|
67
|
+
`arrays.json` + keychain, so neither lands in the client config), backing up the
|
|
68
|
+
existing config first.
|
|
69
|
+
- Installs the `hpe-msa-engineer` skill into `~/.claude/skills` and writes an
|
|
70
|
+
upload zip for Claude Desktop / claude.ai.
|
|
71
|
+
- Modify-aware, with `array list|add|remove` and `skill` subcommands; a
|
|
72
|
+
non-interactive mode (`MSA_SETUP_PASSWORD` + `--yes`) for scripted installs.
|
|
73
|
+
|
|
74
|
+
### Added — `hpe-msa-engineer` skill pack
|
|
75
|
+
|
|
76
|
+
Procedural knowledge for driving the server well, bundled inside the wheel so the
|
|
77
|
+
wizard can install it offline. A router `SKILL.md` plus on-demand references
|
|
78
|
+
(health model, storage model, host mapping, events/alerts, statistics, tool map)
|
|
79
|
+
and seven playbooks (health check, capacity review, disk failure triage, mapping
|
|
80
|
+
audit, performance review, event triage, firmware inventory).
|
|
81
|
+
|
|
82
|
+
### Added — packaging & CI
|
|
83
|
+
|
|
84
|
+
- Console scripts `node804-hpe-msa` and `node804-hpe-msa-setup`.
|
|
85
|
+
- GitHub Actions: CI (ruff, ruff format, mypy `--strict`, pytest on Python 3.13
|
|
86
|
+
and 3.14) and PyPI publishing via Trusted Publishing (OIDC) on `v*` tags.
|
|
87
|
+
- Pre-commit hooks (ruff) pinned to the same ruff version as CI.
|
|
88
|
+
|
|
89
|
+
[Unreleased]: https://github.com/Node804/node804-hpe-msa/compare/v0.1.0...HEAD
|
|
90
|
+
[0.1.0]: https://github.com/Node804/node804-hpe-msa/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael Pope
|
|
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.
|