Hc3 MCP Server
Standalone Model Context Protocol server for Fibaro Home Center 3. Actively maintained fork of jangabrielsson/HC3_mcp with verified write guardrails, bug fixes, and no local dependencies.
Ask AI about Hc3 MCP Server
Powered by Claude Β· Grounded in docs
I know everything about Hc3 MCP Server. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
HC3 MCP Server
Standalone Model Context Protocol server giving Claude, Cursor, or any MCP client live, guard-railed access to a Fibaro Home Center 3.
Not to be confused with the unscoped
mcp-server-hc3package on npm. That package covers a smaller core surface (rooms, devices, scenes). This server adds QuickApp file management, Z-Wave diagnostics, profile orchestration, custom events, alarm partitions, and 130+ tools total, with verified write guardrails on all destructive operations.
This project began as a fork of jangabrielsson/HC3_mcp but has since been substantially rewritten β the tool surface grew roughly 3Γ, every write tool gained read-modify-write + post-write-verify guards, an HTTP transport was added for remote use, and (in 3.4.0) the codebase was rearchitected from a single 7,300-line class into 23 per-domain modules. Credit to jgab for the original concept and starting point.
Install
npm install -g @northernrough/hc3-mcp-server
Or run directly with npx (no install):
npx @northernrough/hc3-mcp-server
Configure
The server reads four environment variables:
| Variable | Required | Default | Description |
|---|---|---|---|
FIBARO_HOST | yes | β | HC3 IP address or hostname (e.g. 192.168.1.57) |
FIBARO_USERNAME | yes | β | HC3 user (admin recommended for full surface) |
FIBARO_PASSWORD | yes | β | HC3 password |
FIBARO_PORT | no | 80 | HC3 port |
For development you can put these in a local .env file (the server uses dotenv automatically).
HTTP transport (for Claude mobile / always-on hosts)
By default the server speaks MCP over stdio, which is what Claude Desktop and Claude Code launch. To run as a long-lived HTTP server (e.g. on a Pi or container, fronted by Cloudflare Tunnel for mobile-app reachability), set:
MCP_TRANSPORT=http
MCP_HTTP_HOST=127.0.0.1 # bind address, default 127.0.0.1
MCP_HTTP_PORT=3000 # listen port, default 3000
MCP_HTTP_TOKEN=<at least 16 chars> # required by default; see "external auth" below
Endpoints:
POST /mcpβ JSON-RPC. RequiresAuthorization: Bearer <MCP_HTTP_TOKEN>(unless external auth mode is enabled, see below).GET /mcpβ SSE stream for server-pushed messages.GET /healthzβ unauthenticated readiness check.
Quick test:
curl -X POST http://127.0.0.1:3000/mcp \
-H "Authorization: Bearer $MCP_HTTP_TOKEN" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
External auth boundary (Cloudflare Access / reverse proxy)
Which Claude surface is talking to the server matters here:
- Claude Desktop and Claude Code with stdio β no HTTP, no token. Skip this section.
- Claude Code with HTTP transport (
claude mcp add --transport http --header "Authorization: Bearer ...") β can pass a static bearer token. UseMCP_HTTP_TOKENand you're done. - claude.ai custom connectors (web app and the iOS / Android mobile apps) β at time of writing these only support OAuth 2.1 with Dynamic Client Registration and cannot send a static
Authorization: Bearer β¦header. This is the case that needs the workaround below.
To use the server from a claude.ai custom connector (web or mobile), disable bearer auth on the MCP layer and rely on an external authentication layer (Cloudflare Access, a reverse proxy with auth, IP allowlists, etc.) to enforce identity:
MCP_TRANSPORT=http
MCP_HTTP_HOST=127.0.0.1
MCP_HTTP_PORT=3000
MCP_HTTP_ALLOW_UNAUTH=true # opt-in; disables bearer check on /mcp
# MCP_HTTP_TOKEN unset
When MCP_HTTP_ALLOW_UNAUTH=true is set and MCP_HTTP_TOKEN is not, the server logs a loud warning at startup and accepts requests on /mcp without checking any header. Anyone able to reach MCP_HTTP_HOST:MCP_HTTP_PORT then has full read+write control of HC3 β device control, scene execution, QuickApp edits, global variable writes. Binding to 127.0.0.1 and exposing the endpoint via Cloudflare Tunnel + Cloudflare Access (with a service token or SSO policy) is the recommended deployment for this mode. See DEPLOYMENT.md for a step-by-step walkthrough.
Both flags must be deliberate: with neither MCP_HTTP_TOKEN nor MCP_HTTP_ALLOW_UNAUTH=true set, the server refuses to start in HTTP mode.
Wire into your MCP client
Claude Desktop
Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"hc3": {
"command": "npx",
"args": ["-y", "@northernrough/hc3-mcp-server"],
"env": {
"FIBARO_HOST": "192.168.1.57",
"FIBARO_USERNAME": "admin",
"FIBARO_PASSWORD": "your_password"
}
}
}
}
Restart Claude Desktop. The HC3 tools appear in the tools menu.
Claude Code
claude mcp add hc3 -- npx -y @northernrough/hc3-mcp-server \
--env FIBARO_HOST=192.168.1.57 \
--env FIBARO_USERNAME=admin \
--env FIBARO_PASSWORD=your_password
Cursor / Cline / Continue
Each client uses a similar JSON shape; consult its docs for the config file location. The shape is the same as Claude Desktop's:
{
"mcpServers": {
"hc3": {
"command": "npx",
"args": ["-y", "@northernrough/hc3-mcp-server"],
"env": {
"FIBARO_HOST": "192.168.1.57",
"FIBARO_USERNAME": "admin",
"FIBARO_PASSWORD": "your_password"
}
}
}
}
Testing
Six-phase MCP test harness in scripts/test/. Read-only phases catch
schema and protocol regressions; mutating phases verify CRUD round-trips
on disposable resources.
| Phase | What it catches | Mutating? |
|---|---|---|
| 0 | tool count / schema validity / name parity vs golden | no |
| 1 | every read tool returns expected response shape | no |
| 2 | create / update / delete round-trips end-to-end | YES β gated |
| 3 | known-bitten regressions (UTF-8, 501, content shape, validation) | partial |
| 6 | every hc3.request(...) URL is live (catches dead endpoints) | no |
Run
npm run compile
node scripts/test/phase0-parity.mjs
node scripts/test/phase1-readonly-sweep.mjs
The harness reads FIBARO_HOST / FIBARO_USERNAME / FIBARO_PASSWORD
from the environment (same vars the server uses; .env is honoured).
Run mutating phases
Phase 2 creates and deletes globals, custom events, rooms, scenes, and QAs on the live HC3. Gated behind an explicit env var so you can't run it by accident:
MCP_TEST_ALLOW_MUTATIONS=1 node scripts/test/phase2-mutations.mjs
All resources are prefixed TEST_${runId}_ (or TEST-${runId}-) and
torn down in finally even on test failure. A pre-flight orphan sweep
removes any leftovers from previously-crashed runs (covers globals,
devices, custom events, and scenes).
CI / pre-commit
Per CLAUDE.md ("Test before commit"): at minimum run phase 0 and
phase 1 before merging anything that touches MCP protocol or HC3 API
code. Phase 2 is worth running before any release that touches CRUD
tools.
See scripts/test/README.md for per-phase detail.
What this does
This server exposes 130+ tools spanning the full HC3 read and write surface, with write guardrails on every destructive operation. Every mutating tool reads the target first, deep-merges the submitted change, writes, refetches, and asserts the change took effect. If HC3 silently dropped or normalised a field, the tool throws rather than reporting a misleading success.
A condensed summary follows. See the live tools/list from the running server (or expand each section below) for the authoritative list.
Available Tools (130+)
Devices and Rooms
get_devices- List devices, with filters for type, room, interface, visibility, and moreget_device_info- Get a single device by IDfilter_devices- Server-side multi-criteria filter with attribute projection (POST /api/devices/filter). Much smaller payloads than get_devices when you know which fields you needfind_devices_by_name- Resolve a name to parent/top-level devices (substring / exact, optional roomId and visibleOnly filters). Trimmed record output β much smaller than get_devices for lookup workflowsfind_device_by_endpoint- Resolve a multi-endpoint child device by (parentId, endpointId). Stable identity for children that survives Z-Wave re-inclusion. Returns an array β endpoint 0 is commonly ambiguousget_device_property- Read a single device property (much smaller than get_device_info for scalar fields)cancel_delayed_action- Cancel a queued delayed device action by (deviceId, timestamp)delete_device- Delete a single device by id. Refuses ids <10, Z-Wave physical devices (without allow_physical), and devices with children (without cascade). Post-delete verifiedcontrol_device- Invoke device actions (turnOn, turnOff, setValue, setColor, etc.)modify_device- Edit top-level fields (name, roomID, enabled, visible) and nested properties in a single verified PUTget_rooms- List rooms and sectionsget_room- Get a single room by idcreate_room- Create a new room (pre-validates 20-char name limit)modify_room- Update a room via read-modify-write + verifydelete_room- Delete a room, with guards for the default room and rooms with devices (reassign_to target)assign_devices_to_room- Batch-move devices to a room (groupAssignment), with per-device post-move verify
Scenes
get_scenes- List scenes with filtersrun_scene- Start a scene (async, returns immediately)run_scene_sync- Run a scene synchronously, waiting for completion. Useful for sequenced automation stepsstop_scene- Stop a running scenemodify_scene- Update scene metadata (name, icon, room, etc.)create_scene- Create a new scene (with HC3-required field defaults; post-create verify)update_scene_content- Replace scene Lua (actions/conditions) contentdelete_scene- Delete a scene by id with read-first-for-recovery-trail, refusal of currently-running scenes, and post-delete refetch verify
Icons
list_icons- List all icons HC3 knows about, grouped by device/room/sceneget_icon- Fetch an icon's binary content base64-encoded; detects HC3's silent SVG-fallback for missing iconsupload_icon- Upload a new user icon. Pre-validates PNG bytes (signature, 128Γ128, palette mode) before posting; HC3's PNG validator silent-500s on non-palette PNGs. Auto-assigned name returneddelete_icon- Delete a user-uploaded icon. Built-in icons return 403
System
get_server_info- Report the MCP server's identity: name, version (read from package.json at startup), transport (stdio/http), and configured HC3 host/port. No HC3 round-trip; useful for "which version am I connected to" questionsget_system_info- HC3 version, serial, and system detailssnapshot- Single-call dump of every mutable HC3 configuration surface (devices, rooms, scenes, QAs with files, globals, custom events, alarm, climate, system, users, HC3 API docs) for backup regimes and drift detection. Per-surface atomicity; opt-in zwave-parameters surfaceget_network_status- Network connectivity statusget_energy_data- Energy consumption dataget_diagnostics- System health diagnosticsget_weather- Current weather dataget_home_status- Current home modeset_home_status- Set home mode (Home/Away/Vacation/Night)get_profiles- List HC3 profiles + activeProfile id (Home/Away/Vacation orchestration)get_profile- Get one profile's detail (devices/scenes/climateZones/partitions)activate_profile- Switch the active profile with post-activation verifymodify_profile- Update a profile (name/icon/devices/scenes/climateZones/partitions) with read-modify-write + verifycreate_profile- Create a new profile (post-create verify)delete_profile- Delete a profile (refuses the active one; post-delete verify)reset_profiles- DESTRUCTIVE: resets every profile to HC3 defaults. Requires explicit confirm=trueset_profile_scene_action- Set how a profile handles a specific scene on activationset_profile_climate_zone_action- Set how a profile handles a specific climate zone on activationset_profile_partition_action- Set how a profile handles a specific alarm partition on activationget_location_info- Home location settingsupdate_location_settings- Update location, timezone, and related settings
Global Variables
get_global_variables- List all global variablesset_global_variable- Update an existing global variable (type-coerced to the stored type)create_global_variable- Create a new global variable (refuses if name exists; validates name regex; supports isEnum with enumValues)delete_global_variable- Delete a global variable by name. Reads lastValue first (returned as recovery trail); refuses readOnly system globals unless allow_system=true. Post-delete verified
Users
get_users- List users and permissionsupdate_user_rights- Modify a user's access rights (devices, scenes, climateZones, profiles, alarmPartitions). Read-modify-write + post-write-verify. Safety guards against writing rights.advanced (privilege escalation) or setting rights.*.all=true (mass grant) unless explicitly overridden; refuses superuser targets
Climate
get_climate_zones- List climate zonesget_climate_zone- Get a single climate zoneupdate_climate_zone- Update climate zone settings
Alarm
get_alarm_partitions- List alarm partitionsget_alarm_partition- Get a single alarm partitionarm_alarm_partition- Arm a partitiondisarm_alarm_partition- Disarm a partitionget_alarm_history- Alarm event historyget_alarm_devices- Security devices
Sprinklers
get_sprinkler_systems- List sprinkler systemsget_sprinkler_system- Get a single sprinkler systemcontrol_sprinkler_system- Start/stop irrigation with duration and delay
Custom Events
get_custom_events- List custom event definitionscreate_custom_event- Create a new custom eventtrigger_custom_event- Emit a custom eventget_custom_event- Read a single custom event by nameupdate_custom_event- Update userDescription and/or rename (read-modify-write)delete_custom_event- Delete by name (captures last userDescription)
Notifications
get_notifications- List notificationsmark_notification_read- Mark a notification readclear_all_notifications- Clear all notificationsget_notification- Read a single notification by idupdate_notification- Update notification fields (wasRead, data, priority) with read-modify-writedelete_notification- Delete by id, capturing last data as recovery trail. Refuses canBeDeleted=false unless allow_system=true
Backups
can_create_backup- Check whether backups can be createdget_local_backup_status- Local backup statusget_remote_backup_status- Remote backup statusget_backups- List backupscreate_backup- Create a new backup
iOS Devices
get_ios_devices- List registered iOS devicesregister_ios_device- Register a new iOS device
Debug
get_debug_messages- Retrieve debug messages with client-side filteringclear_debug_messages- Clear all debug messages (returns count cleared). Useful for test loops
System Events
get_event_history- HC3 system event feed (scene starts, device property changes, device actions) β the data behind /app/history. Supports event_type, object_id/object_type, since_timestamp (client-side) and limit (capped at 1000).get_refresh_states- Live poll of HC3's native event/state-change stream (GET /api/refreshStates?last=cursor). Returns changes (state deltas) + events + new cursor. Complementary to get_event_history β refreshStates is live, event_history is retrospective
Z-Wave Diagnostics
get_zwave_mesh_health- Aggregate mesh health from /api/devices?interface=zwave: dead/unconfigured counts, dead devices with node IDs and reasons, breakdowns by room and manufacturerget_zwave_node_diagnostics- Per-node Z-wave transmission counters (frame totals, outgoing failures, CRC/S0/S2/TransportService/MultiChannel failures, nonce exchanges) enriched with device name, room, and computed outgoing-failed percent. Sources an undocumented endpointget_zwave_reconfiguration_tasks- Active reconfiguration tasks with status, target device and node, child-device summary. Sources an undocumented endpointget_device_parameters- Z-Wave device configuration parameters with human-readable labels, descriptions, defaults, and format. Merges current values with the template catalogue. Flags provenance honestly: on HC3 5.x the mesh read-back path does not work, so most values are template defaults rather than live device readingsset_device_parameter- Write a Z-Wave configuration parameter via thesetConfigurationdevice action. The only working REST path for parameter writes on firmware 5.x β the documentedsetParameterandreconfigureactions return "not implemented", and theproperties.parametersPUT path silently caches without transmitting. Reads-before, polls cache after with backoff, returns{before, after, cacheUpdated, actionResponse, transmissionNote}. Mesh transmission is not programmatically verifiable on HC3 5.x but empirically confirmed working
QuickApps
get_quickapps- List QuickAppsget_quickapp- Get a single QuickAppcreate_quickapp- Create a new empty QuickApp on HC3 from scratch (not from a .fqa file; use import_quickapp for that)get_quickapp_available_types- List the QuickApp device types the current firmware accepts, for picking atypewhen calling create_quickapprestart_quickapp- Restart a QuickAppget_quickapp_variable- Read a single quickAppVariableset_quickapp_variable- Write a single quickAppVariable
QuickApp File Management
list_quickapp_files- List source files for a QuickAppget_quickapp_file- Get a single file's contentcreate_quickapp_file- Create a new source file (arg:fileName; renamed fromnamein 4.0.0)update_quickapp_file- Update an existing source fileupdate_multiple_quickapp_files- Batch update multiple filesdelete_quickapp_file- Delete a source file (main files cannot be deleted)export_quickapp- Export as .fqa (open) or .fqax (encrypted)import_quickapp- Import from .fqa/.fqax
System Intelligence and Context
get_system_context- Comprehensive system overviewget_device_relationships- Device relationships and room assignmentsget_automation_suggestions- Automation recommendationsexplain_device_capabilities- Detailed capability explanations
HC3 Programming Documentation
get_hc3_configuration_guide- HC3 configuration referenceget_hc3_quickapp_programming_guide- QuickApp programming guideget_hc3_lua_scenes_guide- Lua scenes programming guideget_hc3_programming_examples- Code examples and snippets
Plugin Management
get_plugins- All plugins (installed plus available)get_installed_plugins- Installed pluginsget_plugin_types- Plugin type catalogueget_plugin_view- Plugin view/configuration interfaceupdate_plugin_view- Update plugin view componentscall_ui_event- Trigger UI events on plugin interface elementscreate_child_device- Create child devicesmanage_plugin_interfaces- Add or remove interfaces from devicesrestart_plugin- Restart a pluginupdate_device_property- Update device property values directlypublish_plugin_event- Publish system events through the plugin systemget_ip_cameras- Available IP camera typesinstall_plugin- Install a plugindelete_plugin- Uninstall a plugin
Audit (cross-cutting, dev-time)
Read-only batch tools that walk multiple HC3 surfaces (QAs + scenes + globals + devices) to answer questions a single-domain tool can't. Stateless; do not modify HC3. Cost: 30-90s per call on a typical HC3 β they fetch every QA file and every scene to grep through.
audit_id_references- Find every place a device id is referenced across all QuickApp source files, every Lua/scenario scene's actions and conditions, every JSON (block-editor) scene's nested action tree, and every global variable's value. Universal HC3 question: "if I delete or replace this device, what breaks?"audit_qa_devices- For a given QuickApp, parse every numeric device id its source files reference and classify each as ALIVE / DEAD / DELETED via/api/devices/{id}(properties.dead/properties.deleted). OptionalbindAware: truemode also parsesbind("RoleStem", { ... })descriptors and runs the L0-L4 resolver waterfall (cached / endpoint / nameInParent / newParentEndpoint / globalName) on each role entry β useful for spotting descriptors whose cached id has drifted after a Z-Wave Reconfigureintrospect_device_group- Take aDevices.X.Y = { foo = 1234, bar = 5678 }numeric group inside a QA file and return a structured snapshot of the live state behind each id. Auto-detects flat vs endpoint mode. Output formats:json(canonical),markdown-table(pasteable into a doc),bind-lua(ready-to-paste descriptor block matching the SceneManager bind() pattern),yaml
Each tool includes input validation, error handling, and detailed response data to help AI assistants understand and work with your Fibaro HC3 system effectively.
How this differs from upstream and mcp-server-hc3
Upstream was a starting point, not a maintained product. The original author has moved on to a different QuickApp development workflow (his plua repo + skills) and has greenlit independent evolution of this line. Almost no upstream code remains on the runtime path β what's been added since then:
- Write guardrails on every mutating tool. Read-modify-write, post-write verify, refetch-and-compare on every destructive endpoint. Catches HC3's known silent-drop classes (e.g. Z-Wave parameter writes that cache without transmitting; QA file writes that need byte-exact verification; user-rights writes that would 403 if the full record is echoed back). See
CHANGELOG.mdfor the inventory of caught classes. - Z-Wave diagnostics and writes:
get_zwave_mesh_health,get_zwave_node_diagnostics(per-node frame/CRC/security counters),get_zwave_reconfiguration_tasks,get_device_parameters(with honest provenance β values are HC3-stored, not live device readings on this firmware),set_device_parameter(4.3.0 β the working REST path for Z-Wave configuration parameter writes on firmware 5.x viasetConfiguration; the documentedsetParameterandreconfigureactions return "not implemented" on this firmware). - Resilient name β id resolution for manifest-driven sync that survives Z-Wave re-inclusion (
find_devices_by_name,find_device_by_endpoint). - Profile orchestration end-to-end (read, activate, modify, full CRUD, association PUTs).
- Snapshot tool for nightly backup regimes β single-call dump of every mutable surface with per-surface atomicity.
- HTTP transport with bearer auth and a Cloudflare-Access-friendly unauthenticated mode for claude.ai custom connectors. See
DEPLOYMENT.md. - Modular architecture (3.4.0): 24 per-domain tool modules under
src/mcp/tools/, with shared write-verify helpers insrc/mcp/util.tsand a tool-registry dispatcher insrc/mcp/tools/registry.ts. The orchestrator is 244 lines. - Standalone, no dependency on plua or any local development toolchain. Works out of the box with
npx. - Audit family (3.5.0+):
audit_id_references,audit_qa_devices(with optional bind-aware L0-L4 resolver waterfall),introspect_device_group(json / markdown-table / bind-lua / yaml outputs). Read-only batch tools that walk QAs + scenes + globals + devices to surface drift across surfaces.
Migrating from 3.x to 4.x
Single breaking change. The QuickApp file-arg was inconsistently named across the QA-file tools β three used fileName, two used name. 4.0.0 settles on fileName everywhere, immediately, with no deprecation shim.
If you're upgrading from any 3.x release:
create_quickapp_fileβ rename argumentnameβfileName.update_multiple_quickapp_filesβ within each item in thefilesarray, renamenameβfileName.
The other QA-file tools (get_quickapp_file, update_quickapp_file, delete_quickapp_file, list_quickapp_files) already used fileName and need no change. HC3's wire shape still uses name for the file's own name in the request body; the wrapper now remaps automatically β callers don't see HC3's wire form.
If you are happy on a smaller core surface, the unscoped mcp-server-hc3 may suit you better. If you maintain a household HC3 with QuickApps, scenes, and Z-Wave actors and want the agent to be able to do meaningful, safe work over the full system, this is what you want.
Security
This server runs with your HC3 admin credentials and exposes write access to your home: devices, scenes, QuickApps, global variables, profiles, users, rooms, alarm partitions, and the notification centre. Any MCP client (Claude Code, Claude Desktop, Cursor, Cline, etc.) connected to it can read and mutate that state. Treat the credentials and the agent's prompts accordingly.
- Credentials are taken from environment variables (
FIBARO_HOST,FIBARO_USERNAME,FIBARO_PASSWORD, optionalFIBARO_PORT). They are never written to disk by this code. - The published npm tarball contains only compiled JS,
LICENSE,README.md,CHANGELOG.md,SECURITY.md,DEPLOYMENT.md, andKNOWN_DEAD_ENDPOINTS.md. No.env, no local configuration files. - HC3 does not currently expose TLS on its REST surface; the credential transit is HTTP Basic auth. Run this on the same trusted network as the HC3, or front it with a reverse proxy.
To report a vulnerability, see SECURITY.md. Please email rather than file a public issue.
Maintenance
This package is maintained for the author's personal HC3 setup and is published as-is for the wider Fibaro community. There is no SLA. Issues and PRs are welcome; response time is best-effort. Stable interfaces are SemVer-respected β patch releases are bug fixes, minors are additive, majors are breaking. Subscribe to GitHub releases on northernRough/HC3_mcp to track new versions.
Known issues
- IPv6 addresses are not supported
- TLS to the HC3 requires a fronting reverse proxy (HC3 firmware is HTTP-only on the REST surface)
- Some advanced HC3 features (notification centre creation, certain Z-Wave write paths) are firmware-quirky on 5.x; tools that hit those quirks fail loudly rather than silently and document the boundary in their tool descriptions
- A handful of historical HC3 REST endpoints are no longer routed on current firmware (5.20x). Tools that previously called them have been migrated to working alternatives. The full catalogue, with curl reproductions and replacement endpoints for each, lives in KNOWN_DEAD_ENDPOINTS.md. Consult it before authoring a new tool against an HC3 endpoint that hasn't been exercised recently.
Contributing
Pull requests are welcome. The repo follows a strict branch-per-logical-change convention with read-modify-write + post-write verify on every mutating tool. See ~/code/hc3/HC3_mcp/CLAUDE.md (in the local checkout) for the workflow expectations.
License
MIT License. Original work copyright (c) 2024 GsonSoft Development; fork modifications and additions copyright (c) 2026 northernRough.
Release notes
See CHANGELOG.md.
