Skip to content

makeitworkok/mcp4bas

Repository files navigation

MCP4BAS

AI-native building automation. Connect Claude to your BACnet, Modbus, Haystack, MQTT, and SNMP systems — read trends, diagnose faults, read and write schedules, and keep production safe by default.

License: MIT Python >=3.10 Claude Code: Ready CI Code Scanning Dependabot Secret Scanning


What it does

MCP4BAS is a Model Context Protocol server that gives AI assistants like Claude direct, structured access to building systems. Instead of copy-pasting values out of a BAS workstation, you ask Claude and it talks to the protocol layer directly.

Why it matters
Building data is scattered across tools and vendors MCP4BAS unifies it into one conversation
Troubleshooting is slow when context is fragmented Claude reads trends, properties, and schedules in one pass
Schedule and setpoint changes carry real risk Every write goes through enforced safety gates

Protocols

Protocol Capability
BACnet/IP Discovery, property read/write, trend logs, weekly/exception schedules, device inventory, IP adapter MAC lookup, foreign device (BBMD) registration, routed MS/TP access
Modbus TCP Holding/input/coil register reads, guarded single-register writes
Haystack Point discovery and metadata fetch with tag quality scoring
MQTT Telemetry ingest/query, controlled publish with audit envelope
SNMP Read-only get, walk, and device health summary

Tool surface

BACnet

Tool Description
who_is Broadcast discovery and I-Am collection
bacnet_list_discovered_devices List persisted discovery inventory
bacnet_get_discovered_device Load one device record by key
bacnet_tag_discovered_device Add operator tags and notes
bacnet_refresh_discovered_device_identity Enrich with serial / model / firmware
read_property Read any BACnet object property
write_property Write a property at a BACnet priority (safety-gated)
bacnet_get_trend Pull trend-log entries with adaptive ReadRange strategy
bacnet_get_schedule Read weekly + exception schedules with human-readable output
bacnet_set_weekly_schedule Write one day of a weekly schedule (value-type auto-inferred)
bacnet_get_ip_adapter_mac Resolve MAC address for a BACnet/IP peer

Modbus

Tool Description
modbus_read_registers Read holding, input, or coil registers
modbus_write Write a single register or coil (safety-gated)

Haystack

Tool Description
haystack_discover_points Discover and score tagged points
haystack_get_point_metadata Return full metadata for a point

MQTT

Tool Description
mqtt_ingest_message Ingest a raw MQTT payload
mqtt_get_latest_points Query latest values across topics
mqtt_publish_message Publish to an allowlisted topic

SNMP

Tool Description
snmp_get Get a single OID value
snmp_walk Walk a subtree
snmp_device_health_summary Summarize device health from standard OIDs

Quick start

python -m venv .venv
.venv\Scripts\activate           # Windows PowerShell
pip install -r dev-requirements.txt
python -m mcp4bas.server --transport stdio

On first run, if .env does not exist, MCP4BAS creates one from .env.example and attempts to auto-detect a usable BACNET_LOCAL_ADDRESS. It will never overwrite an existing .env.

Windows + Claude Desktop? See CLAUDE_DESKTOP_SETUP.md for a full beginner-friendly walkthrough including the Microsoft Store build path.


What has been live-validated

These capabilities have been tested against real hardware, not just mocked data:

Capability Device / notes
BACnet discovery Trane Symbio 210 (device 50, 192.168.0.62)
Property read/write with priority array Trane Symbio 210
Trend log retrieval (ReadRange) Trane Symbio 210 — adaptive small-count strategy handles 480-byte no-segmentation APDUs
Schedule read — weekly + exception Routed MS/TP controller (device 242631, address 2001:10)
Schedule write — weekly day block Same routed controller; value type auto-inferred from schedule-default
Routed BACnet (MS/TP behind router) DNET:MAC address format (2001:10)
Foreign device (BBMD) registration Env-var wired; cross-subnet BACnet/IP

Schedule read output

Schedule reads return human-readable times and unwrapped values — no BACnet encoding noise:

{
  "status": "ok",
  "target": "2001:10",
  "weekly_schedule": [
    {
      "day": "monday",
      "events": [
        { "time": "06:00:00", "value": 1 },
        { "time": "18:00:00", "value": 0 }
      ]
    }
  ],
  "effective_period": { "start": "any", "end": "any" }
}

The connector reads schedule-default from the device to determine whether values should be encoded as enumerated, boolean, real, or another type. You pass 0 or 1 — the right encoding is automatic.


Safety model

Default state is read-only. Nothing writes until you explicitly enable it.

BAS_OPERATION_MODE=read-only   # enforced globally
BACNET_WRITE_ENABLED=false     # secondary per-protocol gate
BAS_DRY_RUN=true               # validate + audit without sending
Gate What it does
BAS_OPERATION_MODE Global read-only / write-enabled switch
BACNET_WRITE_ENABLED Per-protocol secondary gate
BAS_DRY_RUN Validate, build payload, audit — without executing
BACNET_WRITE_ALLOWLIST Allowlist of object_id:property pairs that may be written
Priority bounds Writes validated to BACnet priority 1–16

Every write response includes a full audit envelope: mode, dry_run, allowed, reason, target, request, timestamp.

Do not write when:

  • Device identity is uncertain or conflicts with expected site/model context
  • The point is not in the approved allowlist
  • Operations staff have not validated the current mode
  • Dry-run output has not been reviewed
  • The change is not recorded in change-control notes

BACnet discovery inventory

Discovered devices are automatically persisted to a local JSON file.

  • Override path: BACNET_DISCOVERY_INVENTORY_PATH
  • Disable: BACNET_DISCOVERY_INVENTORY_ENABLED=false
  • Supports operator tags, notes, and enriched identity fields (serial / model / firmware) from bacnet_refresh_discovered_device_identity

Cross-subnet BACnet (foreign device)

If your PC is on a different IP subnet than the BACnet network, set the BBMD address:

BACNET_FOREIGN_BBMD=10.0.1.1   # IP or IP:port of the BBMD
BACNET_FOREIGN_TTL=30           # registration lifetime in seconds

Leave BACNET_FOREIGN_BBMD blank for same-subnet operation. This is separate from routed MS/TP — field bus devices accessed via DNET:MAC addresses do not require BBMD registration.


Polling historian fallback

If a controller does not reliably serve ReadRange, collect local trend samples by polling:

mcp4bas-historian \
  --target-address 192.168.0.62 \
  --object-id analog-input,1 \
  --property present-value \
  --interval-seconds 60 \
  --output trend_samples.csv

--samples N and --duration-seconds S stop conditions available. CSV includes timestamp, value, numeric value, and per-sample error text.


Operator workflow

Discover → Inventory → Identify → Diagnose → Write (change window only)
  1. Discoverwho_is to find reachable devices; confirm sources before reading
  2. Inventory — work from persisted records; add tags/notes for human context
  3. Identify — refresh serial/model/firmware; treat enrichment as best-effort
  4. Diagnose — read properties, trends, schedules; keep prompts specific
  5. Write — enable intentionally, dry-run first, allowlist required, review audit

Example Claude prompts

Run who_is, summarize discovered device instances and sources, then stop.
List persisted devices, pick the record for AHU-1, and return its device_key.
Read present-value for these object IDs on device X and return a table with timestamps.
Read the weekly schedule for schedule,1 at address 2001:10 and show which days are programmed.
Set Monday on schedule,1 at 2001:10 to occupied 06:00–18:00, dry-run first, then execute if approved.
Before any write, confirm mode and allowlist, run dry-run only — do not execute a live write.
If identity is ambiguous, allowlist is missing, or dry-run is unapproved, block the write and explain why.

Validation status

Check Status
Test suite (pytest) ✅ 100 passing
Type checking (mypy) ✅ Clean (18 source files)
Linting (ruff) ✅ Clean
CI workflow ✅ Passing
Live BACnet reads ✅ Validated on real hardware
Live BACnet writes ✅ Validated (priority array, schedule day blocks)
Haystack / MQTT ✅ Integration-tested with local datasets
SNMP ✅ Unit and integration tested

Developer hygiene

If local environment artifacts were committed previously, clean them up once:

git rm -r --cached .venv .pytest_cache .mypy_cache .ruff_cache
git rm --cached .env bacnet_discovery_inventory.json
git commit -m "chore: stop tracking local environment artifacts"

.env.example stays tracked as the template. Discovery inventory files are .gitignored.


Caveats

MQTT — Quality depends on consistent topic and payload conventions. MCP4BAS validates completeness and surfaces confidence/caveat metadata when context is weak.

Haystack / Niagara — Workflows depend on tagging quality. Missing or inconsistent tags reduce confidence and are surfaced in tool responses with remediation hints.


Roadmap

  • Expand live rollout coverage for Haystack and MQTT environments
  • Extend SNMP validation against live devices
  • Continue publish-batch hardening for public release cadence

Get involved

  • ⭐ Star the project to follow progress
  • 💡 Open an issue with ideas or use cases
  • 🔧 Contribute with PRs as the roadmap grows

Back to top ↑

About

An open-source MCP server that connects AI agents to building automation networks.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages