Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
a0913bb
refactor: implement Work Order Agent MCP server and agent_hive integr…
ShuxinLin Mar 4, 2026
01526d4
refactor(wo): replace stub with full MCP server from api_implementati…
ShuxinLin Mar 4, 2026
607be34
refactor(wo): split main.py into models, data, tools, and entry point
ShuxinLin Mar 4, 2026
8b5de8e
feat: replace CSV data access with CouchDB in WO server
ShuxinLin Mar 4, 2026
d833454
feat: add init_asset_data.py and wire both init scripts into docker-c…
ShuxinLin Mar 4, 2026
fbc363b
refactor: consolidate db init into couchdb_setup.sh, remove db-init c…
ShuxinLin Mar 4, 2026
c90bb4d
refactor: rename COUCHDB_DBNAME→IOT_DBNAME, WO_COUCHDB_DBNAME→WO_DBNAME
ShuxinLin Mar 4, 2026
b478548
feat: add WO integration tests and update INSTRUCTIONS.md with WorkOr…
ShuxinLin Mar 4, 2026
3f50e2b
fix: downgrade doc reference to Python 3.12, fix requires_couchdb to …
ShuxinLin Mar 4, 2026
5cea530
fix: standardize COUCHDB_USERNAME/PASSWORD local var names in iot ser…
ShuxinLin Mar 4, 2026
62fd7ac
feat: add scripts/start_servers.sh to smoke-test all MCP servers
ShuxinLin Mar 4, 2026
0bec54d
fix: rename _dataset to dataset in WO CouchDB init and data layer
ShuxinLin Mar 4, 2026
cad5e6f
feat: register WorkOrderAgent in plan-execute default servers
ShuxinLin Mar 4, 2026
8f83b1c
docs: add WO plan-execute examples and on-demand server note to INSTR…
ShuxinLin Mar 4, 2026
ed93479
chore: remove scripts/ directory and update INSTRUCTIONS.md
ShuxinLin Mar 4, 2026
3df0e81
fix: add --break-system-packages to pip3 install in couchdb_setup.sh
ShuxinLin Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .env.public
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# ── CouchDB (IoTAgent server) ────────────────────────────────────────────────
COUCHDB_URL=http://localhost:5984
COUCHDB_DBNAME=chiller
COUCHDB_USERNAME=admin
COUCHDB_PASSWORD=password
IOT_DBNAME=chiller
WO_DBNAME=workorder

# ── IBM WatsonX (plan-execute runner) ────────────────────────────────────────
WATSONX_APIKEY=
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -199,4 +199,5 @@ benchmark/cods_track2/.env.local
CLAUDE.md
mcp/couchdb/sample_data/bulk_docs.json
.env
mcp/servers/tsfm/artifacts/tsfm_models/
mcp/servers/tsfm/artifacts/tsfm_models/
src/tmp/
2 changes: 1 addition & 1 deletion .python-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.14
3.12
87 changes: 73 additions & 14 deletions INSTRUCTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This directory contains the MCP servers and infrastructure for the AssetOpsBench
- [Utilities](#utilities)
- [FMSRAgent](#fmsragent)
- [TSFMAgent](#tsfmagent)
- [WorkOrderAgent](#workorderagent)
- [Plan-Execute Runner](#plan-execute-runner)
- [How it works](#how-it-works)
- [CLI](#cli)
Expand Down Expand Up @@ -75,15 +76,18 @@ Verify CouchDB is running:
curl -X GET http://localhost:5984/
```

### 4. Run servers locally
### 4. Run servers

Use `uv run` to start the MCP servers (paths relative to repo root):
> **Note:** MCP servers use stdio transport — they are spawned on-demand by clients (Claude Desktop, `plan-execute`) and exit when the client disconnects. They are not long-running daemons.

To start a server manually for testing:

```bash
uv run utilities-mcp-server
uv run iot-mcp-server
uv run fmsr-mcp-server
uv run tsfm-mcp-server
uv run wo-mcp-server
```

---
Expand All @@ -92,10 +96,11 @@ uv run tsfm-mcp-server

| Variable | Required | Description |
|---|---|---|
| `COUCHDB_URL` | IoT server | CouchDB connection URL, e.g. `http://localhost:5984` |
| `COUCHDB_DBNAME` | IoT server | Database name (default fixture: `chiller`) |
| `COUCHDB_USERNAME` | IoT server | CouchDB admin username |
| `COUCHDB_PASSWORD` | IoT server | CouchDB admin password |
| `COUCHDB_URL` | IoT + WO servers | CouchDB connection URL, e.g. `http://localhost:5984` |
| `COUCHDB_USERNAME` | IoT + WO servers | CouchDB admin username |
| `COUCHDB_PASSWORD` | IoT + WO servers | CouchDB admin password |
| `IOT_DBNAME` | IoT server | IoT sensor database name (default: `chiller`) |
| `WO_DBNAME` | WO server | Work order database name (default: `workorder`) |
| `WATSONX_APIKEY` | `--platform watsonx` | IBM WatsonX API key |
| `WATSONX_PROJECT_ID` | `--platform watsonx` | IBM WatsonX project ID |
| `WATSONX_URL` | `--platform watsonx` | WatsonX endpoint (optional; defaults to `https://us-south.ml.cloud.ibm.com`) |
Expand All @@ -112,7 +117,7 @@ uv run tsfm-mcp-server
### IoTAgent

**Path:** `src/servers/iot/main.py`
**Requires:** CouchDB (`COUCHDB_URL`, `COUCHDB_DBNAME`, `COUCHDB_USERNAME`, `COUCHDB_PASSWORD`)
**Requires:** CouchDB (`COUCHDB_URL`, `COUCHDB_USERNAME`, `COUCHDB_PASSWORD`, `IOT_DBNAME`)

| Tool | Arguments | Description |
|---|---|---|
Expand Down Expand Up @@ -143,6 +148,23 @@ uv run tsfm-mcp-server
| `get_failure_modes` | `asset_name` | Return known failure modes for an asset. Uses a curated YAML list for chillers and AHUs; falls back to the LLM for other types. |
| `get_failure_mode_sensor_mapping` | `asset_name`, `failure_modes`, `sensors` | For each (failure mode, sensor) pair, determine relevancy via LLM. Returns bidirectional `fm→sensors` and `sensor→fms` maps plus full per-pair details. |

### WorkOrderAgent

**Path:** `src/servers/wo/main.py`
**Requires:** CouchDB (`COUCHDB_URL`, `COUCHDB_USERNAME`, `COUCHDB_PASSWORD`, `WO_DBNAME`)
**Data init:** Handled automatically by `docker compose -f src/couchdb/docker-compose.yaml up` (runs `src/couchdb/init_wo.py` inside the CouchDB container on first start)

| Tool | Arguments | Description |
|---|---|---|
| `get_work_orders` | `equipment_id`, `start_date?`, `end_date?` | Retrieve all work orders for an equipment within an optional date range |
| `get_preventive_work_orders` | `equipment_id`, `start_date?`, `end_date?` | Retrieve only preventive (PM) work orders |
| `get_corrective_work_orders` | `equipment_id`, `start_date?`, `end_date?` | Retrieve only corrective (CM) work orders |
| `get_events` | `equipment_id`, `start_date?`, `end_date?` | Retrieve all events (work orders, alerts, anomalies) |
| `get_failure_codes` | — | List all failure codes with categories and descriptions |
| `get_work_order_distribution` | `equipment_id`, `start_date?`, `end_date?` | Count work orders per (primary, secondary) failure code pair, sorted by frequency |
| `predict_next_work_order` | `equipment_id`, `start_date?`, `end_date?` | Predict next work order type via Markov transition matrix built from historical sequence |
| `analyze_alert_to_failure` | `equipment_id`, `rule_id`, `start_date?`, `end_date?` | Probability that an alert rule leads to a work order; average hours to maintenance |

### TSFMAgent

**Path:** `src/servers/tsfm/main.py`
Expand Down Expand Up @@ -190,6 +212,8 @@ After `uv sync`, the `plan-execute` command is available:
uv run plan-execute "What assets are available at site MAIN?"
```

> **Note:** `plan-execute` spawns MCP servers on-demand for each query — you do **not** need to start them manually first. Servers are launched as subprocesses, used, then exit automatically.

Flags:

| Flag | Description |
Expand Down Expand Up @@ -223,10 +247,32 @@ uv run plan-execute --model-id litellm_proxy/GCP/claude-4-sonnet "What are the f
uv run plan-execute --show-history --json "How many observations exist for CH-1?" | jq .answer
```

### End-to-end example
### End-to-end examples

All four servers (IoTAgent, Utilities, FMSRAgent, TSFMAgent) are registered by default.
Run a question that exercises three of them with independent parallel steps:
All five servers (IoTAgent, Utilities, FMSRAgent, TSFMAgent, WorkOrderAgent) are registered by default.

#### Work order queries (requires CouchDB + populated `workorder` db)

Equipment IDs in the sample dataset: `CWC04014` (524 WOs), `CWC04013` (431 WOs), `CWC04009` (alert events).

```bash
# Work order count and most common failure code
uv run plan-execute "How many work orders does equipment CWC04014 have, and what is the most common failure code?"

# Preventive vs corrective split
uv run plan-execute "For equipment CWC04013, how many preventive vs corrective work orders were completed?"

# Alert-to-failure probability
uv run plan-execute "What is the probability that alert rule RUL0018 on equipment CWC04009 leads to a work order, and how long does it typically take?"

# Work order distribution + next prediction (multi-step)
uv run plan-execute --show-plan --show-history \
"For equipment CWC04014, show the work order distribution and predict the next maintenance type"
```

#### Multi-server parallel query

Run a question that exercises three servers with independent parallel steps:

```bash
uv run plan-execute --show-plan --show-history \
Expand Down Expand Up @@ -334,6 +380,10 @@ Add the following to your Claude Desktop `claude_desktop_config.json`:
"TSFMAgent": {
"command": "/path/to/uv",
"args": ["run", "--project", "/path/to/AssetOpsBench", "tsfm-mcp-server"]
},
"WorkOrderAgent": {
"command": "/path/to/uv",
"args": ["run", "--project", "/path/to/AssetOpsBench", "wo-mcp-server"]
}
}
}
Expand All @@ -351,6 +401,7 @@ uv run pytest src/ -v

Integration tests are auto-skipped when the required service is not available:
- IoT integration tests require `COUCHDB_URL` (set in `.env`)
- Work order integration tests require `COUCHDB_URL` (set in `.env`)
- FMSR integration tests require `WATSONX_APIKEY` (set in `.env`)
- TSFM integration tests require `PATH_TO_MODELS_DIR` and `PATH_TO_DATASETS_DIR` (set in `.env`)

Expand All @@ -367,9 +418,17 @@ uv run pytest src/servers/iot/tests/test_tools.py -k "not integration"
uv run pytest src/servers/utilities/tests/
uv run pytest src/servers/fmsr/tests/ -k "not integration"
uv run pytest src/servers/tsfm/tests/ -k "not integration"
uv run pytest src/servers/wo/tests/test_tools.py -k "not integration"
uv run pytest src/workflow/tests/
```

### Work order integration tests (requires CouchDB + populated `workorder` db)

```bash
docker compose -f src/couchdb/docker-compose.yaml up -d
uv run pytest src/servers/wo/tests/test_integration.py -v
```

### Integration tests (requires CouchDB + WatsonX)

```bash
Expand All @@ -396,8 +455,8 @@ uv run pytest src/ -v
│ │ stdio │ │
└───────────────────┼────────────┼─────────────────────┘
│ MCP protocol (stdio)
┌──────────┼──────────┬──────────┐
▼ ▼ ▼ ▼
IoTAgent Utilities FMSRAgent TSFMAgent
(tools) (tools) (tools) (tools)
┌──────────┼──────────┬──────────┬──────────────
▼ ▼ ▼ ▼
IoTAgent Utilities FMSRAgent TSFMAgent WorkOrderAgent
(tools) (tools) (tools) (tools) (tools)
```
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ iot-mcp-server = "servers.iot.main:main"
utilities-mcp-server = "servers.utilities.main:main"
fmsr-mcp-server = "servers.fmsr.main:main"
tsfm-mcp-server = "servers.tsfm.main:main"
wo-mcp-server = "servers.wo.main:main"


[dependency-groups]
Expand Down
68 changes: 24 additions & 44 deletions src/couchdb/couchdb_setup.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
#!/bin/sh -xe


COUCHDB_USERNAME=${COUCHDB_USERNAME}
COUCHDB_PASSWORD=${COUCHDB_PASSWORD}
COUCHDB_DBNAME=${COUCHDB_DBNAME}
COUCHDB_URL="http://${COUCHDB_USERNAME}:${COUCHDB_PASSWORD}@127.0.0.1:5984"
INPUT_FILE="/sample_data/chiller6_june2020_sensordata_couchdb.json"
OUTPUT_FILE="/sample_data/bulk_docs.json"

# Convert the JSON file into a coudb bulk insertable JSON file
if [ ! -f "$INPUT_FILE" ]; then
echo "❌ Error: $INPUT_FILE not found."
fi

# Read the array from file (single line) and wrap it
ARRAY_CONTENT=$(cat "$INPUT_FILE")
echo "{\"docs\": $ARRAY_CONTENT}" > "$OUTPUT_FILE"

echo "✅ Wrapped $INPUT_FILE into $OUTPUT_FILE"

cat >/opt/couchdb/etc/local.ini <<EOF
[couchdb]
single_node=true
Expand All @@ -28,31 +9,30 @@ ${COUCHDB_USERNAME} = ${COUCHDB_PASSWORD}
EOF

echo "Starting CouchDB..."

/opt/couchdb/bin/couchdb &
sleep 10

echo "Connecting to CouchDB..."
curl -u ${COUCHDB_USERNAME}:${COUCHDB_PASSWORD} GET http://localhost:5984/
curl -u ${COUCHDB_USERNAME}:${COUCHDB_PASSWORD} GET http://localhost:5984/_all_dbs

# Check if database exists
response=$(curl -s -o /dev/null -w "%{http_code}" "$COUCHDB_URL/$COUCHDB_DBNAME")

if [ "$response" -eq 200 ]; then
echo "⚠️ Database $COUCHDB_DBNAME already exists. Skipping creation."
else
echo "⚠️ Database $COUCHDB_DBNAME does not exist. Creating..."
curl -s -X PUT "$COUCHDB_URL/$COUCHDB_DBNAME"

echo "Uploading documents from $JSON_FILE..."
curl -s -X POST "$COUCHDB_URL/$COUCHDB_DBNAME/_bulk_docs" \
-H "Content-Type: application/json" \
-d @"$OUTPUT_FILE"

curl -s -X POST "$COUCHDB_URL/$COUCHDB_DBNAME/_index" -H "Content-Type: application/json" -d '{ "type" : "json", "index": { "fields": [ "asset_id", "timestamp" ] } }'

echo "✅ Database created and populated."
fi

echo "Waiting for CouchDB to be ready..."
until curl -sf -u "${COUCHDB_USERNAME}:${COUCHDB_PASSWORD}" http://localhost:5984/ >/dev/null; do
sleep 2
done
echo "CouchDB is ready."

echo "Installing Python dependencies..."
apt-get update -qq
apt-get install -y -qq python3 python3-pip
pip3 install -q --break-system-packages requests pandas python-dotenv

echo "Loading IoT asset data..."
COUCHDB_URL="http://localhost:5984" \
python3 /couchdb/init_asset_data.py \
--data-file /sample_data/chiller6_june2020_sensordata_couchdb.json \
--db "${IOT_DBNAME:-chiller}"

echo "Loading work order data..."
COUCHDB_URL="http://localhost:5984" \
python3 /couchdb/init_wo.py \
--data-dir /sample_data/work_order \
--db "${WO_DBNAME:-workorder}"

echo "✅ All databases initialised."
tail -f /dev/null
8 changes: 5 additions & 3 deletions src/couchdb/docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ services:
couchdb:
image: couchdb:3.3
environment:
COUCHDB_PASSWORD: password
COUCHDB_USERNAME: admin
COUCHDB_DBNAME: chiller
COUCHDB_PASSWORD: password
IOT_DBNAME: chiller
WO_DBNAME: workorder
ports:
- "5984:5984"
volumes:
- couchdb-data:/opt/couchdb/data
- ./couchdb_setup.sh:/opt/couchdb/etc/couchdb_setup.sh
- ./sample_data:/sample_data
- ./:/couchdb
command: ["sh", "/opt/couchdb/etc/couchdb_setup.sh"]
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:5984/"]
Expand All @@ -19,4 +21,4 @@ services:
retries: 10

volumes:
couchdb-data:
couchdb-data:
Loading