Skip to content
8 changes: 4 additions & 4 deletions INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Six FastMCP servers cover IoT data, time-series ML, work orders, vibration diagn

| Server | Tools | Categories | Backing service |
| ----------- | ----- | ------------------------ | -------------------------------------- |
| `iot` | 7 | read | CouchDB (telemetry + asset registry) |
| `iot` | 12 | read | CouchDB (telemetry + asset registry) |
| `utilities` | 3 | read | none |
| `fmsr` | 2 | read, LLM-use | LiteLLM + `failure_modes.yaml` |
| `wo` | 14 | read, write | CouchDB |
Expand All @@ -164,17 +164,17 @@ from mcphub import ToolUniverse
tu = ToolUniverse() # 1. init
tu.load_tools() # 2. connect + discover
result = tu.run({ # 3. run
"name": "iot.sensors",
"name": "iot.measured_sensors",
"arguments": {"site_name": "MAIN", "asset_id": "Chiller 6"},
})
tu.close()
```

- Tools are namespaced `<server>.<tool>` (e.g. `iot.sensors`,
- Tools are namespaced `<server>.<tool>` (e.g. `iot.measured_sensors`,
`wo.generate_work_order`); a bare name works when unambiguous.
- `load_tools(servers=["iot", "fmsr"])` limits the set; no args loads all six.
- Discovery: `tu.find_tools("failure mode")`, `tu.list_tools()`,
`tu.tool_specification("iot.sensors")`.
`tu.tool_specification("iot.measured_sensors")`.
- Servers are spawned via `uv run <server>-mcp-server` and inherit your `.env`.
Run inside the project (`uv run python ...`) or an activated venv. Override the
launch table with `ToolUniverse(servers={...})`.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ AssetOpsBench is a **unified framework for developing, orchestrating, and evalua

| MCP Servers | Important tools |
|---|---|
| **IoT** | `get_sites`, `get_history`, `get_assets`, `get_sensors` |
| **IoT** | `sites`, `asset_ids`, `measured_sensors`, `history`, `installed_sensors`, `assets`, `stream_extent`, ... |
| **FMSR** | `get_failure_modes`, `generate_failure_modes`, `add_failure_modes`, `generate_failure_mode_sensor_mapping` |
| **TSFM** | `forecasting`, `timeseries_anomaly_detection` |
| **WO** | `get_work_order_distribution`, `predict_next_work_order`, ... |
Expand Down
39 changes: 24 additions & 15 deletions docs/mcp-servers.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@ Six FastMCP servers expose the AssetOpsBench domain logic. Each is a standalone

## iot — IoT Sensor Data


The IoT server reads from **two** databases: telemetry readings (`IOT_DBNAME`, default `iot`) and an
asset **registry** (`ASSET_DBNAME`, default `asset`, loaded from `asset_profile_sample.json`). The
two answer different questions: `assets()`/`sensors()` reflect TELEMETRY — what actually streams (the
**measured** set); `get_asset()`/`asset_sensors()`/`registry_assets()` reflect the REGISTRY — the
asset nameplate and the **installed** sensor inventory (by name). Comparing `asset_sensors()` against
`sensors()` surfaces sensors that are installed but not streaming. The registry also reconciles ids
across systems (Maximo `assetnum`, telemetry `iot_asset_id`, work-order `wo_assetnum`), so an asset
can be looked up by any of its ids.
two answer different questions: `asset_ids()`/`measured_sensors()` reflect TELEMETRY — what actually
streams (the **measured** set); `get_asset_detail()`/`installed_sensors()`/`assets()` reflect the
REGISTRY — the asset nameplate and the **installed** sensor inventory (by name). Comparing
`installed_sensors()` against `measured_sensors()` surfaces sensors that are installed but not
streaming. Beyond browsing: `stream_extent` and `sensor_coverage` report a stream's time bounds and
per-sensor record counts so you can size a query; `history` returns readings a page at a time
(`limit` rows per page, follow `cursor`); and `sensor_stats`, `latest_reading`, and
`find_assets_by_sensors` provide quick summaries and reverse lookup. Timestamps are ISO 8601 and
time ranges are half-open `[start, end)`.

**Path:** `src/servers/iot/main.py`
**Requires:** CouchDB (`COUCHDB_URL`, `COUCHDB_USERNAME`, `COUCHDB_PASSWORD`, `IOT_DBNAME`, `ASSET_DBNAME`)
Expand All @@ -35,15 +39,20 @@ can be looked up by any of its ids.

Synthetic motor vibration data (`asset_id: Motor_01`, from `motor_01.json`) ships in a separate `vibration` database for the vibration MCP server.

| Tool | Arguments | Description |
| ----------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `sites` | — | List all sites, discovered dynamically from the asset registry (`siteid`) |
| `assets` | `site_name` | List asset ids registered at a site (telemetry id where present, else `assetnum`) |
| `sensors` | `site_name`, `asset_id` | List **measured** sensor names for an asset (union of keys across its telemetry docs) |
| `history` | `site_name`, `asset_id`, `start`, `final?` | Fetch historical sensor readings for a time range (ISO 8601 timestamps) |
| `get_asset` | `site_name`, `asset_id` | Registry/nameplate detail for one asset (description, assettype, status, location, vintage, installed count) |
| `asset_sensors` | `site_name`, `asset_id` | List the **installed** sensors for an asset, by name (registry inventory) |
| `registry_assets` | `site_name`, `assettype?` | List registry assets with metadata (assettype, vintage, sensor count), optionally filtered by assettype |
| Tool | Arguments | Description |
| ------------------------ | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `sites` | — | List all sites, discovered dynamically from the asset registry (`siteid`) |
| `asset_ids` | `site_name` | List the bare asset IDs at a site — use one as `asset_id` in the other tools |
| `assets` | `site_name`, `assettype?` | Assets with metadata (description, assettype, vintage, installed sensor count); optionally filter by assettype |
| `measured_sensors` | `site_name`, `asset_id` | **Measured** sensor names for an asset (what actually streams; union across telemetry docs) |
| `installed_sensors` | `site_name`, `asset_id` | **Installed** sensor names for an asset (registry nameplate inventory) |
| `get_asset_detail` | `site_name`, `asset_id` | Registry/nameplate detail for one asset (description, assettype, status, location, vintage, installed count) |
| `find_assets_by_sensors` | `site_name`, `sensors`, `match?`, `substring?`, `source?` | Reverse lookup: assets that have the given sensor(s); `source`=`measured`\|`installed`, `match`=`all`\|`any` |
| `history` | `site_name`, `asset_id`, `start?`, `end?`, `sensors?`, `limit?`, `cursor?` | Historical readings, one page at a time. ISO 8601, half-open `[start,end)`; omit range for all. Page with `limit` (≤1000) + `cursor`; response gives `next_cursor`/`has_more`. `sensors` projects columns. |
| `stream_extent` | `site_name`, `asset_id`, `sensor?`, `start?`, `end?` | Time bounds + record count for a stream (pre-flight for `history`); flags if it exceeds the page limit |
| `sensor_coverage` | `site_name`, `asset_id`, `max_scan_docs?` | Per-measured-sensor record counts and first/last timestamps (`max_scan_docs`=0 scans all) |
| `sensor_stats` | `site_name`, `asset_id`, `sensor?`, `start?`, `end?` | Numeric summary (count/min/max/mean/stddev) per sensor; omit `sensor` to summarize every measured sensor |
| `latest_reading` | `site_name`, `asset_id`, `sensor?` | Most recent observation for an asset (+ `age_seconds` for staleness) |

## utilities — Utilities

Expand Down
16 changes: 2 additions & 14 deletions src/couchdb/collections.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,8 @@
"doctype": "asset",
"indexes": [
[
"doctype",
"assetnum"
],
[
"doctype",
"assettype"
],
[
"doctype",
"assetnum",
"siteid"
],
[
"doctype",
"iot_asset_id"
]
]
},
Expand Down Expand Up @@ -144,4 +132,4 @@
"id_prefix": "fc",
"indexes": []
}
}
}
26 changes: 1 addition & 25 deletions src/couchdb/scenarios_data/shared/iot/asset_profile_sample.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
[
{
"_id": "asset:CHILLER6",
"doctype": "asset",
"assetnum": "CHILLER6",
"iot_asset_id": "Chiller 6",
"wo_assetnum": "CHILLER6",
"assetnum": "CHILLER 6",
"description": "Chiller 6 (chilled-water plant)",
"siteid": "MAIN",
"orgid": "RIVPLANT",
Expand Down Expand Up @@ -35,11 +31,7 @@
]
},
{
"_id": "asset:mp_1",
"doctype": "asset",
"assetnum": "mp_1",
"iot_asset_id": "mp_1",
"wo_assetnum": null,
"description": "Compressor / pneumatic system",
"siteid": "MAIN",
"orgid": "RIVPLANT",
Expand Down Expand Up @@ -73,11 +65,7 @@
]
},
{
"_id": "asset:hyd_1",
"doctype": "asset",
"assetnum": "hyd_1",
"iot_asset_id": "hyd_1",
"wo_assetnum": null,
"description": "Hydraulic pump condition-monitoring rig",
"siteid": "MAIN",
"orgid": "RIVPLANT",
Expand Down Expand Up @@ -114,11 +102,7 @@
]
},
{
"_id": "asset:Motor_01",
"doctype": "asset",
"assetnum": "Motor_01",
"iot_asset_id": "Motor_01",
"wo_assetnum": null,
"description": "Induction motor (vibration-monitored)",
"siteid": "MAIN",
"orgid": "RIVPLANT",
Expand All @@ -138,11 +122,7 @@
]
},
{
"_id": "asset:PUMP3",
"doctype": "asset",
"assetnum": "PUMP3",
"iot_asset_id": null,
"wo_assetnum": "PUMP3",
"description": "Pump 3",
"siteid": "MAIN",
"orgid": "RIVPLANT",
Expand All @@ -160,11 +140,7 @@
"sensors": []
},
{
"_id": "asset:AHU2",
"doctype": "asset",
"assetnum": "AHU2",
"iot_asset_id": null,
"wo_assetnum": "AHU2",
"description": "Air handling unit 2",
"siteid": "NORTH",
"orgid": "RIVPLANT",
Expand Down
Loading