AC Infinity MCP
This server lets Claude or any MCP-compatible assistant control and monitor AC Infinity grow controllers: read live sensors, analyze historical data, and safely adjust fans, ports, and automations.
Discover controllers and their ports.
Read live temperature, humidity, VPD, and port states for one or all devices.
Fetch historical readings with configurable time buckets and time filters.
Check VPD compliance against grow-stage targets.
Get composite environment health scores (0–100, A–F) with recommendations.
Detect temperature/humidity/VPD trends and 7-day projections.
Generate per-port activity reports: on/off hours, uptime, peak activity.
Inspect port status (power, mode, timer, connected state) and full automation settings.
Set fan/dimmer speed or turn ports on/off.
Configure VPD, temperature, or humidity automation on a port.
Switch ports to any mode: OFF, ON, AUTO, VPD, CYCLE, SCHEDULE, TIMER modes.
Apply one-click grow-stage templates for VPD, temperature, and humidity.
Manage Advance Automations: list, view, enable, disable, create, delete, and edit rules.
Break a port out of automation safely while locking co-governed ports.
All write tools default to dry-run mode, returning the exact payload before executing.
Includes built-in MCP prompts for VPD troubleshooting, new grower setup, and environment alert interpretation.
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 |
† 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 |
| List all your controllers and ports |
🌡️ Readings |
| Live temp, humidity, VPD, and port states for one device |
🌡️ Readings |
| Same, for all devices at once |
🌡️ Readings |
| Time-series data with optional bucketing (raw, 15m, 1h, 1d, …) |
📊 Analytics |
| VPD target compliance check for your current grow stage |
📊 Analytics |
| Composite health score (0–100, A–F) across temp, humidity, VPD |
📊 Analytics |
| Linear trend + 7-day projection per metric |
📊 Analytics |
| Per-port on/off hours, uptime %, and peak activity hour |
🔎 Port Status |
| Live port power level, load detection, active mode, and timer countdown |
🔎 Port Status |
| Full automation config: mode, VPD target, temp/humidity thresholds, schedule, cycle |
🔌 Write |
| Set fan or pump speed 1–10 ( |
🔌 Write |
| Turn a port fully on ( |
🔌 Write |
| Turn a port fully off ( |
⚙️ Automation |
| Enable VPD mode with a kPa target using built-in sensors ( |
⚙️ Automation |
| Enable AUTO mode with min/max °C thresholds ( |
⚙️ Automation |
| Enable AUTO mode with min/max % RH thresholds ( |
⚙️ Automation |
| Switch to any mode: OFF, ON, AUTO, VPD, CYCLE, SCHEDULE, TIMER_TO_ON, TIMER_TO_OFF ( |
🌱 Intelligence |
| One-click VPD + temp + humidity automation for a named grow stage ( |
🤖 Advance Automation |
| List all named Advance Automation programs on a device |
🤖 Advance Automation |
| Get full detail (schedule, port groups, run state) for one automation |
🤖 Advance Automation |
| Enable a disabled automation — reads state first, no-ops if already enabled ( |
🤖 Advance Automation |
| Disable an enabled automation — reads state first, no-ops if already disabled ( |
🤖 Advance Automation |
| Create a new named automation; the first rule can be off, on, cycle, auto (temp/humidity), or VPD ( |
🤖 Advance Automation |
| Delete an automation (disables first if active) ( |
🤖 Advance Automation |
| Add one rule (window + behavior) to an existing program — off, on, cycle, auto (temp/humidity), or VPD ( |
🤖 Advance Automation |
| Edit one rule in place — change its window, speed, mode, or sensor targets ( |
🤖 Advance Automation |
| Remove a single rule from a program, leaving the rest intact ( |
🤖 Advance Automation |
| Safely break a port out of automation control and lock co-governed ports to manual speed ( |
✦ 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. Passdry_run=Falseonly 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 |
| Step-by-step guide: diagnose HIGH or LOW VPD and which tools to call to fix it |
| Onboarding walkthrough: discover devices → apply a stage template → check your health score |
| How to read |
Quick start
Option 1: pip (simplest)
pip install git+https://github.com/ober37/ac-infinity-mcp.gitRequires Python 3.11+.
Option 2: isolated venv (recommended)
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 belowOption 3: uvx (no install)
uvx --from git+https://github.com/ober37/ac-infinity-mcp.git ac-infinity-mcpMCP 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 --buildThe 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 |
| Yes | Your AC Infinity account email |
| 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-auditSecurity
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 toolsapply_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:
Checks that the port is actually under automation (idempotent: no-ops if not).
Finds the governing automation.
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.
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
detect_environment_trendsA
Detect linear trends in temperature, humidity, and VPD over a look-back window.
Args: device_id: The AC Infinity device code (from discover_devices) days: Number of days to look back. Default: 7. Must be 1–30.
Returns: JSON with per-metric trend reports: slope (change/hour), direction, 7-day projection, and alert flag.
Note: The AC Infinity history API returns a maximum of ~1257 records per day regardless of page_size. For longer windows the data may be sparse.
Inputs: device_id (required), days. 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
Scored across 25 tools
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.
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.
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.
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
Related MCP Connectors
Talk to your public-facing AI from any MCP client — Claude, ChatGPT, Cursor, Cline, Windsurf.
Connect ChatGPT and Claude to your Mac, Windows or Linux with secure MCP remote access.
The OpenRouter MCP server plugs OpenRouter into the AI tools you already use. Once connected, your assistant can pull live OpenRouter data (models, prices, your credits, rankings, and docs) and send quick test messages, all without leaving your editor.
Persistent memory for AI assistants. Save once; recall from Claude, ChatGPT, or any MCP client.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA Multi-Agent Conversation Protocol server that provides an interface to Google's Cloud IoT API, allowing agents to interact with and manage IoT devices and registries through natural language.-
- AlicenseNot gradedqualityDmaintenanceAn MCP server that wraps the Ambient Weather REST API. Query your personal weather stations conversationally from Claude Code, Claude.ai, or any MCP-compatible client.1MIT
- AlicenseAqualityDmaintenanceEnables registration, monitoring, and control of IoT devices via AI agents, with local storage and no cloud API key required.9MIT
- AlicenseNot gradedqualityBmaintenanceMCP server that connects AI assistants like Claude to WHOOP health data, enabling natural language queries about recovery, sleep, workouts, and more.331 npm181MIT