crestron-mcp 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENT_GUIDE.md ADDED
@@ -0,0 +1,298 @@
1
+ # Crestron control: guide for an AI assistant
2
+
3
+ You have tools to query and control a Crestron AV system (lighting, AV, HVAC,
4
+ shades, etc.) over MCP. This document tells you how to use them well. Read it
5
+ before acting on the system.
6
+
7
+ ## What you can do
8
+
9
+ Discover the rooms and devices, read the current state of any device, set
10
+ values, and smoothly ramp (fade) analog levels. You cannot add or remove
11
+ devices, change wiring, or configure the processor; you operate what the
12
+ installer exposed.
13
+
14
+ ## Connecting (Claude Code / CLI)
15
+
16
+ The MCP server is a self-contained binary (no runtime to install). Register it
17
+ once, pointing at the processor:
18
+
19
+ ```bash
20
+ claude mcp add crestron \
21
+ -e CRESTRON_HOST=<processor-ip> \
22
+ -e CRESTRON_KEY=<secure-key> \
23
+ -s user \
24
+ -- /path/to/crestron-mcp-<platform>
25
+ ```
26
+
27
+ Or a project `.mcp.json`:
28
+
29
+ ```json
30
+ {
31
+ "mcpServers": {
32
+ "crestron": {
33
+ "command": "/path/to/crestron-mcp-<platform>",
34
+ "env": { "CRESTRON_HOST": "<processor-ip>", "CRESTRON_KEY": "<secure-key>" }
35
+ }
36
+ }
37
+ }
38
+ ```
39
+
40
+ Auth is config, not your concern at runtime: `CRESTRON_KEY` (recommended, secure
41
+ key, enables TLS automatically), or `CRESTRON_AUTH` (password mode), or neither
42
+ (open mode). If a call returns "authentication required", the credentials are
43
+ missing or wrong in the config, tell the user; you can't fix it from a tool call.
44
+
45
+ ## The tools
46
+
47
+ - `discover_crestron_system()` - rooms, categories, device counts. Call this first.
48
+ - `list_crestron_rooms()` - rooms with device counts.
49
+ - `list_crestron_devices(room?, category?)` - devices, optionally filtered. Room
50
+ matching is case-insensitive and accepts the room name or id.
51
+ - `query_crestron_device(device_id)` - live state of one device: value plus whether
52
+ it's idle / ramping / pulsing, and any pending scheduled action (see Reading state).
53
+ - `get_crestron_time()` - the processor's current time (epoch ms + ISO 8601).
54
+ - `control_crestron_device(device_id, value, delay_ms?)` - set a device's value,
55
+ optionally after a delay.
56
+ - `set_crestron_devices([{device_id, value, duration_ms?, delay_ms?}, ...])` - apply a
57
+ scene in one call; each entry can fade (duration_ms, analog) and/or wait (delay_ms).
58
+ - `pulse_crestron_device(device_id, pulse_ms, delay_ms?)` - momentarily pulse a
59
+ DIGITAL device on then off (a simulated button press), optionally after a delay.
60
+ - `ramp_crestron_device(device_id, value, duration_ms)` - fade an ANALOG device
61
+ to a value over a duration.
62
+ - `cancel_crestron_device(device_id)` - stop a ramp/pulse in progress and clear any
63
+ pending delayed action on a device, without otherwise changing its value.
64
+ - `get_room_status(room_name)` - every device in a room with its state.
65
+ - `activate_crestron_license(license_key)` - license the processor with a key the user
66
+ provides (see Licensing / activation).
67
+ - `get_crestron_license_status()` - is it licensed now, is it a time-limited trial, how
68
+ much trial time remains, which trial of how many (`trial_seq` / `trial_max`), the MAC,
69
+ and a buy URL.
70
+ - `start_crestron_trial()` - start a free 7-day trial (no payment), if available.
71
+
72
+ ## Licensing / activation
73
+
74
+ The processor must be licensed before it accepts any control or query command. If it
75
+ isn't, every tool returns an error that explains the fix and includes the processor's
76
+ **activation code (its MAC)**. There are two ways forward, both done in chat:
77
+
78
+ - **Free trial** - call `start_crestron_trial()`. It contacts Solution AV's licensing server,
79
+ which mints a signed trial bound to the processor's MAC and counts it there (that count is
80
+ what enforces the 3-per-processor limit). No account, no card, nothing to paste; the only
81
+ data sent is the processor's MAC, and the signed trial is then stored on the processor. The
82
+ result tells you how many of the 3 one-week trials remain. If asked, describe it accurately
83
+ (issued and counted online by the licensing server, then stored on the box); do not claim
84
+ nothing happens online. Offer this first to a user just trying it out.
85
+ - **Buy a license** - the user gets a key at the buy link (perpetual, AUD $249 inc GST,
86
+ bound to the MAC), pastes it in chat, and you call `activate_crestron_license(license_key)`.
87
+
88
+ **Always hand over the buy link itself.** Whenever buying is on the table (an unlicensed
89
+ box, or trials exhausted), give the user the full purchase URL with their MAC already
90
+ filled in, exactly as it appears in the result (the unlicensed-error guidance, or `buy_url`
91
+ on a `trials_exhausted` result). Offer it up front: don't shorten it to the bare domain, and
92
+ don't wait for the user to ask "where do I buy". The MAC-prefilled link is the single most
93
+ useful thing you can give them, so lead with it.
94
+
95
+ On success, retry whatever they originally asked for. The license (or trial) is stored
96
+ **on the processor**, so it stays licensed for every client and across reboots. A purchased
97
+ key is not a secret (it only works on that one processor, bound to its MAC), so it's fine
98
+ to receive it in chat.
99
+
100
+ **Trials and nudging.** A trial is a license with an expiry; `get_crestron_license_status`
101
+ reports `time_limited`, `remaining_human` (e.g. "2 days"), and, when present, `trial_seq` /
102
+ `trial_max` (e.g. 2 and 3). Weave these into conversation **naturally and sparingly** - e.g.
103
+ "you're on your 2nd of 3 free weeks, about 5 days left" when it's relevant, and as the time
104
+ gets low (under ~2 days) gently offer to start the next free trial or buy a license. The
105
+ sequence is a soft conversion cue (the 3rd trial is the last free one - mention that when on
106
+ it), but nudge, don't nag. When a trial lapses, control commands return the same unlicensed error again; just
107
+ offer the next trial or the buy link. Either way the underlying AV keeps running - licensing
108
+ only gates this natural-language layer.
109
+
110
+ **Describing how it works.** You can't see inside the tools. The trial result names its own
111
+ provenance (`issued_by`, `data_sent`, `stored`), so answer "how/where" questions from those:
112
+ starting a trial fetches a signed license from Solution AV's licensing server (only the
113
+ processor MAC is sent), which the processor then verifies and stores. Do not invent internal
114
+ mechanism: there is no on-box "trial function", and it is not "all local / nothing online". If
115
+ asked something a tool result doesn't tell you, say you don't have the internals rather than
116
+ guessing.
117
+
118
+ ## Device model
119
+
120
+ Each device has an `id`, a human `name`, a `type`, an `access` mode, a `room`,
121
+ and (sometimes) a `category`.
122
+
123
+ IDs follow `<room>_<type><index>`, where type is:
124
+ - `d` = digital (on/off)
125
+ - `a` = analog (level 0-65535)
126
+ - `s` = serial (text)
127
+
128
+ The same index across types is usually the same physical function in different
129
+ facets, e.g. `Lounge_d3` (hallway light on/off), `Lounge_a3` (its dim level),
130
+ `Lounge_s3` (its text). Do not assume this, discover the actual devices and use
131
+ the exact ids you find.
132
+
133
+ ## Value formats
134
+
135
+ - **digital**: `"0"` (off) or `"1"` (on).
136
+ - **analog**: `"0"` to `"65535"`. Treat as 0-100%: 0% = `"0"`, 50% = `"32768"`,
137
+ 100% = `"65535"`. Convert a percentage the user asks for to this range.
138
+ - **serial**: arbitrary text (e.g. `"HDMI 1"`, `"play"`).
139
+
140
+ Values are always passed as strings.
141
+
142
+ ## Ramps
143
+
144
+ `ramp_crestron_device` is ANALOG only and fades smoothly over `duration_ms`
145
+ (e.g. "fade the lounge lights to 50% over 3 seconds" -> ramp `Lounge_a3` to
146
+ `32768` over `3000`). It also takes an optional `delay_ms` to start the fade later
147
+ ("fade down in 30 seconds, over 2 seconds" -> ramp with `duration_ms:2000,
148
+ delay_ms:30000`). For an instant analog change use `control_crestron_device`.
149
+ Ramping a digital or serial device is rejected.
150
+
151
+ ## Scenes
152
+
153
+ `set_crestron_devices` applies many devices in one call, and each entry can carry its
154
+ own timing: `duration_ms` (fade, analog only - ramps instead of snapping) and/or
155
+ `delay_ms` (wait before it runs). So a scene can mix instant, fading, and delayed
156
+ actions. Prefer it over many separate calls when the user describes a named scene or
157
+ a multi-device change. Example "movie night": fade the lights down over 2s while the
158
+ screen drops and the projector switches on ->
159
+ `set_crestron_devices([{device_id:"Lounge_a3", value:"6553", duration_ms:2000},
160
+ {device_id:"Lounge_d5", value:"1"}, {device_id:"Lounge_d6", value:"1"}])`.
161
+
162
+ ## Pulses (momentary presses)
163
+
164
+ `pulse_crestron_device` is DIGITAL only. It drives the device on for `pulse_ms`
165
+ then back off - a simulated momentary button press. Use it whenever the action is
166
+ a *press/trigger* rather than a steady on/off state: doorbells, gate/garage
167
+ triggers, projector or display power buttons, "tap"/"press"/"trigger" requests.
168
+ Use `control_crestron_device` instead when the user wants something to stay on or
169
+ off. Pulsing an analog or serial device is rejected. Typical `pulse_ms` is a few
170
+ hundred ms (e.g. `500`); follow the user if they specify a duration.
171
+
172
+ ## Delays
173
+
174
+ `control_crestron_device` and `pulse_crestron_device` take an optional `delay_ms`:
175
+ the processor performs the action that many milliseconds later (e.g. "turn the
176
+ porch light on in 30 seconds" -> `control_crestron_device("porch_d1", "1", 30000)`).
177
+ The tool returns as soon as the action is *scheduled*, not when it runs, so don't
178
+ immediately query to confirm a delayed action - it hasn't happened yet. For
179
+ fade-over-time use `ramp_crestron_device` (delay is "wait, then act"; ramp is
180
+ "act gradually").
181
+
182
+ **Last command wins.** Each device holds only ONE pending scheduled action. Any new
183
+ command on that device (an immediate set/pulse/ramp, another scheduled action, or a
184
+ cancel) replaces the pending one. So you don't need to track timers: to change your
185
+ mind about a scheduled action, just issue the new intent, or `cancel_crestron_device`
186
+ to drop it entirely. The slot is per device id, so a pending action on `Lounge_a3`
187
+ (dim) is independent of one on `Lounge_d3` (on/off).
188
+
189
+ ## Cancelling / stopping
190
+
191
+ `cancel_crestron_device(device_id)` halts whatever is happening on a device without
192
+ otherwise changing its value: it stops a ramp in progress (leaving the level where
193
+ it is), releases a pulse in progress to off, and clears any pending delayed action.
194
+ A device that is simply on (set high normally) stays on. Use it for "stop the fade",
195
+ "stop ringing the bell", "cancel that timer". It's safe to call when nothing is
196
+ running. To stop activity on every facet of a device, cancel each id (`_d`, `_a`,
197
+ `_s`) you care about.
198
+
199
+ ## Reading state
200
+
201
+ `query_crestron_device` returns the **live picture** of a device, not just a value:
202
+
203
+ - `state: "idle"` - resting at `value`.
204
+ - `state: "ramping"` - analog fade in progress: `target` and `completes_at` (epoch
205
+ ms) plus `remaining_ms` (how long until it arrives).
206
+ - `state: "pulsing"` - digital momentary in progress: `releases_at` plus `remaining_ms`.
207
+ - `pending: { action, value, fires_at, in_ms }` - a delayed set/pulse is scheduled
208
+ (present on top of any state; `in_ms` is how long until it fires).
209
+
210
+ So one read distinguishes "13020 and resting" from "13020 and climbing to 60000"
211
+ from "0 now but a pulse fires in 8s". To verify a cancel, catch a pulse, or time a
212
+ fade, read once and look at `state` / `remaining_ms` / `pending`, don't poll a clock.
213
+ The `*_at` fields are epoch ms matching `get_crestron_time`; `remaining_ms` / `in_ms`
214
+ are self-contained, so prefer them for "how long to wait" (no clock needed).
215
+
216
+ Value accuracy still depends on the installer wiring real device state back to the
217
+ processor's feedback. If `value` consistently reads `0`/blank right after successful
218
+ writes, feedback isn't wired on that system, control still works, but you can't
219
+ confirm state by reading; tell the user rather than assuming the command failed.
220
+ (`state`/`pending`/ramp/pulse info comes from the processor itself, so it's accurate
221
+ regardless of feedback wiring.)
222
+
223
+ ## Confirmation after an action
224
+
225
+ Every `control_crestron_device`, `ramp_crestron_device`, `pulse_crestron_device`, and
226
+ `set_crestron_devices` result already carries the outcome, so you usually do not need a
227
+ separate query:
228
+
229
+ - `status` - a short phrase: `"now 50000"`, `"fading to 0, ~10s left"`,
230
+ `"pulsing, releases in ~500ms"`, `"scheduled to set 1 in ~30s"`, or, on a
231
+ discrepancy, `"feedback reads 47000 (set 50000)"`.
232
+ - `confirmed` - the full live state (the same shape `query_crestron_device` returns).
233
+
234
+ Read these instead of assuming success. A mismatch in `status` (or `confirmed.value` not
235
+ matching what you set) is real signal: surface it rather than reporting "done".
236
+
237
+ One nuance for fades: the confirmation is taken the instant the action starts, so a ramp
238
+ reports the in-flight picture (`"fading to X, ~Ns left"`) and its target, not the final
239
+ resting value. That is deliberate (we never block for a multi-minute fade). To confirm a
240
+ long fade actually landed, re-query after `remaining_ms` (or `completes_at`) has passed;
241
+ the target in the status already tells you where it is heading.
242
+
243
+ Tuning (host-side): an immediate set waits a short settle (~350ms) for the feedback join
244
+ to move before reading back; ramps, pulses, and scheduled actions read instantly because
245
+ the processor tracks them itself. The host can set `CRESTRON_SETTLE_MS`, or disable the
246
+ read-back entirely with `CRESTRON_CONFIRM=0`.
247
+
248
+ ## Time
249
+
250
+ `get_crestron_time()` returns the processor's clock as `epoch_ms` and `iso`. You
251
+ rarely need it just to wait (use `remaining_ms` / `in_ms` from a query instead), but
252
+ it's there to turn an absolute `*_at` into a human time, or whenever you need the
253
+ system's real date/time.
254
+
255
+ ## Access modes
256
+
257
+ - `readwrite`: query and control both work.
258
+ - `readonly`: query works; control is rejected (`ACCESS_DENIED`).
259
+ - `writeonly`: control works; query is rejected.
260
+
261
+ Check `access` before trying to set a read-only device.
262
+
263
+ ## Recommended workflow
264
+
265
+ 1. `discover_crestron_system()` (or `list_crestron_rooms`) to learn the layout.
266
+ 2. `list_crestron_devices(room)` to get exact ids for the room you care about.
267
+ 3. Act with `control_crestron_device` / `ramp_crestron_device` using exact ids.
268
+ 4. The action result already confirms the outcome (`status` / `confirmed`); query
269
+ separately only to re-check a long fade after it completes, or to read an unrelated device.
270
+ 5. Prefer the user's words mapped to discovered ids; never invent an id.
271
+
272
+ ## Examples
273
+
274
+ - "Turn on the lounge hallway light" -> `control_crestron_device("Lounge_d3", "1")`
275
+ - "Dim the lounge lights to 50%" -> `control_crestron_device("Lounge_a3", "32768")`
276
+ - "Fade the bedroom lights to 75% over 4 seconds" ->
277
+ `ramp_crestron_device("Bedroom_a3", "49152", 4000)`
278
+ - "Ring the front doorbell" -> `pulse_crestron_device("Lounge_d8", 500)`
279
+ - "Tap the projector power button" -> `pulse_crestron_device("Lounge_d5", 300)`
280
+ - "Turn the porch light on in 30 seconds" ->
281
+ `control_crestron_device("Lounge_d1", "1", 30000)`
282
+ - "Movie night" (lights fade, screen + projector on) ->
283
+ `set_crestron_devices([{device_id:"Lounge_a3", value:"6553", duration_ms:2000},
284
+ {device_id:"Lounge_d5", value:"1"}, {device_id:"Lounge_d6", value:"1"}])`
285
+ - "Fade the lounge down in 30 seconds over 2 seconds" ->
286
+ `ramp_crestron_device("Lounge_a3", "0", 2000, 30000)`
287
+ - "Stop the lounge fade" -> `cancel_crestron_device("Lounge_a3")`
288
+ - "Actually, cancel that" (after scheduling something) ->
289
+ `cancel_crestron_device(<that device id>)`
290
+ - "What's on in the dining room?" -> `get_room_status("Dining")`
291
+ - "Set the bedroom source to HDMI 1" -> `control_crestron_device("Bedroom_s4", "HDMI 1")`
292
+
293
+ ## Errors
294
+
295
+ Tool results may include an `error` string or an `ERROR:<code>:<message>`. Common
296
+ ones: device not found (check the id with a list call), `ACCESS_DENIED` (wrong
297
+ access mode), `authentication required` (config issue, not yours to fix). Surface
298
+ the cause to the user rather than retrying blindly.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Solution AV Automation Pty Ltd
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.