Skip to main content
Glama
AC Infinity MCP

AC Infinity MCP

by ober37

What is this?

ac-infinity-mcp is an MCP server that connects Claude (and other AI assistants) directly to your AC Infinity controllers — so you can read live sensor data, run analytics, and adjust fan speeds or port states using natural language, without opening the AC Infinity app.

Built with FastMCP and the AC Infinity cloud API.

Related MCP server: ambient-mcp

Example prompts

"What's the VPD trend in my tent over the last 7 days?" "Is my environment in the right range for late flower?" "Turn off port 3 on my 69 Pro controller — dry run first." "Show me which ports have been running the most this week." "Apply the veg stage template to port 1 — dry run first so I can see the settings." "My VPD is too high — what should I adjust?"

Compatible hardware

Controller

Reads

Writes

Notes

UIS Controller 69 Pro

✅

✅

Legacy protocol

UIS Controller 69 Pro+

✅

✅

Legacy protocol

UIS Controller 89 AI+

✅

✅ †

New AI+ protocol — port writes require the minversion header (Quirk 14)

† Per-port writes (on/off, speed, mode, temperature/humidity/VPD automation) all work — that is the scope of Quirk 14's minversion finding, which covers addDevMode only. Multi-port Advance Automations are only partly verified on AI+: reads work, creates and in-place edits have both landed on a devType 20 (#326), creates time out on a different AI+ controller (#290), and enable/disable are unexercised on any AI+. Separately, apply_grow_stage_template / break_out_of_automation are held back pending hardware verification (#316) — both refuse with an explanation instead of writing. See AI+ controllers.

Tools

Category

Tool

What it does

🔍 Discovery

discover_devices

List all your controllers and ports

🌡️ Readings

get_device_reading

Live temp, humidity, VPD, and port states for one device

🌡️ Readings

get_all_device_readings

Same, for all devices at once

🌡️ Readings

get_historical_readings

Time-series data with optional bucketing (raw, 15m, 1h, 1d, …)

📊 Analytics

check_vpd_drift

VPD target compliance check for your current grow stage

📊 Analytics

get_environment_health

Composite health score (0–100, A–F) across temp, humidity, VPD

📊 Analytics

detect_environment_trends

Linear trend + 7-day projection per metric

📊 Analytics

get_port_activity_report

Per-port on/off hours, uptime %, and peak activity hour

🔎 Port Status

get_port_status

Live port power level, load detection, active mode, and timer countdown

🔎 Port Status

get_port_settings

Full automation config: mode, VPD target, temp/humidity thresholds, schedule, cycle

🔌 Write

set_port_speed

Set fan or pump speed 1–10 (dry_run=True by default)

🔌 Write

set_port_on

Turn a port fully on (dry_run=True by default)

🔌 Write

set_port_off

Turn a port fully off (dry_run=True by default)

⚙️ Automation

set_vpd_automation

Enable VPD mode with a kPa target using built-in sensors (dry_run=True by default)

⚙️ Automation

set_temperature_automation

Enable AUTO mode with min/max °C thresholds (dry_run=True by default)

⚙️ Automation

set_humidity_automation

Enable AUTO mode with min/max % RH thresholds (dry_run=True by default)

⚙️ Automation

set_port_mode

Switch to any mode: OFF, ON, AUTO, VPD, CYCLE, SCHEDULE, TIMER_TO_ON, TIMER_TO_OFF (dry_run=True by default)

🌱 Intelligence

apply_grow_stage_template

One-click VPD + temp + humidity automation for a named grow stage (dry_run=True by default)

🤖 Advance Automation

list_advance_automations

List all named Advance Automation programs on a device

🤖 Advance Automation

get_advance_automation

Get full detail (schedule, port groups, run state) for one automation

🤖 Advance Automation

enable_advance_automation

Enable a disabled automation — reads state first, no-ops if already enabled (dry_run=True by default)

🤖 Advance Automation

disable_advance_automation

Disable an enabled automation — reads state first, no-ops if already disabled (dry_run=True by default)

🤖 Advance Automation

create_advance_automation

Create a new named automation; the first rule can be off, on, cycle, auto (temp/humidity), or VPD (dry_run=True by default)

🤖 Advance Automation

delete_advance_automation

Delete an automation (disables first if active) (dry_run=True by default)

🤖 Advance Automation

add_automation_rule

Add one rule (window + behavior) to an existing program — off, on, cycle, auto (temp/humidity), or VPD (dry_run=True by default)

🤖 Advance Automation

update_automation_rule

Edit one rule in place — change its window, speed, mode, or sensor targets (dry_run=True by default)

🤖 Advance Automation

delete_automation_rule

Remove a single rule from a program, leaving the rest intact (dry_run=True by default)

🤖 Advance Automation

break_out_of_automation

Safely break a port out of automation control and lock co-governed ports to manual speed (dry_run=True by default)

✦ All write tools (Write, Automation, and Intelligence categories) default to dry_run=True — they return the exact payload they would send without making any changes to your equipment. Pass dry_run=False only when you're ready to execute.

📖 For a complete grower's guide with conversation examples for every tool, see docs/GUIDE.md.

MCP Prompts

Three built-in prompts are registered alongside the tools. In Claude Desktop and other MCP clients, these appear as slash commands or prompt suggestions.

Prompt

What it does

vpd_troubleshooting

Step-by-step guide: diagnose HIGH or LOW VPD and which tools to call to fix it

new_grower_setup

Onboarding walkthrough: discover devices → apply a stage template → check your health score

environment_alert_interpretation

How to read check_vpd_drift status values and get_environment_health grades (A–F, score weighting, what to do)

Quick start

Option 1: pip (simplest)

pip install git+https://github.com/ober37/ac-infinity-mcp.git

Requires Python 3.11+.

python3 -m venv ~/.venvs/ac-infinity-mcp
source ~/.venvs/ac-infinity-mcp/bin/activate
pip install git+https://github.com/ober37/ac-infinity-mcp.git
which ac-infinity-mcp   # copy this path — you'll need it below

Option 3: uvx (no install)

uvx --from git+https://github.com/ober37/ac-infinity-mcp.git ac-infinity-mcp

MCP client configuration

Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):

{
  "mcpServers": {
    "ac-infinity": {
      "command": "/path/to/ac-infinity-mcp",
      "env": {
        "AC_INFINITY_EMAIL": "you@example.com",
        "AC_INFINITY_PASSWORD": "yourpassword"
      }
    }
  }
}

Replace /path/to/ac-infinity-mcp with the path printed by which ac-infinity-mcp.

Cursor / Windsurf

Add to your MCP settings:

{
  "ac-infinity": {
    "command": "/path/to/ac-infinity-mcp",
    "env": {
      "AC_INFINITY_EMAIL": "you@example.com",
      "AC_INFINITY_PASSWORD": "yourpassword"
    }
  }
}

Docker

cp .env.example .env
# fill in AC_INFINITY_EMAIL and AC_INFINITY_PASSWORD
docker compose up --build

The container runs over stdio. Connect via a stdio bridge such as mcp-proxy.

Credentials are injected at runtime via env_file — never baked into the image.

Environment variables

Variable

Required

Description

AC_INFINITY_EMAIL

Yes

Your AC Infinity account email

AC_INFINITY_PASSWORD

Yes

Your AC Infinity account password

⚠️ HTTP-only upstream: The AC Infinity cloud API does not support HTTPS. Credentials and sensor data traverse the network in plain text. This is an upstream limitation — see docs/API.md Quirk 8. Avoid running this server on untrusted networks.

Development

git clone https://github.com/ober37/ac-infinity-mcp.git
cd ac-infinity-mcp
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env   # fill in credentials

# Lint + type-check
ruff check src/ tests/
mypy src/ac_infinity_mcp/

# Tests (unit + integration)
pytest tests/ -v

# Security audit
pip-audit

Security

Vulnerabilities should be reported via GitHub Private Vulnerability Reporting. See SECURITY.md for the full disclosure policy, scope, and known limitations (including the HTTP-only upstream API). Accepted dependency CVEs are tracked in docs/SECURITY-RISKS.md.

License

MIT

Available Tools

25 tools
apply_grow_stage_templateA

Apply a grow-stage automation template (VPD + temperature + humidity) in one call.

Issues a single atomic write that puts the port in VPD mode (atType=8) with the stage's VPD midpoint as the active target, and simultaneously stores the stage's temperature and humidity thresholds on the controller for fallback when the user later switches modes. Defaults to dry_run=True — set dry_run=False to write.

Stage targets (VPD midpoint used as single target):

Stage

VPD (kPa)

Temp (°C)

Humidity (%)

clones

1.00

22–26

70–80

seedling

1.00

22–26

65–75

veg

1.25

20–28

50–70

early_flower

1.40

20–26

40–60

mid_flower

1.60

18–25

35–55

late_flower

1.50

18–24

30–50

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. stage: Growth stage name. One of: clones, seedling, veg, early_flower, mid_flower, late_flower. dry_run: If True (default), returns the payload without writing.

Returns: JSON with action, device_id, port, stage, dry_run, controller_type, sent, per-target summary (vpd/temperature/humidity), and payload (when dry_run=True). On failure returns {"error": "..."}.

Inputs: device_id (required), port (required), stage (required), dry_run. Returns: result

break_out_of_automationA

Break a port out of Advance Automation control and lock co-governed ports.

This is the safe way to manually override a port that is currently under Advance Automation. It:

  1. Checks that the port is actually under automation (idempotent: no-ops if not).

  2. Finds the governing automation.

  3. Identifies all other ports in the same automation as the target port (co-ports). Only those ports are locked — ports in other automations or empty ports are unaffected.

  4. On dry_run=False: a. Disables the automation. b. Locks each co-port to its current manual speed (prevents unexpected speed changes). c. Leaves the target port free for your manual change.

Defaults to dry_run=True. For live execution (dry_run=False), you must supply confirm_automation_name matching the automation name (case-insensitive) as a safety confirmation.

Args: device_id: The AC Infinity device code (from discover_devices). port: The port number you want to break free (1-based). dry_run: If True (default), returns the execution plan without making changes. confirm_automation_name: Required when dry_run=False — the name of the automation to disable, for safety confirmation.

Returns: Dry-run: JSON plan with sequence of steps, co_ports_to_lock, estimated_duration. Live: JSON with co_ports_locked and target_port_freed. Idempotent: {"info": "Port is not currently under automation control."} On failure returns {"error": "..."}.

Inputs: device_id (required), port (required), dry_run, confirm_automation_name. Returns: result

check_vpd_driftA

Check if current VPD is within target range for a growth stage.

Args: device_id: The AC Infinity device code (from discover_devices) stage: Growth stage - one of: clones, seedling, veg, early_flower, mid_flower, late_flower

Returns: JSON example::

    {
      "device_id": "C58ZA",
      "current_vpd": 1.58,
      "target_range": [1.0, 1.5],
      "stage": "veg",
      "status": "HIGH",
      "deviation": 0.08,
      "alert": "VPD 1.58 exceeds target 1.00–1.50. Raise humidity or lower temperature."
    }

``status`` is one of ``"OK"``, ``"LOW"``, or ``"HIGH"``.
``deviation`` is 0 when OK; positive when HIGH (kPa above upper bound);
negative when LOW (kPa below lower bound).
``alert`` is ``null`` when status is ``"OK"``.
On failure returns ``{"error": "...", "detail": "..."}``.

Inputs: device_id (required), stage. Returns: result

create_advance_automationA

Create a new Advance Automation on a device.

Defaults to dry_run=True for safety. Set dry_run=False to send the automation to the device. The port bitmask (grouptDevType) is computed automatically from the port number (Port N → 2^(N-1)).

Args: device_id: The AC Infinity device code (from discover_devices). name: Automation name (max 64 chars, control chars stripped). on_speed: Fan speed when automation is active (1–10). port: 1-based port number the automation should control (1–8). off_speed: Not used — On mode relies on the port's own minimum speed setting. Parameter accepted for compatibility but not sent to the device. begin_time: Schedule start in minutes since midnight (0–1439, or 255=always active). Default: 0 (midnight). Use 255 for "always active" (runs 00:00–23:59 every day). end_time: Schedule end in minutes since midnight (0–1439, or 255=always active). Default: 1439 (23:59). Use 255 for "always active". dry_run: If True (default), previews the automation without sending it. Set to False to create the automation on the device.

Returns: JSON with action, name, port, port_name, on_speed, min_speed (the port's configured minimum speed — used when the automation is inactive), begin_time, end_time, schedule_summary, dry_run, sent. Live responses also include automation_id (for programmatic chaining — do not surface to the user; use name instead). On failure returns {"error": "..."}. When the specified port does not exist on the device, returns {"error": "Port N not found on device X", "available_ports": [{"port": N, "name": "..."}], "suggested_reply": "..."}. Port names absent or empty in the API response fall back to "Port N"; control chars are sanitized.

Inputs: device_id (required), name (required), on_speed (required), port (required), +4 more. Returns: result

delete_advance_automationA

Delete an Advance Automation from a device.

If the automation is currently enabled, it is disabled first before deletion. Defaults to dry_run=True — set dry_run=False to delete.

Args: device_id: The AC Infinity device code (from discover_devices). automation_id: The automation_id from list_advance_automations. dry_run: If True (default), returns the action plan without executing.

Returns: JSON with action, automation_name, automation_id, was_enabled, dry_run, sent. On failure returns {"error": "..."}.

Inputs: device_id (required), automation_id (required), dry_run. Returns: result

disable_advance_automationA

Disable a currently enabled Advance Automation.

Reads current state before toggling — no-ops if already disabled. Defaults to dry_run=True — set dry_run=False to execute.

Live-tested (2026-05-22): disabling sets governed ports to OFF; re-enabling immediately restores ADVANCE mode at automation-defined speeds — no next-trigger wait. Use break_out_of_automation for a controlled handoff that also locks co-governed ports to safe manual speeds.

Args: device_id: The AC Infinity device code (from discover_devices). automation_id: The automation_id from list_advance_automations. dry_run: If True (default), returns the action plan without executing.

Returns: JSON with action, automation_name, automation_id, governed_ports (list of {port, port_name} dicts decoded from the automation's grouptDevType bitmasks), human_summary, dry_run, sent, and to_restore (natural-language hint for re-enabling). On failure returns {"error": "..."}.

Inputs: device_id (required), automation_id (required), dry_run. Returns: result

discover_devicesA

Discover all AC Infinity devices from the cloud API. Returns device IDs, names, and online status. Use this to find device_ids for use in other tools.

Returns: JSON example::

    {
      "devices": [
        {"device_id": "C58ZA", "device_name": "Towlie Tent", "status": "online"},
        {"device_id": "D91XB", "device_name": "Veg Tent",    "status": "online"}
      ]
    }

Empty account returns ``{"devices": [], "message": "No devices found"}``.
On failure returns ``{"error": "...", "detail": "..."}``.

Inputs: no named parameters. Returns: result

enable_advance_automationA

Enable a previously disabled Advance Automation.

Reads current state before toggling — no-ops if already enabled. Defaults to dry_run=True — set dry_run=False to execute.

IMPORTANT: The AC Infinity API uses a toggle endpoint (updateGroupsIsOn). This tool reads the current enabled state first and only calls the API if the automation is currently disabled, ensuring the toggle results in enabled.

Args: device_id: The AC Infinity device code (from discover_devices). automation_id: The automation_id from list_advance_automations. dry_run: If True (default), returns the action plan without executing.

Returns: JSON with action, automation_name, automation_id, dry_run, sent. On failure returns {"error": "..."}.

Inputs: device_id (required), automation_id (required), dry_run. Returns: result

get_advance_automationA

Get full detail for a single Advance Automation by ID.

Args: device_id: The AC Infinity device code (from discover_devices). automation_id: The automation_id from list_advance_automations.

Returns: JSON with automation detail including name, enabled status, schedule (with mode: "continuous" or "scheduled" per Quirk 21; begin_time/end_time as "HH:MM" or null; optional schedule_note when scheduled mode has no time window configured), port_groups (each entry has device_type listing the actual port names governed by that group, resolved from the grouptDevType bitmask — e.g. "Left Fan (Port 5), Right Fan (Port 6)", formatted as "Name (Port N)" for each bit set; "Unknown" when bitmask is 0), governed_ports (list of ports this automation controls, decoded from the automation's port_group bitmasks), port_resolution status ("resolved" or "error"), and human_summary (adapts to continuous/scheduled/no-window variants). On failure returns {"error": "..."}.

Inputs: device_id (required), automation_id (required). Returns: result

get_all_device_readingsA

Get current sensor readings for all AC Infinity devices. Useful for a full status check across all controllers. Returns a list of readings keyed by device_id.

Returns: JSON with "readings" list — one entry per device, same shape as get_device_reading. Devices that fail to parse individually include an "error" key instead of sensor fields. ports[].plug_status is present on not-powered port entries (same loadState == 0 AND speak == 0 condition as get_device_reading, and only on default-named "Port N" ports); omitted otherwise. external_sensors excludes phantom entries (API-reported sensor slots with no physical hardware connected — see API Quirk 20). On auth/API failure returns {"error": "...", "detail": "..."}.

Inputs: no named parameters. Returns: result

get_device_readingA

Get current sensor reading for a device by its AC Infinity device_id. Returns temperature, humidity, VPD, and timestamp.

Args: device_id: The AC Infinity device code (from discover_devices)

Returns: JSON example::

    {
      "device_id": "C58ZA",
      "device_name": "Towlie Tent",
      "temperature": 75.7,
      "unit": "°F",
      "humidity": 58.2,
      "vpd": 1.31,
      "timestamp": "2026-05-20T09:32:00 CDT",
      "ports": [
        {"port": 1, "name": "Inline Fan", "speed": 5},
        {"port": 2, "name": "Port 2", "speed": 0, "plug_status": "not powered"}
      ],
      "external_sensors": []
    }

Temperature and timestamp use the device's own unit preference and timezone
(from ``deviceInfo.unit`` and ``zoneId`` in the API response). Devices
without a configured timezone fall back to UTC.
``external_sensors`` excludes phantom entries (API-reported sensor slots
with no physical hardware connected — see API Quirk 20).
``plug_status`` is only present on a port entry when no current is detected,
the port is not running (speed 0 and no load), **and the port still has its
default name** (``"Port N"``). Custom-named ports are assumed to have a device
intentionally connected — ``loadState=0`` alone cannot distinguish "nothing
plugged in" from "device is off" for on/off devices. This matches the signal
used in ``get_port_status``.
On failure returns ``{"error": "...", "detail": "..."}``.

Inputs: device_id (required). Returns: result

get_environment_healthA

Calculate composite environment health score (0–100) for a device.

Args: device_id: The AC Infinity device code (from discover_devices) stage: Growth stage — one of: clones, seedling, veg, early_flower, mid_flower, late_flower. Default: veg.

Returns: JSON with score (0–100), grade (A–F), per-metric sub-scores, top_recommendation, actual sensor readings (temperature_c, temperature_f, humidity_pct, vpd_kpa), and a human_summary one-liner.

Inputs: device_id (required), stage. Returns: result

get_historical_readingsA

Query AC Infinity environment data across a date range with configurable sampling.

Args: device_id: The AC Infinity device code (from discover_devices) start_date: Start date in YYYY-MM-DD format end_date: End date in YYYY-MM-DD format sample_interval: Bucket size for averaging readings. Use "raw" for all records unmodified, or a duration string like "1m", "5m", "15m", "30m", "1h", "2h", "6h", "12h", "1d". "daily" is accepted as an alias for "1d". Default: "1h" (one averaged reading per hour). time_start: Optional UTC time filter in HH:MM format (e.g., "16:00"). If provided, only readings at or after this time are returned. Invalid HH:MM strings return a structured error. Note: time_start/time_end filters are in UTC. Use discover_devices to get the device's timezone for conversion. time_end: Optional UTC time filter in HH:MM format (e.g., "16:15"). If provided, only readings at or before this time are returned. Invalid HH:MM strings return a structured error.

    When both bounds are set and time_start > time_end (e.g. "22:00"–"06:00"),
    the window crosses midnight: the OR of [time_start, 24:00) and
    [00:00, time_end] is returned.

Returns: JSON with "readings" list and "statistics" summary. Each reading contains timestamp, temperature_c/f, humidity, vpd, and ports list. Statistics include min/avg/max per metric across the returned window. If any readings were dropped because their timestamps could not be parsed, the response also includes "dropped_readings" (count) and "drop_reason". See docs/API.md for full shape.

On failure returns ``{"error": "...", "detail": "..."}``.

Inputs: device_id (required), start_date (required), end_date (required), sample_interval, +2 more. Returns: result

get_port_activity_reportA

Build a per-port runtime activity report from historical data.

Args: device_id: The AC Infinity device code (from discover_devices) days: Number of days to analyze. Default: 7. Must be 1–30.

Returns: JSON with window_start_local and window_end_local (the exact local time range analyzed, e.g. 'May 23, 10:35 AM CDT' to 'May 24, 10:35 AM CDT'), per-port on_hours (total hours ON over the full period), off_hours, transitions, avg_speed_when_running, uptime_pct, and peak_hour_local (device-local time string with peak date, e.g. '3:00 PM CDT (peak on May 23)', or null if the port never ran). Note: data_quality is an internal classification field stripped from the JSON output before serialization — it is NOT present in the response JSON. Its effects are visible only in human_summary: toggle hardware (heaters, lights, humidifiers — loadType 4 or 128 on standard devices, or pattern-detected on devType=18/22 where loadType is unreliable) produces a ▎-prefixed caveat line; devType=22 (Q0KT4 Genetics Lab) produces a device-level Note about missing power-draw data. devType=18 (UIS 69 Pro+) does NOT emit this Note — its active ports produce reliable runtime data in historical records even though portsLoad is always 0. ports_excluded_count is the number of ports removed by the ghost-port filter, capped at devPortCount when the device's physical port count is known (prevents over-counting on sub-8-port devices; unknown/zero devPortCount means no cap). Six rules apply: Rule A (constant 100%% uptime + zero load), Rule B (auto-named Port N with low average runtime or zero load), Rule C (named port with zero transitions + zero load + < 1 h/day average runtime), Rule D (non-toggle named port with speed history ≤ 1 and zero load — confirmed toggle hardware with transitions > 0 is exempt; see Quirk 22 in docs/API.md), Rule E (named port, non-toggle hardware, zero current load, sub-threshold runtime — stale configured speed from a port previously set to OFF), and Rule F (phantom clone detection — custom-named ports sharing identical activity signatures with low average on-time are excluded as legacy controller artifacts; fires only when port_loads data is available; proper-subset guard ensures at least one port is always retained). The human_summary field already includes a brief note about excluded ports when ports_excluded_count > 0. Do not repeat the exclusion count in prose response. The transitions count uses debouncing (_MIN_DWELL_READINGS=2): single-reading state changes at automation window edges are not counted — only transitions where the new state persists for ≥ 2 consecutive readings are recorded.

Ports whose timing data is unreliable appear only as ▎-prefixed caveat
lines in human_summary grouped by current state, e.g.
"▎ Currently ON: Heater (Port 2)." or
"▎ Currently OFF: Humidifier (Port 3)."
Do NOT quote on_hours or uptime_pct for these ports —
relay the caveat lines verbatim instead.

All ports listed under the main runtime sentences have reliable timing data
and should be presented normally. When a device-level Note about missing load
data appears in human_summary (devType=22 devices only), relay it once — do not
add further caveats.

Presentation guidance: - Always refer to ports as 'Name (Port N)', e.g., 'Exhaust Fan (Port 3)'. - When presenting on_hours to a grower, translate it from raw hours to natural language, e.g.: "The fan ran for 36.0 hours over the past 3 days (about 50% of the time)." Do NOT describe on_hours as hours per day. - window_start_local and window_end_local show the exact analysis window in the device's local timezone. Use these when explaining why a device shows activity from multiple calendar days (the window is a rolling 24h/N-day span, not a calendar-day boundary). - peak_hour_local is in device-local time with the peak date, e.g. '3:00 PM CDT (peak on May 23)'. - Ports with a ▎ caveat line: relay the caveat verbatim, no runtime numbers.

Inputs: device_id (required), days. Returns: result

get_port_settingsA

Get the full automation configuration for a port.

Calls the getdevModeSettingList endpoint and returns the active mode, speed target, and all configured automation targets (VPD, temperature, humidity, schedule, timer, cycle).

Args: device_id: The AC Infinity device code (from discover_devices) port: 1-based port number

Returns: JSON example (non-ADVANCE port)::

    {
      "device_id": "C58ZA",
      "port": 1,
      "mode": "AUTO",
      "speed_target": 5,
      "vpd_target_kpa": null,
      "temp_range": null,
      "humidity_range_pct": null,
      "schedule_window": null,
      "cycle_on_seconds": 300,
      "cycle_off_seconds": 60
    }

When ``mode`` is ``"ADVANCE"``, ``speed_target`` is null (an automation governs
the port), and the response includes three additional enrichment fields:

- ``automation_running``: ``true`` if the governing automation has
  ``run_state=True``; ``false`` if an automation was found but none active;
  ``null`` when the secondary API call failed (degraded path).
- ``automation_configured``: ``true`` if the automations list is non-empty;
  ``false`` if empty; ``null`` when degraded.
- ``human_summary``: grower-readable description of the ADVANCE state.
  Three variants:
  - Governing found: ``"Port is running under 'Name' automation (target
    speed: N, current live speed: M). The automation is active."``
  - All disabled: ``"Port is in automation mode, but all automations are
    disabled. The port hasn't fully released. Ask me to list your
    automations for details."``
  - Degraded: ``"Port is in ADVANCE automation mode. Automation details
    could not be retrieved."``

``current_speed`` reflects the live fan speed from the device.
``automation_name``/``automation_id`` are populated from the governing
automation (or null if none active or secondary lookup degrades).
``automation_on_speed`` is read from the port group of the governing
automation whose ``grouptDevType`` bitmask covers this port (bitmask-matched);
null when no governing automation, no matching port group, or degraded.
``vpd_target_kpa`` is non-null only when VPD automation is active.
``temp_range`` / ``humidity_range_pct`` are non-null only when those
thresholds are enabled. ``schedule_window`` times are in device local time
(not UTC).

When the port appears to have nothing connected (primary: ``portResistance == 65535``;
fallback for old firmware: default-named ``"Port N"`` with zero load, or a devType=18/22
controller), the response includes a staleness-aware ``note`` field.
On the non-ADVANCE path, ``human_summary`` is overridden with a staleness statement
and ``note`` is set to a redirect hint, so the response doesn't contradict itself
(e.g. "Humidity automation: 60–100%") for a port with nothing connected.
On the ADVANCE path, ``human_summary`` is preserved (it already describes the
automation state) and only ``note`` is appended.
On failure returns ``{"error": "...", "detail": "..."}``.

Inputs: device_id (required), port (required). Returns: result

get_port_statusA

Get the live operational status of a single port.

Reads real-time fields from the device info response: actual current power level, active automation mode, and remaining timer seconds.

Args: device_id: The AC Infinity device code (from discover_devices) port: 1-based port number

Returns: JSON example (port not powered)::

    {
      "device_id": "C58ZA",
      "port": 1,
      "port_name": "Port 1",
      "power_level": 0,
      "mode": "OFF",
      "plug_status": "not powered"
    }

``mode`` is one of: OFF, ON, AUTO, TIMER_TO_ON, TIMER_TO_OFF, CYCLE, SCHEDULE, VPD,
Automation. ``plug_status`` is only present when no current is detected on the port (the
port is not powered or nothing is connected). It is omitted when the port is running.
Only emitted for default-named ports (``"Port N"``) — custom-named ports are assumed to
have a device intentionally connected; ``loadState=0`` alone cannot distinguish "nothing
plugged in" from "device is off".
``remain_time_seconds`` is only present when a countdown timer is active (value > 0);
it is omitted when there is no active timer.
When ``mode`` is ``Automation``, the port is governed by a named Advance Automation
program in the AC Infinity app. ``automation_name`` is present only when the port is
under automation control and the governing automation name was successfully resolved;
absent otherwise.

When the port appears to have nothing connected (primary: ``portResistance == 65535``;
fallback for old firmware: default-named ``"Port N"`` with zero load, or a devType=18/22
controller), the response also includes a ``note`` field alerting the grower
(e.g. ``"Port 7 doesn't appear to have anything connected."``).

On failure returns ``{"error": "...", "detail": "..."}``.

Note on ADVANCE detection: ``isOpenAutomation==1`` in devInfoListAll is the primary
signal. For AI+ devices (no curMode field) and older firmware without isOpenAutomation,
a secondary call to getdevModeSettingList is made to check modeType.

Inputs: device_id (required), port (required). Returns: result

list_advance_automationsA

List all Advance Automations configured on a device.

Advance Automations (also called "programs" in the AC Infinity app) are named schedules that can govern one or more ports simultaneously.

Args: device_id: The AC Infinity device code (from discover_devices).

Returns: JSON with "automations" list. Each entry includes automation_id, name, enabled status, and currently_running flag. Empty: {"device_id": "...", "automations": []}. On failure returns {"error": "...", "detail": "..."}.

Inputs: device_id (required). Returns: result

set_humidity_automationA

Enable humidity automation on a port using the built-in humidity sensor.

Switches the port to AUTO mode (atType=3) and sets the humidity thresholds. The controller speeds up when humidity exceeds max_rh and slows down below min_rh. Uses read-before-write. Defaults to dry_run=True.

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. min_rh: Minimum relative humidity threshold (%), range 0–100. Sub-percent values are rounded to the nearest integer (e.g. 50.5 → 51). max_rh: Maximum relative humidity threshold (%), range 0–100. Must exceed min_rh. Sub-percent values are rounded to the nearest integer. dry_run: If True (default), returns the payload that would be sent without writing.

Returns: JSON with action, device_id, port, min_rh, max_rh, dry_run, controller_type, sent, and payload (when dry_run=True). On failure returns {"error": "..."}.

Inputs: device_id (required), port (required), min_rh (required), max_rh (required), +1 more. Returns: result

set_port_modeA

Switch a port to a specific automation mode.

All 8 AC Infinity automation modes are supported. Mode-specific parameters are required for CYCLE, SCHEDULE, TIMER_TO_ON, and TIMER_TO_OFF modes. Uses read-before-write. Defaults to dry_run=True.

For setting automation targets alongside the mode, prefer the dedicated tools: set_vpd_automation (VPD mode), set_temperature_automation and set_humidity_automation (AUTO mode).

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. mode: One of OFF, ON, AUTO, VPD, CYCLE, SCHEDULE, TIMER_TO_ON, TIMER_TO_OFF. dry_run: If True (default), returns the payload without writing. cycle_on_seconds: CYCLE mode — seconds the port runs per cycle. Required for CYCLE. cycle_off_seconds: CYCLE mode — seconds the port is off per cycle. Required for CYCLE. schedule_start: SCHEDULE mode — start time as "HH:MM" in device local time. Required for SCHEDULE. schedule_end: SCHEDULE mode — end time as "HH:MM" in device local time. Required for SCHEDULE. timer_duration_seconds: TIMER_TO_ON / TIMER_TO_OFF — countdown duration in seconds. Required for TIMER_TO_ON and TIMER_TO_OFF.

Returns: JSON with action, device_id, port, mode, dry_run, controller_type, sent, and payload (when dry_run=True). On failure returns {"error": "..."}.

Inputs: device_id (required), port (required), mode (required), dry_run, +5 more. Returns: result

set_port_offA

Sets mode to OFF (atType=1) and zeros speed (onSpead=0).

Works for all device types including toggle hardware (heaters, lights, on/off outlets). Uses read-before-write. Defaults to dry_run=True — set dry_run=False to write to the device.

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. dry_run: If True (default), returns the payload that would be sent without writing.

Returns: JSON with action, device_id, port, dry_run, controller_type, sent, and payload (when dry_run=True).

When the port appears to have nothing connected (primary: ``portResistance == 65535``;
fallback for old firmware: default-named ``"Port N"`` with zero load, or a devType=18/22
device), the response also includes a ``warning`` field alerting the grower.

On failure returns ``{"error": "..."}``.

Inputs: device_id (required), port (required), dry_run. Returns: result

set_port_onA

Turn a port on at full speed (onSpead=10).

Works for fan-type and on/off toggle devices. Uses read-before-write. Defaults to dry_run=True — set dry_run=False to write to the device.

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. dry_run: If True (default), returns the payload that would be sent without writing.

Returns: JSON with action, device_id, port, dry_run, controller_type, sent, and payload (when dry_run=True).

When the port appears to have nothing connected (primary: ``portResistance == 65535``;
fallback for old firmware: default-named ``"Port N"`` with zero load, or a devType=18/22
device), the response also includes a ``warning`` field alerting the grower.

On failure returns ``{"error": "..."}``.

Inputs: device_id (required), port (required), dry_run. Returns: result

set_port_speedA

Set fan or dimmer speed on a specific port.

Uses read-before-write: reads current mode settings then overlays the new speed value. Defaults to dry_run=True — set dry_run=False to write to the device.

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. speed: Target speed 1–10 (10 = full speed). dry_run: If True (default), returns the payload that would be sent without writing. Set to False to execute the change.

Returns: JSON with action, device_id, port, speed, dry_run, controller_type, sent, and payload (when dry_run=True).

When the port is in OFF mode (atType=0 or atType=1) at call time, the
response also includes a ``warning`` field telling the grower to ask
Claude to switch the port to ON mode to activate it. The speed is stored
on the controller but the port will not run until the mode is changed.

Example (dry_run=True)::

    {
      "action": "set Exhaust Fan (Port 2) speed to 5",
      "device_id": "C58ZA",
      "port": 2,
      "speed": 5,
      "dry_run": true,
      "controller_type": "legacy",
      "sent": false,
      "payload": { ... }
    }

On failure returns ``{"error": "..."}``.

Inputs: device_id (required), port (required), speed (required), dry_run. Returns: result

set_temperature_automationA

Enable temperature automation on a port using the built-in temperature sensor.

Switches the port to AUTO mode (atType=3) and sets the temperature thresholds. The controller speeds up when temperature exceeds max_temp and slows down below min_temp. Uses read-before-write. Defaults to dry_run=True.

Pass values in the device's preferred unit (°F or °C). Call discover_devices first to check temp_unit. Valid range: 32–122°F or 0–50°C (device API cap = 50°C).

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. min_temp: Minimum temperature threshold in the device's preferred unit. Sub-degree values are rounded to the nearest integer. max_temp: Maximum temperature threshold in the device's preferred unit. Must exceed min_temp. Sub-degree values are rounded to the nearest integer. dry_run: If True (default), returns the payload that would be sent without writing.

Returns: JSON with action, device_id, port, min_temp, max_temp, unit, dry_run, controller_type, sent, and payload (when dry_run=True). On failure returns {"error": "..."}.

Inputs: device_id (required), port (required), min_temp (required), max_temp (required), +1 more. Returns: result

set_vpd_automationA

Enable VPD automation on a port using the built-in temperature and humidity sensors.

Switches the port to VPD mode (atType=8) and sets the VPD target. Uses read-before-write. Defaults to dry_run=True — set dry_run=False to write to the device.

Args: device_id: Device code from discover_devices (e.g. "C58ZA"). port: 1-based port number. target_vpd: Target VPD in kPa, range 0.1–3.0. Typical ranges by stage: seedling/clones 0.8–1.2, veg 1.0–1.5, early_flower 1.0–1.8, mid_flower 1.2–2.0, late_flower 1.2–1.8. dry_run: If True (default), returns the payload that would be sent without writing.

Returns: JSON with action, device_id, port, target_vpd_kpa, dry_run, controller_type, sent, and payload (when dry_run=True). On failure returns {"error": "..."}.

Inputs: device_id (required), port (required), target_vpd (required), dry_run. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A4.4/5.0

    Scored across 25 tools

    Disambiguation5/5

    Every tool has a clearly distinct purpose: discovery, reading, port control, automation management, and environmental analysis. Even closely related tools like set_port_on and set_port_off are differentiated by their specific action.

    Naming Consistency5/5

    All tool names follow a consistent verb_noun pattern using lowercase and underscores (e.g., discover_devices, set_port_speed, list_advance_automations). No mixing of conventions.

    Tool Count4/5

    With 25 tools, the server is comprehensive but slightly heavy. Each tool serves a clear purpose within the AC Infinity ecosystem, but the count is on the higher end of the ideal range.

    Completeness4/5

    The tool surface covers core CRUD operations, automations, port control, and environmental monitoring. Missing an update_advance_automation tool and some scheduling tweaks, but overall well-rounded.

    Maintenance

    ActivityNo data
    ResponsivenessSyncing

    Related MCP Connectors

    Related MCP Servers