ignitionLLens
by DMDuFresne
README.md
<div align="center">
<img src="images/ignitionllens-icon.png" alt="ignitionLLens" width="120">
# ignitionLLens
### Give AI Eyes and Hands on Ignition
**A multi-target MCP server for Ignition 8.3 gateways: 81 tools that browse, explain, diagnose and, when you allow it, change a gateway's configuration, projects, tags and live values.**
[](https://modelcontextprotocol.io)
[](https://inductiveautomation.com/ignition)
[](https://www.python.org)
[](https://docs.astral.sh/uv/)
[](https://www.docker.com)
[](LICENSE)
[](https://github.com/DMDuFresne/ignitionllens/actions/workflows/ci.yml)
[](https://github.com/DMDuFresne/ignitionllens/actions/workflows/stack.yml)
[Getting Started](#getting-started) · [Tools](#tools) · [Domain Context](#domain-context) · [Docs](#docs)
</div>
---
## Why ignitionLLens?
A model asked about an Ignition gateway is working from memory: it does not know your tag
providers, your UDT definitions, which project binds which tag, or what is alarming right
now. The server connects one or many gateways to any [MCP](https://modelcontextprotocol.io)
client and answers from the gateway itself.
- **Four planes, one interface.** The native 8.3 HTTP API (config, projects, tags, diagnostics),
project exports, a WebDev bridge for anything live, and a committed reference corpus of Ignition
knowledge that needs no gateway at all.
- **Multi-target.** Every plane tool takes `target` first. A plant gateway and an edge gateway are
two slugs in one `targets.yaml`, each with its own credentials, TLS setting and write policy.
- **Five gates refuse a write; a sixth records it.** Off process-wide, off per target, scoped by
OAuth when `IGNITIONLLENS_MCP_AUTH_MODE=okta`, `confirm=True` on anything destructive, and a
separate flag for script execution — then one audit line per call, allowed or refused. Details:
[`docs/tools.md#the-write-gate`](docs/tools.md#the-write-gate).
- **Nothing disappears except the writes you switch off.** A target with no bridge still lists
every runtime tool; calling one returns a `BRIDGE_MISSING` envelope with the command that fixes
it, and an unsupported version or a refused permission answers in an envelope of its own. The
one exception is the operator's own switch: `IGNITIONLLENS_WRITES=off` never registers a write
tool at all.
- **It knows Ignition, not just this gateway.** API routes, resource types, quality codes,
`system.*` scripting functions, expression functions, Perspective and Vision components, topics
and authoring conventions, all queryable offline.
- **It ships as one distroless container.** A health check, Prometheus metrics, OpenTelemetry
tracing, a JSONL audit trail and an optional Grafana stack come with it.
## Getting Started
### Prerequisites
- **Ignition 8.3.x.** Only 8.3 is supported; any other `ignitionVersion` marks the target
unsupported and every plane tool that reads that gateway returns `VERSION_UNSUPPORTED`, with
`refresh` the one exception, which re-probes instead of refusing and so corrects a stale verdict.
The reference plane takes no target and answers anyway.
- **A gateway API key** (Platform → Security → API Keys, type "Basic Token") whose security level
is granted in the Gateway Read / Write / Access permission trees. A key with only the default
`Authenticated` level and untouched permission trees gets a bare 403 on every route; that gotcha
and its fix are in [`docs/DEPLOY.md`](docs/DEPLOY.md#when-a-valid-key-still-answers-403).
- **Optional: the WebDev module**, licensed and active, for the runtime plane (live values, tag
writes, alarms, tag history, script execution). Everything else works without it.
- **Python 3.11 or 3.12**, or Docker, which needs nothing else installed.
### Quick Start (uv)
```bash
git clone https://github.com/DMDuFresne/ignitionllens.git
cd ignitionllens
uv sync
cp .env.example .env
cp targets.example.yaml targets.yaml # then edit it (below)
echo "T_PLANT1=ignitionllens:<the api key secret>" >> .env
uv run ignitionllens
```
The credentials `targets.yaml` names may live in `.env` rather than your shell: the server reads
that file at startup and exports anything in it that is not already set into its own environment,
so the same file feeds `uv run ignitionllens` and `docker compose`. Exporting the variable in the
shell still works and still wins.
The MCP endpoint is `http://localhost:3000/mcp` and the health check is
`http://localhost:3000/health`.
### Quick Start (Docker)
Nothing but the image. One gateway, declared inline:
```bash
docker run -p 3000:3000 \
-e IGNITIONLLENS_TARGETS='[{"slug":"plant1","url":"https://gw1.example.com:8043","token_env":"T_PLANT1"}]' \
-e T_PLANT1='ignitionllens:<the api key secret>' \
ghcr.io/dmdufresne/ignitionllens:0.1.0
```
`IGNITIONLLENS_TARGETS` is a JSON array of the same keys `targets.yaml` takes (below), plus
`slug`. The token is still named by variable, never written inline. `/health` reports the target
count; a `no targets configured` line in the log means the JSON or the mount below was forgotten.
From PowerShell, where the JSON quoting fights the shell, put both lines in a file and pass it
with `--env-file`. Docker reads that file verbatim — no quote stripping — so the JSON goes
unquoted:
```text
IGNITIONLLENS_TARGETS=[{"slug":"plant1","url":"https://gw1.example.com:8043","token_env":"T_PLANT1"}]
T_PLANT1=ignitionllens:<the api key secret>
```
```powershell
docker run -p 3000:3000 --env-file ignitionllens.env ghcr.io/dmdufresne/ignitionllens:0.1.0
```
Several gateways, or declarations you want under version control, go in a `targets.yaml`
mounted over the path the image already expects:
```bash
docker run -p 3000:3000 \
-v "$PWD/targets.yaml:/app/targets.yaml:ro" \
-e T_PLANT1='ignitionllens:<the api key secret>' \
ghcr.io/dmdufresne/ignitionllens:0.1.0
```
`docker-compose.yml` runs the same image with the Prometheus/Grafana/Loki/Tempo stack behind
profiles (`observe`, `infra`, `tunnel`); pick files and profiles once in `.env` (`COMPOSE_FILE`,
whose separator is the OS path separator — `:` on Linux and macOS, `;` on Windows — and
`COMPOSE_PROFILES`), then `docker compose up -d --build` (dev, builds from the checkout) or
`docker compose pull && docker compose up -d` (prod, pulls from GHCR). Every environment variable
and the hardening applied: [`docs/DEPLOY.md`](docs/DEPLOY.md).
### targets.yaml
Gateways are declared in a YAML file. Credentials are never written here — a target names the
*environment variable* that holds each secret. The full annotated version is
[`targets.example.yaml`](targets.example.yaml).
```yaml
plant1:
url: https://gw1.example.com:8043
token_env: T_PLANT1 # holds "<key name>:<key secret>"
verify_tls: true
bridge:
user_env: BU_PLANT1 # holds the bridge user name
password_env: BP_PLANT1 # holds the bridge password
writes: true
scripts: false
edge7:
url: http://10.0.7.20:8088
token_env: T_EDGE7
writes: false
```
So `plant1` needs `T_PLANT1`, `BU_PLANT1` and `BP_PLANT1` in the environment, and `edge7` needs
`T_EDGE7`. `verify_tls` takes `true`, `false` (dev only) or a path to a CA bundle that must exist
in the process's filesystem — inside the container, that means a mounted file.
`IGNITIONLLENS_TARGETS` holds the same declarations as a JSON array (each object carries its
`slug`) and takes precedence over the file; it is the form the Docker quick start above uses.
### Connect to Claude Desktop
```json
{
"mcpServers": {
"ignitionllens": { "url": "http://localhost:3000/mcp" }
}
}
```
With password-mode OAuth enabled (`IGNITIONLLENS_MCP_AUTH_MODE=password`), add the three OAuth
routes:
```json
{
"mcpServers": {
"ignitionllens": {
"url": "http://localhost:3000/mcp",
"authorizationUrl": "http://localhost:3000/oauth/authorize",
"tokenUrl": "http://localhost:3000/oauth/token",
"registrationUrl": "http://localhost:3000/oauth/register"
}
}
}
```
### Connect to Claude Code
```json
{
"mcpServers": {
"ignitionllens": { "type": "url", "url": "http://localhost:3000/mcp" }
}
}
```
Any MCP-compatible client works the same way (Cursor, Windsurf, VS Code MCP extensions, custom
clients). Transport is streamable HTTP at `/mcp`; there is no stdio mode.
### Skills
`skills/` bundles eight [Claude skills](https://docs.claude.com/en/docs/claude-code/skills)
(`ignitionllens-using`, `ignitionllens-explore-a-gateway`, `ignitionllens-trace-a-tag`,
`ignitionllens-triage-an-alarm`, `ignitionllens-audit-a-project`,
`ignitionllens-deploy-the-bridge`, `ignitionllens-verify-a-deployment`,
`ignitionllens-diagnose-gateway-performance`) that teach a client how to
drive this server. Start with `ignitionllens-using`, the router; copy the directories into your
project's `.claude/skills/` and Claude Code picks the right one from each skill's trigger phrases.
Index: [`skills/README.md`](skills/README.md).
## Tools
81 tools in eight groups. 29 of them write; with `IGNITIONLLENS_WRITES=off` those 29 are never
registered and the server offers 52. Parameters, planes, the gateway routes or bridge ops behind
each tool, and an example call: [`docs/tools.md`](docs/tools.md).
| Group | Tools | Writes | What it covers |
|---|---|---|---|
| Targets | 3 | 0 | Server identity, the configured gateways and their planes, re-reading and re-probing them. |
| Gateway | 20 | 8 | Gateway identity and health, system metrics, modules and the module toggle, logs and logger levels, Perspective diagnostics, the audit trail, thread dumps, sessions, gateway backups, the saved backup files themselves, and the trial timer. |
| Resources | 13 | 5 | Config resource types and instances, database and OPC connections, devices, tag providers. |
| Tags | 9 | 2 | Browse, describe, search, UDT definitions, raw exports, the tag dependency graph, and tag creation and edits. |
| Projects | 15 | 9 | Project metadata and lifecycle, exports and imports, and reading, searching and editing the resources inside a project. |
| Runtime | 14 | 5 | Live values, tag writes and deletes, active alarms and acknowledgement, the alarm journal, tag history, gateway scripts, a TCP reachability check run from the gateway, an OPC browse and direct OPC read, one Perspective component's props schema, and the bridge itself. |
| Fusion | 3 | 0 | `get_ontology` (what a gateway is, in one call), `trace_tag` (one tag end to end), `diagnose` (what is wrong right now). |
| Reference | 4 | 0 | Ignition knowledge with no gateway involved: API routes, resource types, quality codes, `system.*` and expression functions, components, topics, conventions. |
## Domain Context
Point `IGNITIONLLENS_DOMAIN_CONTEXT_FILE` at a markdown file (template:
[`ops/context.md`](ops/context.md)) describing what your gateways run, your naming conventions, and
the gotchas a model should know before it touches them. It is woven into the MCP server's
`instructions` field and `get_ontology` output, so the assistant sees it before its first call.
Optional; the shipped template is inert until the `ignitionllens:template` marker is removed.
## Docs
| Doc | Owns |
|---|---|
| [`docs/tools.md`](docs/tools.md) | Every tool: parameters, plane, write and destructive flags, routes and bridge ops, an example call, the write gate, the reference corpus. |
| [`docs/DEPLOY.md`](docs/DEPLOY.md) | The Ignition-side recipe, the bridge, every `IGNITIONLLENS_*` variable, `targets.yaml`, Docker and compose tiers, auth modes, health check, supply chain, compatibility. |
| [`docs/runbook.md`](docs/runbook.md) | Failure modes and what to do about them, with the error-code table. |
| [`docs/OBSERVABILITY.md`](docs/OBSERVABILITY.md) | Metrics, tracing, the audit trail and its sinks, alert rules, `/metrics` exposure. |
| [`docs/OKTA.md`](docs/OKTA.md) | `MCP_AUTH_MODE=okta` resource-server setup and the two scopes. |
| [`docs/STYLE.md`](docs/STYLE.md) | The LLens markdown style every tool response follows. |
## License
Apache License 2.0. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE) for full terms, third-party
dependency licenses, and trademark notices. Ignition is a trademark or registered trademark of
Inductive Automation, LLC; this project is not affiliated with or endorsed by them. Provided "as
is" with no warranty of any kind.
<br><br>
<div align="center">
<a href="https://abelara.com">
<img src="images/abelara-logo.svg" alt="Abelara" width="200">
</a>
**Built by [Abelara](https://abelara.com)**
Abelara builds ignitionLLens as part of its toolkit for industrial AI and edge computing.
[Report Bug](https://github.com/DMDuFresne/ignitionllens/issues) · [Request Feature](https://github.com/DMDuFresne/ignitionllens/issues) · [Learn More](https://abelara.com)
</div>
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues