Skip to main content
Glama

mcp-fsolar

Live Felicity Solar battery data for Claude, REST APIs, Node.js apps, and event-driven pipelines — all from one package.

Connects to the Felicity Solar cloud and exposes per-cell voltages, SOC, SOH, temperatures, BMS counters, balancing state, and computed health metrics.

Usage modes

Mode

When to use

MCP server → Claude

Ask Claude natural-language questions about your batteries

Standalone REST API

Query battery data over HTTP from any language or tool

JS / TS library

Embed directly in a Node.js or Next.js app — no separate server

Event-driven

React to battery events (alerts, periodic snapshots) via webhooks or EventEmitter


Related MCP server: EG4 MCP Server

MCP server → Claude

The same fsolar-mcp process serves both MCP and REST from one port.

Claude Code (CLI)

Start the server manually, then register it:

npm install -g fsolar-mcp
FELICITY_USER=you@example.com FELICITY_PASS=yourpass fsolar-mcp
claude mcp add felicity --transport sse http://localhost:3010/sse

Or let Claude Code auto-launch it on demand — no separate terminal needed. Run once to register:

claude mcp add felicity \
  -e FELICITY_USER=you@example.com \
  -e FELICITY_PASS=yourpass \
  -- npx fsolar-mcp

Credentials tip: the -e KEY=val flags appear in shell history. To avoid that, store credentials in .env and use the JSON config approach (Claude Desktop / Cursor sections below) — credentials stay in the config file, not the command line.

Claude Code launches a fresh fsolar-mcp process for each session (via stdio) and kills it when done. Each process starts its own poller, so get_balance_trend needs ~10 min of uptime before trend data is available.

Ask Claude things like "what's the battery SOC?", "is any cell imbalanced?", or "show me the cell voltages for Bat2".

Claude Desktop

Open Claude Desktop → Settings → Developer → Edit Config and add:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json

  • Windows: %APPDATA%\Claude\claude_desktop_config.json

{
  "mcpServers": {
    "felicity": {
      "command": "npx",
      "args": ["fsolar-mcp"],
      "env": {
        "FELICITY_USER": "your@example.com",
        "FELICITY_PASS": "yourpassword"
      }
    }
  }
}

Restart Claude Desktop — a hammer icon appears in the toolbar when the server is connected.

Cursor

Add to .cursor/mcp.json in your project root (or the global ~/.cursor/mcp.json):

{
  "mcpServers": {
    "felicity": {
      "command": "npx",
      "args": ["fsolar-mcp"],
      "env": {
        "FELICITY_USER": "your@example.com",
        "FELICITY_PASS": "yourpassword"
      }
    }
  }
}

Any MCP client (SSE transport)

Start the server, then point your client at the SSE endpoint:

http://localhost:3010/sse

The MCP message endpoint is http://localhost:3010/messages?sessionId=<id> (handled automatically by the SDK).

Auth note: when FELICITY_API_KEY is set, /sse requires the same auth header as every other endpoint. Add "X-API-Key": "your-key" to your MCP client's SSE headers config.

MCP tools

Tool

Description

get_all_batteries

Live status of all batteries — SOC, power, voltage, temperature, charging state

get_battery

Detailed status of one battery by alias (Bat1) or serial number

get_cell_voltages

Individual cell voltages (mV) — useful for detecting cell imbalance

get_fleet_summary

Compact health summary: total energy, worst cell delta, temperatures

get_balance_trend

Cell delta trend over the last ~60 min (improving / stable / degrading)

get_snapshots

Raw snapshots for the last ~60 min (one per ~10 min)

get_health

Per-battery health: cell delta status, temperature, SOH, outlier cells, C-rate

get_autonomy

Fleet autonomy: hours until pack hits minSoc, hours to full, SOC-at-sunrise projection

get_alerts

Active alert list ranked by severity (CRIT/WARN/INFO)

get_energy_history

Daily charge/discharge kWh totals up to 90 days — peak power and net balance

get_cell_stats

Per-cell voltage statistics: mean, stddev, min/max, deviation from pack average, trend

get_module_health

Per-module voltage aggregates (min/max/mean/delta) with outlier-cell flags

get_limit_headroom

Headroom to BMS protection limits — voltage and current headroom per battery

get_lifetime_stats

Cycle count, full-charge events, under-voltage events, remaining LFP cycle life

get_capacity_estimate

Estimated real capacity vs rated capacity per battery

get_power_stats

Peak/average charge and discharge kW, C-rate, fraction above 0.5C

get_cost_savings

Estimated monetary savings from discharged energy × tariff


Standalone REST API

The same fsolar-mcp process exposes a plain HTTP API on the same port. No MCP client needed — any language or tool that can make HTTP requests works.

npm install -g fsolar-mcp
FELICITY_USER=you@example.com FELICITY_PASS=yourpass fsolar-mcp

Endpoint reference

Method

Path

Description

GET

/batteries

All batteries — SOC, power, voltage, temperature, charging state

GET

/batteries/:id

One battery by alias (Bat1) or serial number

GET

/batteries/:id/cell-stats

Per-cell voltage statistics from intraday snapshots

GET

/batteries/:id/module-health

Per-module voltage aggregates with outlier flags

GET

/alerts

Active alert list ranked by severity (?id= to filter one battery)

GET

/energy

Daily charge/discharge kWh history up to 90 days

GET

/limit-headroom

Voltage/current headroom to BMS limits (?id= to filter)

GET

/lifetime-stats

Cycle count, full-charges, under-voltage events (?id= to filter)

GET

/capacity

Estimated vs rated capacity with degradation % (?id= to filter)

GET

/power-stats

Peak/average power and C-rate statistics from snapshots

GET

/cost-savings

Energy savings estimate (?tariff= override, else FELICITY_TARIFF_KWH)

GET

/events

Real-time SSE stream — snapshot events every 30 s; state on connect

GET

/snapshots/intraday

Download intraday snapshot store (JSON file)

GET

/snapshots/daily

Download daily snapshot store (JSON file)

GET

/snapshots/state

Download latest persisted state (JSON file)

DELETE

/snapshots/intraday

Clear intraday snapshot history

DELETE

/snapshots/daily

Clear daily snapshot history

DELETE

/snapshots/all

Clear all snapshot stores

POST

/hooks

Register a webhook URL

GET

/hooks

List registered webhooks

GET

/hooks/:id/deliveries

Delivery log for a hook (last 50, newest first)

DELETE

/hooks/:id

Remove a webhook

GET

/health

Server health — uptime, version, last poll error

GET

/sse

MCP SSE transport endpoint (requires auth when FELICITY_API_KEY is set)

Examples

# all batteries
curl http://localhost:3010/batteries

# one battery
curl http://localhost:3010/batteries/Bat1

# download snapshot history for analysis
curl http://localhost:3010/snapshots/intraday -o intraday.json
curl http://localhost:3010/snapshots/daily    -o daily.json

# register a webhook for all events
curl -X POST http://localhost:3010/hooks \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://your-server.com/webhook"}'

GET /batteries response shape:

{
  "batteries": [
    {
      "alias": "Bat1",
      "soc": 87,
      "soh": 98,
      "chargingState": "charging",
      "power": 1240,
      "voltage": 53.2,
      "current": 23.3,
      "cellDelta": 12,
      "tempMin": 28,
      "tempMax": 31,
      "remainingKwh": 8.7,
      "isBalancing": false,
      "cellVoltages": [3310, 3312, 3308, "…16 cells total"]
    }
  ],
  "fetchedAt": "2025-06-01T12:00:00.000Z",
  "fromCache": true,
  "pollError": null
}

GET /batteries/:id response shape:

{
  "battery": { "…full battery object including modules…" },
  "fetchedAt": "2025-06-01T12:00:00.000Z",
  "fromCache": true
}

Pass X-Last-Fetched-At: <ISO timestamp> to bypass the cache when you already hold fresh data.


JS / TS library

Import the client directly in your app — no separate server or network hop required. The library handles RSA login, token refresh, and caching internally.

import { FelicityClient, MemoryCacheAdapter } from 'fsolar-mcp'

const client = new FelicityClient({
  user: process.env.FELICITY_USER,
  pass: process.env.FELICITY_PASS,
  cache: new MemoryCacheAdapter(),
  ttl: 30,  // cache TTL in seconds
})

const { batteries } = await client.getBatteries()
const { battery }   = await client.getBattery('Bat1')

To also start the background poller (keeps data fresh, enables snapshots and events):

import { FelicityClient, MemoryCacheAdapter, startPoller } from 'fsolar-mcp'

const client = new FelicityClient({ user, pass, cache: new MemoryCacheAdapter(), ttl: 30 })
startPoller(client)   // polls every FELICITY_POLL_MS (default 30 s)

TypeScript types are generated from source — the package ships dist/index.d.ts automatically. No separate @types package needed.


TypeScript

The package is written in TypeScript. All types are exported from the package root:

import type {
  Battery, BatteryModule,
  BatteryHealth, AutonomyResult, AutonomyPerBattery, AutonomyOptions,
  BatterySnapshot, BalanceTrend,
  BatteriesResult, BatteryResult,
  FelicityClientOptions,
  MaterializedState,
  SnapshotPayload,
  CacheAdapter,
  // Analytics
  Alert, EnergyDay, CellStat, PowerStats,
  // Persistent energy store
  DailyEnergy,
} from 'fsolar-mcp'

import { ChargingState, HealthStatus, TrendDirection, HookEvent,
         AlertSeverity } from 'fsolar-mcp'
import { DailyEnergyStore, dailyEnergyStore } from 'fsolar-mcp'

Battery

The core data object — one per physical battery pack. Returned inside BatteriesResult and BatteryResult.

interface Battery {
  // Identity
  sn:    string               // serial number
  alias: string               // human name (Bat1, Bat2, …)
  model: string               // model string from BMS
  status: "NM" | "AL" | "FL" | "OF"  // Normal / Alarm / Fault / Offline

  // State of charge / health
  soc: number                 // % state of charge
  soh: number                 // % state of health

  // Electrical
  voltage: number             // pack voltage (V)
  current: number             // pack current (A)
  power:   number             // pack power (W) — positive = charging, negative = discharging
  chargingState: ChargingState  // "charging" | "discharging" | "standby"

  // Energy
  remainingKwh:   number      // estimated remaining energy (kWh)
  capacityAh:     number      // rated capacity (Ah)
  ratedEnergyKwh: number | null  // rated energy (kWh) from BMS — null if not reported

  // Cell voltages (16 cells, mV)
  cellVoltages:   number[]
  cellVoltageMin: number | null
  cellVoltageMax: number | null
  cellDelta:      number | null  // spread max−min (mV) — primary imbalance indicator
  minCellNum:     number | null  // 1-based index of weakest cell
  maxCellNum:     number | null  // 1-based index of strongest cell

  // Temperature (°C) — 4 physical sensors; 3276.7 °C sentinel filtered out
  cellTemps: number[]
  tempMin:   number
  tempMax:   number

  // Module breakdown (4 modules × 4 cells)
  modules: BatteryModule[]

  // BMS protection limits
  chargeVoltLimit:    number | null  // max charge voltage (V)
  dischargeVoltLimit: number | null  // min discharge voltage (V)
  chargeCurrLimit:    number | null  // max charge current (A)
  dischargeCurrLimit: number | null  // max discharge current (A)

  // BMS lifecycle counters
  batCycleIndex:        number | null  // total charge cycles
  batFullCount:         number | null  // times reached full charge
  batUnderVoltageCount: number | null  // under-voltage events
  warningCount:         number

  // Metadata
  isBalancing:   boolean        // bit 6 of bmsState — BMS actively balancing cells
  bmsState:      number | null  // raw BMS state register
  dataTime:      string | null  // ISO timestamp of last Felicity API report
  reportFreqSec: number | null  // reporting interval (s)
  wifiSignal:    number         // dBm
}

BatteryModule

One of the 4 modules inside a pack (each has 4 cells).

interface BatteryModule {
  index: number    // 1–4
  cells: number[]  // 4 cell voltages (mV)
  temp:  number | null  // physical sensor for this module (°C)
  min:   number    // lowest cell in this module (mV)
  max:   number    // highest cell in this module (mV)
  delta: number    // max − min spread within this module (mV)
}

BatteriesResult

Returned by client.getBatteries().

interface BatteriesResult {
  batteries: Battery[]
  fetchedAt: string                      // ISO timestamp of the fetch
  fromCache: boolean
  trend:     Record<string, BalanceTrend>  // keyed by serial number
}

BatteryResult

Returned by client.getBattery(id).

interface BatteryResult {
  battery:   Battery | null  // null when id not found
  fetchedAt: string
  fromCache: boolean
}

BatteryHealth

Returned per battery by computeHealth(batteries, snapshots). Keyed by serial number.

interface BatteryHealth {
  alias:           string
  cellDeltaStatus: "ok" | "warn" | "crit" | null  // null when cellDelta unavailable
  cellDelta:       number | null  // live spread (mV)
  dischargeDelta:  number | null  // median spread during discharge snapshots (mV) — more reliable than live
  tempStatus:      "ok" | "warn" | "crit" | null
  tempMax:         number | null
  sohStatus:       "ok" | "warn" | null  // SOH never reaches "crit"
  soh:             number | null
  outliers:        number[]    // 1-based cell indices persistently below pack average
  avgCRate:        number | null  // average C-rate over last ~6 snapshots
}

AutonomyResult

Returned by computeAutonomy(batteries, snapshots, opts).

interface AutonomyResult {
  totalRemainingKwh:     number       // sum of remainingKwh across all batteries
  totalCapacityKwh:      number       // sum of rated (or back-calculated) capacity
  dischargeRateKw:       number       // fleet rate used for all estimates
  estimatedHours:        number       // hours until fleet hits minSocPct
  estimatedHoursToFull:  number | null  // hours until fully charged; null if not charging
  estimatedSocAtSunrise: number | null  // % SOC at next sunrise; null if sunriseAt not given
  hoursToSunrise:        number | null
  estimatedDischargeKwh: number | null  // kWh discharged between now and sunrise
  estimatedRemainingKwh: number | null  // kWh remaining at sunrise
  perBattery:            AutonomyPerBattery[]
}

interface AutonomyPerBattery {
  sn:                   string
  alias:                string
  remainingKwh:         number
  estimatedHours:       number
  estimatedHoursToFull: number | null
}

interface AutonomyOptions {
  sunriseAt?:          string | Date | null  // ISO string or Date — enables sunrise fields
  packCapacityKwh?:    number | null         // explicit override; otherwise derived from BMS
  minSocPct?:          number                // reserve floor (default 5)
  defaultDischargeKw?: number                // fallback when no history (default 1.5)
}

BalanceTrend

Returned by snapshotStore.getTrend(sn) and snapshotStore.getAllTrends(batteries).

interface BalanceTrend {
  direction:              "improving" | "stable" | "degrading"
  deltaChange:            number    // mV change newest − oldest (negative = improving)
  history:                number[]  // cellDelta values oldest → newest
  balancingCount:         number    // snapshots where isBalancing = true
  snapshotCount:          number
  currentBalancingStreak: number    // consecutive trailing snapshots with balancing on
}

BatterySnapshot

One entry in the snapshot store — written every FELICITY_SNAPSHOT_MS (default 10 min).

interface BatterySnapshot {
  ts:        string  // ISO timestamp
  batteries: Array<{
    sn:          string
    alias:       string
    soc:         number
    soh:         number
    power:       number
    cellDelta:   number | null
    cellMin:     number | null
    cellMax:     number | null
    maxCellNum:  number | null
    minCellNum:  number | null
    voltages:    number[]
    temps:       number[]
    tempMax:     number
    tempMin:     number
    isBalancing: boolean
    warningCount:   number
    batCycleIndex:  number | null
  }>
}

MaterializedState

Returned by readState() — pre-computed state written by the poller on every tick. Zero-latency read; no recomputation needed.

interface MaterializedState {
  updatedAt: string                        // ISO timestamp of last poller tick
  batteries: Battery[]
  health:    Record<string, BatteryHealth>
  trend:     Record<string, BalanceTrend>
  autonomy:  AutonomyResult
}

SnapshotPayload

Emitted by snapshotEmitter and delivered to snapshot webhook subscribers.

interface SnapshotPayload {
  batteries: Battery[]
  health:    Record<string, BatteryHealth>
  ts:        string  // ISO emission timestamp
}

Alert

Returned as an array by computeAlerts(batteries, health) and by GET /alerts.

interface Alert {
  severity: "crit" | "warn" | "info"
  battery:  string           // alias
  code:     string           // machine-readable key e.g. "cell_delta_crit"
  message:  string           // human-readable description
}

const AlertSeverity = { CRIT: "crit", WARN: "warn", INFO: "info" } as const

EnergyDay

One day's energy totals — returned by computeEnergyHistory(snapshots) and GET /energy.

interface EnergyDay {
  date:            string  // "YYYY-MM-DD"
  kwhCharged:      number
  kwhDischarged:   number
  kwhNet:          number  // charged − discharged (positive = net import)
  peakChargeKw:    number
  peakDischargeKw: number
  snapshotCount:   number
}

DailyEnergyStore

Persistent 90-day energy accumulator — singleton exported as dailyEnergyStore.

class DailyEnergyStore {
  update(entries: DailyEnergy[]): void  // merge new entries; old days are never overwritten
  get(): DailyEnergy[]                  // sorted by date ascending
}

CellStat / PowerStats

Returned by computeCellStats(snapshots, sn) and computePowerStats(snapshots, batteries).

interface CellStat {
  cell:          number   // 1-based index
  module:        number   // 1-based module number
  mean:          number   // average voltage (mV)
  stddev:        number   // standard deviation (mV)
  min:           number
  max:           number
  meanDeviation: number   // mean − pack average (mV)
  trend:         "improving" | "stable" | "degrading"
}

interface PowerStats {
  totalSamples:      number
  chargeSamples:     number
  dischargeSamples:  number
  peakChargeKw:      number
  avgChargeKw:       number
  peakDischargeKw:   number
  avgDischargeKw:    number
  avgCRate:          number | null   // null when no ratedEnergyKwh available
  pctAboveHalfC:     number | null
}

Enums

All discriminant strings are exported as frozen const objects — use them instead of bare strings for autocomplete and compile-time safety.

import { ChargingState, HealthStatus, TrendDirection, HookEvent } from 'fsolar-mcp'

// ChargingState
ChargingState.CHARGING    // "charging"
ChargingState.DISCHARGING // "discharging"
ChargingState.STANDBY     // "standby"

// HealthStatus
HealthStatus.OK    // "ok"
HealthStatus.WARN  // "warn"
HealthStatus.CRIT  // "crit"

// TrendDirection
TrendDirection.IMPROVING  // "improving"
TrendDirection.STABLE     // "stable"
TrendDirection.DEGRADING  // "degrading"

// HookEvent
HookEvent.CELL_DELTA_CRIT     // "cell_delta_crit"
HookEvent.CELL_DELTA_WARN     // "cell_delta_warn"
HookEvent.TEMP_CRIT           // "temp_crit"
HookEvent.TEMP_WARN           // "temp_warn"
HookEvent.SOH_WARN            // "soh_warn"
HookEvent.LOW_SOC             // "low_soc"
HookEvent.FULL                // "full"
HookEvent.ONLINE              // "online"
HookEvent.OFFLINE             // "offline"
HookEvent.OUTLIER             // "outlier"
HookEvent.BMS_WARNINGS        // "bms_warnings"
HookEvent.UNDERVOLTAGE_EVENTS // "undervoltage_events"
HookEvent.STALE_DATA          // "stale_data"
HookEvent.ALERT               // "alert"  — fleet catch-all
HookEvent.SNAPSHOT            // "snapshot"

Types match their string values — battery.chargingState === ChargingState.CHARGING compiles and narrows correctly.


Event-driven: webhooks & emitter

React to battery state changes without polling. Two delivery mechanisms — use one or both.

HTTP webhooks

Register a URL to receive POST requests when events fire:

POST /hooks         body: { url, events? }   # register
GET  /hooks         # list registered hooks
DELETE /hooks/:id   # remove a hook
# receive all events
curl -X POST http://localhost:3010/hooks \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://your-server.com/webhook"}'

# receive only critical cell alerts and periodic snapshots
curl -X POST http://localhost:3010/hooks \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://your-server.com/webhook", "events": ["cell_delta_crit", "snapshot"]}'

Real-time SSE stream

Connect any browser or HTTP client to GET /events for a live push feed:

const es = new EventSource('http://localhost:3010/events', {
  headers: { 'X-API-Key': 'your-key' }
})
es.addEventListener('state',    (e) => console.log('initial:', JSON.parse(e.data)))
es.addEventListener('snapshot', (e) => console.log('tick:',    JSON.parse(e.data)))
  • event: state — sent once on connect with the last persisted MaterializedState

  • event: snapshot — sent every poll tick (~30 s) with { batteries, health, ts }

  • Reconnects automatically using retry: 5000 in the stream header

EventEmitter (same-process)

Subscribe directly in Node.js without an HTTP round-trip:

import { startPoller, snapshotEmitter } from 'fsolar-mcp'

snapshotEmitter.on('snapshot', ({ batteries, health, ts }) => {
  // fires every poll tick (~30 s by default)
  console.log(batteries[0].soc, health)
})

startPoller(client)

The startPoller onTick callback is useful for integrating poll errors into a larger application:

startPoller(client, {
  onTick: (err, batteries) => {
    if (err) myMonitoring.recordError(err)
    else     myMonitoring.recordSuccess(batteries!.length)
  },
})

Hook events

Event

Trigger

Payload extras

Cooldown

cell_delta_crit

Cell delta ≥ 200 mV

sn, alias, value, threshold

1 h

cell_delta_warn

Cell delta ≥ 120 mV

sn, alias, value, threshold

4 h

temp_crit

tempMax ≥ 50 °C

sn, alias, value, threshold

1 h

temp_warn

tempMax ≥ 40 °C

sn, alias, value, threshold

4 h

soh_warn

SOH < 90 %

sn, alias, value, threshold

24 h

low_soc

SOC ≤ FELICITY_LOW_SOC_PCT % (default 20)

sn, alias, value, threshold

2 h

full

SOC = 100 % + standby

sn, alias

8 h

online

Battery appears after absence

sn, alias

1 h

offline

Battery disappears

sn, alias

1 h

outlier

Persistent outlier cells detected

sn, alias

24 h

bms_warnings

BMS active warning count > 0

sn, alias

4 h

undervoltage_events

Cumulative under-voltage counter > 0

sn, alias

24 h

stale_data

Last BMS report > 30 min old

sn, alias

2 h

alert

Fleet catch-all — any alert active or new alerts appeared

alerts[], newAlerts[], count, newCount

1 h (bypassed for new alerts)

snapshot

Time-based (every FELICITY_TELEMETRY_MS, default 5 min)

full SnapshotPayload

none

Thresholds match computeHealth constants (HEALTH_CELL_DELTA_CRIT, HEALTH_TEMP_WARN, etc. — see docs/ALGORITHMS.md).

The alert event is diff-based: it fires immediately when a new alert code appears (bypassing the 1 h cooldown), and then on the cooldown schedule while the same alerts persist. The newAlerts[] field contains only the newly-appeared alerts since the last tick; alerts[] is the full current list.


Configuration

Variable

Required

Default

Description

FELICITY_USER

Yes

Felicity Solar account email

FELICITY_PASS

Yes

Felicity Solar account password

FELICITY_PORT

No

3010

HTTP server port

FELICITY_API_KEY

No

If set, all REST + MCP SSE requests must supply Authorization: Bearer <key> or X-API-Key: <key>

FELICITY_CORS_ORIGIN

No

localhost origins only

Allowed CORS origin. Set to * to open fully, or an explicit origin to lock down

FELICITY_RATE_LIMIT

No

60

Max REST requests per minute per IP. Set to 0 to disable

FELICITY_POLL_MS

No

30000

Felicity API poll interval (ms)

FELICITY_TOKEN_TTL_H

No

6

Felicity auth token lifetime in hours before proactive refresh

FELICITY_LOW_SOC_PCT

No

20

SOC % threshold that triggers the low_soc webhook event

FELICITY_TARIFF_KWH

No

Electricity tariff in currency/kWh used by the get_cost_savings MCP tool

FELICITY_TELEMETRY_MS

No

300000

Snapshot emitter / webhook interval (ms)

FELICITY_SNAPSHOT_ENABLED

No

true

Enable background snapshot store

FELICITY_SNAPSHOT_MS

No

600000

Snapshot store interval (ms, min 60 000)

FELICITY_SNAPSHOT_DAYS

No

3

Intra-day snapshot retention (days)

FELICITY_DAILY_DAYS

No

90

Daily snapshot retention (days)

SNAPSHOT_DIR

No

os.tmpdir()

Directory for all persisted JSON files: battery-snapshots.json, battery-daily.json, battery-state.json, battery-energy.json, battery-hooks.json, battery-hook-cooldowns.json, battery-hook-retries.json


What you can build

Real-time fleet view — per-cell voltages, SOC, power flow, SOH, balancing state and temperature for every battery in the pack.

Fleet view — real-time battery status

Cell-level inspection — voltage, deviation from pack average, module spread, LiFePO4 charge %, weakest/strongest cell.

Cell inspection tooltip — weakest/strongest cell detail

Historical trends — cell-delta and temperature over the last 24 h, per-cell deviation heatmap, daily SOH trend, lifetime cycle-count with projected remaining battery life.

Battery history — cell delta, temperature and deviation heatmap

Discharging view — live power flow and SOC during active discharge across the fleet.

Fleet discharging — live power flow and SOC


Algorithms & metrics

Formulas, thresholds, hook event conditions, and snapshot behaviour: docs/ALGORITHMS.md.

How it works

The Felicity cloud API requires passwords to be RSA-encrypted (public key extracted from the Android APK). The client handles login, token refresh, and caching automatically. A background poller keeps data fresh so MCP tool calls and REST requests are instant.

Setup from source

git clone https://github.com/RicardoSantos/mcp-fsolar
cd mcp-fsolar
npm install
npm run build          # compile TypeScript → dist/
cp .env.example .env   # fill in your credentials
node dist/server.js
node probe.js          # dump raw API responses for every device

License

MIT

Available Tools

17 tools
get_alertsA

Active alert list ranked by severity. Checks cell imbalance, temperature, SOH, outlier cells, BMS warnings, under-voltage events, and data staleness.

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoAlias (Bat1/Bat2/Bat3) or serial number; omit for all batteries

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the burden of explaining behavior. It does specify what checks are performed and that the list is ranked, but it lacks details about return structure, staleness criteria, or whether the operation is read-only (though 'get' implies safe). It provides some context but not comprehensive behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, front-loaded with the primary purpose, and efficiently lists the checks. No wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter tool with no output schema, the description covers the main context: what the alert list contains, how it is ordered, and the optional filtering. It could benefit from noting response format or sorting order beyond severity, but overall it is adequate for an AI agent to invoke it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (the 'id' property is fully described). The description does not add extra parameter semantics but also does not need to since the schema already explains the alias/serial and optional omission.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Clear verb and resource: 'get_alerts' with description specifying an 'Active alert list ranked by severity.' It distinguishes itself from sibling tools by enumerating specific alert types (cell imbalance, temperature, SOH, etc.) rather than general health or stats.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description states it returns active alerts ranked by severity, clarifying when to use it (for current alerts). It does not explicitly mention alternatives or exclusions, but the context is clear enough for a straightforward list tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_all_batteriesA

Live status of all Felicity batteries: SOC, power, voltage, temperature, charging state.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry behavioral disclosure on its own. It communicates that the data is 'live' and lists the provided fields, which implies a read-only status query. However, it does not explicitly state whether the operation is side-effect-free or if any caching or rate limits apply.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that front-loads the core purpose and lists the key data fields. Every word contributes useful information with no redundancy or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given there is no output schema, the description provides a helpful field list (SOC, power, voltage, temperature, charging state) that conveys the return content. It is slightly incomplete because it does not explicitly state that the result is an array of battery statuses, but the phrase 'all batteries' strongly implies that structure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so there is nothing to clarify. The description's mention of the returned metrics fully covers the only meaningful semantic content, matching the baseline of 4 for parameterless tools.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it returns the live status of all Felicity batteries, listing specific metrics (SOC, power, voltage, temperature, charging state). This distinguishes it from siblings like get_battery (single battery) and get_health (health-focused) by scope and content.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no explicit guidance on when to use this tool versus alternatives. While 'all batteries' implies a fleet-wide view, it does not mention specific use cases or contrast with sibling tools such as get_battery or get_fleet_summary.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_autonomyA

Fleet autonomy estimate: hours until the pack hits minSoc, hours to full charge, and optional SOC projection at a given sunrise time.

ParametersJSON Schema
NameRequiredDescriptionDefault
minSocPctNoDischarge stop threshold in %. Default 5.
sunriseAtNoISO timestamp of next sunrise — enables SOC-at-sunrise projection
packCapacityKwhNoKnown total fleet capacity in kWh (improves accuracy when SOC is not 100%)

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses the core behaviors (estimating time to discharge/charge, optional projection) but does not explicitly state that the tool is read-only or safe to call, nor does it mention edge cases like missing parameters. The low risk of a 'get' tool mitigates this, but the description could be more explicit.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that conveys all essential information without extraneous words. It is concise and well-structured, making it easy for an agent to quickly grasp the tool's purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description is largely complete for a read-only estimation tool with three optional parameters. It explains the main outputs and the optional projection feature, but it does not mention what happens when optional parameters are omitted or any limitations. Given the simplicity of the tool, this is a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds semantic context by linking minSocPct to 'minSoc' and sunriseAt to the 'SOC projection', but it does not provide additional syntax or format details beyond what the schema already states. No extra value beyond the existing parameter descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool provides a fleet autonomy estimate, specifically hours until minSoc and hours to full charge, plus an optional SOC projection. This is a specific verb+resource pairing that distinguishes it from sibling tools like get_health or get_energy_history.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for fleet autonomy assessment, but it does not explicitly state when to use this tool over alternatives, nor does it mention any exclusions. Since sibling tools cover different aspects (health, alerts, energy), the implicit context is sufficient but lacks explicit guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_balance_trendA

Balance trend for batteries over the last ~60 min. Shows whether cell delta (mV spread) is improving, stable, or degrading.

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoAlias (Bat1/Bat2/Bat3) or serial number; omit for all batteries

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description discloses the time window and the nature of the output (categorical status). It does not reveal whether the tool is purely read-only, but the 'get' prefix implies that. It also doesn't mention any potential side effects or additional behavior like pagination or data freshness, which would be relevant.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description consists of two short sentences, both informative and free of filler. It front-loads the core purpose and then clarifies the output meaning, making it highly efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple with one optional parameter, and the description conveys the purpose and output interpretation. However, without an output schema or more detail on the return format (e.g., whether it's a time series or just a single classification), there is slight ambiguity. Overall it's adequate for a basic monitoring tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already provides full coverage for the single 'id' parameter, including allowed formats and the default for omission. The description adds no extra parameter-related details, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool provides a balance trend for batteries over the last ~60 minutes and defines the output as a categorical assessment (improving, stable, degrading). This distinguishes it from sibling tools like get_cell_stats or get_snapshots by focusing on trend classification rather than raw data.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives context that this is for assessing balance trend over a recent 60-minute window, implying it's useful for monitoring battery health. However, it doesn't explicitly state when to use this instead of alternatives like get_cell_stats or get_energy_history, nor does it mention exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_batteryA

Detailed status of one battery by alias (Bat1/Bat2/Bat3) or serial number.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesAlias (Bat1/Bat2/Bat3) or serial number

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It states the tool returns 'detailed status' but does not disclose what fields are included, whether the operation is read-only (though 'get' implies it), or any error/edge-case behavior. For a simple read tool, this is adequate but lacks depth.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence that is direct and front-loaded with the core purpose. No unnecessary words or repetition. It is optimally concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple with one required parameter and no output schema. The description covers the core purpose and identification method, making it largely usable. However, 'detailed status' is vague about the exact contents of the response, which could be an issue for an agent expecting a specific structure.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema already describes the 'id' parameter with 100% coverage. The description restates the same alias/serial number info without adding new details such as examples, formatting, or constraints. Baseline 3 is appropriate because the schema handles the semantics.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool retrieves detailed status for one battery, specified by alias or serial number. This distinguishes it from sibling tools like get_all_batteries (multiple batteries) and get_cell_stats (specific stats). The verb 'get' and resource 'status' are specific and unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for a single battery's detailed status but does not explicitly contrast with alternatives like get_all_batteries or get_health. There is no when-not-to-use guidance, so the usage context is implied rather than stated.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_capacity_estimateA

Estimates real usable capacity (remainingKwh ÷ SOC%) vs rated capacity per battery. Most accurate at 20–80% SOC.

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoAlias (Bat1/Bat2/Bat3) or serial number; omit for all batteries

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description must carry the full burden, and it does so by disclosing the calculation method and the SOC accuracy limitation. It does not explicitly state that the operation is read-only or describe output format, but the 'get' name and 'estimates' phrasing imply a non-mutating calculation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two tight sentences lead with the tool's purpose, followed by a key accuracy condition. Every word contributes meaning, and there is no redundant information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple tool shape (one optional parameter, no output schema, no annotations), the description provides adequate context about purpose, formula, and accuracy. It doesn't specify the exact return format, but 'per battery' and the formula give a sufficient indication of what to expect.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema fully documents the single optional parameter 'id' with its accepted values (alias or serial number) and default behavior (omit for all batteries). The description adds no additional parameter semantics, so it falls at the baseline for 100% schema coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: estimating real usable capacity via a specific formula (remainingKwh ÷ SOC%) and comparing it to rated capacity per battery. This distinctly sets it apart from sibling tools that measure other battery metrics such as health or autonomy.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear usage context by noting that the estimate is most accurate at 20–80% SOC, implying when the tool should be used for reliable results. It does not explicitly name alternative tools, but it offers a condition that guides appropriate usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_cell_statsA

Per-cell voltage statistics from intraday snapshots: mean, stddev, min/max, deviation from pack average, and trend direction.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesAlias (Bat1/Bat2/Bat3) or serial number

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses the data source (intraday snapshots) and the computed statistics, implying read-only behavior, but does not state side effects, performance characteristics, or any limitations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, information-dense sentence that front-loads the purpose and lists concrete outputs without wasted words. It is appropriately sized and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the simple one-parameter schema and absence of an output schema, the description covers what the tool returns but does not detail the response shape or units. However, it is sufficient for a basic stats tool and adequately complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already describes the parameter 'id' as 'Alias (Bat1/Bat2/Bat3) or serial number' with 100% coverage. The description adds no additional meaning or context for the parameter, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly specifies the resource (per-cell voltage) and the exact outputs (mean, stddev, min/max, deviation from pack average, trend direction). This distinguishes it from sibling tools like get_cell_voltages and get_balance_trend, making the purpose unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No explicit guidance is given on when to use this tool versus alternatives. The phrase 'from intraday snapshots' provides some context, but there is no mention of when not to use it or which sibling tools to prefer for other needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_cell_voltagesA

Individual cell voltages (mV) for one battery. Useful for detecting cell imbalance.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesAlias (Bat1/Bat2/Bat3) or serial number

TDQS

A3.8/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses the unit (mV) and scope (one battery), and the read-only nature is implied by the name 'get'. However, it doesn't mention return format, error conditions, or any side effects. The context is minimal but not misleading.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two concise sentences: the first front-loads the main purpose, and the second adds a use case. No wasted words, and the structure is easy to scan.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter getter with no output schema, this description provides enough context: what data is returned (cell voltages in mV), scope (one battery), and a use case. It lacks detail about the exact return structure (e.g., array with cell indices), but the simplicity of the tool makes this acceptable.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% because the 'id' parameter has a description ('Alias (Bat1/Bat2/Bat3) or serial number'). The description adds no parameter-level information, so the baseline of 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the resource (individual cell voltages in mV) and scope (one battery), and adds a use case for imbalance detection. However, it lacks an explicit verb (e.g., 'returns' or 'lists'), relying on the tool name 'get' to convey the action, and does not explicitly distinguish from sibling tools like get_cell_stats.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear context for when to use this tool: 'Useful for detecting cell imbalance.' It does not mention alternatives or exclusions, but the use case is specific enough to guide an agent. Without naming when not to use it, it falls short of a 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_cost_savingsA

Estimated monetary savings from discharged energy × electricity tariff. Pass tariffKwh or set FELICITY_TARIFF_KWH env var.

ParametersJSON Schema
NameRequiredDescriptionDefault
tariffKwhNoElectricity tariff in currency-per-kWh (e.g. 0.25 for €0.25/kWh)

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description explains the calculation and the tariff input options, but with no annotations it carries the full burden. It does not disclose what happens if neither tariffKwh nor the env var is provided, nor does it mention output format or that it is a read-only calculation. The formula and input dependency are clear, but behavior under missing input is left undetermined.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

One concise sentence front-loaded with the tool's purpose and the input requirement. No wasted words or redundant content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple tool with one optional parameter, the description covers purpose, calculation, and input mechanism. It lacks a note on fallback behavior when no tariff is provided, but overall it is sufficiently complete given the tool's simplicity and the rich sibling tool context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema already fully describes tariffKwh (coverage 100%), so baseline is 3. The description adds value by introducing the FELICITY_TARIFF_KWH environment variable as an alternative source and by clarifying the parameter's role in the savings formula, which goes beyond the schema's basic unit description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: 'Estimated monetary savings from discharged energy × electricity tariff.' It specifies the exact formula and the resource (cost savings), distinguishing it from sibling tools about health, autonomy, and energy history.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Provides clear usage context: 'Pass tariffKwh or set FELICITY_TARIFF_KWH env var.' This explains how to invoke the tool with the necessary input. However, it does not explicitly discuss when to prefer this over alternatives, though none of the sibling tools overlap with cost savings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_energy_historyA

Daily charge/discharge energy totals (kWh) — up to 90 days from persistent store, merged with live intraday data. Shows peak rates and net balance.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.2/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description discloses the data source (persistent store), time range (up to 90 days), and merging behavior with live intraday data. It adds useful context but stops short of explaining the merge mechanics or what 'peak rates' specifically include, leaving some ambiguity.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the core purpose and free of redundancy. Each clause adds value, covering scope, data source, and output fields efficiently.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 0-parameter tool with no output schema, the description covers the essential aspects: content, units, time window, and additional outputs. Minor gaps include the exact nature of the merge and return format, but it is sufficiently complete for correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has no properties, so there are no parameters requiring explanation. The description adds no parameter-level detail, but this is unnecessary given the zero-parameter design; baseline of 4 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly identifies the tool as providing daily charge/discharge energy totals in kWh, with a 90-day range and additional metrics such as peak rates and net balance. This distinguishes it from siblings like get_power_stats or get_lifetime_stats by specifying temporal granularity and data source.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context for use: it returns daily energy totals over up to 90 days, merging historical and intraday data. While it does not explicitly name alternatives or exclusions, the context strongly implies the intended use case for historical daily aggregation.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_fleet_summaryB

Compact health summary: total energy, worst cell imbalance, temperatures.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

There are no annotations, so the description must disclose behavior. It lists included fields but does not explain what the summary aggregates (e.g., fleet-wide vs. per battery), units, or how metrics like 'worst cell imbalance' are calculated. This is minimal behavioral disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that directly states the tool's output type and contents. Every word earns its place, with no redundancy or filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple summary tool with no output schema, the description lists the key fields, which is helpful. However, it lacks context on the scope (fleet vs. individual), units, and output structure, leaving some ambiguity for the agent about exactly what will be returned.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the schema is empty. The description adds no parameter information, but none is required. The baseline for zero-parameter tools is 4, and no deduction is needed.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it provides a 'Compact health summary' and specifies the key fields: total energy, worst cell imbalance, and temperatures. While the verb 'get' is not explicitly restated, the intent is clear, and the content distinguishes it from per-battery tools via the 'fleet' scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is given on when to use this tool versus alternatives such as get_health or get_cell_stats. The description does not mention exclusions, prerequisites, or comparison to sibling tools, leaving the agent to infer applicability from the name alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_healthB

Per-battery health report: cell delta status, temperature status, SOH, weak/outlier cell indices, average C-rate, and discharge-phase delta.

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoAlias (Bat1/Bat2/Bat3) or serial number; omit for all batteries

TDQS

B3.3/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description must carry the behavioral burden. It accurately lists the report contents, which implies a read-only operation, but it does not explicitly state read-only behavior, error conditions, or any side effects. The listing covers the main output but omits edge-case behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, front-loaded sentence that efficiently lists all key report elements without unnecessary words. Every item earns its place, and the structure is highly scannable.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given one optional parameter, no output schema, and no annotations, the description gives a good outline of the report's contents but does not describe the return format or structure (e.g., object fields vs. list). It is sufficient for basic understanding but lacks some details a caller might need.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema provides 100% description coverage for the single optional parameter 'id' (alias/serial number, omit for all batteries). The description adds only the notion of 'per-battery' scope, which is already implied by the schema. Baseline 3 is appropriate since the schema fully documents the parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool provides a 'per-battery health report' and enumerates specific data elements (cell delta status, temperature status, SOH, weak/outlier cell indices, average C-rate, discharge-phase delta). This distinguishes it from broader tools like get_battery and get_fleet_summary, though it could explicitly name sibling alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to use this tool versus sibling tools such as get_cell_stats or get_module_health. It does not mention any exclusions or alternatives, leaving the agent to infer usage from the content list.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_lifetime_statsA

Cycle count, full-charge events, under-voltage events, warning count, and projected remaining LFP cycle life (nominal 4000 cycles).

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoAlias (Bat1/Bat2/Bat3) or serial number; omit for all batteries

TDQS

A3.6/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden. It adds context by noting the projection is for LFP batteries and based on nominal 4000 cycles, which is a useful behavioral detail. It doesn't explicitly state that the operation is read-only or describe any error conditions, but the 'get' prefix implies read-only, and there are no side effects to disclose. Overall it provides some additional behavior context beyond a bare list.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, tightly packed sentence listing exactly the returned metrics with no filler or repetition. Each item (cycle count, events, warning count, projected life) is substantive, and the nominal-4000-cycles qualification adds precision without extra words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the return values comprehensively, serving as a de facto output schema for a tool with no output schema. It also provides the LFP-specific projection context. It doesn't specify numeric units or time periods, but the simple nature and optional parameter make this adequate.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description for the only parameter 'id' is 100% comprehensive, explaining alias/serial and the default behavior when omitted. The tool description adds no additional parameter guidance, so it relies on the schema, which is already strong. Baseline 3 applies since schema coverage is high.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description enumerates the key lifetime metrics (cycle count, events, warning count, projected LFP cycle life), making clear this is a getter for battery lifetime stats. It distinguishes from siblings like get_health and get_autonomy by focusing on lifetime-specific data. However, it lacks an explicit verb such as 'Retrieves' and is just a noun phrase, which slightly reduces clarity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage when lifetime statistics are needed, but it doesn't provide explicit when-to-use or when-not-to-use guidance or reference alternative sibling tools. It doesn't mention that get_health would be more appropriate for health-specific queries, so guidance is only implied by the metric list.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_limit_headroomA

Headroom between current voltage/current and BMS protection limits. Useful for spotting packs running close to cutoff thresholds.

ParametersJSON Schema
NameRequiredDescriptionDefault
idNoAlias (Bat1/Bat2/Bat3) or serial number; omit for all batteries

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description defines the computed metric but doesn't disclose behavioral details such as return format, units, or behavior for multiple batteries. It implies a read operation but lacks explicit safety/permission context. The definition adds some transparency but leaves gaps.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two short sentences: the first defines the tool's output, the second gives a use case. No fluff, front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple getter with one optional parameter and no output schema, the description covers the core purpose and situational context. It could mention specific output details, but it's adequate for an agent to invoke correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema describes the single optional 'id' parameter with 100% coverage ('Alias (Bat1/Bat2/Bat3) or serial number; omit for all batteries'). The description does not add parameter information, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly defines the metric as 'Headroom between current voltage/current and BMS protection limits,' which is specific to this tool and distinguishable from sibling get_* tools (e.g., get_health, get_cell_stats). The noun phrase effectively communicates the tool's function, and the tool name 'get_limit_headroom' reinforces it.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description states it's 'useful for spotting packs running close to cutoff thresholds,' providing a clear use case. It doesn't explicitly exclude alternatives, but the context is sufficient for an agent to select this tool when monitoring limit proximity.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_module_healthB

Per-module voltage aggregates (min/max/mean/delta) for one battery using live cell voltages. Flags modules containing persistent outlier cells.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYesAlias (Bat1/Bat2/Bat3) or serial number

TDQS

B3.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It does explain that the tool computes aggregates from live cell voltages and flags modules with persistent outlier cells, which is useful. However, it does not explicitly state that the operation is read-only, define 'persistent', or describe the return format. Given the absence of annotations, this is a partial disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, compact sentence with two meaningful clauses: one stating the main output and one highlighting the outlier flagging feature. Every word adds value, and the most important information is front-loaded. No redundant or filler content.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's simplicity (one parameter, no output schema, no nested objects), the description is reasonably complete. It conveys the core output and a distinctive feature, though it leaves 'persistent' and 'delta' undefined. For a one-parameter get operation, this is adequate but leaves slight ambiguity about edge cases.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%: the only parameter 'id' is described as 'Alias (Bat1/Bat2/Bat3) or serial number'. The tool description adds no additional parameter semantics beyond what the schema already provides, so the baseline score of 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's function: it produces per-module voltage aggregates (min/max/mean/delta) and flags modules with persistent outlier cells. It specifies the resource ('one battery') and differentiates from siblings like get_cell_stats by focusing on module-level aggregation and outlier flagging. Although it lacks an explicit verb like 'returns' or 'gets', the meaning is unambiguous.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies a use case (checking module health from live cell voltages) but provides no explicit when-to-use or when-not-to-use guidance, nor does it mention alternatives among the many sibling tools. Without such guidance, an agent may not know to choose this over get_cell_stats or get_cell_voltages for related needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_power_statsA

Charge/discharge power statistics from intraday snapshots: peak kW, average kW, C-rate, and fraction of samples above 0.5C.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.6/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It does not state whether the tool is read-only, whether it involves any side effects, or any prerequisites such as recent snapshot availability. It only describes the output metrics and data source, leaving operational behavior unclear.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, well-structured sentence that front-loads the core purpose and lists the specific metrics returned. There is no wasted verbiage, and every phrase adds value.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-parameter, read-only tool with no output schema, the description is reasonably complete. It lists all the computed metrics and the data source. It does not explain the exact time range covered by 'intraday' or how to interpret the values, but given the tool's simplicity, this is a minor gap.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the input schema is empty. According to the rubric, a baseline score of 4 is appropriate when there are no parameters, as the description does not need to compensate for missing schema info. The description provides context about what the returned stats represent, which is sufficient.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states what the tool returns: charge/discharge power statistics including peak kW, average kW, C-rate, and fraction of samples above 0.5C. It also specifies the data source (intraday snapshots), making it distinct from sibling tools like get_cell_stats or get_lifetime_stats.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no explicit guidance on when to use this tool vs alternatives. It mentions 'from intraday snapshots' as a data source, but does not say when this tool is preferable to get_energy_history or get_cell_stats, nor are any exclusions or alternatives named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_snapshotsA

Raw battery pack snapshots for the last ~60 min (one per ~10 min). Includes cell voltages, delta, SOC, and balancing state.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A3.8/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of disclosure. It reveals useful behavioral details: the snapshots are raw, cover the last ~60 minutes, are sampled every ~10 minutes, and include cell voltages, delta, SOC, and balancing state. It does not explicitly state that it is read-only, but the 'raw snapshot' phrasing implies observation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded, and every clause contributes meaning: time window, cadence, and content. No filler or irrelevant details.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple read-only tool with no parameters and no output schema, the description sufficiently explains what is returned, the time range, and the sampling interval. It supports confident tool selection without requiring additional detail.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has zero parameters, so coverage is vacuously 100%. The baseline for zero-parameter tools is 4, and the description does not need to add parameter semantics because none exist.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states that the tool returns raw battery pack snapshots for the last ~60 minutes, with a specific cadence of one per ~10 minutes, and lists the included metrics. This distinguishes it from siblings like get_cell_voltages or get_balance_trend, though it does not explicitly contrast with them.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus the many sibling get_* tools. There are no explicit alternative recommendations or conditions for use, so the agent must infer from the raw snapshot context alone.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

TDQS

A3.7/5.0
Disambiguation4/5

Most tools target distinct scopes: fleet vs battery vs cell-level, raw vs statistics, health vs history. However, get_health, get_module_health, get_cell_stats, get_cell_voltages, and get_balance_trend all touch on cell/module health and could be confusing without careful reading, though descriptions clarify granularity.

Naming Consistency5/5

All tool names follow a consistent get_ + noun pattern, using snake_case for compound names. There are no mixed conventions or vague verbs, making the naming predictable and uniform.

Tool Count3/5

With 17 tools, the server sits in the 16–25 range, feeling somewhat heavy for a monitoring API. While each tool serves a specific data need, the count could be trimmed by merging closely related health/cell tools without losing functionality.

Completeness4/5

The read-only monitoring surface is fairly complete: live status, health, alerts, energy history, capacity estimates, power stats, cost savings, and lifetime data are all covered. Minor gaps include lack of a dedicated temperature history or per-module raw voltage detail, but these are workarounds via snapshots.

Maintenance

ActivitySlowing
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    C
    quality
    D
    maintenance
    An MCP server that enables natural language interaction with the MeteoControl VCOM API v2 for monitoring solar array installations. It allows users to retrieve real-time and historical energy data, system alerts, and detailed asset configuration metrics.
    4
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    MCP server for EG4 solar inverters, enabling real-time monitoring, performance analysis, battery health, alerts, maintenance insights, and historical data via natural language.
    MIT
  • A
    license
    B
    quality
    D
    maintenance
    Read-only MCP server integrating with the Victron VRM API to monitor solar systems, batteries, alarms, and more.
    41
    25
    6
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A powerful MCP server that enables AI assistants like Claude to access, analyze, and optimize solar energy data from FoxESS inverters.
    2
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/RicardoSantos/mcp-fsolar'

If you have feedback or need assistance with the MCP directory API, please join our Discord server