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/PROTOCOL.md ADDED
@@ -0,0 +1,650 @@
1
+ # MCP-Crestron Protocol Specification v1.0
2
+
3
+ ## Overview
4
+
5
+ Text-based protocol for AI control of Crestron systems via MCP (Model Context Protocol).
6
+
7
+ **Design Principles:**
8
+ - Human-readable for debugging
9
+ - Simple to parse in both C# and Python
10
+ - Extensible for future features
11
+ - Clear error handling
12
+ - Low latency
13
+
14
+ ---
15
+
16
+ ## Connection
17
+
18
+ **Protocol:** TCP/IP
19
+ **Port:** 50794 (configurable)
20
+ **Encoding:** UTF-8
21
+ **Line Termination:** `\n` (LF)
22
+ **Max Message Size:** 8KB per message
23
+
24
+ ---
25
+
26
+ ## Message Format
27
+
28
+ ### Command Structure
29
+ ```
30
+ COMMAND[:PARAM1[:PARAM2[:...]]]
31
+ ```
32
+
33
+ ### Response Structure
34
+ ```
35
+ OK[:data]
36
+ ERROR:code:message
37
+ DATA:json_string
38
+ ```
39
+
40
+ **Rules:**
41
+ - Commands and parameters are case-insensitive
42
+ - Device IDs are case-sensitive
43
+ - The **value** is always the LAST field of a command/response, so it is parsed
44
+ positionally and **may contain colons** (e.g. `SET:dvd_s1:now playing 12:34`). Earlier
45
+ fields (command, device ID, room, category) must not contain colons; device IDs never do.
46
+ - `BATCH_SET` pairs are comma-separated and each split on its first colon, so a value in a
47
+ batch may contain colons but **not commas**.
48
+ - A value must not contain a raw newline (`\n` is the frame delimiter / line terminator).
49
+ - Empty parameters are represented by empty string between colons
50
+
51
+ ---
52
+
53
+ ## Core Commands
54
+
55
+ ### 1. HELLO
56
+ **Purpose:** Initial handshake, get system information
57
+ **Format:** `HELLO`
58
+
59
+ **Response:**
60
+ ```
61
+ OK:MCP-CRESTRON:1.0:PROCESSOR_ID
62
+ ```
63
+
64
+ **Example:**
65
+ ```
66
+ Client: HELLO
67
+ Server: OK:MCP-CRESTRON:1.0:PROC-CP4-01
68
+ ```
69
+
70
+ ---
71
+
72
+ ### 1a. AUTH
73
+ **Purpose:** Authenticate the connection when the server is in a non-open auth mode
74
+ **Format:** `AUTH:credential` (mode 1) or bare `AUTH` then `AUTH:<digest>` (mode 2)
75
+
76
+ - **Mode 0 (open):** no `AUTH` needed; any client may issue commands. `AUTH` is still accepted (returns `OK:AUTH`).
77
+ - **Mode 1 (password):** the client must send `AUTH:<password>` after `HELLO`; until it succeeds, every command except `HELLO` and `AUTH` is rejected with `ERROR:1009`. The password is sent in plaintext, so this is for a trusted LAN only.
78
+ - **Mode 2 (secure key):** runs over **TLS** (the processor listens with `SecureTCPServer`), so the whole channel is encrypted, not just the credential. On top of TLS the client authenticates by HMAC-SHA256 challenge-response: it sends a bare `AUTH` (no argument); the server replies `CHALLENGE:<nonce>` (a one-time random value); the client returns `AUTH:<digest>` where `digest = lowercase-hex( HMAC-SHA256(key, nonce) )`. The server recomputes with its copy of the key and compares in constant time. The nonce is single-use per connection (a failed attempt needs a fresh bare `AUTH`). The same `ERROR:1009` gate applies until authenticated. The processor presents `SecureTCPServer`'s built-in self-signed certificate; on a trusted LAN the client encrypts without verifying it (no PKI). Modes 0 and 1 are plaintext TCP.
79
+ - Authentication is per-connection and lasts the life of the TCP connection. The server's mode is set program-side (the `MCP_Server_Config` SIMPL+ module). In mode 2 the processor mints and persists the key and surfaces it on the module's `Key$` output; copy it to the client once. The client supplies its credential via `CRESTRON_AUTH` (mode-1 password) or `CRESTRON_KEY` (mode-2 key) as env vars or in `config.json`; `CRESTRON_KEY` takes precedence and also turns on TLS (set `CRESTRON_TLS=1` to force TLS without a key).
80
+
81
+ **Response:**
82
+ ```
83
+ CHALLENGE:<nonce> (mode 2, after a bare AUTH)
84
+ OK:AUTH (authenticated)
85
+ ERROR:1010:... (wrong credential)
86
+ ```
87
+
88
+ **Example (mode 1, password):**
89
+ ```
90
+ Client: HELLO
91
+ Server: OK:MCP-CRESTRON:1.0:PROC-CP4-01
92
+ Client: AUTH:s3cret
93
+ Server: OK:AUTH
94
+ ```
95
+
96
+ **Example (mode 2, secure key):**
97
+ ```
98
+ Client: HELLO
99
+ Server: OK:MCP-CRESTRON:1.0:PROC-CP4-01
100
+ Client: AUTH
101
+ Server: CHALLENGE:3eaae71c1c862bf21863c0d36cf06558
102
+ Client: AUTH:40c3180c8311a87dbf15f9e4b5dc0d1bcf4cbfb4f92d866f5810126a684ce9e1
103
+ Server: OK:AUTH
104
+ ```
105
+
106
+ ---
107
+
108
+ ### 1b. ACTIVATE (licensing)
109
+
110
+ **Format:** `ACTIVATE:<license>` (alias: `LICENSE:<license>`)
111
+
112
+ Licensing is **per-processor and stored on the box**. A license is an offline-verifiable token
113
+ signed by the vendor's RSA-2048 private key and bound to the processor's MAC. The processor embeds
114
+ only the public key; it verifies the signature, checks the license is for its own MAC, and (if
115
+ valid) **persists** it to the data store, so the box stays licensed across reboots and for every
116
+ client. No network call, no per-connection license exchange.
117
+
118
+ - The license is **installed once** via `ACTIVATE`, typically through the MCP client's
119
+ `activate_crestron_license` tool: the user pastes the key in chat and the LLM activates the box.
120
+ - `ACTIVATE` is allowed before authentication (like `HELLO`/`AUTH`), so an unlicensed box can be
121
+ activated. The license blob is base64url (no colons); whitespace in a pasted key is tolerated.
122
+ - When **license enforcement is on**, every command except `HELLO`, `AUTH`, and `ACTIVATE` is
123
+ rejected with `ERROR:1011` until the processor is licensed. The error names the processor's MAC
124
+ (its activation code) so the client can tell the user what to get a key for.
125
+ - Enforcement is enabled only if the processor's boot **RSA self-test passes** (so a crypto issue
126
+ disables the gate rather than bricking the box). The license is orthogonal to auth: a command
127
+ must pass **both** the license gate and the auth gate.
128
+
129
+ **Responses:**
130
+ ```
131
+ OK:ACTIVATED:<customer> (verified for this processor + persisted)
132
+ ERROR:1012:<reason> (bad signature, wrong MAC, malformed, or expired)
133
+ ERROR:1011:license required for this processor (MAC <mac>) (gated command while unlicensed)
134
+ ```
135
+
136
+ **Example:**
137
+ ```
138
+ Client: HELLO
139
+ Server: OK:MCP-CRESTRON:1.0:CRESTRON-MCP
140
+ Client: LIST_ROOMS
141
+ Server: ERROR:1011:license required for this processor (MAC 00107ff0ab17)
142
+ Client: ACTIVATE:eyJ2Ijox...<signed license>
143
+ Server: OK:ACTIVATED:Acme Integrators
144
+ Client: LIST_ROOMS
145
+ Server: DATA:[...]
146
+ ```
147
+
148
+ ### 1c. Trials (time-limited licenses)
149
+
150
+ A **trial** is just a normal signed license that carries an `expiry` (the vendor issues 7-day
151
+ trials, capped per MAC by the licensing server). It activates through the same `ACTIVATE` path and
152
+ persists the same way. Two differences from a perpetual license:
153
+
154
+ - The **signature is verified once** (at boot / on `ACTIVATE`); the **expiry is re-checked on every
155
+ command** (a cheap timestamp compare, no crypto). So a trial lapses on its own at the next command
156
+ with no timer, after which gated commands return `ERROR:1011` again.
157
+ - A lapsed trial only re-gates the MCP layer; the underlying control program is unaffected.
158
+
159
+ The cap (default 3 trials per processor) is enforced by the **licensing server**, not the box; the
160
+ box only knows "valid until `<expiry>`". The MCP client's `start_crestron_trial` tool fetches a
161
+ signed trial from the server and relays it via `ACTIVATE`.
162
+
163
+ ### 1d. LICENSE_STATUS
164
+
165
+ **Format:** `LICENSE_STATUS`
166
+
167
+ Reports the processor's current license state. Allowed before the gates (like `HELLO`), so a client
168
+ can detect an unlicensed box and read its MAC. Lets the client surface a trial's remaining time.
169
+
170
+ **Response:** `DATA:<json>`
171
+ ```json
172
+ { "licensed": true, "enforced": true, "time_limited": true,
173
+ "remaining_ms": 518400000, "expiry_epoch_ms": 1782432000000,
174
+ "trial_seq": 2, "trial_max": 3,
175
+ "customer": "Trial", "mac": "00107ff0ab17" }
176
+ ```
177
+ `time_limited` is true for a trial; `remaining_ms` / `expiry_epoch_ms` are present only then. For a
178
+ perpetual license `time_limited` is false and those fields are omitted. When unlicensed,
179
+ `licensed` is false and `mac` still reports the activation code. `trial_seq` / `trial_max` (which
180
+ trial this is, 1-based, and the total allowed) appear only on a server-minted trial that carries
181
+ them, so the LLM can say "trial 2 of 3".
182
+
183
+ Licenses are issued with the vendor `licgen` tool (`licgen sign --mac <mac> --customer "<name>"`),
184
+ which signs `{mac, customer, tier, issued, expiry?, trial_seq?, trial_max?}`. Perpetual licenses
185
+ omit `expiry` (and the trial fields); an `expiry` date, if present, is re-checked on each
186
+ boot/verify. The trial fields are appended after `expiry` and minted by the licensing server.
187
+
188
+ ---
189
+
190
+ ### 2. DISCOVER
191
+ **Purpose:** Get complete system capabilities
192
+ **Format:** `DISCOVER`
193
+
194
+ **Response:**
195
+ ```
196
+ DATA:{"processor_id":"PROC-CP4-01","version":"1.0","rooms":[...],"categories":[...],"total_devices":47}
197
+ ```
198
+
199
+ **JSON Structure:**
200
+ ```json
201
+ {
202
+ "processor_id": "PROC-CP4-01",
203
+ "version": "1.0",
204
+ "capabilities": ["query", "control", "pulse", "ramp", "delay", "cancel", "batch", "state", "time"],
205
+ "rooms": [
206
+ {
207
+ "id": "conf_rm_a",
208
+ "name": "Conference Room A",
209
+ "device_count": 12
210
+ }
211
+ ],
212
+ "categories": ["Lighting", "AV", "HVAC", "Shades"],
213
+ "total_devices": 47
214
+ }
215
+ ```
216
+
217
+ ---
218
+
219
+ ### 3. LIST_ROOMS
220
+ **Purpose:** Get list of all rooms
221
+ **Format:** `LIST_ROOMS`
222
+
223
+ **Response:**
224
+ ```
225
+ DATA:[{"id":"conf_rm_a","name":"Conference Room A","device_count":12},...]
226
+ ```
227
+
228
+ ---
229
+
230
+ ### 4. LIST_DEVICES
231
+ **Purpose:** Get all devices, optionally filtered by room or category
232
+ **Format:**
233
+ - `LIST_DEVICES` - All devices
234
+ - `LIST_DEVICES:room_id` - Devices in specific room
235
+ - `LIST_DEVICES:room_id:category` - Devices in room and category
236
+
237
+ **Response:**
238
+ ```
239
+ DATA:[{"id":"conf_rm_a_lights","type":"analog","name":"Lights",...},...]
240
+ ```
241
+
242
+ **JSON Device Structure:**
243
+ ```json
244
+ {
245
+ "id": "conf_rm_a_lights_level",
246
+ "name": "Conference Room A Lights Level",
247
+ "type": "analog",
248
+ "access": "readwrite",
249
+ "room": "Conference Room A",
250
+ "category": "Lighting",
251
+ "description": "Dimmer control for overhead lights",
252
+ "current_value": "32767",
253
+ "min_value": "0",
254
+ "max_value": "65535",
255
+ "unit": "level"
256
+ }
257
+ ```
258
+
259
+ **Device Types:**
260
+ - `digital` - Boolean/binary (0 or 1)
261
+ - `analog` - Unsigned short (0-65535)
262
+ - `serial` - Text string
263
+ - `string` - Text string
264
+
265
+ **Access Modes:**
266
+ - `readonly` - Can only query
267
+ - `writeonly` - Can only set
268
+ - `readwrite` - Can query and set
269
+
270
+ ---
271
+
272
+ ### 5. QUERY
273
+ **Purpose:** Get current state of one or more devices
274
+ **Format:**
275
+ - `QUERY:device_id` - Single device
276
+ - `QUERY:device_id1,device_id2,...` - Multiple devices (comma-separated)
277
+
278
+ **Response (single device):**
279
+ ```
280
+ OK:device_id:value
281
+ ```
282
+
283
+ **Response (multiple devices):**
284
+ ```
285
+ DATA:[{"id":"device_id1","value":"1"},{"id":"device_id2","value":"32767"}]
286
+ ```
287
+
288
+ **Examples:**
289
+ ```
290
+ Client: QUERY:conf_rm_a_lights_on
291
+ Server: OK:conf_rm_a_lights_on:1
292
+
293
+ Client: QUERY:conf_rm_a_lights_on,conf_rm_a_lights_level
294
+ Server: DATA:[{"id":"conf_rm_a_lights_on","value":"1"},{"id":"conf_rm_a_lights_level","value":"32767"}]
295
+ ```
296
+
297
+ ---
298
+
299
+ ### 5a. STATE
300
+ **Purpose:** Get the live state of one device (value + in-flight / pending activity)
301
+ **Format:** `STATE:device_id`
302
+
303
+ A richer single-device read than `QUERY`: the current value plus whether the device is **idle**,
304
+ **ramping** (analog: `target` + `completes_at`), or **pulsing** (digital: `releases_at`), and any
305
+ **pending** scheduled action (a delayed `SET_AFTER`/`PULSE`: `action`, `value`, `fires_at`). All
306
+ `*_at` fields are **epoch milliseconds** (see `TIME`); `remaining_ms` / `in_ms` give the time left
307
+ directly so a client can decide how long to wait without correlating a clock. `value` is omitted
308
+ for a write-only device; `pending` is omitted when nothing is scheduled.
309
+
310
+ **Response:**
311
+ ```
312
+ DATA:{json}
313
+ ```
314
+
315
+ **Examples:**
316
+ ```
317
+ Client: STATE:lounge_a3
318
+ Server: DATA:{"device_id":"lounge_a3","value":"13020","type":"analog","state":"ramping","target":"60000","completes_at":1718671410000,"remaining_ms":4200}
319
+
320
+ Client: STATE:lounge_d8
321
+ Server: DATA:{"device_id":"lounge_d8","value":"1","type":"digital","state":"pulsing","releases_at":1718671406380,"remaining_ms":380}
322
+
323
+ Client: STATE:porch_d1
324
+ Server: DATA:{"device_id":"porch_d1","value":"0","type":"digital","state":"idle","pending":{"action":"pulse","value":"500","fires_at":1718671413600,"in_ms":7600}}
325
+
326
+ Client: STATE:lounge_a3
327
+ Server: DATA:{"device_id":"lounge_a3","value":"13020","type":"analog","state":"idle"}
328
+ ```
329
+
330
+ ---
331
+
332
+ ### 6. SET
333
+ **Purpose:** Set device value
334
+ **Format:** `SET:device_id:value`
335
+
336
+ **Value Types:**
337
+ - Digital: `0` or `1`
338
+ - Analog: `0` to `65535`
339
+ - Serial/String: any text; may contain colons (the value is the last field), but not a raw newline
340
+
341
+ **Response:**
342
+ ```
343
+ OK:device_id:value_set
344
+ ```
345
+
346
+ **Examples:**
347
+ ```
348
+ Client: SET:conf_rm_a_lights_on:1
349
+ Server: OK:conf_rm_a_lights_on:1
350
+
351
+ Client: SET:conf_rm_a_lights_level:49151
352
+ Server: OK:conf_rm_a_lights_level:49151
353
+
354
+ Client: SET:conf_rm_a_display_source:HDMI 1
355
+ Server: OK:conf_rm_a_display_source:HDMI 1
356
+ ```
357
+
358
+ ---
359
+
360
+ ### 6b. SET_AFTER
361
+ **Purpose:** Set a device value after a delay (deferred set)
362
+ **Format:** `SET_AFTER:device_id:delay_ms:value`
363
+
364
+ - Works for any device type. `value` is the **last field** (may contain colons); `delay_ms` is numeric.
365
+ - The processor validates the device + access immediately and returns `OK` right away; the
366
+ actual set runs `delay_ms` milliseconds later (a value error at fire time is logged on the
367
+ processor console, not returned).
368
+ - `delay_ms` of `0` is equivalent to a plain `SET`.
369
+
370
+ **Response:**
371
+ ```
372
+ OK:device_id:value
373
+ ```
374
+
375
+ **Example:**
376
+ ```
377
+ Client: SET_AFTER:porch_light_on:30000:1
378
+ Server: OK:porch_light_on:1
379
+ ```
380
+
381
+ ---
382
+
383
+ ### 6a. RAMP
384
+ **Purpose:** Smoothly ramp (fade) an analog device to a value over a duration
385
+ **Format:** `RAMP:device_id:value:duration_ms[:delay_ms]`
386
+
387
+ - **Analog devices only.** Digital/serial return an error.
388
+ - `value` is the target `0`-`65535`; `duration_ms` is the ramp time in milliseconds.
389
+ - Optional `delay_ms` (default 0): start the fade after that wait. A delayed ramp occupies
390
+ the device's pending-action slot (see Scheduling and supersede); the `OK` means it's scheduled.
391
+ - All fields are numeric, so the line is split positionally on every colon.
392
+ - The ramp runs on the processor (a timer steps the output from its last commanded
393
+ value to the target); the `OK` response means the ramp has started (or is scheduled).
394
+ - A subsequent `SET`/`RAMP`/`CANCEL` on the same device cancels an in-progress or pending ramp.
395
+
396
+ **Response:**
397
+ ```
398
+ OK:device_id:target_value
399
+ ```
400
+
401
+ **Examples:**
402
+ ```
403
+ Client: RAMP:conf_rm_a_lights_level:32767:3000
404
+ Server: OK:conf_rm_a_lights_level:32767
405
+
406
+ Client: RAMP:conf_rm_a_lights_on:1:1000
407
+ Server: ERROR:1001:Device 'conf_rm_a_lights_on' is not analog; only analog devices ramp
408
+ ```
409
+
410
+ ---
411
+
412
+ ### 6c. PULSE
413
+ **Purpose:** Momentarily pulse a digital device (a simulated button press)
414
+ **Format:** `PULSE:device_id:pulse_ms[:delay_ms]`
415
+
416
+ - **Digital devices only.** Analog/serial return an error.
417
+ - After an optional `delay_ms` (default 0) the processor drives the output **high**, holds it
418
+ for `pulse_ms` milliseconds, then drives it **low**. All fields are numeric.
419
+ - Runs on the processor (a timer drives the high/low writes); the `OK` response means the
420
+ pulse has been scheduled/started.
421
+ - A subsequent `SET` or `PULSE` on the same device cancels a pulse in progress.
422
+
423
+ **Response:**
424
+ ```
425
+ OK:device_id:pulse_ms
426
+ ```
427
+
428
+ **Examples:**
429
+ ```
430
+ Client: PULSE:conf_rm_a_doorbell:500
431
+ Server: OK:conf_rm_a_doorbell:500
432
+
433
+ Client: PULSE:conf_rm_a_gate:1000:5000
434
+ Server: OK:conf_rm_a_gate:1000
435
+
436
+ Client: PULSE:conf_rm_a_lights_level:500
437
+ Server: ERROR:1001:Device 'conf_rm_a_lights_level' is not digital; only digital devices pulse
438
+ ```
439
+
440
+ ---
441
+
442
+ ### 6d. CANCEL
443
+ **Purpose:** Stop activity on a device and clear its pending scheduled action
444
+ **Format:** `CANCEL:device_id`
445
+
446
+ - Clears the device's pending scheduled action (a delayed `SET_AFTER` or delayed `PULSE`).
447
+ - **Digital:** if a pulse is in progress (line held high *by the pulse*), it is released to **off**.
448
+ A line that is high from a plain `SET` (no pulse running) is left untouched.
449
+ - **Analog:** a ramp in progress is **stopped where it is** (the level is not snapped anywhere).
450
+ - Otherwise it changes nothing; it is safe to call when nothing is pending/running.
451
+
452
+ **Response:**
453
+ ```
454
+ OK:device_id:cancelled
455
+ ```
456
+
457
+ **Example:**
458
+ ```
459
+ Client: CANCEL:conf_rm_a_lights_level
460
+ Server: OK:conf_rm_a_lights_level:cancelled
461
+ ```
462
+
463
+ ---
464
+
465
+ ### Scheduling and supersede (SET_AFTER / PULSE / RAMP delay)
466
+
467
+ Each device has **one** pending scheduled-action slot (a delayed `SET_AFTER`, `PULSE`, or `RAMP`).
468
+ **Last command wins:** any new command on a device (an immediate `SET`/`PULSE`/`RAMP`/`BATCH_SET`, a
469
+ new delayed action, or a `CANCEL`) replaces/clears that device's pending action. Scheduling a future action does **not**
470
+ abort an action already executing right now (e.g. a pulse mid-press finishes, then a separately
471
+ scheduled action fires later); only immediate commands and `CANCEL` interrupt an in-flight action.
472
+ The slot is per **device id**, so the `_d` / `_a` / `_s` facets of one physical device are
473
+ independent.
474
+
475
+ ---
476
+
477
+ ### 7. GET_INFO
478
+ **Purpose:** Get detailed information about a specific device
479
+ **Format:** `GET_INFO:device_id`
480
+
481
+ **Response:**
482
+ ```
483
+ DATA:{"id":"conf_rm_a_lights_level","type":"analog","name":"Conference Room A Lights","room":"Conference Room A","category":"Lighting","access":"readwrite","current_value":"32767","min_value":"0","max_value":"65535"}
484
+ ```
485
+
486
+ ---
487
+
488
+ ### 8. BATCH_SET
489
+ **Purpose:** Set multiple devices in one command
490
+ **Format:** `BATCH_SET:device_id1:value1,device_id2:value2,...`
491
+
492
+ **Response:**
493
+ ```
494
+ DATA:[{"id":"device_id1","success":true},{"id":"device_id2","success":true}]
495
+ ```
496
+
497
+ **Example:**
498
+ ```
499
+ Client: BATCH_SET:conf_rm_a_lights_on:1,conf_rm_a_lights_level:49151
500
+ Server: DATA:[{"id":"conf_rm_a_lights_on","success":true,"value":"1"},{"id":"conf_rm_a_lights_level","success":true,"value":"49151"}]
501
+ ```
502
+
503
+ ---
504
+
505
+ ### 9. PING
506
+ **Purpose:** Keep-alive / connectivity check
507
+ **Format:** `PING`
508
+
509
+ **Response:**
510
+ ```
511
+ OK:PONG
512
+ ```
513
+
514
+ ---
515
+
516
+ ### 10. TIME
517
+ **Purpose:** Get the processor's current time in a machine-friendly form
518
+ **Format:** `TIME`
519
+
520
+ Reads the processor's own clock. `epoch_ms` is UTC epoch milliseconds (use it to correlate the
521
+ `*_at` fields from `STATE`); `iso` is local ISO 8601 with offset for human legibility.
522
+
523
+ **Response:**
524
+ ```
525
+ DATA:{"epoch_ms":1718671406000,"iso":"2026-06-18T12:43:26+10:00"}
526
+ ```
527
+
528
+ ---
529
+
530
+ ## Error Responses
531
+
532
+ **Format:** `ERROR:code:message`
533
+
534
+ ### Error Codes
535
+
536
+ | Code | Name | Description |
537
+ |------|------|-------------|
538
+ | 1000 | UNKNOWN_COMMAND | Command not recognized |
539
+ | 1001 | INVALID_PARAMETERS | Wrong number or format of parameters |
540
+ | 1002 | DEVICE_NOT_FOUND | Device ID does not exist |
541
+ | 1003 | ACCESS_DENIED | Device is read-only or write-only |
542
+ | 1004 | VALUE_OUT_OF_RANGE | Value exceeds device limits |
543
+ | 1005 | DEVICE_OFFLINE | Device is not responding |
544
+ | 1006 | INTERNAL_ERROR | Server-side error |
545
+ | 1007 | PARSE_ERROR | Could not parse command |
546
+ | 1008 | TIMEOUT | Command execution timeout |
547
+ | 1009 | AUTH_REQUIRED | Authentication required before this command |
548
+ | 1010 | AUTH_FAILED | Authentication credential rejected |
549
+ | 1011 | LICENSE_REQUIRED | Processor not licensed; activate it before this command |
550
+ | 1012 | LICENSE_INVALID | License rejected (bad signature, wrong MAC, malformed, or expired) |
551
+
552
+ **Examples:**
553
+ ```
554
+ ERROR:1002:Device 'invalid_device' not found
555
+ ERROR:1003:Device 'temp_sensor' is read-only
556
+ ERROR:1004:Value 70000 out of range (0-65535) for device 'lights'
557
+ ```
558
+
559
+ ---
560
+
561
+ ## Connection Lifecycle
562
+
563
+ ### 1. Client Connects
564
+ ```
565
+ [TCP connection established]
566
+ ```
567
+
568
+ ### 2. Handshake
569
+ ```
570
+ Client: HELLO
571
+ Server: OK:MCP-CRESTRON:1.0:PROC-CP4-01
572
+ ```
573
+
574
+ ### 3. Discovery (optional)
575
+ ```
576
+ Client: DISCOVER
577
+ Server: DATA:{...full system capabilities...}
578
+ ```
579
+
580
+ ### 4. Normal Operations
581
+ ```
582
+ Client: QUERY:conf_rm_a_lights_on
583
+ Server: OK:conf_rm_a_lights_on:1
584
+
585
+ Client: SET:conf_rm_a_lights_level:49151
586
+ Server: OK:conf_rm_a_lights_level:49151
587
+ ```
588
+
589
+ ### 5. Disconnect
590
+ ```
591
+ [TCP connection closed by either party]
592
+ ```
593
+
594
+ ---
595
+
596
+ ## Best Practices
597
+
598
+ ### For Crestron Implementation
599
+ - Validate all incoming commands before execution
600
+ - Return errors immediately for invalid requests
601
+ - Use thread-safe device registry
602
+ - Implement command timeouts (5 seconds default)
603
+ - Log all SET commands for audit trail
604
+
605
+ ### For MCP Client Implementation
606
+ - Send HELLO immediately after connection
607
+ - Cache DISCOVER results to minimize queries
608
+ - Implement reconnection logic with exponential backoff
609
+ - Use PING for keep-alive (every 30 seconds)
610
+ - Handle ERROR responses gracefully
611
+
612
+ ### Performance
613
+ - Batch queries when possible (QUERY with multiple devices)
614
+ - Batch sets for scene activation (BATCH_SET)
615
+ - Keep device IDs short but descriptive
616
+ - Limit LIST_DEVICES queries (cache results)
617
+
618
+ ---
619
+
620
+ ## Future Extensions (v2.0)
621
+
622
+ Potential additions for future versions:
623
+ - `SUBSCRIBE:device_id` - Real-time change notifications
624
+ - `UNSUBSCRIBE:device_id` - Stop notifications
625
+ - `EVENT:device_id:old_value:new_value` - Push notifications
626
+ - `AUTH:username:password` - Authentication
627
+ - `MACRO:macro_name` - Execute predefined macros
628
+ - `HISTORY:device_id:duration` - Get device value history
629
+
630
+ ---
631
+
632
+ ## Security Considerations
633
+
634
+ **v1.0 Security Model:**
635
+ - No built-in authentication (assumes secure local network)
636
+ - Access control enforced at device level (readonly/writeonly)
637
+ - Command validation prevents malformed requests
638
+ - Rate limiting recommended at firewall/network level
639
+
640
+ **Recommendations:**
641
+ - Run on isolated control network
642
+ - Use firewall to restrict access to MCP port
643
+ - Consider VPN for remote access
644
+ - Implement authentication in v2.0
645
+
646
+ ---
647
+
648
+ ## Version History
649
+
650
+ - **v1.0** - Initial specification