Two MCP (Model Context Protocol) servers that let AI assistants (Claude Desktop, Claude Code, Cursor, Windsurf, or anything speaking MCP) read and — where safe — write to your Victron system.
victron-tcp— local LAN access over Modbus TCP + MQTT. Real-time, offline-capable, 900+ raw registers.victron-vrm-mcp— remote access over the VRM cloud API. Self-hosted, MCP Connector compatible.
Both are MIT-licensed and published on npm. You can run them individually or together.
Contents
- Which server do I want?
- Server A —
victron-tcp(local) - Server B —
victron-vrm-mcp(cloud) - Using both together
- Example prompts
- Links
1. Which server do I want?
victron-tcp |
victron-vrm-mcp |
|
|---|---|---|
| Data source | Modbus TCP + MQTT on your LAN | VRM cloud API |
| Transport | stdio (client spawns process) | Streamable HTTP (you self-host) |
| Needs LAN access to the GX | ||
| Works away from home | ||
| Works when internet is down | ||
| Latency | ~50 ms (real-time) | ~15 min (VRM sampling interval) |
| Raw register access | ||
| Tools | 32 | 52 |
| Prompts / workflows | 23 | — |
| Writes | Read-only today; MQTT writes on roadmap | Alarms, tags, Dynamic ESS, access mgmt — confirm-gated |
| MCP Connector API compatible | ||
| Auth | None (trusts LAN) | Per-request VRM personal access token |
Rule of thumb: on your LAN → victron-tcp. Anywhere else, or building an API-backed app → victron-vrm-mcp. Both at once is fine and often useful.
2. Server A — victron-tcp (local)
2.1 Prerequisites
- Victron GX device on your LAN — Cerbo GX, Cerbo GX MK2, Ekrano GX, Venus GX, CCGX, or a Raspberry Pi running Venus OS.
- Node.js 18+ on the machine running your MCP client.
- MQTT (enabled by default on Venus OS) or Modbus TCP enabled on the GX.
2.2 Enable data access on the GX
Option A — MQTT (recommended, richer data model, auto-discovery):
MQTT-on-LAN is enabled by default on modern Venus OS. To verify: Settings → Services → MQTT on LAN (SSL) or MQTT on LAN (Plain text).
Option B — Modbus TCP (needed for raw-register access):
Settings → Services → Modbus TCP → ON. Default port: 502, default unit ID for the GX: 100.
You can enable both; the server will use whichever transport you configure.
2.3 Install per client
Claude Desktop / Cursor / Windsurf (JSON config)
Add to claude_desktop_config.json (Claude Desktop), ~/.cursor/mcp.json (Cursor), or the equivalent Windsurf config:
{
"mcpServers": {
"victron-tcp": {
"command": "npx",
"args": ["-y", "victron-tcp"],
"env": {
"VICTRON_HOST": "192.168.1.50",
"VICTRON_TRANSPORT": "mqtt",
"VICTRON_PORTAL_ID": "your-portal-id"
}
}
}
}
Restart the client after saving. You should see victron-tcp appear in the MCP servers list with 32 tools and 23 prompts.
Config file locations:
- macOS (Claude Desktop):
~/Library/Application Support/Claude/claude_desktop_config.json - Windows (Claude Desktop):
%APPDATA%\Claude\claude_desktop_config.json - Cursor:
~/.cursor/mcp.json
Claude Code (CLI)
Single-line install:
claude mcp add-json victron-tcp \
'{"type":"stdio","command":"npx","args":["-y","victron-tcp"],
"env":{"VICTRON_HOST":"192.168.1.50","VICTRON_TRANSPORT":"mqtt"}}'
Verify:
claude mcp list
Remove if needed:
claude mcp remove victron-tcp
Don't know your GX IP? Let the AI find it
Install with no environment variables, then prompt:
“Find my Victron GX device on the network and set it up.”
The victron_network_scan + victron_setup tools will probe the LAN, test transports, discover devices, and return a config block you can paste back.
2.4 Environment variables
All optional — set them to avoid passing arguments on every call.
| Variable | Default | Description |
|---|---|---|
VICTRON_HOST |
(none) | GX IP or hostname |
VICTRON_TRANSPORT |
modbus |
modbus or mqtt |
VICTRON_PORTAL_ID |
(auto) | VRM portal ID (MQTT only — shown on the GX under Settings → VRM online portal) |
VICTRON_MODBUS_PORT |
502 |
Modbus TCP port |
VICTRON_MQTT_PORT |
1883 |
MQTT broker port |
VICTRON_UNIT_ID |
100 |
Default Modbus unit ID (the GX itself) |
2.5 First-run smoke test
In your MCP client, ask:
“Give me a system overview from victron-tcp.”
That calls victron_system_overview and should return battery SOC, PV power, grid power, AC consumption, and ESS status. If it works, you’re done.
2.6 Troubleshooting
| Symptom | Likely cause & fix |
|---|---|
ECONNREFUSED |
Modbus TCP not enabled on the GX, or wrong port. |
ETIMEDOUT |
Wrong VICTRON_HOST, firewall, or GX on a different VLAN. |
No devices found via MQTT |
VICTRON_PORTAL_ID missing or wrong. Check Settings → VRM online portal → VRM Portal ID. |
| “Unknown unit ID” on Modbus | Run victron_discover — some devices sit on non-default unit IDs. |
| Tool call hangs | Check client logs (see below). Usually a transport mismatch. |
Log locations (Claude Desktop):
- macOS:
~/Library/Logs/Claude/mcp-server-victron-tcp.log - Windows:
%APPDATA%\Claude\logs\mcp-server-victron-tcp.log
For deeper debugging, use the MCP Inspector:
npx -y @modelcontextprotocol/inspector npx -y victron-tcp
3. Server B — victron-vrm-mcp (cloud)
3.1 Prerequisites
- Node.js 18+ on the host that will run the server.
- A VRM personal access token (see below).
- Public HTTPS if you want the Anthropic MCP Connector API to reach it. Not required for local testing.
3.2 Get a VRM personal access token
- Log in to https://vrm.victronenergy.com.
- Preferences → Integrations → Access tokens → Create access token.
- Copy it immediately — VRM will only show it once.
VRM Bearer tokens (from
/auth/login) are deprecated on 2026-06-01 per the official VRM OpenAPI spec. Use personal access tokens for anything new. The server still supports the Bearer scheme viax-vrm-auth-scheme: Bearerfor backwards compatibility, but logs a warning.
For a quick read-only smoke test without an account of your own, the demo tenant works:
curl https://vrmapi.victronenergy.com/v2/auth/loginAsDemo
Use the returned short-lived token with header x-vrm-auth-scheme: Bearer.
3.3 Run the server
Option A — npm (recommended)
# One-off, no install
npx victron-vrm-mcp
# Or install globally
npm install -g victron-vrm-mcp
victron-vrm-mcp
Listens on http://127.0.0.1:3000/mcp by default.
Option B — from source
git clone https://github.com/lubosstrejcek/victron-vrm-mcp.git
cd victron-vrm-mcp
npm install
npm run build
npm start
Option C — Docker (one-liner)
docker run --rm -it -p 3000:3000 \
-e HOST=0.0.0.0 \
-e ALLOWED_ORIGINS="https://claude.ai" \
node:22-alpine \
sh -c "npx -y victron-vrm-mcp"
A dedicated image is on the roadmap.
3.4 Expose over HTTPS (Cloudflare Tunnel)
The MCP Connector API requires a public HTTPS endpoint. Cloudflare Tunnel is the simplest path because it needs no open inbound ports.
# One-time
cloudflared tunnel login
cloudflared tunnel create vrm
cloudflared tunnel route dns vrm victron-vrm.example.com
# Run
cloudflared tunnel run --url http://127.0.0.1:3000 vrm
Your server is now reachable at https://victron-vrm.example.com/mcp with a valid cert. Alternatives: nginx/Caddy reverse proxy in front of Let’s Encrypt, Fly.io, or the upcoming Cloudflare Workers adapter (shipped in v0.3.0).
Set ALLOWED_ORIGINS to restrict which origins may call you:
ALLOWED_ORIGINS="https://claude.ai,https://api.anthropic.com" \
HOST=0.0.0.0 \
npx victron-vrm-mcp
3.5 Connect clients
Anthropic Messages API (MCP Connector)
{
"model": "claude-opus-4-7",
"messages": [{"role": "user", "content": "Summarize my sites."}],
"mcp_servers": [{
"type": "url",
"url": "https://victron-vrm.example.com/mcp",
"name": "victron-vrm",
"authorization_token": ""
}],
"tools": [{"type": "mcp_toolset", "mcp_server_name": "victron-vrm"}]
}
The authorization_token is forwarded as Authorization: Bearer <token> on every request.
Claude Code
claude mcp add-json victron-vrm '{
"type":"http",
"url":"https://victron-vrm.example.com/mcp",
"headers":{"Authorization":"Bearer YOUR_VRM_TOKEN"}
}'
Cursor / Windsurf (HTTP transport)
{
"mcpServers": {
"victron-vrm": {
"type": "http",
"url": "https://victron-vrm.example.com/mcp",
"headers": {
"Authorization": "Bearer YOUR_VRM_TOKEN"
}
}
}
}
MCP Inspector (testing)
# Terminal 1 — start the server
npx victron-vrm-mcp
# Terminal 2 — launch the Inspector
npx -y @modelcontextprotocol/inspector
In the Inspector UI:
- Transport: Streamable HTTP
- URL:
http://127.0.0.1:3000/mcp - Headers:
Authorization: Bearer <your-vrm-token>x-vrm-auth-scheme: Token(orBearerfor/auth/loginAsDemotokens)
3.6 Environment variables
| Variable | Default | Description |
|---|---|---|
PORT |
3000 |
HTTP port |
HOST |
127.0.0.1 |
Bind address (use 0.0.0.0 behind a reverse proxy) |
MCP_PATH |
/mcp |
Endpoint path |
VRM_AUTH_SCHEME |
Token |
Default forwarding scheme. Bearer deprecated 2026-06-01 |
VRM_ALLOWED_SITES |
(unset) | Comma-separated idSite allowlist. If unset, any site the token can access is allowed |
ALLOWED_ORIGINS |
(unset) | Comma-separated allowed Origin values. If unset, only same-origin + loopback accepted |
3.7 Per-request headers
| Header | Purpose |
|---|---|
Authorization: Bearer <vrm-token> |
Required. The VRM token to forward. |
x-vrm-auth-scheme: Token|Bearer |
Overrides VRM_AUTH_SCHEME. Use Bearer for demo tokens. |
x-vrm-skip-confirms: 1 |
Bypasses the confirm: true gate on destructive tools. Automation only — logged as a warning. |
mcp-protocol-version |
Must be 2025-06-18 or 2025-03-26. |
3.8 Destructive tools & confirm gate
Every tool that writes, deletes, or changes access is confirm-gated server-side. A first call without confirm: true in the arguments is refused with an explanation of what would happen. The client must resend with { "confirm": true } to actually execute.
Destructive tool categories:
- Alarms (
vrm_add_alarm,vrm_edit_alarm,vrm_delete_alarm,vrm_clear_alarm) - Tags (
vrm_tags_add,vrm_tags_remove,vrm_set_favorite) - Forecasts (
vrm_reset_forecasts) - Site settings (
vrm_set_site_settings) - Dynamic ESS (
vrm_set_dynamic_ess_settings— writes schedules, b2g, price schedule, battery kWh) - Access management (
vrm_invite_user,vrm_unlink_user,vrm_set_user_rights, …) - Access tokens (
vrm_create_access_token,vrm_delete_access_token— the latter can self-lockout with"*") - Custom widgets (create/patch/delete)
For scripted automation you can opt out system-wide by sending x-vrm-skip-confirms: 1. Do not do this in interactive sessions.
3.9 Security model at a glance
- Origin header validation (DNS-rebinding defense)
- Strict
Accept+Content-Typeon POST - Bearer token never stored, never logged (logger strips
token/authorizationkeys) - Zod schema validation on every tool argument
- Base-URL host pin — client refuses anything other than
vrmapi.victronenergy.comover HTTPS - Optional
VRM_ALLOWED_SITESallowlist - Error body redaction — VRM errors reduced to
error_code: errors-text, truncated
Full details in SECURITY.md.
3.10 Troubleshooting
| Symptom | Likely cause & fix |
|---|---|
401 |
Token missing, malformed, or expired. Check Authorization header. |
403 |
Token valid but site not accessible (or blocked by VRM_ALLOWED_SITES). |
400 Origin not allowed |
Add the client origin to ALLOWED_ORIGINS. |
400 Unsupported protocol version |
Client sent an old mcp-protocol-version. Update the client. |
| Tool refused with “confirmation required” | Expected behavior on destructive tools. Resend with { "confirm": true }. |
429 with Retry-After |
VRM rate limit (200/window, 3 req/s). Back off. |
| Works locally but not via Cloudflare Tunnel | Check that cloudflared points to 127.0.0.1:3000 and the public hostname resolves. |
Server logs are one-line JSON to stderr.
4. Using both together
Running both in the same MCP client is the common case: victron-tcp for live state and raw registers, victron-vrm-mcp for history, alarms you can change, and remote sites. When you prompt, name the server or the AI will pick based on context:
“Using victron-tcp, show me current AC loads per phase.”
“Using victron-vrm, list all my sites and any active alarms.”
5. Example prompts
Daily operations (victron-tcp):
- “Run the
daily-reportprompt.” - “What’s my SOC and PV power right now?”
- “Is my Quattro in Passthru? If so, why?”
- “Explain why today’s yield is lower than yesterday.”
Planning & optimization (victron-tcp):
- “Run
energy-optimizerwith goal = cost savings.” - “Check
storm-prep— forecast is bad tomorrow.” - “Based on current loads, will my battery make it through the night?”
Commissioning & diagnostics (victron-tcp):
- “Run
commissioningfor a new install.” - “Scan for devices and map the system topology.”
- “Read registers 840–855 and explain them.”
Remote & multi-site (victron-vrm-mcp):
- “List all my VRM sites and show any active alarms.”
- “Get battery stats for site 12345 over the last 7 days.”
- “Review my Dynamic ESS schedule and compare it to OTE spot prices.”
- “Add an alarm on site 12345 when battery SOC drops below 20%.”
6. Links
victron-tcp: GitHub - lubosstrejcek/victron-tcp: MCP server for Victron GX devices — real-time solar, battery, grid, and inverter data over Modbus TCP + MQTT. Local LAN, no cloud. · GitHub · npmvictron-vrm-mcp: GitHub - lubosstrejcek/victron-vrm-mcp: MCP server for Victron VRM cloud API — remote access to solar, battery, and ESS data. Streamable HTTP, MCP Connector compatible. · GitHub · npm- VRM API docs: https://vrm-api-docs.victronenergy.com
- CCGX Modbus TCP register list (Rev 3.71): https://www.victronenergy.com/support-and-downloads/technical-information
- MCP spec: https://modelcontextprotocol.io
- Anthropic MCP Connector API: MCP connector - Claude API Docs
Questions, bug reports, and feature requests → GitHub issues on the respective repo. Replies in this thread welcome for setup help and example use cases.