{
  "markdown": "<!-- mcp-name: io.github.industrial-aiops/iaiops-energy -->\n\n# industrial-aiops-energy — Energy Edition (Substation / Utility)\n\n**English** · [中文](README.zh-CN.md)\n\nThe **energy edition** of [Industrial-AIOps](https://github.com/industrial-aiops/industrial-aiops),\nsplit out into its own repo: read-only OT connectors for **substation / utility\ntelecontrol** protocols, built **on top of `iaiops.core`**.\n\n- **IEC 60870-5-104** (`c104`) — RTU / substation telemetry\n- **DNP3 / IEEE 1815** (`pydnp3`) — outstation monitoring\n- **IEC 61850 MMS** (`pyiec61850`, linux-only wheel) — substation IED reads\n\nIt reuses the base package's shared **governance** (audit / budget / risk-tier /\nundo), **cross-protocol brain** (data-flow / alarm / OEE / downtime RCA on the\nnormalized ISA-95/18.2 model), and MCP server infrastructure — this repo only adds\nthe three energy connectors + their session builders + MCP tools. Read-first: no\ncontrol-direction writes are exposed.\n\nCurrent release: **0.1.11** (requires `iaiops>=0.20.3,<1.0`). New in 0.1.11 — **all three\nmonitor paths are CI-gated for the first time**, and a skipped live test now fails the build\ninstead of passing it. DNP3 was the holdout: `pip install pydnp3` fails on any current Linux,\nso this repo inherited the ecosystem's \"unbuildable on hosted runners\" and\n`tests/test_dnp3_live.py` skipped on every build. It is not unbuildable — opendnp3 compiles\nclean, and the 2019 binding layer needed three mechanical fixes, now scripted in\n`scripts/build_pydnp3.sh` and running on real GitHub runners. Also inherits two governance\nfixes from `iaiops` 0.20.3 (pin raised): a call that **failed** is no longer audited as a\nsuccess (which also mis-informed the pattern circuit breaker on every failure), and the\nrunaway guard can now see a caller retrying a denial forever. Previously in 0.1.10 — a **security\nfix inherited from the base package**: three egress tools this edition mirrors\n(`stream_publish`, `stream_publish_event`, `historian_push`) wrote their credential — a NATS\nauth token, a TSDB password — into the audit log in the clear, and `audit_forward` shipped\nthat row to the configured SIEM. They are defined in `iaiops`, so this edition could not fix\nit alone; the pin moves to `iaiops>=0.20.2`, and a contract test now fails the build if the\nwhole registered surface ever again carries an undeclared credential parameter. **If you have\npassed a token or historian password to these tools, rotate it** and check existing audit\nrows. The energy connectors themselves take no credential parameters. Previously in 0.1.9 — every tool\nships the MCP `ToolAnnotations` hints (`readOnlyHint` / `destructiveHint` / `openWorldHint`),\n**derived** from the `@governed_tool` harness rather than hand-written, so a client can tell a\nmonitor read from a tool that acts without parsing the `[READ]`/`[WRITE]` docstring tag. On the\nwire that is 59 tools, 55 read-only and **0 destructive** — this edition exposes no control\ndirection, and that is now enforced by a test rather than only documented. They are hints, not a\ngate: the MCP spec forbids relying on annotations for security decisions, and enforcement stays in\n`@governed_tool`. The base pin moved to `iaiops>=0.20.1` so the hint derivation is imported from\n`mcp_server.hints` instead of duplicated here.\n\nPreviously in 0.1.8 — the base\n`IAIOPS_READ_ONLY` gate was **removed** in iaiops 0.19.0 (read/write authorisation is the\ncaller's decision — agent judgement / account management — not the tap's; every tool is\ngoverned and audited via the base `@governed_tool` harness), so this edition drops it too.\nIt keeps the **`IAIOPS_NO_EGRESS=1`** gate — a data-exfiltration / airgap axis that withholds\ndata-shipping tools from `list_tools()` at registration time. This edition runs its own\n`FastMCP` instance, so the gate is wired into its own `main()`; without it `IAIOPS_NO_EGRESS=1`\nwould still expose `historian_push`, `rca_narrate` and the `stream_publish*` pair mirrored in\nfrom the base brain. The energy connectors themselves are monitor-only and survive the gate\nintact. See `CHANGELOG.md`.\n\nPreviously in 0.1.6 — an\n**audit-hardening pass** over the three read-only connectors: DNP3 no longer reports an\noffline outstation as online or returns a partial integrity-poll database; IEC-61850 gained\na bounded connect/request timeout and stopped fabricating `0.0`/empty-success on failure; the\nsubstation analyzer no longer calls a lone breaker-open a \"selective trip\"; tests isolate\n`IAIOPS_HOME`; and the base pin was raised to `iaiops>=0.14` so the governance endpoint-scoping\nfix applies. See `CHANGELOG.md` §0.1.6. (0.1.5 verified the IEC-104 monitor path — a real `c104`\nclient↔server round-trip in a Linux container, `tests/test_iec104_live.py`.)\n**Physical RTU / IED remains unverified.** Since 0.1.3 the server has\nits **own MCP identity** — `iaiops-energy-mcp` runs a dedicated `FastMCP(\"iaiops-energy\")`\ninstance with energy-specific instructions (IEC-104 / DNP3 / IEC-61850, read-first,\nno control/operate), with the base cross-protocol brain tools mirrored onto it — plus\nan **edition skill** (`skills/iaiops-energy/SKILL.md`, anti-drift-tested against the\nregistered tool surface) and **protocol-consistency contract tests** (every tool must\ncarry the governance marker, a `[READ]`-style risk tag, an `Args:` section, and the\ncanonical `{error, hint}` error shape; the server refuses to start if any registered\ntool lacks the governance marker).\n\n## 🧪 Beta testing & co-creation\n\n**Live substation RTU / IED / IEC-104 field testing is what this package needs most.**\nThe IEC-104, DNP3 and IEC-61850 monitor paths are library-loopback-verified, and **all three\nnow run on every CI build** — a skip fails the build rather than passing it. (Since\n2026-08-01: DNP3's evidence used to be a single manual run on 2026-07-02, because `pydnp3`\nwas believed unbuildable on hosted runners. That belief was wrong — see\n`scripts/build_pydnp3.sh`.) **Real RTU / IED hardware remains unverified.** If you can run\n`iaiops doctor` against real substation gear in an authorised test environment, we would\nvery much like to hear the result — verified devices are credited by name in the support\nmatrix. Report results (protocol + device model + `iaiops doctor` output) via the base\nrepo's pinned issue:\n👉 [industrial-aiops#28 — Call for field-testing partners (v0.10.0)](https://github.com/industrial-aiops/industrial-aiops/issues/28)\n\n## Why a separate repo\n\nEnergy targets a distinct buyer (utilities / substations), has heavier\nplatform-specific deps (`pyiec61850` is a linux-only SWIG wheel; `pydnp3` builds a\nnative ext), and its own compliance surface (China's *Security Protection of Power\nMonitoring Systems* regime). Splitting keeps\nthe base install light. See the base repo's `docs/ENERGY-SPINOUT.md` for the plan.\n\n## Install\n\n```bash\npip install iaiops-energy[energy]      # all three energy protocols\npip install iaiops-energy[iec104]      # just IEC-104\n```\n\n`iaiops-energy` pulls in `iaiops` (the shared core) automatically.\n\n## Use (MCP)\n\n```bash\niaiops-energy-mcp                       # brain + energy tools over stdio\n```\n\nPoint a target at your substation gear in `~/.iaiops/config.yaml`\n(`protocol: iec104|dnp3|iec61850`, `host`, `port`, `common_address` / `unit_id`).\n\n## Edge deployment & ecosystem (edge-native / Margo)\n\nLike the base package, the energy edition rides on a hardened, centrally-managed **edge host** as a\nportable, governed **edge application** — mapping onto the [Margo](https://margo.org/)\nedge-interoperability roles (immutable host · compliant orchestrator · **iaiops-energy = the\nOT-domain app**), deployable as an OCI **Managed Container** (outbound-only to substation RTUs/IEDs,\nno inbound). A container + `margo.org/v1-alpha1` application-description skeleton is in\n[`deploy/margo/`](deploy/margo/); the full alignment + honest gap analysis lives in the base repo's\n[`docs/MARGO-ALIGNMENT.md`](https://github.com/industrial-aiops/industrial-aiops/blob/main/docs/MARGO-ALIGNMENT.md).\nThe descriptor is validated against Margo's published `margo.org/v1-alpha1` LinkML schema on every\nPR (CI job `margo-descriptor`) and passes clean — structural validity only, see\n[`deploy/margo/schema/PROVENANCE.md`](deploy/margo/schema/PROVENANCE.md).\n\n> **Honest status:** a natural Margo edge application, but **NOT Margo-compliant yet** — image build,\n> hosted+signed package, and a published conformance result are roadmap `⏳`. No claim of compliance\n> until that result exists, and the schema pass above is **not** a step toward it: the compliance\n> test suite cannot be run today because it does not exist yet (no conformance repo in the `margo`\n> org; a first PR1 vertical slice was still being scoped as of 2026-01-15).\n\n## Validation status (honest)\n\nThe same honesty ladder as the base repo. Driver **codec / API surface** is verified\nagainst the real libraries; the mock/monkeypatched **unit tests run in CI** without\nhardware. See the base repo's `docs/PREVIEW-VERIFICATION.md` runbook for how a\nprotocol is promoted.\n\n| Protocol | Status | CI coverage | Evidence |\n| --- | --- | --- | --- |\n| **DNP3 / IEEE 1815** | **verified (monitor path)** | ✅ runs every push ¹ | Real master↔outstation round-trip against a live **opendnp3** outstation (`pydnp3`): `is_online()` reflects the real channel `OnStateChange`, and `integrity_poll()` (Class 0/1/2/3) returns the seeded binary/analog/counter database grouped by type. See `tests/test_dnp3_live.py` (`@pytest.mark.integration`). No physical RTU. |\n| **IEC 60870-5-104** | **verified (monitor path)** | ✅ runs every push | Real client↔server round-trip against an in-process **`c104`** server (`tests/test_iec104_live.py` + `tests/iec104_server_harness.py`, `@pytest.mark.integration`, passes in a Linux container): `iec104_connection_info` discovers the seeded station, `iec104_interrogate` (general interrogation / C_IC) returns the seeded `M_ME_NC_1` + `M_SP_NA_1` points with quality, `iec104_read_point` reads the measurand, a bad IOA yields `found=False` with **no fabricated value**, and server-side ASDU capture proves **no control ASDU** (C_SC / C_DC / C_SE) is ever issued. `c104` ships no macOS wheel so the test skips on macOS (runs in CI / Linux). No physical RTU. Monitor/read only. |\n| **IEC 61850 (MMS)** | **verified (monitor path)** | ✅ runs every push | Real client↔server MMS round-trip against an in-process **libiec61850** MMS server built with `pyiec61850`'s server API: `iec61850_device_directory` lists the logical device (and browses its logical nodes / data objects), and `iec61850_read` returns a seeded measurand (`TotW.mag.f`, FC `MX`) over real ISO-on-TCP; a bad reference surfaces an MMS data-access error instead of a fabricated value. See `tests/test_iec61850_live.py` (`@pytest.mark.integration`, skips when `pyiec61850` / its server API is absent). No physical IED. Read/monitor only — control / GOOSE / SV out of scope. |\n\n**¹ All three monitor paths are now CI-gated**, and a skip fails the build rather than\npassing it (`no live protocol test may skip`). This used to read: *\"only IEC-104 is\nCI-gated; DNP3 and IEC-61850 rest on out-of-CI runs, not on the green badge.\"* That was\ntrue and is no longer.\n\nDNP3 was the last one, and the reason is worth recording because the old note here — and\nthe CI job, and this README — all repeated the same wrong conclusion for months:\n`pip install pydnp3` fails on any current Linux, so it was taken as **unbuildable** on\nhosted runners. It is not. opendnp3 itself compiles clean; what had rotted was the 2019\nbinding layer. Three mechanical fixes, no patches to opendnp3:\n\n1. Python headers must be present (`python3-dev`) — without them the build dies at\n   `Python.h: No such file or directory`, which is what \"unbuildable\" looked like;\n2. 214 vendored headers `#include <python2.7/Python.h>`, rewritten to `<Python.h>`;\n3. the vendored pybind11 predates CPython 3.11 (it reads `PyFrameObject` internals that\n   3.11 made opaque) and GCC 13 (`std::uint16_t` without `<cstdint>`) — replaced with\n   pybind11 v2.13.6.\n\n`scripts/build_pydnp3.sh` applies all three and verifies the import. ~3 min cold on two\ncores. The lesson generalises: *\"the ecosystem says it cannot be built\"* is a claim to\ntest, not to inherit.\n\nDNP3 notes: read-only / monitor direction only (no control). Its\n`DNP3Manager.Shutdown()` can block in a long-lived interpreter, so the connector bounds\nteardown (`_Pydnp3MasterAdapter.shutdown`) and the test drives the round-trip in a\nshort-lived child process.\n\n## License\n\nMIT — © wei. Part of the vendor-neutral, governed Industrial-AIOps line.\n",
  "bytes": 12640,
  "sha": "cd64439c024a7376edd609fdcdbb8c66dc3c5b706b092949c6528d470ac3bcc4",
  "repo_slug": "industrial-aiops/industrial-aiops-energy",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_industrial_aiops_iaiops_energy_16ccbc7b/readme"
}