GoodWe MCP Server
The GoodWe MCP Server allows you to monitor and control GoodWe solar inverters over a local network via the Model Context Protocol (MCP).
Connection Management
Connect to an inverter by IP/hostname with optional port, family override, timeout, and retry configuration
Check connection status and retrieve device info (model name, serial number, firmware version)
Live Monitoring
Read all runtime sensor data (PV production, battery state, grid import/export, load consumption), with optional filtering by sensor kind (PV, AC, BAT, GRID, UPS, BMS)
List all available sensors with their IDs and names
Read a single sensor by its ID (e.g.,
pv1_voltage,battery_soc,grid_power)
Settings Management
Read all configurable settings and their current values, or read a single setting by ID
Write a new value to any configurable setting
Operation Mode Control
Get the current operation mode and list of supported modes
Set the operation mode to:
general,off_grid,backup,eco,peak_shaving,eco_charge, oreco_discharge(with optional eco power % and SOC % thresholds)
Grid Export & Battery Control
Get and set the grid export power limit (0–10000 W; use 0 to disable export)
Get and set the battery depth-of-discharge (DoD) limit (0–99%)
MCP Resources & Prompts
Access 7 MCP resources:
inverter://status,inverter://runtime,inverter://settings,inverter://power/now,inverter://energy/today,inverter://battery, andinverter://sensorsUse 6 built-in prompt templates for status overviews, diagnostics, battery optimisation, grid export configuration, operation mode changes, and daily energy summaries
Deployment & Security
Supports four transport modes: stdio, SSE, Streamable HTTP, and combined server
Bearer token authentication for HTTP transports
Flexible deployment via environment variables, Docker, Docker Compose, and Kubernetes
GoodWe Inverter MCP Server
MCP server for monitoring and controlling GoodWe solar inverters over the local network.
Built on the goodwe library and the Model Context Protocol Python SDK.
Based on the Home Assistant GoodWe integration — the sensor definitions, operation modes, settings, and inverter family support are modelled directly after that implementation.
Features
Read live runtime data: PV production, battery state, grid import/export, load consumption
Read and write all configurable inverter settings
Switch operation modes (general, eco, backup, peak-shaving, off-grid, …)
Control grid export limit and battery depth-of-discharge
7 MCP resources: status, runtime, settings, power flow, daily energy, battery, sensor catalog
6 built-in prompt templates for common workflows (status overview, diagnostics, optimisation, …)
Bearer token authentication for all HTTP transports
Four transport modes: stdio, SSE, Streamable HTTP, and server (SSE + Streamable HTTP combined)
Auto-connect via environment variables
Related MCP server: Enapter MCP Server
Requirements
Python 3.10+
GoodWe inverter reachable on the local network (UDP port 8899 or Modbus/TCP port 502)
Installation
# with uv (recommended) — installs the locked dependency set from uv.lock
uv sync
# or a plain (editable) install
uv pip install -e .Usage
stdio (Claude Desktop)
goodwe-mcpAdd to ~/.claude/claude_desktop_config.json:
{
"mcpServers": {
"goodwe": {
"command": "goodwe-mcp",
"env": {
"GOODWE_HOST": "192.168.1.100"
}
}
}
}SSE transport
goodwe-mcp --transport sse --port 8080
# Server listens on http://0.0.0.0:8080/sseStreamable HTTP transport
goodwe-mcp --transport streamable-http --port 8080
# Server listens on http://0.0.0.0:8080/mcpServer transport (SSE + Streamable HTTP combined)
Serves both transports on a single port — useful when you need to support legacy SSE clients and modern Streamable HTTP clients simultaneously.
goodwe-mcp --transport server --host 0.0.0.0 --port 8080
# SSE: http://0.0.0.0:8080/sse (GET) and /messages/ (POST)
# Streamable HTTP: http://0.0.0.0:8080/mcpOptions
--transport {stdio,sse,streamable-http,server} Transport mode (default: stdio)
--host HOST Bind address for SSE/HTTP (default: 127.0.0.1)
--port PORT Listen port for SSE/HTTP (default: 8000)
--log-level {DEBUG,INFO,WARNING,ERROR} Logging verbosity (default: INFO)
--auth-token TOKEN Bearer token required on all HTTP requests (env: MCP_AUTH_TOKEN)
--base-url URL Public base URL, e.g. https://mcp.example.com (env: MCP_BASE_URL)
--allowed-hosts HOSTS Comma-separated Host header values to accept (env: MCP_ALLOWED_HOSTS)
--allowed-origins ORIGINS Comma-separated Origin header values to accept (env: MCP_ALLOWED_ORIGINS)Environment variables
Variable | Description | Default |
| Inverter IP / hostname for auto-connect on startup | — |
| Inverter UDP/TCP port |
|
| Inverter family override (ET, EH, BT, BH, ES, EM, BP, DT, MS, NS, XS) | auto-detect |
| Bearer token required on all HTTP requests | — (auth disabled) |
| Public base URL of the server (used as OAuth issuer URL) |
|
| Comma-separated | — (protection only on loopback binds) |
| Comma-separated | — |
| Listen port used by the container's default command and health check (Docker only) |
|
Authentication
Bearer token authentication is supported for all HTTP transports (sse, streamable-http, server).
When enabled, every MCP request must include an Authorization: Bearer <token> header.
The /health endpoint is always unprotected so Kubernetes probes continue to work.
Enable via environment variable (recommended)
export MCP_AUTH_TOKEN="$(openssl rand -hex 32)"
goodwe-mcp --transport server --host 0.0.0.0 --port 8080Enable via CLI flag
goodwe-mcp --transport streamable-http --port 8080 --auth-token my-secret-tokenClaude Desktop / MCP client configuration
Add the token to your client's MCP server configuration. For example, with Claude Desktop
using the streamable-http transport via a proxy that injects the header, or with any
client that supports Authorization headers:
{
"mcpServers": {
"goodwe": {
"url": "http://localhost:8080/mcp",
"headers": {
"Authorization": "Bearer my-secret-token"
}
}
}
}Docker / Docker Compose
Pass the token through the environment:
MCP_AUTH_TOKEN=my-secret-token GOODWE_HOST=192.168.1.100 \
docker compose -f docs/docker-compose.yml up -dKubernetes
Set the token in docs/k8s/secret.yaml before applying the manifests:
stringData:
GOODWE_HOST: "192.168.1.100"
MCP_AUTH_TOKEN: "my-secret-token"If MCP_AUTH_TOKEN is empty or not set, authentication is disabled and all HTTP endpoints are publicly accessible. The server will log a warning at startup when bound to a non-loopback address without a token.
DNS rebinding protection
The MCP SDK validates the Host and Origin headers automatically when the server is bound to a loopback address. For any other bind address (including the Docker default 0.0.0.0), list the hostnames or host:port pairs that clients legitimately use to reach the server:
MCP_ALLOWED_HOSTS="mcp.example.com,192.168.1.10:8000" \
goodwe-mcp --transport server --host 0.0.0.0 --port 8000Requests whose Host header is not listed are rejected with HTTP 421. A :* suffix matches any port (e.g. 192.168.1.10:*). Use MCP_ALLOWED_ORIGINS to additionally allow browser-based clients from specific origins. The server logs a warning at startup when bound to a non-loopback address without this setting.
TLS / HTTPS
The MCP server itself does not terminate TLS. For any non-localhost deployment, place a TLS-terminating reverse proxy in front of it (nginx, Caddy, Traefik). Serving inverter data — which constitutes personal data under GDPR when linked to a household — over plain HTTP is a security risk.
Example with Caddy (simplest option):
mcp.example.com {
reverse_proxy localhost:8000
}Data Processing Notice
This server processes data from a GoodWe solar inverter, including the inverter's IP address, serial number, and energy consumption metrics. When deployed in a home and operated by the homeowner for personal use, this processing falls under the GDPR household exemption (Art. 2(2)(c)) and GDPR does not apply. If deployed commercially — for example to monitor inverters belonging to third-party customers — the operator becomes a data controller under GDPR (EU) 2016/679 and must establish a lawful basis for processing (Art. 6), maintain records of processing activities (Art. 30), and ensure appropriate technical and organisational measures (Art. 32), including TLS encryption and access control.
Docker
Build
Build for the current machine's architecture:
docker build -t goodwe-inverter-mcp:latest .Multi-platform builds (amd64 + arm64)
Use docker buildx to produce an image that runs on both x86-64 servers and ARM boards (Raspberry Pi, Apple Silicon, AWS Graviton, etc.).
One-time setup — create a builder that supports cross-compilation:
docker buildx create --name multi --driver docker-container --bootstrap --useBuild both platforms and load into the local daemon — requires the containerd image store (enabled by default in Docker Desktop 4.34+; on Linux run dockerd --snapshotter=overlayfs or enable it in /etc/docker/daemon.json):
docker buildx build --platform linux/amd64,linux/arm64 -t goodwe-inverter-mcp:latest --load .Build both platforms and push to a registry (e.g. Docker Hub or GHCR):
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t youruser/goodwe-inverter-mcp:latest \
--push .Build both platforms and export as a local OCI tar (no registry needed):
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t goodwe-inverter-mcp:latest \
--output type=oci,dest=goodwe-inverter-mcp.tar .Run
docker run -d \
--name goodwe-mcp \
-e GOODWE_HOST=192.168.1.100 \
-p 8000:8000 \
goodwe-inverter-mcp:latestThe container defaults to --transport server (SSE + Streamable HTTP on port 8000).
Change the port with MCP_PORT so the built-in health check follows it:
docker run -d -e GOODWE_HOST=192.168.1.100 -e MCP_PORT=9000 -p 9000:9000 \
goodwe-inverter-mcp:latestTo change the transport, override the command. Keep MCP_PORT in sync, since the health check probes it:
docker run -d -e GOODWE_HOST=192.168.1.100 -e MCP_PORT=9000 -p 9000:9000 \
goodwe-inverter-mcp:latest \
goodwe-mcp --transport streamable-http --host 0.0.0.0 --port 9000Docker Compose
GOODWE_HOST=192.168.1.100 docker compose -f docs/docker-compose.yml up -ddocs/docker-compose.yml uses network_mode: host by default so the container can reach the inverter on the local LAN. Remove that line if your network already routes LAN traffic into containers.
Kubernetes
Prerequisites
The GoodWe inverter communicates over UDP/TCP on the local network. The pod needs to reach the inverter's IP. The simplest setup is hostNetwork: true on a node in the same subnet; remove it if your cluster has flat networking or another routing solution.
Deploy
# 1. Edit the inverter IP
vi docs/k8s/secret.yaml
# 2. Apply all manifests
kubectl apply -f docs/k8s/
# 3. Check status
kubectl rollout status deployment/goodwe-mcp
kubectl logs -f deployment/goodwe-mcpHealth endpoints
Both liveness and readiness probes hit GET /health, which returns:
{ "status": "ok", "inverter_connected": true }The pod becomes ready once the HTTP server is up. inverter_connected will be false until the server successfully connects to the inverter (auto-connect fires on the first MCP client session).
Tools
Tool | Description |
| Connect to a GoodWe inverter by IP/host |
| Check if connected and show device info |
| Model name, serial number, firmware version |
| All live sensor values (optional filter by kind: PV/AC/BAT/GRID/UPS/BMS) |
| List all sensor IDs and names |
| Read a single sensor by ID |
| All configurable settings and current values |
| Read a single setting by ID |
| Write a value to a configurable setting |
| Current mode and supported modes |
| Set mode: general, eco, backup, off_grid, peak_shaving, eco_charge, eco_discharge |
| Grid export limit in watts |
| Set grid export limit (0 = disabled) |
| Battery depth-of-discharge setting |
| Set battery depth-of-discharge (0–99%) |
Prompts
Pre-written prompt templates that MCP clients can fetch and use directly.
Prompt | Arguments | Description |
| — | Full status report: connection, live power flow, battery, grid |
| — | Review battery settings and suggest DoD / mode improvements |
| — | Check and adjust the grid export power limit |
| — | Explain available modes and help switch to the right one |
|
| Collect full diagnostics and identify problems |
| — | Today's energy counters as a human-readable table |
Resources
URI | Description |
| Connection status and device info (JSON) |
| All live sensor values (JSON) |
| All configurable settings (JSON) |
| Real-time power flow — PV, battery, grid and load in watts, grouped by kind |
| Today's energy counters — production, load, grid buy/sell, battery charge/discharge (kWh) |
| Battery sensors, DoD limit and current operation mode in one payload |
| Static sensor catalog — id, name, unit and kind for every sensor (no live values) |
Inverter families
Family | Models | Notes |
ET / EH / BT / BH | Hybrid 3-phase | Battery support, up to 4 PV strings |
ES / EM / BP | Hybrid single-phase | Battery support |
DT / MS / NS / XS | Grid-tie only | No battery |
Development
# install runtime + dev dependencies from the lockfile
uv sync
# run the test suite
uv run pytest
# with a coverage report
uv run pytest --cov=goodwe_mcp --cov-report=term-missing
# lint
uv run ruff check src testsTests run entirely against an in-memory fake inverter, so no hardware or network access is needed. They drive the server through a real MCP client session and the actual ASGI apps, covering tool input validation, resource payloads, bearer authentication, DNS-rebinding protection and the CLI. GitHub Actions runs the suite on Python 3.10 through 3.13 and builds the container image on every push and pull request.
License
See LICENSE file in the root of the repository.
Disclaimer
This software is not affiliated with or endorsed by GoodWe Inc. Use at your own risk. This software is a personal project that I maintain in my free time. Refer to the licence for more information.
Available Tools
15 toolsconnect_inverterA
Connect to a GoodWe inverter over the local network.
Args: host: IP address or hostname of the inverter (e.g. "192.168.1.100"). port: UDP port — default 8899. Use 502 for Modbus/TCP. family: Optional inverter family override: ET, EH, BT, BH, ES, EM, BP, DT, MS, NS, XS. Leave blank for auto-detection. timeout: Per-attempt timeout in seconds. retries: Number of retry attempts on failure.
Inputs: host (required), port, family, timeout, +1 more. Returns: result
get_battery_dodA
Get the battery depth-of-discharge setting (percentage of capacity that may be used).
Inputs: no named parameters. Returns: result
get_connection_statusB
Return the current connection status and basic device info.
Inputs: no named parameters. Returns: result
get_device_infoA
Get model name, serial number and firmware version of the connected inverter.
Inputs: no named parameters. Returns: result
get_grid_export_limitA
Get the current grid export power limit in watts.
Inputs: no named parameters. Returns: result
get_operation_modeA
Get the current operation mode and the list of modes supported by this inverter.
Inputs: no named parameters. Returns: result
get_runtime_dataA
Read current live data from all inverter sensors.
Args: sensor_kind: Optional filter — one of PV, AC, UPS, BAT, GRID, BMS. Omit to return all sensors.
Returns power, voltage, current, energy, battery state, grid status and more.
Inputs: sensor_kind. Returns: result
get_settings_dataA
Read all configurable inverter settings and their current values.
Inputs: no named parameters. Returns: result
list_sensorsA
List all sensor IDs and names available on the connected inverter.
Args: sensor_kind: Optional filter — one of PV, AC, UPS, BAT, GRID, BMS.
Inputs: sensor_kind. Returns: result
read_sensorA
Read the current value of a single sensor by its ID.
Args: sensor_id: Sensor identifier, e.g. "pv1_voltage", "battery_soc", "grid_power". Use list_sensors to discover available IDs.
Inputs: sensor_id (required). Returns: result
read_settingA
Read the current value of a single configurable setting.
Args: setting_id: Setting identifier. Use get_settings_data to see all IDs.
Inputs: setting_id (required). Returns: result
set_battery_dodA
Set the battery depth-of-discharge limit.
Args: depth_percent: How much of the battery capacity may be discharged (0–99). Higher values allow deeper cycling but can reduce battery lifespan.
Inputs: depth_percent (required). Returns: result
set_grid_export_limitA
Set the maximum power the inverter may export to the grid.
Args: limit_watts: Power limit in watts (0–10000). Use 0 to disable export.
Inputs: limit_watts (required). Returns: result
set_operation_modeB
Set the inverter operation mode.
Args: mode: One of: general, off_grid, backup, eco, peak_shaving, eco_charge, eco_discharge. eco_mode_power: Power level % for eco modes (0–100, default 100). eco_mode_soc: Battery SOC % threshold for eco modes (0–100, default 100).
Inputs: mode (required), eco_mode_power, eco_mode_soc. Returns: result
write_settingA
Write a new value to a configurable inverter setting.
Args: setting_id: Setting identifier. Use get_settings_data to see available IDs. value: New value as a string (converted to int/float automatically when possible).
Warning: Incorrect values can affect inverter operation. Verify the value before writing.
Inputs: setting_id (required), value (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 15 tools
Each tool has a clear, distinct purpose. Getters and setters are separated by domain (battery, grid, operation mode, settings, sensors), and there is no ambiguity between similar tools like read_sensor vs list_sensors or get_settings_data vs read_setting.
All tool names follow a consistent verb_noun pattern using snake_case. Verbs are uniform (connect, get, list, read, set, write) and nouns are specific, making the naming predictable and easy to understand.
The server provides 15 tools, which is an appropriate number for a feature-rich inverter management server. Each tool serves a necessary function without redundancy, covering connection, monitoring, and configuration.
The tool set covers all essential operations for interacting with a GoodWe inverter: connection, device info, live data, settings retrieval and modification, and key parameter adjustments (battery DoD, grid export limit, operation mode). There are no obvious gaps for standard remote management tasks.
Maintenance
Related MCP Connectors
MCP server wrapping the Tesla Fleet API and TeslaMate API
Related MCP Servers
- AlicenseNot gradedqualityNot gradedmaintenanceProvides access to comprehensive U.S. and international energy data from the EIA API, including electricity, natural gas, petroleum, coal, renewables, CO2 emissions, and energy forecasts.MIT

Enapter MCP Serverofficial
AlicenseNot gradedqualityDmaintenanceMCP server providing tools for AI-agents to interact with Enapter EMS.Apache 2.0- AlicenseBqualityDmaintenanceRead-only MCP server integrating with the Victron VRM API to monitor solar systems, batteries, alarms, and more.4139 npm6MIT
- AlicenseNot gradedqualityDmaintenanceMCP server for Solplanet ASW5-T inverter, enabling natural language queries about solar energy system status and control.MIT