Skip to content

MCP ​

Homecast provides an MCP endpoint that AI assistants use to read and control smart home devices.

Endpoint: https://api.homecast.cloud/mcp

Transport: Streamable HTTP (GET, POST, DELETE)

Authentication ​

MCP supports two authentication methods:

MethodSetupBest for
OAuth 2.1Auto-discovered via /.well-known/oauth-authorization-serverMCP clients with OAuth support (Claude Desktop, etc.)
Access TokenAuthorization: Bearer hc_... headerClients without OAuth

See the Authentication reference for details on both.

When using OAuth, users see a consent screen to choose homes and permissions:

OAuth consent

Tools ​

Tool descriptions are personalized per account: tools/list embeds your actual home keys and room names into the get_state and get_automations descriptions, so assistants know valid filter values without an unfiltered discovery call. Only homes the token can access are included.

get_state ​

Read the current state of all accessible devices. Returns a hierarchical view of homes, rooms, and accessories.

ParameterTypeRequiredDescription
filter_by_homestringNoFilter to a specific home
filter_by_roomstringNoFilter to a specific room
filter_by_typestringNoFilter by device type (e.g., lightbulb)
filter_by_namestringNoFilter by device name

Virtual accessories are included alongside your HomeKit devices, so an assistant can read a mode or a counter the same way it reads a light. They are the state Apple Home has nowhere to store — "is the house in Away mode?" is a get_state call, not something to be inferred.

Annotations: Read-only tool. Does not modify device state.

set_state ​

Control one or more devices by setting characteristic values.

ParameterTypeRequiredDescription
updatesarrayYesList of device updates

Each update object:

FieldTypeRequiredDescription
homestringYesHome name
roomstringYesRoom name
accessorystringYesAccessory name
onbooleanNoPower state
brightnessintegerNoBrightness 0–100
hueintegerNoColor hue 0–360
saturationintegerNoColor saturation 0–100
color_tempintegerNoColor temperature (mireds)
activebooleanNoActive state
heat_targetfloatNoHeating target temperature
cool_targetfloatNoCooling target temperature
hvac_modestringNooff, heat, cool, auto
lock_targetintegerNo1 (lock), 0 (unlock)
alarm_targetintegerNoSecurity alarm state
speedintegerNoFan speed 0–100
volumeintegerNoSpeaker volume 0–100
mutebooleanNoSpeaker mute
targetintegerNoPosition 0–100

Virtual accessories are set here too, using the characteristic get_state reports for them:

FieldTypeDescription
virtual_modestringMode — the option to select
virtual_countintegerCounter — sets the count, it does not add to it
virtual_numbernumberNumber
virtual_textstringText
virtual_datetimestringDate & time
virtual_timerstringTimer — "active" starts it, anything else cancels it

A virtual Switch uses on, like any other switch. One that isn't adjustable ("Adjustable here" turned off) is reported in failed rather than changed.

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

query_history ​

Bulk access to recorded history for pattern and behaviour analysis — the assistant does the analysis; this returns the data flexibly and fast. Any subset of accessories and characteristics, any date range, many series in one call.

ParameterTypeRequiredDescription
homestringNoHome name substring. Omit for the first home
accessoriesstring[]NoAccessory name/slug/id substrings. Omit for all recorded
characteristicsstring[]NoCharacteristic types (e.g. current_temperature, power_state, motion). Omit for all
startstringNoISO 8601 start (default: 24h before end)
endstringNoISO 8601 end (default: now)
resolutionstringNoauto | raw | hourly | daily
max_points_per_seriesnumberNoPer-series cap (default 500, max 2000); 50k points per response

Resolution is the speed lever: hourly and daily read precomputed summaries, so a month of data is a few hundred rows per series — numeric buckets carry [time, min, avg, max], on/off and mode buckets carry [time, transitions, msInEachState]. raw returns every recorded change for short ranges and exact event times. Truncated series include continue_from — repeat the call with start=continue_from to page through large pulls. Requires Analytics to be enabled.

Annotations: Read-only.

get_history ​

Recorded history for one accessory — how its characteristics changed over time. Answers questions like "what was the temperature last night?", "when did the door open?", "how long was the heating on today?". Numeric characteristics come back as [isoTime, value] pairs with a min/avg/max summary; on/off and mode characteristics as [isoTime, state] transitions.

ParameterTypeRequiredDescription
accessorystringYesAccessory name substring, slug, or id
homestringNoHome name substring — narrows the search
characteristicstringNoOne characteristic (e.g. current_temperature, on, motion). Omit for all recordable ones (up to 6)
hoursnumberNoHow far back to look (default 24)
max_pointsnumberNoMaximum points per series (default 200)

History is opt-in and off by default — if nothing is recorded, the home owner has to turn it on in Settings → Homes → the home first; the tool says so rather than returning an error. See the Analytics guide.

Annotations: Read-only.

run_scene ​

Execute a HomeKit scene by name.

ParameterTypeRequiredDescription
homestringYesHome name
namestringYesScene name

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

create_scene ​

Create a scene: a named snapshot of device states that can be run on demand with run_scene.

ParameterTypeRequiredDescription
homestringYesHome slug key
namestringYesScene name (must end with a letter or number)
actionsarrayYesDevice property changes the scene applies

Each action entry uses the set_state vocabulary: { "accessory": "<slug>", ...properties } (e.g. on, brightness, heat_target). Get home/accessory slugs and property names from get_state. Requires a relay app version with scene management support.

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

update_scene ​

Update a scene identified by name: rename it and/or replace all of its actions.

ParameterTypeRequiredDescription
homestringYesHome slug key
namestringYesCurrent scene name
new_namestringNoNew name
actionsarrayNoNew actions (same format as create_scene) — replaces all existing actions

Built-in scenes and scenes that belong to an automation cannot be modified — for the latter, edit the automation instead (the error names it). Requires a relay app version with scene management support.

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

delete_scene ​

Permanently delete a scene by name. Cannot be undone.

ParameterTypeRequiredDescription
homestringYesHome slug key
namestringYesScene name

Built-in scenes and scenes that belong to an automation cannot be deleted this way — for the latter, delete the automation instead (the error names it). Requires a relay app version with scene deletion support.

Annotations: Mutating and destructive tool. Requires mcp:write scope or control home permission.

Two automation engines ​

Homecast can create automations in two different systems, and they are not interchangeable. Picking the wrong one is the most common mistake an assistant makes, because HomeKit fails by simply not being able to express what you asked — there is no error to notice.

HomeKit (create_automation)Homecast (create_hc_automation)
Runs onThe Apple home hub (HomePod / Apple TV)The Homecast relay Mac
Works when the Mac is offYesNo
Appears in the Apple Home appYesNo (appears in Homecast)
Numeric thresholds (above / below)No — equality onlyYes
Multiple conditions with AND/OR/NOTNo — all conditions ANDed, equality onlyYes
Remembers state between runsNoYes, via virtual accessories
Virtual accessoriesInvisible — slugs will not resolveYes
Delays, notifications, scenes as actionsDevice properties onlyYes
Needs the relay's Apple ID to have edit accessYesNo
Automation id survives an editNo — editing the trigger recreates itYes

The rule: default to create_hc_automation. Reach for create_automation only when the automation must keep running while the relay Mac is off, or when the user explicitly wants it in the Apple Home app.

Two requests that HomeKit cannot serve, and that should never be reported as impossible:

  • A threshold — "when humidity goes above 65%", "if it drops below 5°C". HomeKit triggers and conditions test equality only. Use a Homecast numeric trigger.
  • Anything stateful — "don't start it again if it's already running", "only once per day", "remember what the temperature was". HomeKit has nowhere to store that. Use a Homecast automation with a virtual accessory.

get_automations ​

List HomeKit automations across all homes. Returns {home_key: [automation]} where each automation has id, name, enabled, editable, trigger, actions, and last_fired.

ParameterTypeRequiredDescription
filter_by_homestringNoFilter to a specific home

The trigger and actions objects are returned in exactly the format create_automation accepts, so an assistant can read an automation, modify it, and send it back.

Automations created outside Homecast with presence, location, or app-specific triggers are returned with editable: false — they can be renamed, enabled/disabled, and deleted, but their trigger and actions cannot be changed. A trigger.activation_issue field (e.g. disabledNoHomeHub) indicates HomeKit has deactivated the automation, usually because no home hub is available.

Homes where the relay's Apple ID has view-only access in Apple Home are listed in _meta.view_only_homes — their automations are read-only from Homecast (view-only is a supported configuration; everything except HomeKit-automation editing works normally). The personalized tool descriptions also annotate these homes with "HomeKit automations read-only".

Annotations: Read-only tool.

create_automation ​

Create a HomeKit automation: when the trigger fires (and all conditions pass), the actions set device properties. Homes, accessories, and properties use the same slug keys and names as get_state/set_state.

ParameterTypeRequiredDescription
homestringYesHome slug key
namestringYesAutomation name
triggerobjectYesTimer or event trigger (see below)
actionsarrayYesDevice property changes to apply

Timer trigger — fires at a time of day:

json
{ "type": "timer", "hour": 7, "minute": 30, "recurrenceType": "daily" }

or a one-off: { "type": "timer", "fireDate": "2026-08-01T07:00:00Z" }. Optional timeZone (IANA identifier).

Event trigger — fires on device or environmental events:

json
{
  "type": "event",
  "events": [{ "type": "significantTime", "significantEvent": "sunset", "offsetMinutes": -15 }],
  "conditions": [{ "type": "characteristic", "accessory": "alarm_eeff", "characteristic": "alarm_state", "value": "away" }],
  "recurrences": [{ "weekday": 1 }, { "weekday": 7 }]
}

Supported event types:

EventFieldsFires when
characteristicaccessory, characteristic, valueA device property becomes a value (e.g. motion = true)
significantTimesignificantEvent (sunrise/sunset), offsetMinutes (optional, negative = before)Sunrise or sunset
calendarcalendarComponents (hour, minute, optional weekday/day/month)A time of day
durationdurationSecondsA repeating interval

Optional event-trigger fields: conditions (all must be true; equality only), endEvents (deactivating events), recurrences (limit days; weekday 1=Sunday…7=Saturday), executeOnce.

Each action entry uses the set_state vocabulary: { "accessory": "<slug>", ...properties } (e.g. on, brightness, alarm_target).

Limitations (imposed by Apple's HomeKit framework):

  • The relay Mac's Apple ID must have edit access in Apple Home ("Add & Edit Accessories", called "Allow Editing" on older iOS). Edit access is optional — homes without it are read-only for automations, and create/update/delete fail with INSUFFICIENT_HOMEKIT_PRIVILEGES — see Troubleshooting.
  • Presence and location triggers cannot be created — only the Apple Home app can create them.
  • Conditions support equality only (no greater/less-than or time windows).
  • Actions set device properties only; they cannot run a scene.
  • Automation names must end with a letter or number — HomeKit rejects trailing punctuation (e.g. Lights (evening) fails; the error suggests a valid variant).

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

update_automation ​

Update an existing automation. Only the provided fields change.

ParameterTypeRequiredDescription
homestringYesHome slug key
idstringYesAutomation id from get_automations
namestringNoNew name
triggerobjectNoNew trigger (same format as create_automation)
actionsarrayNoNew actions — replaces all existing actions
enabledbooleanNoEnable or disable the automation

Changing the trigger deletes and recreates the automation inside HomeKit, so the response may contain a new id — always use the returned id afterwards. Automations with editable: false accept only name and enabled changes. Like create_automation, this requires the relay's Apple ID to have edit access in Apple Home.

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

delete_automation ​

Permanently delete an automation. Cannot be undone — to stop an automation temporarily, use update_automation with enabled: false.

ParameterTypeRequiredDescription
homestringYesHome slug key
idstringYesAutomation id from get_automations

Annotations: Mutating and destructive tool. Requires mcp:write scope or control home permission.

Homecast automation tools ​

These drive the Homecast automation engine rather than HomeKit — see Two automation engines for which to use. Automations created here are ordinary automations in the app's visual editor: laid out automatically, fully editable afterwards.

get_hc_automations ​

List Homecast automations across all homes. Returns {home_key: [automation]} where each has id, name, enabled, mode, triggers, conditions, actions, last_triggered, trigger_count and editable_via_mcp.

ParameterTypeRequiredDescription
filter_by_homestringNoFilter to a specific home

Triggers, conditions and actions come back in exactly the format create_hc_automation accepts, so an assistant can read one, modify it, and send it back.

editable_via_mcp: false means the automation uses nodes this grammar has no words for — code nodes, HTTP requests, branching, sub-workflows — because it was built in the visual editor. It can still be renamed, enabled/disabled or deleted, but resending its triggers and actions would drop those nodes. Parts that could not be read are marked _unsupported.

Annotations: Read-only tool.

create_hc_automation ​

ParameterTypeRequiredDescription
homestringYesHome slug key
namestringYesAutomation name (no trailing-punctuation rule, unlike HomeKit)
triggersarrayYesAny one firing runs the automation
actionsarrayYesRun in order
conditionsarrayNoAll must pass; nest a block for OR/NOT
enabledbooleanNoDefaults to true
modestringNosingle (default), restart, queued, parallel
descriptionstringNoNote shown in the editor

Triggers

TypeShape
device{"type":"device","accessory":"<slug>","characteristic":"<prop>","to":<v>,"from":<v>,"for":<seconds>}
numeric{"type":"numeric","accessory":"<slug>","characteristic":"<prop>","above":<n>,"below":<n>,"for":<seconds>}
time{"type":"time","at":"07:30","weekdays":[1,2,3,4,5]} (0 = Sunday)
sun{"type":"sun","event":"sunrise"|"sunset","offset":{"minutes":-30}}
webhook{"type":"webhook","webhook_id":"<id>"}

device and numeric can take "virtual":"<slug>" instead of "accessory" to trigger off a virtual accessory, or "service_group":"<id>" for a group. Durations accept bare seconds or {hours, minutes, seconds}.

Conditions

TypeShape
device{"type":"device","accessory":"<slug>","characteristic":"<prop>","value":<v>}
numeric{"type":"numeric","accessory":"<slug>","characteristic":"<prop>","above":<n>,"below":<n>}
virtual{"type":"virtual","virtual":"<slug>","equals":<v>} (or above/below)
time{"type":"time","after":"09:00","before":"21:00","weekdays":[...]}
sun{"type":"sun","after":"sunset","before":"sunrise"}
template{"type":"template","expression":"<expression>"}

Nest {"operator":"or"\|"not","conditions":[...]} for anything other than AND.

Actions

TypeShape
device{"type":"device","accessory":"<slug>", ...properties} — same vocabulary as set_state; several properties in one action is fine
virtual{"type":"virtual","virtual":"<slug>","operation":"set","value":<v>}
scene{"type":"scene","scene":"<name>"}
delay{"type":"delay","seconds":300}
notify{"type":"notify","message":"...","title":"..."}

Virtual operations: set, turn_on, turn_off, toggle, increment, decrement, reset, start, pause, resume, cancel, finish.

Annotations: Mutating tool. Requires mcp:write scope or control home permission.

update_hc_automation ​

Same parameters as create_hc_automation, plus id. Everything except home and id is optional.

Every part is optional and merged independently against what is stored, so you can change one thing without resending the rest: enabled alone pauses an automation, actions alone rewrites what it does while keeping its triggers and conditions, and anything you omit is left exactly as it was.

Passing triggers, conditions or actions replaces that whole list — send the complete set for the part you are changing. Passing conditions: [] explicitly is how you clear conditions; omitting the field keeps them. Unlike HomeKit, the id is stable across updates.

Annotations: Mutating tool.

delete_hc_automation ​

ParameterTypeRequiredDescription
homestringYesHome slug key
idstringYesAutomation id from get_hc_automations

Annotations: Mutating and destructive tool.

create_virtual_accessory ​

Create a value the home remembers — the state HomeKit has nowhere to put. See the virtual accessories guide for the concept.

ParameterTypeRequiredDescription
homestringYesHome slug key
namestringYesDisplay name
typestringYesswitch, mode, number, counter, timer, text or date
optionsarrayFor modeThe named choices
initial—NoStarting value; defaults to the first option for mode
min / maxnumberFor numberRange
stepnumberNoIncrement for number / counter
unitstringNoDisplay unit for number
durationnumberNoDefault duration for timer, in seconds
controllablebooleanNoWhether a person can change it from the dashboard

The accessory appears in get_state alongside real devices and is writable with set_state. HomeKit cannot see it, so it will not resolve in create_automation — automations that depend on one must be Homecast automations.

Annotations: Mutating tool.

update_virtual_accessory ​

Change an accessory's definition — not its value. Provide home and id plus only the fields you are changing; everything from create_virtual_accessory except type is accepted.

To change the accessory's current value, use set_state, exactly as you would for a real device.

type cannot be changed — a mode is not a counter, and the stored value would not survive. Delete and recreate instead.

Replacing a mode's options swaps the whole list. If the option it starts on is no longer in it, the start value moves to the first option, so the accessory can't be left holding a value nothing can select and no condition can match.

Automations referencing the accessory keep working — they resolve it by id, not by name — but a rename changes its slug, so re-read get_state before referring to it again.

Annotations: Mutating tool.

delete_virtual_accessory ​

ParameterTypeRequiredDescription
homestringYesHome slug key
idstringYesVirtual accessory slug or id

Any automation still referencing it will fail to resolve it — check get_hc_automations first.

Annotations: Mutating and destructive tool.

Worked example: a stateful, threshold-driven automation ​

"Set the bedroom aircon to dehumidify when the humidity goes above 65%, but don't fight me if I turn it off myself."

Neither half of that is expressible in HomeKit: above 65 is a threshold, and "don't fight me" needs memory of whether the cycle was ours. Both are ordinary Homecast automations.

1. Create the memory. A mode accessory holds where the cycle is up to:

json
{
  "home": "george_street_bcab",
  "name": "Bedroom 1 Dry Cycle",
  "type": "mode",
  "options": ["Idle", "Running", "Cancelled"]
}

The response carries the slug — say bedroom_1_dry_cycle_4ad9 — to reference below.

2. Start the cycle when it gets humid, but only if we aren't already running and the user hasn't got the unit on themselves:

json
{
  "home": "george_street_bcab",
  "name": "Bedroom 1 dry cycle start",
  "triggers": [
    {"type": "numeric", "accessory": "bedroom_1_underfloor_heating_4ad9",
     "characteristic": "relative_humidity", "above": 65, "for": {"minutes": 10}}
  ],
  "conditions": [
    {"type": "virtual", "virtual": "bedroom_1_dry_cycle_4ad9", "equals": "Idle"},
    {"type": "device", "accessory": "bedroom_1_air_conditioner_7f60",
     "characteristic": "active", "value": false}
  ],
  "actions": [
    {"type": "device", "accessory": "bedroom_1_air_conditioner_7f60",
     "active": true, "hvac_mode": "cool", "cool_target": 18},
    {"type": "virtual", "virtual": "bedroom_1_dry_cycle_4ad9",
     "operation": "set", "value": "Running"}
  ]
}

The "for": {"minutes": 10} matters: without it a sensor flickering across 65% starts a cycle every time it crosses.

3. Stop when it's dry, and hand the memory back to Idle:

json
{
  "home": "george_street_bcab",
  "name": "Bedroom 1 dry cycle finish",
  "triggers": [
    {"type": "numeric", "accessory": "bedroom_1_underfloor_heating_4ad9",
     "characteristic": "relative_humidity", "below": 60}
  ],
  "conditions": [
    {"type": "virtual", "virtual": "bedroom_1_dry_cycle_4ad9", "equals": "Running"}
  ],
  "actions": [
    {"type": "device", "accessory": "bedroom_1_air_conditioner_7f60", "active": false},
    {"type": "virtual", "virtual": "bedroom_1_dry_cycle_4ad9",
     "operation": "set", "value": "Idle"}
  ]
}

4. Notice a manual override. If the unit goes off while it's still humid, that wasn't us — step 3 only runs below 60%:

json
{
  "home": "george_street_bcab",
  "name": "Bedroom 1 dry cycle cancelled",
  "triggers": [
    {"type": "device", "accessory": "bedroom_1_air_conditioner_7f60",
     "characteristic": "active", "to": false}
  ],
  "conditions": [
    {"type": "virtual", "virtual": "bedroom_1_dry_cycle_4ad9", "equals": "Running"},
    {"type": "numeric", "accessory": "bedroom_1_underfloor_heating_4ad9",
     "characteristic": "relative_humidity", "above": 60}
  ],
  "actions": [
    {"type": "virtual", "virtual": "bedroom_1_dry_cycle_4ad9",
     "operation": "set", "value": "Cancelled"}
  ]
}

Notes worth carrying to other stateful automations:

  • Hysteresis. Starting at 65% and stopping at 60% — rather than both at 65% — is what stops the pair thrashing.
  • Guard the override detector. Step 3 also turns the unit off, which fires step 4's trigger. The above 60 condition is what distinguishes "finished normally" from "someone intervened", because step 3 only ever runs below 60.
  • Reset the memory. As written, Cancelled only returns to Idle when a fourth automation says so — add a time trigger at, say, 07:00 that sets Cancelled → Idle if you want "leave it alone for today" rather than "leave it alone forever".
  • Restoring what you overwrote. Step 2 writes cool_target: 18 and never puts back what was there. If that matters, stash the old value in a number virtual accessory first and restore it in step 3.

OAuth scopes ​

ScopePermissions
mcp:readget_state, get_automations, get_hc_automations
mcp:writeAll tools including set_state, scene management, and automation management in both engines
mcp:adminFull access including management

Client configuration ​

You can find the MCP endpoint URL in the Share dialog for any home under AI Assistants.

MCP endpoint

json
{
  "mcpServers": {
    "homecast": {
      "type": "url",
      "url": "https://api.homecast.cloud/mcp"
    }
  }
}

The client discovers OAuth endpoints automatically and prompts for authorization.

With access token ​

json
{
  "mcpServers": {
    "homecast": {
      "type": "url",
      "url": "https://api.homecast.cloud/mcp",
      "headers": {
        "Authorization": "Bearer hc_your_token_here"
      }
    }
  }
}

For a step-by-step setup guide, see Connect an AI Assistant.