# SCADA MCP Server

[![Crates.io](https://img.shields.io/crates/v/mcp-scada.svg)](https://crates.io/crates/mcp-scada)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
[![ADK-Rust Enterprise](https://img.shields.io/badge/ADK--Rust-Enterprise-purple.svg)](https://enterprise.adk-rust.com)
[![Registry Ready](https://img.shields.io/badge/ADK_Registry-Ready-green.svg)](https://www.zavora.ai)
[![Risk: Critical](https://img.shields.io/badge/risk-critical-red.svg)](#safety-posture)

A SCADA / industrial-control platform for [ADK-Rust Enterprise](https://enterprise.adk-rust.com) energy & utility agents. 27 MCP tools covering sites & assets, tags/points telemetry, real-time readings + a historian, an alarm lifecycle, **safety-interlocked and approval-gated control commands**, outage triage, and maintenance work orders — with a full command audit trail.

> ⚠️ **Critical-risk domain.** This platform models supervisory control of physical infrastructure (grid breakers, gas regulators, water pumps, inverters). Every control action is interlock-validated and approval-gated. See [Safety posture](#safety-posture).

## A platform, not a point solution

This is modeled as a general SCADA backbone (à la a control-room SCADA/EMS/DMS), so the energy-utility agents are clients of one shared platform:

| Agent | Uses |
|-------|------|
| **Grid Outage Triage Agent** | `report_outage`, `outage_queue`, `update_outage_status`, `list_alarms` |
| **Gas Safety Incident Agent** | `report_outage` (hazard→P1), `raise_alarm`, `set_lockout`, `list_tags` |
| **Renewable Dispatch Assistant** | `tag_trend`, `check_interlocks`, `issue_command` (setpoint curtailment) |
| **Water Leak Response Agent** | `tag_trend`, `ingest_reading`, `issue_command` (pump control), `report_outage` |
| **Asset Maintenance Planner** | `create_work_order`, `update_work_order`, `list_work_orders`, `set_lockout` |

## Architecture

<p align="center">
  <img src="https://raw.githubusercontent.com/zavora-ai/mcp-scada/main/docs/architecture.svg" alt="SCADA MCP Architecture" width="780"/>
</p>

## Capabilities

- **Sites & assets** — sites by sector (electric/gas/water/renewable); assets with operating state (in_service/out_of_service/locked_out/faulted) and lockout/tagout.
- **Tags / points** — addressable telemetry and control points typed `analog`/`status` (read-only) or `setpoint`/`control` (writable), with engineering ranges and warn/crit thresholds.
- **Telemetry & historian** — `ingest_reading` records history and **auto-raises/clears threshold alarms**; `tag_history` and `tag_trend` for analysis.
- **Alarms** — lifecycle of active → acknowledged → cleared, by severity.
- **Control** — `issue_command` runs the interlock chain and applies the value **only on an all-pass**; `check_interlocks` is a read-only dry-run.
- **Outages** — `report_outage` auto-triages priority (P1 highest); `outage_queue` is the priority-ordered work queue.
- **Maintenance** — work orders (preventive/corrective/inspection/emergency) with scheduling and assignment.

## Safety posture

Control acts on real equipment, so the platform treats it as critical:

- **Interlock chain on every command** (`issue_command`), checked in order; the value is applied **only if all pass**, and a rejected command is recorded but never executed:
  1. `writable_point` — point is a setpoint/control, not a read-only input
  2. `lockout_tagout` — asset is not under a LOTO hold
  3. `asset_not_faulted` — asset is not in a faulted state
  4. `no_active_critical_alarm` — no unresolved critical alarm on the asset
  5. `setpoint_in_range` — setpoint within engineering min/max
  6. `binary_control_value` — control points accept only 0/1
- **Dry-run first** — `check_interlocks` (read-only) lets an agent confirm feasibility before requesting an approval-gated command.
- **Approval-gated external writes** — `issue_command`, `set_lockout`, and `set_asset_state` are `external_write` + `requires_approval` in the manifest; `risk_level = "critical"`.
- **Full audit trail** — every command (executed *and* rejected) and state change is recorded (`command_history`, `audit_log`).
- **Reference engine** — this is an in-memory reference implementation. A production deployment **must** additionally enforce these interlocks at the RTU/PLC/field level and through hardware protection; software gates are defense-in-depth, not a substitute. Sample data is fictitious.

## Tools (27)

### Sites & Assets (7)
`create_site` · `list_sites` · `create_asset` · `get_asset` · `list_assets` · `set_asset_state` (gated) · `set_lockout` (gated)

### Tags & Telemetry (6)
`create_tag` · `get_tag` · `list_tags` · `ingest_reading` · `tag_history` · `tag_trend`

### Alarms (4)
`list_alarms` · `raise_alarm` · `acknowledge_alarm` · `clear_alarm`

### Control (3)
`check_interlocks` (dry-run) · `issue_command` (gated, external, interlocked) · `command_history`

### Outages (3)
`report_outage` · `update_outage_status` · `outage_queue`

### Maintenance & Audit (4)
`create_work_order` · `update_work_order` · `list_work_orders` · `audit_log`

## Example

```jsonc
// Dry-run the interlocks, then issue a gated control command
{"name": "check_interlocks", "arguments": {"tag_id": "TAG-1015", "value": 1500}}
{"name": "issue_command", "arguments": {"tag_id": "TAG-1015", "action": "set", "value": 1500}}

// Telemetry auto-raises a threshold alarm
{"name": "ingest_reading", "arguments": {"tag_id": "TAG-1004", "value": 595}}
{"name": "list_alarms", "arguments": {"active_only": true}}

// Outage triage
{"name": "report_outage", "arguments": {"site_id": "SITE-1005", "cause": "gas odor", "safety_hazard": true}}
{"name": "outage_queue", "arguments": {}}
```

## Install & run

```bash
cargo install mcp-scada
mcp-scada            # serves MCP over stdio
```

Or build from source:

```bash
git clone https://github.com/zavora-ai/mcp-scada
cd mcp-scada && cargo build --release
./target/release/mcp-scada
```

## Registry manifest

```toml
server_id = "mcp_scada"
display_name = "SCADA / Industrial Control"
version = "1.0.0"
domain = "energy-utilities"
risk_level = "critical"
writes_allowed = "gated"
```

The full [`mcp-server.toml`](mcp-server.toml) declares all 27 tools with risk classes and approval gates for registry onboarding.

## License

Apache-2.0
