Skip to content

makeitworkok/niagaraMCP

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Niagara MCP Module

Niagara MCP Read Only Claude Validated License

A Niagara 4 custom module that exposes read-only station data through the Model Context Protocol (MCP).

MCP-compatible AI clients (Claude Desktop, VS Code Copilot, etc.) can discover and call tools that introspect the station safely and without mutation.

Test status: successfully validated end-to-end with Claude (via MCP proxy).

✨ At a Glance

  • βœ… Live Niagara station integration verified
  • βœ… Claude MCP workflow verified
  • βœ… Secure read-only tool surface
  • βœ… SCRAM auth handled by local proxy

🎯 Design Philosophy

The correct v0.1 is not the most powerful version. The correct v0.1 is the safest useful version.

v0.1 is read-only. No point writes, overrides, alarm acknowledgements, schedule edits, or station configuration changes are implemented or planned for this release.


πŸ—οΈ Architecture

Niagara Station
  └── BMcpService (BAbstractService)
        β”œβ”€β”€ McpHttpServlet          – HTTP POST /mcp
        β”œβ”€β”€ McpJsonRpcHandler       – JSON-RPC 2.0 dispatcher
        β”œβ”€β”€ McpToolRegistry         – tool name β†’ handler map
        β”œβ”€β”€ NiagaraSecurity         – allowlist & sensitive-slot enforcement
        β”œβ”€β”€ NiagaraComponentTools   – station.info, component.read/children/slots
        β”œβ”€β”€ NiagaraBqlTools         – bql.query (SELECT only)
        β”œβ”€β”€ NiagaraAlarmTools       – alarm.query
        β”œβ”€β”€ NiagaraHistoryTools     – history.list
        β”œβ”€β”€ NiagaraBacnetTools      – bacnet.devices
        └── NiagaraJson             – zero-dependency JSON parser/builder

πŸ“ File Structure

niagaraMCP/
β”œβ”€β”€ build.gradle          Gradle build (stubs + main + test source sets)
β”œβ”€β”€ module.xml            Niagara module descriptor
β”œβ”€β”€ settings.gradle
β”œβ”€β”€ README.md
└── src/
    β”œβ”€β”€ stubs/java/       Niagara API stubs (compile-time only; replaced by SDK at runtime)
    β”‚   β”œβ”€β”€ javax/baja/sys/
    β”‚   └── javax/baja/web/
    β”œβ”€β”€ main/java/com/makeitworkok/niagaramcp/
    β”‚   β”œβ”€β”€ BMcpService.java
    β”‚   β”œβ”€β”€ McpHttpServlet.java
    β”‚   β”œβ”€β”€ McpJsonRpcHandler.java
    β”‚   β”œβ”€β”€ McpToolRegistry.java
    β”‚   β”œβ”€β”€ McpTool.java
    β”‚   β”œβ”€β”€ McpToolResult.java
    β”‚   β”œβ”€β”€ McpErrors.java
    β”‚   β”œβ”€β”€ NiagaraSecurity.java
    β”‚   β”œβ”€β”€ NiagaraComponentTools.java
    β”‚   β”œβ”€β”€ NiagaraBqlTools.java
    β”‚   β”œβ”€β”€ NiagaraAlarmTools.java
    β”‚   β”œβ”€β”€ NiagaraHistoryTools.java
    β”‚   β”œβ”€β”€ NiagaraBacnetTools.java
    β”‚   └── NiagaraJson.java
    └── test/java/com/makeitworkok/niagaramcp/
        β”œβ”€β”€ McpJsonRpcHandlerTest.java
        β”œβ”€β”€ McpToolRegistryTest.java
        β”œβ”€β”€ NiagaraSecurityTest.java
        └── NiagaraJsonTest.java

πŸ”§ Building

The project compiles against Niagara API stubs so it can be built without a Niagara SDK installation. You need Java 11+ and Gradle.

# Compile only
gradle compileJava

# Run unit tests (59 tests, no Niagara runtime required)
gradle test

# Build JAR
gradle jar

🧰 Building against a real Niagara SDK

Replace compileOnly project(':stubs') in build.gradle with the actual Niagara JAR paths:

compileOnly fileTree(dir: System.getenv('NIAGARA_HOME') + '/lib', include: '*.jar')

πŸš€ Installation

  1. Build the module (gradle jar) to produce build/libs/niagaraMcp-0.1.0.jar.
  2. Package it as a Niagara module (.nmodule) following the Tridium module-packaging guide for your Niagara version.
  3. Install the module in the station using Niagara Workbench.
  4. Navigate to Config β†’ Services in the station tree.
  5. Drag BMcpService from the niagaraMcp palette entry into Services.
  6. Start the service. The MCP endpoint is now live at http://<station-host>:<port>/mcp.

The current implementation has been validated on a live Niagara station and returns working MCP JSON-RPC responses.


πŸ€– Claude Desktop Setup (Validated)

Niagara 4.15 uses SCRAM-based web auth, so the easiest client integration path is to run the included local HTTP proxy.

  1. Ensure BMcpService is running in the station.
  2. Start the proxy:
python mcp_proxy.py \
  --niagara-url http://localhost \
  --niagara-user mcp \
  --niagara-pass <your-password> \
  --token <your-client-token> \
  --port 8765

Notes:

  • --backend-mcp-token is optional. If omitted, the proxy auto-discovers it from GET /mcp after login.
  • By default, the proxy listens on 127.0.0.1:8765.

Configure Claude Desktop (or any HTTP MCP client) to call:

  • URL: http://127.0.0.1:8765/mcp
  • Header: X-MCP-Token: <your-client-token>

Quick verification call:

curl -X POST http://127.0.0.1:8765/mcp \
  -H "X-MCP-Token: <your-client-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

βš™οΈ Configuration

Property Default Description
enabled true Enable / disable the MCP endpoint
endpointPath /mcp HTTP path
readOnly true Must remain true for v0.1
allowBql true Permit BQL SELECT queries
maxResults 500 Maximum rows / items per call
allowlistedRoots see below Comma-separated ORD prefixes

Default allowlisted roots:

  • station:|slot:/Drivers
  • station:|slot:/Services
  • station:|slot:/Config

Any ORD that does not start with one of these roots is immediately rejected.


🧩 MCP Tools Reference

niagara.station.info

Returns basic station metadata.

{}

Response:

{
  "stationName": "MySupervisor",
  "hostId": "host-abc123",
  "niagaraVersion": "4.12",
  "moduleVersion": "0.1.0",
  "readOnly": true
}

niagara.component.read

Reads a component by ORD.

{ "ord": "station:|slot:/Drivers" }

niagara.component.children

Lists immediate children of an ORD.

{ "ord": "station:|slot:/Drivers", "limit": 100 }

niagara.component.slots

Lists all slots on a component. Sensitive slots (password, token, key, …) are masked as ***.

{ "ord": "station:|slot:/Drivers/BacnetNetwork" }

niagara.bql.query

Runs a read-only BQL SELECT query. Mutation keywords are rejected.

{ "query": "SELECT * FROM control:NumericPoint", "limit": 100 }

niagara.alarm.query

Returns recent alarm records.

{ "limit": 100 }

niagara.history.list

Lists available history identifiers.

{ "limit": 100 }

niagara.bacnet.devices

Lists BACnet devices under a network ORD.

{ "networkOrd": "station:|slot:/Drivers/BacnetNetwork", "limit": 100 }

πŸ”’ Security

  1. Read-only by default – write operations are explicitly absent from v0.1.
  2. Allowlisted roots – every ORD is checked against configured prefixes.
  3. Sensitive-slot masking – slots whose name contains password, secret, token, key, credential, auth, etc. return *** instead of the real value.
  4. BQL SELECT-only – mutation keywords (SET, DELETE, INSERT, UPDATE, …) cause immediate rejection.
  5. Result caps – all queries honour maxResults.
  6. Fail-closed – if a security check is uncertain, access is denied.

🌐 MCP Resources

Resources are exposed with the URI scheme niagara://station/slot/<path>.

niagara://station/slot/Drivers        β†’  station:|slot:/Drivers
niagara://station/slot/Services       β†’  station:|slot:/Services
niagara://station/slot/Config         β†’  station:|slot:/Config

resources/list returns the allowlisted root ORDs. resources/read delegates to niagara.component.read.


πŸ§ͺ Sample curl Requests

# List available tools
curl -X POST http://127.0.0.1:8765/mcp \
  -H "X-MCP-Token: <your-client-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list",
    "params": {}
  }'

# Get station info
curl -X POST http://127.0.0.1:8765/mcp \
  -H "X-MCP-Token: <your-client-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 2,
    "method": "tools/call",
    "params": {
      "name": "niagara.station.info",
      "arguments": {}
    }
  }'

# List children under /Drivers
curl -X POST http://127.0.0.1:8765/mcp \
  -H "X-MCP-Token: <your-client-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 3,
    "method": "tools/call",
    "params": {
      "name": "niagara.component.children",
      "arguments": {
        "ord": "station:|slot:/Drivers",
        "limit": 20
      }
    }
  }'

# Run a BQL SELECT query
curl -X POST http://127.0.0.1:8765/mcp \
  -H "X-MCP-Token: <your-client-token>" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 4,
    "method": "tools/call",
    "params": {
      "name": "niagara.bql.query",
      "arguments": {
        "query": "SELECT * FROM control:NumericPoint",
        "limit": 50
      }
    }
  }'

πŸ›£οΈ Development Phases

Phase Status Scope
1 – Read-Only Core βœ… Done Module loads, service starts/stops, HTTP endpoint, JSON-RPC, tools/list, station.info, component.children
2 – Component Introspection βœ… Done component.read, component.slots, slot masking, ORD validation, allowlist enforcement
3 – BQL βœ… Done bql.query, SELECT-only validation, result limiting
4 – Niagara Subsystems βœ… Done alarm.query, history.list, bacnet.devices
5 – MCP Client Compatibility βœ… Done Validated end-to-end with Claude via local auth proxy
6 – Write Support β›” Out of scope Not in v0.1

🧹 Repository Hygiene

To keep the repository professional and reproducible, follow these guidelines.

Commit these:

  • Source code under src/
  • Gradle wrapper files and build scripts
  • Module metadata (module.xml, module.palette)
  • Tests and documentation
  • Public certificate material only (for example .cer), if needed

Do not commit these:

  • Local secrets (.p12, keystores, private keys, passwords, tokens)
  • Runtime/debug artifacts (*.log, cookies.txt, ad-hoc payload files)
  • Temporary inspection/research files (tmpinspect/)
  • Build outputs (build/, .gradle/, generated binaries)
  • Local tool settings (.claude/, IDE-local state)

Working rule:

  • If a file is machine-local, environment-specific, or sensitive, keep it out of git.

Before pushing:

  1. Run git status and check for unexpected files.
  2. Confirm no secrets appear in staged changes.
  3. Keep commits focused by purpose (code, docs, cleanup, etc.).

β›” Explicitly Out of Scope (v0.1)

The following will not be implemented in v0.1:

  • niagara.point.write
  • niagara.point.override
  • niagara.component.invokeAction
  • niagara.alarm.ack
  • niagara.schedule.write
  • niagara.component.create / .delete
  • niagara.station.restart
  • niagara.driver.discoverAndAdd

πŸ“„ License

MIT License

About

A Niagara 4 custom module that exposes read-only station data through the Model Context Protocol (MCP).

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages