sigenergy-mcp
Monitor a Sigenergy energy system locally over Modbus TCP; this schema exposes read-only tools (writes off by default).
Read live plant, battery, grid, inverter, and EV charger status (power, SOC, temperatures, alarms, etc.)
Read energy counters (kWh) for PV, load, battery, grid, and EV charger
Read inverter model, serial number, firmware, and ratings
Read current control settings (remote EMS, power limits, SOC limits)
List, read named registers, or read arbitrary raw registers by address
Discover Modbus device IDs for plant, inverters, and AC/DC chargers
With
SIGENERGY_ALLOW_WRITES=true(not in this schema): set EMS mode, battery/grid/PV limits, SOC limits, start/stop EV charging, and write registersWith HTTP transport: record and query local history via SQLite (per README)
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@sigenergy-mcp what's my solar generation, battery charge, and grid usage?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
sigenergy-mcp
An MCP server for monitoring and controlling a Sigenergy energy system (SigenStor, Sigen hybrid inverters) locally over Modbus TCP — no cloud account needed.
It exposes live PV, battery, grid and load data, inverter details and Sigen EV charger status as MCP tools, and (optionally) control tools for remote EMS mode, battery charge/discharge limits, grid export/import limits, SOC limits and EV charging.
Prerequisites
Enable Modbus TCP on your Sigenergy system. In the mySigen app (installer account) go to the device → Settings → Communication → Modbus TCP Server and turn it on. Your installer can do this for you if you don't have access.
Give the system a static IP / DHCP reservation on your LAN.
Note the Modbus device ids: the plant (whole system, aggregated by the EMS) is
247; inverters start at1(multiple inverters are1,2, …). A Sigen AC charger normally takes id1, so the inverter is moved to another id (e.g.2). A DC charger answers on its inverter's id. If you're unsure, run thediscover_devicestool once — it reports the ids and the settings to use.Python 3.10+.
Related MCP server: EG4 MCP Server
Install
pip install git+https://github.com/aydho/sigenergy-mcp
# or, from a checkout
pip install -e .Or run without installing using uv:
uvx --from git+https://github.com/aydho/sigenergy-mcp sigenergy-mcp --host 192.168.1.50Configuration
Environment variable | CLI flag | Default | Description |
|
| — | IP address of the Sigenergy system (required) |
|
|
| Modbus TCP port |
|
|
| Plant device id |
|
|
| Comma-separated inverter device ids |
|
| — | AC charger (EVAC) device ids, usually |
|
| — | DC charger (EVDC) ids = the inverter's id |
|
|
| Enable control (write) tools |
|
| — | File to append a JSON line to for every write |
| — | Cloudflare Access team ( | |
| — | Cloudflare Access application AUD tag (required with the team domain) | |
| — | Allowed | |
|
| Modbus request timeout, seconds | |
|
| Retries for a read that gets no response | |
|
| Minimum seconds between Modbus requests | |
|
| SQLite file for recorded history (HTTP transport only); empty to disable | |
|
| Seconds between history samples; | |
| system | IANA time zone for daily history buckets, e.g. | |
|
| Server log level ( |
Writes are off by default: the control tools aren't even listed to the client until you opt in.
History
When run with the HTTP transport, the server samples the plant once a minute (3 Modbus requests) into a local SQLite database:
Table | Contents | Kept |
| Every sample | 30 days |
| Each completed hour: avg/min/max per gauge, kWh per counter | 90 days |
| Each completed local day, same columns | Forever |
Gauges are PV, battery, grid and load power and battery SOC; counters are the lifetime PV, load, battery
charge/discharge, grid import/export and EV charger energy, reported as the kWh in each interval. The
get_history tool reads it, choosing a resolution to suit the range and filling in the hour or day in progress.
History is only recorded by a long-lived HTTP server, never with the stdio transport: a stdio server is started
for each client session, so recording there would poll the system once per open session. Run a single HTTP
server per system. A stdio server's get_history still reads the database that HTTP server writes.
Request pacing
The Sigenergy Modbus protocol (section 4.2, "Interaction timeout") sets a minimum request period of
1000 ms, so the server sends one request at a time and waits at least SIGENERGY_MIN_REQUEST_INTERVAL
seconds between them, including for retries and discover_devices. Nearby registers are read in a single
request to keep the count down, but a tool still takes roughly one second per request it needs:
get_plant_status takes about 3 s, and discover_devices about 2-4 s per id scanned. Raise the interval on
a slow or busy link; lowering it below 1 s goes against the protocol.
Claude Desktop / Claude Code
{
"mcpServers": {
"sigenergy": {
"command": "sigenergy-mcp",
"env": {
"SIGENERGY_HOST": "192.168.1.50",
"SIGENERGY_INVERTER_IDS": "2",
"SIGENERGY_AC_CHARGER_IDS": "1",
"SIGENERGY_ALLOW_WRITES": "false"
}
}
}
}With Claude Code:
claude mcp add sigenergy -e SIGENERGY_HOST=192.168.1.50 -- sigenergy-mcpHTTP transport
To run it as a long-lived network service (e.g. on a box next to the inverter), bind loopback and put an authenticating proxy in front — this is how the Mac mini deploy works, with Cloudflare Access terminating auth:
export SIGENERGY_HTTP_ALLOWED_HOSTS=pi.local:8000
sigenergy-mcp --host 192.168.1.50 --transport streamable-http --http-host 127.0.0.1 --http-port 8000
# MCP endpoint: http://127.0.0.1:8000/mcp (reached through the proxy/tunnel)The server refuses to listen on a non-loopback address unless Cloudflare Access JWT validation is
configured (SIGENERGY_ACCESS_TEAM_DOMAIN + SIGENERGY_ACCESS_AUD), or you pass
--allow-unauthenticated (only when a different authenticating proxy sits in front).
The HTTP app also answers GET /health with 200 ok, before any auth — for deploy and
load-balancer health checks.
Authentication (Cloudflare Access)
The HTTP transport has no token of its own; it trusts Cloudflare Access
to authenticate clients at the edge. Set both of these and every request must carry a valid Access JWT
(the Cf-Access-Jwt-Assertion header Access injects, or the CF_Authorization cookie):
export SIGENERGY_ACCESS_TEAM_DOMAIN=acme # your <team>.cloudflareaccess.com
export SIGENERGY_ACCESS_AUD=<application-AUD-tag> # from the Access applicationThe server verifies the JWT's signature against your team's JWKS (https://<team>.cloudflareaccess.com/cdn-cgi/access/certs)
and checks its issuer and audience, so the origin only trusts requests that came through Access. Enable
Managed OAuth on the Access application and clients (Claude, MCP Inspector, …) authenticate with an
interactive OAuth login — no static token or custom headers. GET /health stays open for health checks.
Deploy to the Mac mini
Pushing to main deploys to the Mac mini automatically, via the self-hosted GitHub Actions
runner (labels self-hosted, mini) and .github/workflows/mac-mini.yml —
the same pattern as the shopping-agent, agentic and claude-pro-proxy services. Each push
rsyncs the repo to /srv/sigenergy-mcp, builds/updates a venv, pip install -e ., restarts the
service and health-checks GET /health. workflow_dispatch offers deploy / restart / logs.
The service listens on 8003 (8000/8001/8002/8080 are taken) and binds 127.0.0.1; it's
reached through the mini's cloudflared tunnel, so authentication is enforced at the edge rather
than by the app. History recording is on (the server samples once a minute, see History).
Two files in deploy/ are the version-controlled reference:
deploy/sigenergy-mcp.service— the systemd unit. Install it by hand once (the runner isn't given sudo to write/etc/systemd/system):sudo cp /srv/sigenergy-mcp/deploy/sigenergy-mcp.service /etc/systemd/system/ sudo systemctl daemon-reload && sudo systemctl enable --now sigenergy-mcpdeploy/sigenergy-mcp.env— just thePORT, rsynced on every deploy.
Connection settings and secrets go in /etc/sigenergy-mcp/env (untracked, root:andrewsaunders
0640), populated once by hand:
SIGENERGY_HOST=sigenstor.vista.aydho.com
SIGENERGY_TIMEZONE=Australia/Melbourne
SIGENERGY_HISTORY_DB=/srv/sigenergy-mcp/data/history.db
# Cloudflare Access JWT validation (see below). The server validates every request's Access JWT.
SIGENERGY_ACCESS_TEAM_DOMAIN=acme
SIGENERGY_ACCESS_AUD=<application-AUD-tag>
SIGENERGY_HTTP_ALLOWED_HOSTS=sigenergy.aydho.comExpose it with cloudflared + Cloudflare Access (GitHub)
Add an ingress rule to the mini's /etc/cloudflared/config.yml (above the http_status:404
catch-all) and restart cloudflared:
- hostname: sigenergy.aydho.com
service: http://localhost:8003cloudflared tunnel route dns 85dac53c-cdd5-4122-8fc5-47c0736130c8 sigenergy.aydho.com
sudo systemctl restart cloudflaredThen protect sigenergy.aydho.com in the Cloudflare Zero Trust → Access controls dashboard:
Add the application and attach your identity provider. The purpose-built path is AI controls → MCP servers → Add an MCP server (HTTP URL
https://sigenergy.aydho.com/mcp); a self-hosted application for the hostname works too.Add an Allow policy that includes your GitHub identity (your account/email). This is the interactive browser sign-in.
Enable Managed OAuth on the application (⋯ → Edit → Advanced settings → Managed OAuth, with dynamic client registration on). Access then runs the OAuth login that MCP clients like Claude need — no static token or custom headers. Managed OAuth forwards a signed JWT to the origin, which this server validates via
SIGENERGY_ACCESS_TEAM_DOMAIN+SIGENERGY_ACCESS_AUD(copy the AUD tag from the application's settings).
Add it in Claude (chat: Settings → Connectors → Add custom connector; or the CLI below) with just the URL — the OAuth flow handles sign-in:
claude mcp add --transport http sigenergy https://sigenergy.aydho.com/mcpFor a non-interactive CLI client you can instead create a service token (Access → Service Auth), add a Service Auth policy that includes it, and pass its two headers — Access still forwards a valid JWT that the server validates:
claude mcp add --transport http sigenergy https://sigenergy.aydho.com/mcp \
--header "CF-Access-Client-Id: <id>.access" \
--header "CF-Access-Client-Secret: <secret>"Security
Modbus TCP itself has no authentication or encryption. Anything that can reach the Sigenergy system on port 502 can read every register and change its settings; the device id is just an address, not a secret. This server can't change that, so:
Never port-forward 502 to the inverter. Keep it on your LAN, ideally on its own VLAN/IoT network with firewall rules so only the machine running this server (or Home Assistant) can reach port 502.
SIGENERGY_ALLOW_WRITESonly controls what this server will do. It doesn't stop other devices on your network writing to the inverter directly.
What the server does to protect its own interface:
Read-only by default. Without
SIGENERGY_ALLOW_WRITES=truethe control tools are removed from the server, so a client can't see or call them. Every write is still range- and enum-checked. Tools that write several registers report which writes already took effect if a later one fails. Plant and inverter start/stop can't be written at all.Audit log. Set
SIGENERGY_AUDIT_LOG=~/sigenergy-audit.jsonlto record every write attempt (time, device, register, previous value, new value, success or error), so you can see and undo what was changed.HTTP transport. It binds to
127.0.0.1by default, with Host/Origin checks against DNS rebinding, and refuses a non-loopback bind unless authentication is configured. Authentication is delegated to Cloudflare Access: setSIGENERGY_ACCESS_TEAM_DOMAIN+SIGENERGY_ACCESS_AUDand every request must carry a valid Access JWT (signature checked against your team's JWKS, plus issuer and audience), so the origin only trusts traffic that came through Access. SetSIGENERGY_HTTP_ALLOWED_HOSTSto the public hostname to keep the Host/Origin checks too.TLS. The server speaks plain HTTP. For HTTPS or access from outside your home, put it behind Tailscale / WireGuard or a reverse proxy such as Caddy rather than opening a port.
Tools
Monitoring (read-only)
Tool | Description |
| PV, battery, grid and load power, SOC, EMS mode, running state, decoded active alarms |
| SOC, SOH, power, rated/available energy, power limits, cut-off SOCs |
| Grid power (total and per phase), on/off-grid status |
| kWh counters: PV today/yesterday/lifetime, load, battery charge/discharge, grid import/export |
| Per-inverter power, battery, energy counters, temperatures, AC phases, PV strings |
| Model, serial number, firmware, ratings |
| AC charger state (no vehicle / connected / charging…), power, energy, alarms; DC charger state, power, vehicle SOC, session energy |
| Recorded history from the local database: raw, hourly or daily rows plus kWh totals |
| Scan device ids 1..10 (or |
| Current remote EMS settings, power limits and SOC limits |
| All named registers with address, type, unit and gain |
| Read named registers |
| Read any address (for registers not in the map) |
Control (requires SIGENERGY_ALLOW_WRITES=true)
Tool | Description |
| Enable remote EMS and set mode (standby, self-consumption, command charge/discharge…) |
| Enable/disable remote EMS (disable to hand control back to the built-in EMS) |
| Max charge / discharge power (kW) |
| Max grid export / import (kW) |
| Limit PV output (kW) |
| Backup SOC, charge cut-off and discharge cut-off (%) |
| Start or stop charging on the AC or DC charger |
| AC charging current (A), checked against the charger's rated and breaker current |
| DC charger max charge / discharge (V2G) power (kW) |
| Write any writable register in the map by name (except start/stop registers) |
Sign conventions
grid_active_power> 0 = importing from the grid, < 0 = exporting.ess_power> 0 = battery charging, < 0 = discharging.load_poweris read from the system's load register (30282) on newer firmware, otherwise derived as plant AC output + grid power + third-party PV − AC charger power (so it excludes EV charging).
Example: force-charge the battery from the grid at 5 kW
set_remote_ems_mode(mode=3)— command charging (grid first)set_battery_power_limits(max_charge_kw=5)Later,
set_remote_ems_enabled(false)to return to normal operation.
Register map
The register map lives in src/sigenergy_mcp/registers.py
and follows the Sigenergy Modbus Protocol (V2.x). Available registers can vary with
firmware version; registers the device rejects are reported per-register in an
errors field instead of failing the whole call. Registers that answer "illegal data
address" are remembered per device and skipped on later calls. Use read_raw_registers
for anything not yet mapped.
Addresses, types and gains are cross-checked against TypQxQ/Sigenergy-Local-Modbus, the field-tested Home Assistant integration; alarm bit descriptions are adapted from it (MIT).
Development
pip install -e '.[dev]'
pytestThe tests run the MCP tools end-to-end against an in-process pymodbus simulator.
Disclaimer
Not affiliated with Sigenergy. Writing Modbus registers changes how your system operates — use the control tools at your own risk and check settings in the mySigen app afterwards.
Available Tools
12 toolsdiscover_devicesARead-only
Scan Modbus device ids to find the plant, inverters, DC chargers and AC chargers.
Probes ids 1..max_device_id in order and stops at the first id that doesn't answer. Use the result to set SIGENERGY_INVERTER_IDS, SIGENERGY_AC_CHARGER_IDS and SIGENERGY_DC_CHARGER_IDS.
Inputs: max_device_id. Returns: object
get_battery_statusBRead-only
Battery (ESS) state for the whole plant: SOC, SOH, power, capacity, available energy and limits.
Inputs: no named parameters. Returns: object
get_control_settingsBRead-only
Current plant control settings: remote EMS enable/mode, power limits and SOC limits.
Inputs: no named parameters. Returns: object
get_energy_totalsARead-only
Plant energy counters (kWh): PV, load, battery, grid import/export and EV charger totals.
These registers need Modbus protocol V2.5+ firmware; any the system doesn't support are listed in errors.
Inputs: no named parameters. Returns: object
get_ev_charger_statusARead-only
Status of every configured Sigen EV charger.
AC chargers (EVAC): state (IEC 61851: no vehicle / connected / charging...), charging power, energy, ratings, alarms. DC chargers (EVDC, built into a SigenStor): state, output power, vehicle SOC, session energy and duration, lifetime totals and power limits.
Inputs: no named parameters. Returns: object
get_grid_statusBRead-only
Grid connection point: total and per-phase active/reactive power and on/off-grid status.
Inputs: no named parameters. Returns: object
get_inverter_infoARead-only
Static inverter information: model, serial number, firmware and ratings.
Args: inverter_id: Modbus device id of the inverter. Omit to read every configured inverter.
Inputs: inverter_id. Returns: object
get_inverter_statusARead-only
Detailed live data for a hybrid inverter: power, battery, temperatures, AC voltages/currents and PV strings.
Args: inverter_id: Modbus device id of the inverter. Omit to read every configured inverter.
Inputs: inverter_id. Returns: object
get_plant_statusARead-only
Live overview of the whole system: PV, battery, grid and load power, battery SOC, mode and active alarms.
load_power comes from the system's own load register on newer firmware; otherwise it is derived as
plant AC output + grid power + third-party PV - AC EV charger power.
Inputs: no named parameters. Returns: object
list_registersBRead-only
List the named registers known to this server for the plant or an inverter.
Inputs: scope. Returns: result
read_raw_registersARead-only
Read arbitrary registers by address, for registers not in the built-in map.
Args: address: Start register address as given in the Sigenergy Modbus protocol (e.g. 30014). count: Number of 16-bit words to read (1-125). device_id: Modbus device id. Defaults to the plant id (247). register_type: "input" (function 0x04) or "holding" (0x03). Defaults to input for 3xxxx, holding otherwise. data_type: Optionally decode the words as this type.
Inputs: address (required), count, data_type, device_id, +1 more. Returns: object
read_registersARead-only
Read named registers (see list_registers) and return scaled values with units.
Args: names: Register names, e.g. ["ess_soc", "pv_power"]. scope: "plant" (system level), "inverter", "ac_charger" or "dc_charger". device_id: Modbus device id. Defaults to the plant id (247) or the first configured device of that kind.
Inputs: names (required), scope, device_id. Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 12 tools
Each tool targets a distinct subsystem (plant, grid, battery, inverter, EV charger, energy totals, registers), so boundaries are mostly clear. The only mild overlap is that get_plant_status summarizes data also exposed by the individual status tools, but descriptions clarify the live-overview vs subsystem-detail distinction.
Names follow a consistent verb_noun snake_case pattern (get_*_status, get_*_info, list_registers, read_registers, read_raw_registers, discover_devices). The verbs (get/list/read/discover) map predictably to action type.
12 tools is well within the sweet spot for a monitoring/inspection domain, and each earns its place by covering a distinct device or data access layer.
The read surface is thorough: live status, static info, energy counters, settings, named-register and raw-register access, device discovery, and EV charging. The notable gap is write/control operations (e.g. setting power or SOC limits) implied by get_control_settings, though read_raw_registers partially mitigates unmapped reads.
Related MCP Connectors
Protocol-native energy infrastructure orchestration for AI data centers. Provides 46 MCP tools across 8 grid protocols (IEC-61850, DNP3, Modbus, OCPP, OpenADR, IEEE 2030.5, IEC 60870-5-104, ICCP) with 5 core API primitives: connect, dispatch, settle, comply, and intel. Enables AI agents to programmatically interact with substations, grid interfaces, and energy assets for real-time workload-grid coordination.
Home energy for AI assistants: live Power flow, Energy history, solar forecast, and device control.
Operate a network of EV charge points over OCPP: status, sessions, tariffs, prices, remote commands
Unofficial integration! ## ✨ Key Features ### 💰 Financial Intelligence - **Smart Charging Cost An…
Related MCP Servers
- AlicenseAqualityBmaintenanceConnect AI assistants to Victron Energy systems to read real-time solar, battery, grid, and inverter data from your local network via Modbus TCP or MQTT.32134 npm7MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for EG4 solar inverters, enabling real-time monitoring, performance analysis, battery health, alerts, maintenance insights, and historical data via natural language.15 PyPIMIT
- FlicenseAqualityDmaintenanceEnables monitoring and control of IDM Navigator 2.0 heat pumps via Modbus TCP.13-
- AlicenseNot gradedqualityCmaintenanceEnables Entra-authorized monitoring and control of an EG4 inverter via MCP, with OBO delegation and safety gates.MIT