solar-mcp
This server provides read-only local access to Huawei SUN2000 inverter telemetry.
Call
get_solar_statuswith no arguments to retrieve inverter model, software version, device status code/description, generation power, daily yield, and lifetime yield.Results include observation timestamp, age in seconds, cache status, and source (
huawei_modbus).Readings are cached for less than 30 seconds; cache hits do not refresh the observation timestamp.
No control, write, historical FusionSolar, or arbitrary register tools are exposed in this schema.
Provides read-only telemetry from Huawei SUN2000 inverters over local Modbus TCP, exposing inverter model, firmware version, active power, daily and lifetime generation, and operating status without allowing control or configuration changes.
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., "@solar-mcpHow much power is my solar inverter producing right now?"
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.
solar-mcp and solar-stats
Two independent stdio MCP servers: solar-mcp connects locally to an inverter;
solar-stats reads historical plant
production from an authenticated FusionSolar browser session. The cloud-history
server never connects to or controls the inverter.
solar-mcp: local inverter
A local stdio Model Context Protocol server for Huawei SUN2000 inverter telemetry, read-only by default, with optional percentage and fixed-watt generation-cap control.
Tool | Availability | Purpose |
| Always | Generation, energy counters, device status, and freshness |
| Always | Fresh mode-aware percentage or watt-cap readbacks |
| Explicit local opt-in | Change the percentage power cap with expected-value and readback checks |
| Explicit local opt-in | Select a percentage or whole-watt cap, including guarded mode changes and restoration |
Generation is not household consumption, grid export, or available surplus. There are no arbitrary-register, start/stop, grid-code, reactive-power, discovery, HTTP, or scheduling tools. Installer login and one session heartbeat are used only for an explicit cap change when an installer password has been configured.
Related MCP server: solax-cloud-mcp
Requirements
Python 3.12 or newer and uv.
A Huawei SUN2000 inverter reachable through a compatible local Modbus TCP connection, with Modbus access enabled by the device owner/installer.
The inverter's unit ID. The verified SUN2000-8KTL-M0 / SDongleA-05 installation uses unit 1, not the library's unit-0 default.
Telemetry, percentage-cap writes/restoration, and fixed-watt writes/readbacks on that inverter/dongle combination have been exercised on hardware. On-grid output curtailment following a fixed-watt command has been observed; instantaneous power can fluctuate around the setting rather than equal it exactly. Other SUN2000 models must provide the same registers; missing data is an error rather than an invented reading. Software updates and commissioning are outside this server's scope. Do not start it during an active firmware update.
Run one server instance per inverter endpoint, not separate instances in several MCP clients. Close competing commissioning sessions when troubleshooting. The server serializes its own requests, but does not coordinate other processes. Keep Modbus on a trusted LAN; never expose port 502 to the internet.
Install and configure
git clone https://github.com/vasilyevstan/solar.git
cd solar
uv sync --frozen --no-dev
cp .env.example .envEdit the ignored .env with your actual address. 192.0.2.10 in the example
is a documentation placeholder, not a discoverable device.
Setting | Default | Meaning |
| Required | Hostname or LAN IP, without a URL scheme |
|
| Modbus TCP port |
|
| Inverter unit, between 0 and 247 |
| Unset | Optional expected inverter serial; kept local and never returned by the tool |
|
| Set exactly |
|
| Private, durable cap-change journal; overrides must be absolute paths |
| Unset | Optional local installer password, used only during an explicit cap change |
The server reads environment variables, not .env files itself. For a direct
stdio launch, let uv load the local file:
uv run --frozen --no-dev --env-file .env solar-mcpThis starts an MCP protocol process, not an interactive dashboard. Use an MCP
client to send requests. Host, port, and unit can alternatively be set with
--inverter-host, --inverter-port, and --unit-id; command-line values take
precedence over environment variables.
MCP client configuration
Use the following stdio entry in your client's MCP server configuration, replacing
the directory and placeholder IP. The exact outer configuration file is
client-specific. Ensure uv is on the client's executable search path.
{
"mcpServers": {
"solar-mcp": {
"command": "uv",
"args": [
"run", "--directory", "/absolute/path/to/solar",
"--frozen", "--no-dev", "solar-mcp"
],
"env": {
"SOLAR_INVERTER_HOST": "192.0.2.10",
"SOLAR_INVERTER_UNIT_ID": "1"
}
}
}
}Tool result
get_solar_status takes no arguments. It returns structured data with:
Field | Meaning |
| Inverter model and running inverter firmware, not dongle firmware |
| Signed active power in watts; zero is a valid observation |
| Daily inverter generation counter |
| Accumulated inverter generation counter |
| Raw operating code and description |
| UTC time at the start of the telemetry read sequence |
| Age since that observation, measured with a monotonic clock |
| Whether this call reused an observation less than 30 seconds old |
| Always |
Readings are sequential, not an atomic meter snapshot. The daily counter follows the inverter's own day boundary; the observation timestamp is UTC. The server does not persist telemetry or contact FusionSolar.
Successful readings are cached on demand for less than 30 seconds. Cache hits do not move the observation timestamp. There is no background polling. A known disconnected connection invalidates cache use. Call again after the cache window for another live observation.
Each new connection waits one second before reading and verifies the model and
serial. The first accepted identity is retained for the process lifetime, so a
different device after reconnect is rejected. Set SOLAR_EXPECTED_SERIAL to
enforce that identity across process restarts as well.
Connection/response timeouts are 10 seconds. A tool call, including waiting for another request, has a 30-second deadline, plus at most two seconds for connection cleanup. There are no hidden reconnect/retry loops: after a failed read, the connection is closed and the next tool call makes one fresh attempt.
Failures produce an MCP isError response, never successful stale values or
fabricated zeroes. Invalid register sentinels and missing/unsupported data are
errors. Unknown operating codes are preserved and explicitly labelled unknown.
Logs go to stderr; stdout is reserved for MCP messages.
Generation caps: power, not energy
A cap limits instantaneous output power (W or kW), not daily energy (kWh). For example, output held at 4 kW for one hour generates 4 kWh; at 2 kW for half an hour it generates 1 kWh. A cap cannot force more production than sunlight makes available and is not a household export limit.
Supported controls are percentage mode at 40125 (0.1% steps) and
fixed-watt mode at 40126 (one unsigned 32-bit value, 1 W steps). A percentage
uses the inverter's own power reference; it is not converted into promised watts.
Whole-watt targets must be between zero and the freshly read hardware Pmax
at 30075. The server does not change Pmax or override other scheduling modes.
The active mode/value/command block at 35300-35303 is always read together.
get_generation_limit has no arguments and never uses a cache. Its mode is
percent or watts; the corresponding configured/active fields are
percent/active_percent or watts/active_watts. Fields in the other unit are
null. Results also contain observed_at (UTC) and control_enabled.
Unsupported mode/command combinations and invalid register values are explicit
errors. A stored cap and its active adjustment can differ.
Enable and use control
Control remains off in the example configuration. To opt in, set the following in your private environment and restart the MCP:
SOLAR_ALLOW_CONTROL=1
SOLAR_EXPECTED_SERIAL=your-inverter-serialKeep the installer password in private local configuration only if your device requires authentication. It is never a tool argument or result, is not logged, and is not used by telemetry or cap reads. An authentication failure is an error; the server does not guess credentials or retry failed writes.
For an explicitly requested percentage change:
Call
get_generation_limitand inspect bothpercentandactive_percent.Call
set_generation_limitwith the requestedpercent,expected_current_percentfrom the configured readback, andexpected_active_percentfrom the active readback. All are numeric values in 0.1% steps, from 0 to 100.Keep the result's
previous_percentif you intend to restore it. Restore explicitly using the same setter with that previous percentage and both freshly read expectations.
expected_active_percent defaults to expected_current_percent for callers
whose readbacks agree. When they differ, supply the actual active expectation
explicitly; otherwise the setter rejects the request without writing. This also
allows guarded restoration of a stored change that has not become active.
For example, only when the current cap is 100% and you actually want 50%:
{
"percent": 50.0,
"expected_current_percent": 100.0,
"expected_active_percent": 100.0
}Zero can stop generation. 100% removes this percentage restriction, not
other inverter limits. A request for the already active value returns
changed: false without writing or authenticating; it does not prove write
permission.
Whole-watt caps and mode changes
Use set_power_limit for a whole-watt cap or an explicit transition between the
two supported modes. Read get_generation_limit first. Pass its mode as
expected_mode, and both configured/active values in that mode's unit as
expected_current_value and expected_active_value.
For example, only when the fresh configured and active readbacks are both 100% and you actually want a 222 W maximum:
{
"mode": "watts",
"value": 222,
"expected_mode": "percent",
"expected_current_value": 100,
"expected_active_value": 100
}Save the result's previous mode and value for restoration. To restore a previous
100% cap after a fresh read confirms configured/active values of 222 W, call this
same tool with mode: "percent", value: 100, expected_mode: "watts", and both
expected values set to 222. The original set_generation_limit remains a
percentage-mode-only convenience; it will not implicitly switch out of watt mode.
Unlike that convenience tool's no-op optimization, set_power_limit deliberately
sends one command even when its target matches the visible state. This permits
explicit reassertion/restoration when another mode's command is still pending.
write_performed: true means the command was sent and its stored value verified.
It does not promise constant generation or prove that physical curtailment has
already occurred.
Verification and persistence
Both setters share the same serialization, identity, fresh-expectation, optional
installer-login, durable-journal, and readback safeguards. They send one named
Huawei-library write: function 06 to 40125 for percentages, or function 16 with
two words at 40126 for watts. No separate mode register is written. They check
the acknowledgment and stored target, then inspect the active adjustment up to
three times, but never automatically retry a cap write.
changed: true means the stored setting changed, not that output is already
limited. active_readback_matches reports whether the requested mode and active
adjustment agree. For set_power_limit, configuration_verified refers to
requested_mode/requested_value; limit still describes the active mode's
readbacks, which may be the previous mode while a transition is pending.
If it does not, the result includes a warning: the cap may be pending or overridden,
and its output restriction is unconfirmed. This distinction is necessary because
the two readbacks can differ while the inverter is in no-irradiation standby.
Physical curtailment must be checked separately while sufficient sunlight exists.
An earlier standby check changed the stored cap from 100% to 99% and restored
100%, while the active readback stayed at 100%. A later on-grid fixed-watt check
verified both configured/active watt values and observed output curtailment.
Treat changes as persistent. Exiting or disconnecting does not trigger a
restore. If a write times out, is cancelled, or fails verification, it may still
have applied. Read the current cap before any further action; do not blindly
retry or assume the original cap was restored. The private journal contains
paired prepared/verified records, including previous/requested modes and values,
the target register's previous value, active readback, and a request ID.
verified means the configured value was
verified; its active_readback_matches field records the separate active result.
An unpaired prepared record has an uncertain outcome and
preserves the information needed for an explicit recovery.
The journal is permission-restricted and must be writable; inability to persist
intent blocks the cap write. It contains device identity, so do not publish it.
The repository ignores *.jsonl files.
Do not run competing controllers, including simultaneous commissioning/cloud setting changes. Expected-value checking is not an atomic lock against other Modbus clients or FusionSolar; a later external change can override the result.
solar-stats: FusionSolar history
solar-stats exposes two read-only tools:
get_generation(start_date, end_date=None, format="json", refresh=False)
get_hourly_generation(start_date, end_date=None, format="json", refresh=False)Dates use YYYY-MM-DD. Omitting the end date selects one day; a period is
inclusive and may cross years. The default structured result includes:
generation_kwh: the requested period's sum, in kWh, not instantaneous kW.daily: each date, itsgeneration_kwh,source_missing,from_cache, and originalsource_retrieved_at.missing_dates,source_complete, source/plant identity, and UTCretrieved_at(when this result was assembled).retrieval_mode:stored_file,portal, ormixed, withcached_daysandportal_dayscounting returned days from each source.date_basis: "plant_report_calendar": source daily labels are not shifted to UTC.
The metric is Plant Report / PV Yield (kWh), not inverter lifetime counters, household consumption, export, or revenue.
Saved-file preference
Queries now prefer existing date records in local yearly CSVs, including genuine measured zeroes and explicitly flagged missing-value zero fills. They validate the matching metadata's plant identity, metric, units, date coverage, missing flags, total, and CSV checksum. A complete file hit does not launch Chrome, contact FusionSolar, or read Keychain credentials.
Only dates not covered by saved files are requested from the portal.
Requests are grouped by the monthly reporting periods actually needed; saved
measurements are retained rather than overwritten by unrelated rows returned in
the same monthly report. Portal failures remain errors, never partial results or
fabricated zeroes. refresh=True in MCP, or --refresh in the CLI, bypasses local
files entirely when you need updated measurements, to recheck known source gaps,
or to refresh a still-changing current day's generation. A stored missing-value
zero remains source_missing: true; using it from a file never turns it into a
confirmed measurement.
Configure SOLAR_STATS_DATA_DIR (default: state/solar-stats under the process
working directory). Preferred files are generation-YEAR.csv with matching
.metadata.json sidecars. If the canonical file is absent, matching partial
exports such as generation-2026-jan-sep.csv are accepted; conflicting overlapping
measurements are errors. An invalid file or mismatched pair fails explicitly.
Use refresh to bypass it, and an explicit export to replace it when appropriate.
Original files without per-date timestamps remain readable using their original
retrieval timestamp; new sidecars retain per-date source timestamps.
Storage management remains deferred. A query reads existing files but does
not automatically create a database, synchronize files, or rewrite exports.
Use --output for a standalone snapshot. Use --output-dir for ongoing yearly
files: it merges new dates into stable generation-YEAR.csv names, preserving
earlier months, existing measured values when a new reading is missing, and newer
measurements when an older cached result overlaps. Appends must cover intervening
dates rather than inventing values for unqueried gaps. Coordinated reads/writes
use a local file lock. All plant data remains private and untracked.
GitHub Secrets and private Actions fetching
For a local MCP that must not read the FusionSolar password from macOS Keychain,
set SOLAR_STATS_BACKEND=github_actions. Saved-file hits remain entirely local.
Uncovered dates are fetched by a dispatch-only job in a private GitHub
repository; the local process never downloads the login credentials.
Copy automation/solar-stats.yml into .github/workflows/solar-stats.yml in the
private automation repository. Replace both __SOLAR_SOURCE_SHA__ placeholders
with the reviewed, published 40-character source commit. The workflow checks out
that exact revision, uses pinned actions, has read-only repository permissions,
and accepts only dates, daily/hourly granularity and a request ID. Do not enable
this data-producing workflow in the public source repository.
Create these repository Actions secrets through GitHub's secret forms or
gh secret set's private input prompt:
Secret | Value |
| The FusionSolar login account |
| The FusionSolar password, not the Mac/Keychain unlock password |
| The configured plant page, without query parameters or tokens |
| The plant report's IANA time zone |
Configure the local MCP's existing private environment:
SOLAR_STATS_BACKEND=github_actions
SOLAR_STATS_ACTIONS_REPOSITORY=your-owner/private-solar-runner
SOLAR_STATS_ACTIONS_SOURCE_SHA=the-published-40-character-source-commit
SOLAR_STATS_GH_COMMAND=/absolute/path/to/gh
SOLAR_STATS_TIMEOUT_SECONDS=600Keep the existing plant URL, time zone and data directory for local file
validation. gh must already be authenticated to GitHub with access to dispatch
Actions, read private artifacts and delete consumed artifacts in that repository.
The browser-profile setting is optional and unused by this backend. No FusionSolar
password is stored in local configuration, and no Keychain fallback is attempted.
The local client verifies repository privacy, workflow revision, caller/run ownership, unique request ID, artifact digest, pinned source revision and report shape. Reports travel as private artifacts, are deleted after consumption, and otherwise expire after one day. Browser profiles and login state are never uploaded. Artifacts are temporary transport, not the permanent data-storage solution; CSV output and explicit yearly append behavior remain unchanged.
Each live miss uses GitHub Actions runner time; file hits do not. Concurrent
portal jobs are serialized, and cancellations/timeouts remain explicit failures.
Update the workflow's source pin and local SOLAR_STATS_ACTIONS_SOURCE_SHA
together when deploying new source. GitHub Secrets removes the Keychain prompt
from portal fetching, but cannot bypass FusionSolar MFA, CAPTCHA, denied access,
or missing historical measurements.
Inside the runner only, SOLAR_STATS_LOGIN_SOURCE=environment selects
SOLAR_STATS_USERNAME and SOLAR_STATS_PASSWORD injected from the secrets.
Missing credentials fail before opening Chrome. The same login-origin checks,
single-attempt authentication and disabled browser-debug logging apply.
Fresh sign-ins can first land on the plant overview while the application
initializes. The client waits for that overview to be ready and follows the
configured plant's report route without repeating login or dismissing unrelated
dialogs.
Browser-session setup
Two browser modes are available. The default attach mode preserves existing
behavior: reuse a dedicated, signed-in Chrome session through loopback remote
debugging. managed mode starts installed Google Chrome automatically, with
a persistent private profile, and closes only that owned browser after the query.
Managed mode is headless by default and does not expose a debugging port.
Do not use your normal browsing profile. For attach mode, keep your configured
plant open and do not expose its debugging port to the network. For example, on
macOS, launch a separate Chrome window:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--user-data-dir=/absolute/private/path/solar-stats-browser \
--remote-debugging-port=0 --remote-debugging-address=127.0.0.1 \
"https://your-region.fusionsolar.huawei.com"Sign in directly in that window and open your plant. Copy .env.solar-stats.example to the ignored
.env.solar-stats and configure:
Setting | Meaning |
| Your authenticated plant page containing |
| Absolute dedicated profile path; managed mode creates it privately and requires directory mode |
| Whole-query deadline, including waiting for the shared profile; default 300 |
| Directory of private yearly CSVs and metadata; default |
| Hourly queries only: the plant report's IANA time zone, validated against the portal request |
|
|
|
|
| Optional macOS generic-password item name for automatic login; unset by default |
For automatic startup, set SOLAR_STATS_BROWSER_MODE=managed and use a separate
private profile on persistent storage. Chrome must already be installed; no
browser download, desktop, scheduler, or background service is required.
macOS and Linux browser sessions are supported (flock is required); the optional
credential reader described below is macOS-only.
Treat the profile as sensitive: it retains normal browser session credentials. The server does not export cookies or copy another browser's authentication storage. Missing permissions, changed portal layouts, and rate limits remain explicit errors. This is a web-report integration, not an official northbound/OpenAPI account.
The adapter opens a native child tab from the authenticated plant page, retaining
the browser's normal tab-scoped session without extracting it. It reads monthly reports and
checks all reported pages, dates, units, and plant identity. A local profile lock
serializes simultaneous solar-stats processes. Attach mode closes only its own
tab and connection, never the shared Chrome browser. Managed mode restores its
own profile's last session before attempting login.
Optional macOS Keychain login
In Keychain Access, select the login keychain and create a new password
item named solar-stats.fusionsolar. Its account name is your FusionSolar
username/email; its password is your FusionSolar password. Enter both directly
in Keychain Access, not in chat, shell arguments, environment variables, or Git.
Configure only this reference:
SOLAR_STATS_KEYCHAIN_SERVICE=solar-stats.fusionsolarThe reader uses the native /usr/bin/security helper. Authorize that application
for this specific item through Keychain Access when prompted; do not allow
all applications. That authorization trusts the helper executable, not just this
Python project. The login Keychain must be accessible to the signed-in macOS
account. No new Python credential dependency or plaintext fallback is used.
Queries reuse an authenticated session first. Only a recognized login page on
the configured FusionSolar region triggers a Keychain read and one login attempt.
The adapter follows the application's login popup as well as same-tab redirects;
a login URL carrying the plant's return address is not treated as a plant tab.
If a restored application tab stays blank, configured Keychain login may reopen
the normal regional sign-in page once before attempting authentication.
The username/password stay in local process memory and the browser login form;
the helper's output is captured privately, never returned through MCP or logs.
DEBUG/PWDEBUG must be unset when Keychain login is enabled to avoid browser
diagnostics exposing form values.
Keychain access is bounded; denial, a locked Keychain, rejected credentials, MFA, and CAPTCHA are explicit errors, not zero generation. There are no automatic login retries. Resolve an authentication error before repeating queries. For an interactive challenge, stop managed queries, open that profile manually with the Chrome command above, and sign in normally. Close that manual browser before resuming managed queries. Challenges are never bypassed. Without the Keychain option, sign in manually and reuse the profile. Headless Linux credential provisioning is not implemented by this macOS integration.
MCP and terminal use
With no subcommand, this starts the stdio MCP:
uv run --no-sync --env-file .env.solar-stats solar-statsFor other MCP clients, use the same command with absolute project/configuration
paths and enable get_generation and get_hourly_generation. This registration is independent of
solar-mcp; it requires no inverter address or installer password.
Terminal query mode prints raw JSON or CSV to stdout; diagnostics go to stderr:
uv run --no-sync --env-file .env.solar-stats solar-stats query \
--start-date 2025-01-01
uv run --no-sync --env-file .env.solar-stats solar-stats query \
--start-date 2024-12-31 --end-date 2025-01-02
uv run --no-sync --env-file .env.solar-stats solar-stats query \
--start-date 2025-01-01 --end-date 2025-12-31 --format csv \
--output state/solar-stats/generation-2025.csv
uv run --no-sync --env-file .env.solar-stats solar-stats query \
--start-date 2021-10-01 --end-date 2026-09-30 --format csv \
--output-dir state/solar-stats
uv run --no-sync --env-file .env.solar-stats solar-stats query \
--start-date 2025-01-01 --refresh
uv run --no-sync --env-file .env.solar-stats solar-stats query \
--start-date 2026-07-01 --end-date 2026-07-31 --granularity hour \
--format csv --output-dir state/solar-stats--output-dir requires CSV format and updates a separate generation-YEAR.csv
and metadata sidecar for every intersecting year. The first/last year may cover
only part of that year, such as October-December 2021 or January-September 2026.
The filename stays generation-2026.csv as subsequent months are added.
--output writes a single result
instead of printing it. CSV output also creates a .metadata.json sidecar with
the source, interval, missing dates, original retrieval timestamps, and CSV checksum. Keep both together: the
checksum detects an interrupted export or subsequent edits. Existing CSVs are
not modified if fetching fails.
In MCP mode, format="csv" places CSV in the tool's text content and retains
structured data/provenance. It does not print raw CSV into the MCP protocol.
Missing data and matrix interpretation
Missing source readings for real requested dates are deliberately returned as
0, with source_missing: true. A measured zero has source_missing: false.
When any day is missing, source_complete is false and the total is a
sum with missing readings treated as zero, not a verified total of all
electricity actually produced. No interpolation or historical repair is done.
Authentication errors, failed requests, incomplete pagination, invalid values, or wrong-period data are not converted to zero.
The CSV columns are year,month,1,2,...,31. Rows follow chronological year/month
order. A full year has January at the top and December at the bottom; partial
and multi-year periods use only their intersecting months. Cells outside the
requested period or on nonexistent dates remain blank. Thus 2024 has 366
numeric date cells (including February 29) and six nonexistent-date blanks;
2025 has 365 numeric date cells and seven blanks.
Exports contain private plant data. The repository excludes state/, real
.env files, and vendor documentation; never commit report data, profiles,
cookies, or credentials.
Hourly history
get_hourly_generation (CLI: query --granularity hour) reads PV Yield in kWh
per hour, not instantaneous power or an estimate from daily totals. It uses the
portal's normal By time range / Hourly statistics view, splits live periods
into at most 31 days, and reads all returned pages. It validates dates, labeled
units, source time zone and daylight-saving labels before accepting data.
Only completed plant-calendar days are supported. Large uncached requests remain
subject to the configured query deadline. With --output-dir, the CLI saves each
month-sized batch before continuing, so rerunning resumes from saved dates.
Explicit empty unsuccessful reports are listed as unavailable, not zero-filled
periods; other failures stop the run. If any period is unavailable the command
exits with an error after retaining completed batches.
Hourly data is separate: generation-hourly-YEAR.csv and its metadata sidecar.
The CSV has a date column, columns 00:00 through 23:00, and an extra #2
column for a repeated clock hour when that year's time zone requires one.
For example, 03:00#2 retains the second occurrence without combining its energy
with the first. Nonexistent clock hours and unused repeated-hour cells are blank.
Real source-missing hours are zero with an explicit metadata flag.
IANA time-zone data must be available to Python; configure the zone shown by the
actual plant report, not the computer's current offset.
Saved hourly measurements retain original per-hour source timestamps. Appending
can fill gaps, but a missing new reading never erases a measured value, and an
older result never replaces a newer measurement. Partial yearly hourly files
list their actual covered_dates and unavailable_dates between the first and
last saved days. An unavailable date means it is not covered by that file, not
proof that its generation was zero or that the portal never stored it.
The normal query fails if any requested live range fails; it does not turn HTTP, authentication, or unsuccessful report responses into zero-hour rows. Historical hourly availability can differ from daily availability. A successful daily report does not imply that the same date's hourly measurements can be recovered, and the server never distributes a daily total into invented hourly values.
Development
uv sync --frozen --group dev
uv run --frozen pytest
uv buildTests use fake clients and a loopback Modbus server, not physical hardware. The stdio integration tests exercise the actual SDK and Huawei library, verify schemas and errors, assert that default-mode traffic is only function-03 reads, and check exact function-06 percentage and function-16 two-word watt writes in opt-in mode. Tests also cover Pmax bounds, guarded mode transitions/reassertion, identity and expected-value checks, durable intent, permission failures, explicit restoration with differing stored/active readbacks, cancellation, serialization, and unchanged monitoring behavior.
License and dependencies
Licensed under AGPL-3.0-only; see LICENSE.
Register decoding and Modbus operations use
huawei-solar (AGPLv3). The transport is
composed with its tmodbus dependency so automatic
reconnect cannot bypass identity checks. The official
MCP Python SDK is MIT-licensed.
Dependency versions are pinned and resolved in uv.lock.
Vendor documents and firmware, local settings, device identifiers, and diagnostic
artifacts are not distributed here. The local docs/ directory is ignored.
Available Tools
1 toolget_solar_statusARead-onlyIdempotent
Read inverter identity, software, power, energy, and status; cache for at most 30 seconds.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| model | Yes | |
| source | No | |
| from_cache | Yes | |
| age_seconds | Yes | |
| observed_at | Yes | |
| generation_w | Yes | |
| device_status | Yes | |
| daily_yield_kwh | Yes | |
| software_version | Yes | |
| device_status_code | Yes | |
| lifetime_yield_kwh | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, destructiveHint=false, and openWorldHint, so safety is covered. The description adds genuinely new behavioral context beyond them: a 30-second cache bound that tells the agent data may be slightly stale.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single tight sentence that front-loads the resource and payload, then appends the caching caveat. Every clause earns its place with no redundancy.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a no-arg read-only tool with a full output schema and rich annotations, the description covers the remaining gap (cache staleness) that structured fields do not. Nothing an agent needs to call it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes no parameters, so there is nothing to document; baseline is 4. The description correctly spends no words on parameters, focusing instead on output and caching.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Read) and resource (inverter) and enumerates exactly what is returned: identity, software, power, energy, and status. An agent can tell immediately what this tool yields; with no siblings, no differentiation is needed.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use/when-not statement, but with zero sibling tools there is no alternative to route between. The 'cache for at most 30 seconds' clause gives implicit freshness guidance, which is the closest thing to usage context here.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.0- First observed
get_solar_status
TDQS
Scored across 1 tool
Only one tool is exposed, so there is no possibility of confusing it with another tool. Its purpose—reading solar inverter status—is clearly distinct.
The single tool follows a clear snake_case verb_noun pattern: get_solar_status. With only one name, there is no inconsistency across the set.
One tool is borderline thin for a server named solar-mcp. It may be sufficient if the server is intentionally scoped to status retrieval only, but it offers little surface for broader solar operations.
The tool covers a comprehensive current snapshot: identity, software, power, energy, and status. However, it lacks notable related operations such as historical data, fault/alert listing, device discovery, or configuration/control.
Maintenance
Related MCP Connectors
Read-only access to authorized SolarSENS solar plants, performance, devices, and alarms.
Home energy for AI assistants: live Power flow, Energy history, solar forecast, and device control.
Read-only MCP access to a documented IT fleet: state, changes, posture. 15 tools.
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.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProvides access to Tesla vehicle telemetry data via the Tessie API, enabling real-time monitoring of battery status, charging state, climate controls, location, and other vehicle metrics through 30+ tools with intelligent caching.-
- AlicenseAqualityDmaintenanceProvides real-time access to solar inverter data from the SolaX Cloud API, enabling queries of power output, energy yields, battery status, and grid import/export data.2MIT
- AlicenseBqualityBmaintenanceEnables AI assistants to monitor and control Growatt solar installations via the ShineServer API, including reading PV generation, load, grid import/export, battery SOC, and adjusting inverter settings.26MIT
- AlicenseAqualityBmaintenanceEnables Claude or any MCP client to read live and historical Sunsynk inverter, battery, and solar data, plus inverter settings, through the Sunsynk Connect cloud API. It is read-only and lets users ask natural-language questions about generation, consumption, charging, and configuration.7MIT