@owine/unifi-protect-mcp 2.14.2 → 2.15.0
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/README.md +3 -3
- package/dist/client.d.ts +7 -1
- package/dist/client.js +17 -6
- package/dist/schemas/cameras.js +2 -1
- package/dist/schemas/devices.d.ts +0 -10
- package/dist/schemas/devices.js +34 -25
- package/dist/schemas/misc.d.ts +38 -17
- package/dist/schemas/misc.js +20 -13
- package/dist/schemas/pos.d.ts +1 -1
- package/dist/schemas/pos.js +1 -1
- package/dist/tools/alarm-manager.d.ts +3 -0
- package/dist/tools/alarm-manager.js +42 -0
- package/dist/tools/arm-profiles.js +19 -11
- package/dist/tools/cameras.js +62 -16
- package/dist/tools/devices.d.ts +1 -0
- package/dist/tools/devices.js +21 -17
- package/dist/tools/files.js +41 -33
- package/dist/tools/index.js +2 -0
- package/dist/tools/liveviews.js +27 -9
- package/dist/tools/pos.d.ts +4 -2
- package/dist/tools/pos.js +5 -3
- package/dist/tools/subscriptions.js +2 -2
- package/dist/tools/system.js +1 -1
- package/dist/tools/users.js +2 -2
- package/package.json +6 -6
|
@@ -6,10 +6,21 @@ const responses_js_1 = require("../utils/responses.js");
|
|
|
6
6
|
const safety_js_1 = require("../utils/safety.js");
|
|
7
7
|
const url_js_1 = require("../utils/url.js");
|
|
8
8
|
const misc_js_1 = require("../schemas/misc.js");
|
|
9
|
-
const
|
|
9
|
+
const activationDelay = zod_1.z
|
|
10
|
+
.union([zod_1.z.literal(0), zod_1.z.literal(60000), zod_1.z.literal(300000), zod_1.z.literal(600000)])
|
|
11
|
+
.describe("Activation delay in milliseconds. Allowed: 0 (none), 60000 (1m), 300000 (5m), 600000 (10m).");
|
|
12
|
+
const armProfileFields = {
|
|
13
|
+
name: zod_1.z.string().min(1).max(255).describe("Name of the arm profile"),
|
|
14
|
+
automations: zod_1.z.array(zod_1.z.string()).describe("List of automation IDs associated with this arm profile"),
|
|
15
|
+
schedules: zod_1.z
|
|
16
|
+
.array(zod_1.z.record(zod_1.z.string(), zod_1.z.unknown()))
|
|
17
|
+
.describe('List of arm schedules, e.g. [{ start: "0 22 * * *", end: "0 6 * * *" }] (cron strings)'),
|
|
18
|
+
recordEverything: zod_1.z.boolean().describe("Whether to record everything when this arm profile is active"),
|
|
19
|
+
activationDelay,
|
|
20
|
+
};
|
|
10
21
|
function registerArmProfileTools(server, client, readOnly) {
|
|
11
22
|
server.registerTool("protect_list_arm_profiles", {
|
|
12
|
-
description: "List all arm profiles (only available when using the local alarm manager — the standalone NVR alarm system, not Protect cloud alerts). Returns array; each profile (7.
|
|
23
|
+
description: "List all arm profiles (only available when using the local alarm manager — the standalone NVR alarm system, not Protect cloud alerts). Returns array; each profile (7.3.70 docs): id, name, automations[], creator, schedules[], recordEverything, activationDelay (0 | 60000 | 300000 | 600000), createdAt, updatedAt.",
|
|
13
24
|
outputSchema: misc_js_1.armProfileListOutputSchema,
|
|
14
25
|
annotations: safety_js_1.READ_ONLY,
|
|
15
26
|
}, async () => {
|
|
@@ -26,13 +37,7 @@ function registerArmProfileTools(server, client, readOnly) {
|
|
|
26
37
|
server.registerTool("protect_create_arm_profile", {
|
|
27
38
|
description: "Create a new arm profile. Only available when using local alarm manager.",
|
|
28
39
|
inputSchema: {
|
|
29
|
-
|
|
30
|
-
automations: zod_1.z.array(zod_1.z.string()).describe("List of automation IDs associated with this arm profile"),
|
|
31
|
-
schedules: zod_1.z.array(zod_1.z.record(zod_1.z.string(), zod_1.z.unknown())).describe("List of arm schedules"),
|
|
32
|
-
recordEverything: zod_1.z.boolean().describe("Whether to record everything when this arm profile is active"),
|
|
33
|
-
activationDelay: zod_1.z
|
|
34
|
-
.union([zod_1.z.literal(0), zod_1.z.literal(60000), zod_1.z.literal(300000), zod_1.z.literal(600000)])
|
|
35
|
-
.describe("Activation delay in milliseconds. Allowed: 0 (none), 60000 (1m), 300000 (5m), 600000 (10m)."),
|
|
40
|
+
...armProfileFields,
|
|
36
41
|
dryRun: zod_1.z.boolean().optional().describe("If true, return what would happen without making changes"),
|
|
37
42
|
},
|
|
38
43
|
outputSchema: misc_js_1.armProfileOutputSchema,
|
|
@@ -75,8 +80,11 @@ function registerArmProfileTools(server, client, readOnly) {
|
|
|
75
80
|
inputSchema: {
|
|
76
81
|
id: zod_1.z.string().describe("Arm profile ID"),
|
|
77
82
|
settings: zod_1.z
|
|
78
|
-
.
|
|
79
|
-
.
|
|
83
|
+
.object(armProfileFields)
|
|
84
|
+
.partial()
|
|
85
|
+
.passthrough()
|
|
86
|
+
.refine((o) => Object.keys(o).length > 0, "settings must include at least one field")
|
|
87
|
+
.describe("Partial arm profile settings to update"),
|
|
80
88
|
dryRun: zod_1.z.boolean().optional().describe("If true, return what would happen without making changes"),
|
|
81
89
|
},
|
|
82
90
|
outputSchema: misc_js_1.armProfileOutputSchema,
|
package/dist/tools/cameras.js
CHANGED
|
@@ -9,7 +9,7 @@ const cameras_js_1 = require("../schemas/cameras.js");
|
|
|
9
9
|
function registerCameraTools(server, client, readOnly) {
|
|
10
10
|
// --- Read-only tools (always registered) ---
|
|
11
11
|
server.registerTool("protect_list_cameras", {
|
|
12
|
-
description: "List all cameras managed by UniFi Protect. Returns array; each camera includes (Integration API 7.3.
|
|
12
|
+
description: "List all cameras managed by UniFi Protect. Returns array; each camera includes (Integration API 7.3.70-verified fields): id, mac, name, modelKey, state (CONNECTED/DISCONNECTED), type (hardware model name, e.g. UVC G6 Turret), guid (hardware MODEL GUID — every camera of the same model returns the same value, so it is NOT a per-camera identifier; use id), activePatrolSlot, hasPackageCamera, hdrType, isMicEnabled, micVolume, videoMode, featureFlags (hasHdr, hasMic, hasSpeaker, hasLedStatus, smartDetectTypes[], smartDetectAudioTypes[], videoModes[], supportFullHdSnapshot), lcdMessage (type, resetAt, text), ledSettings (isEnabled, floodLed, welcomeLed), osdSettings (isNameEnabled, isDateEnabled, isLogoEnabled, isDebugEnabled, overlayLocation), smartDetectSettings (objectTypes[], audioTypes[]). The Integration API does NOT expose recording state, motion timestamps, connection/last-seen, firmware, host, or per-channel stream config.",
|
|
13
13
|
outputSchema: cameras_js_1.cameraListOutputSchema,
|
|
14
14
|
annotations: safety_js_1.READ_ONLY,
|
|
15
15
|
}, async () => {
|
|
@@ -22,7 +22,7 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
22
22
|
}
|
|
23
23
|
});
|
|
24
24
|
server.registerTool("protect_get_camera", {
|
|
25
|
-
description: "Get details for a specific camera by ID. The Protect Integration API returns the SAME field set as protect_list_cameras entries (id, mac, name, modelKey, state, type, guid, activePatrolSlot, hasPackageCamera, hdrType, isMicEnabled, micVolume, videoMode, featureFlags, lcdMessage, ledSettings, osdSettings, smartDetectSettings) — there is no extended/by-id-only payload (confirmed live on 7.3.
|
|
25
|
+
description: "Get details for a specific camera by ID. The Protect Integration API returns the SAME field set as protect_list_cameras entries (id, mac, name, modelKey, state, type, guid, activePatrolSlot, hasPackageCamera, hdrType, isMicEnabled, micVolume, videoMode, featureFlags, lcdMessage, ledSettings, osdSettings, smartDetectSettings) — there is no extended/by-id-only payload (confirmed live on 7.3.70). Recording state, motion events, zones, and channel/RTSP config are NOT exposed by this API surface.",
|
|
26
26
|
inputSchema: { id: zod_1.z.string().describe("Camera ID") },
|
|
27
27
|
outputSchema: cameras_js_1.cameraOutputSchema,
|
|
28
28
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -36,13 +36,13 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
36
36
|
}
|
|
37
37
|
});
|
|
38
38
|
server.registerTool("protect_get_snapshot", {
|
|
39
|
-
description: "Get a JPEG snapshot from a camera. Returns a base64-encoded image/jpeg (rendered directly by MCP clients). Use highQuality=true
|
|
39
|
+
description: "Get a JPEG snapshot from a camera. Returns a base64-encoded image/jpeg (rendered directly by MCP clients). Use highQuality=true to force a 1080p-or-higher capture; set channel=package to capture from the secondary package camera on doorbells with hasPackageCamera=true. Fails with HTTP 503 when the camera is offline or unreachable.",
|
|
40
40
|
inputSchema: {
|
|
41
41
|
id: zod_1.z.string().describe("Camera ID"),
|
|
42
42
|
highQuality: zod_1.z
|
|
43
43
|
.boolean()
|
|
44
44
|
.optional()
|
|
45
|
-
.describe("If true,
|
|
45
|
+
.describe("If true, force a 1080p-or-higher resolution snapshot"),
|
|
46
46
|
channel: zod_1.z
|
|
47
47
|
.enum(["main", "package"])
|
|
48
48
|
.optional()
|
|
@@ -76,7 +76,7 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
78
|
server.registerTool("protect_get_rtsp_streams", {
|
|
79
|
-
description: "Get active RTSPS stream sessions for a camera. Returns
|
|
79
|
+
description: "Get active RTSPS stream sessions for a camera. Returns: high, medium, low, package — each an rtsps:// URL, or null (or absent) when no session is active at that quality. Use protect_create_rtsp_stream to start one.",
|
|
80
80
|
inputSchema: { id: zod_1.z.string().describe("Camera ID") },
|
|
81
81
|
outputSchema: cameras_js_1.rtspStreamOutputSchema,
|
|
82
82
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -93,12 +93,38 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
93
93
|
if (readOnly)
|
|
94
94
|
return;
|
|
95
95
|
server.registerTool("protect_update_camera", {
|
|
96
|
-
description: "Update camera settings (partial update via PATCH)",
|
|
96
|
+
description: "Update camera settings (partial update via PATCH). Returns: the full updated camera (same fields as protect_get_camera).",
|
|
97
97
|
inputSchema: {
|
|
98
98
|
id: zod_1.z.string().describe("Camera ID"),
|
|
99
99
|
settings: zod_1.z
|
|
100
|
-
.
|
|
101
|
-
|
|
100
|
+
.object({
|
|
101
|
+
name: zod_1.z.string().optional().describe("Camera name"),
|
|
102
|
+
osdSettings: zod_1.z
|
|
103
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("On-screen display (isNameEnabled, isDateEnabled, isLogoEnabled, isDebugEnabled, overlayLocation)"),
|
|
106
|
+
ledSettings: zod_1.z
|
|
107
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
108
|
+
.optional()
|
|
109
|
+
.describe("LED settings (isEnabled, welcomeLed, floodLed)"),
|
|
110
|
+
lcdMessage: zod_1.z
|
|
111
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
112
|
+
.optional()
|
|
113
|
+
.describe('Doorbell LCD message (type, resetAt, text), e.g. { type: "DO_NOT_DISTURB", resetAt: 0 }'),
|
|
114
|
+
micVolume: zod_1.z.number().min(0).max(100).optional().describe("Mic volume (0-100)"),
|
|
115
|
+
videoMode: zod_1.z
|
|
116
|
+
.enum(["default", "highFps", "sport", "slowShutter", "lprReflex", "lprNoneReflex"])
|
|
117
|
+
.optional()
|
|
118
|
+
.describe("Video mode"),
|
|
119
|
+
hdrType: zod_1.z.enum(["auto", "on", "off"]).optional().describe("HDR mode"),
|
|
120
|
+
smartDetectSettings: zod_1.z
|
|
121
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
122
|
+
.optional()
|
|
123
|
+
.describe("Smart detection (objectTypes[], audioTypes[])"),
|
|
124
|
+
})
|
|
125
|
+
.passthrough()
|
|
126
|
+
.refine((o) => Object.keys(o).length > 0, "settings must include at least one field")
|
|
127
|
+
.describe("Partial camera settings to update"),
|
|
102
128
|
dryRun: zod_1.z
|
|
103
129
|
.boolean()
|
|
104
130
|
.optional()
|
|
@@ -119,12 +145,13 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
119
145
|
}
|
|
120
146
|
});
|
|
121
147
|
server.registerTool("protect_create_rtsp_stream", {
|
|
122
|
-
description: "Create an RTSPS stream session for a camera",
|
|
148
|
+
description: "Create an RTSPS stream session for a camera. Returns: one key per requested quality (high, medium, low, package), each an rtsps:// URL. The package quality is only available on cameras with hasPackageCamera=true.",
|
|
123
149
|
inputSchema: {
|
|
124
150
|
id: zod_1.z.string().describe("Camera ID"),
|
|
125
151
|
qualities: zod_1.z
|
|
126
152
|
.array(zod_1.z.enum(["high", "medium", "low", "package"]))
|
|
127
|
-
.
|
|
153
|
+
.min(1)
|
|
154
|
+
.describe("Quality levels to include in the RTSPS stream (at least one)"),
|
|
128
155
|
dryRun: zod_1.z
|
|
129
156
|
.boolean()
|
|
130
157
|
.optional()
|
|
@@ -152,7 +179,8 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
152
179
|
id: zod_1.z.string().describe("Camera ID"),
|
|
153
180
|
qualities: zod_1.z
|
|
154
181
|
.array(zod_1.z.enum(["high", "medium", "low", "package"]))
|
|
155
|
-
.
|
|
182
|
+
.min(1)
|
|
183
|
+
.describe("Quality levels to remove from the RTSPS stream (at least one)"),
|
|
156
184
|
dryRun: zod_1.z
|
|
157
185
|
.boolean()
|
|
158
186
|
.optional()
|
|
@@ -161,10 +189,16 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
161
189
|
annotations: safety_js_1.DESTRUCTIVE,
|
|
162
190
|
}, async ({ id, qualities, dryRun }) => {
|
|
163
191
|
try {
|
|
192
|
+
// Protect expects repeated keys (qualities=high&qualities=medium), not
|
|
193
|
+
// a comma-joined value — safePath would encode the comma as %2C.
|
|
194
|
+
const params = new URLSearchParams();
|
|
195
|
+
for (const quality of qualities)
|
|
196
|
+
params.append("qualities", quality);
|
|
197
|
+
const url = (0, url_js_1.safePath) `/cameras/${id}/rtsps-stream` + `?${params.toString()}`;
|
|
164
198
|
if (dryRun) {
|
|
165
|
-
return (0, safety_js_1.formatDryRun)("DELETE",
|
|
199
|
+
return (0, safety_js_1.formatDryRun)("DELETE", url);
|
|
166
200
|
}
|
|
167
|
-
const data = await client.delete(
|
|
201
|
+
const data = await client.delete(url);
|
|
168
202
|
return (0, responses_js_1.formatSuccess)(data);
|
|
169
203
|
}
|
|
170
204
|
catch (err) {
|
|
@@ -195,19 +229,27 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
195
229
|
}
|
|
196
230
|
});
|
|
197
231
|
server.registerTool("protect_disable_mic", {
|
|
198
|
-
description: "IRREVERSIBLE: Permanently disable the microphone on a camera. Can only be re-enabled by factory resetting the camera.",
|
|
232
|
+
description: "IRREVERSIBLE: Permanently disable the microphone on a camera. Can only be re-enabled by factory resetting the camera. Returns: the full updated camera (same fields as protect_get_camera).",
|
|
199
233
|
inputSchema: {
|
|
200
234
|
id: zod_1.z.string().describe("Camera ID"),
|
|
201
235
|
confirm: zod_1.z
|
|
202
236
|
.literal(true)
|
|
203
237
|
.describe("Must be true to confirm this irreversible action"),
|
|
238
|
+
dryRun: zod_1.z
|
|
239
|
+
.boolean()
|
|
240
|
+
.optional()
|
|
241
|
+
.describe("If true, return what would happen without making changes"),
|
|
204
242
|
},
|
|
243
|
+
outputSchema: cameras_js_1.cameraOutputSchema,
|
|
205
244
|
annotations: safety_js_1.DESTRUCTIVE,
|
|
206
|
-
}, async ({ id, confirm }) => {
|
|
245
|
+
}, async ({ id, confirm, dryRun }) => {
|
|
207
246
|
const denied = (0, safety_js_1.requireConfirmation)(confirm, "permanently disable the microphone");
|
|
208
247
|
if (denied)
|
|
209
248
|
return denied;
|
|
210
249
|
try {
|
|
250
|
+
if (dryRun) {
|
|
251
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/disable-mic-permanently`);
|
|
252
|
+
}
|
|
211
253
|
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/disable-mic-permanently`);
|
|
212
254
|
return (0, responses_js_1.formatSuccess)(data);
|
|
213
255
|
}
|
|
@@ -264,7 +306,11 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
264
306
|
description: "Move camera PTZ to a preset position",
|
|
265
307
|
inputSchema: {
|
|
266
308
|
id: zod_1.z.string().describe("Camera ID"),
|
|
267
|
-
slot: zod_1.z
|
|
309
|
+
slot: zod_1.z
|
|
310
|
+
.number()
|
|
311
|
+
.int()
|
|
312
|
+
.min(-1)
|
|
313
|
+
.describe("PTZ preset slot: -1 for the home position, 0 or higher for saved presets"),
|
|
268
314
|
dryRun: zod_1.z
|
|
269
315
|
.boolean()
|
|
270
316
|
.optional()
|
package/dist/tools/devices.d.ts
CHANGED
package/dist/tools/devices.js
CHANGED
|
@@ -7,10 +7,11 @@ const responses_js_1 = require("../utils/responses.js");
|
|
|
7
7
|
const safety_js_1 = require("../utils/safety.js");
|
|
8
8
|
const url_js_1 = require("../utils/url.js");
|
|
9
9
|
const devices_js_1 = require("../schemas/devices.js");
|
|
10
|
-
// Field lists below are from the UniFi Protect Integration API 7.3.
|
|
10
|
+
// Field lists below are from the UniFi Protect Integration API 7.3.70 docs.
|
|
11
11
|
// 7.3.x added `type` (hardware model name) and `guid` to every device. NOTE:
|
|
12
12
|
// `guid` identifies the hardware MODEL, not the unit — every camera of the same
|
|
13
|
-
// model returns the same guid (verified live 7.3.47). `id` is the
|
|
13
|
+
// model returns the same guid (verified live 7.3.47 and 7.3.70). `id` is the
|
|
14
|
+
// device identity.
|
|
14
15
|
// Chimes are additionally verified against live console responses; the rest
|
|
15
16
|
// have no instances on the available console (top-level fields documented,
|
|
16
17
|
// nested shapes intentionally left opaque in the output schema).
|
|
@@ -21,23 +22,23 @@ const DEVICES = [
|
|
|
21
22
|
plural: "lights",
|
|
22
23
|
label: "Light",
|
|
23
24
|
hint: "Known fields: name (string), isLightForceEnabled (boolean), lightModeSettings (object with mode, enableAt), lightDeviceSettings (object with isIndicatorEnabled, pirDuration, pirSensitivity, ledLevel)",
|
|
24
|
-
returns: "id, modelKey, name, mac, state, type, guid, lightModeSettings (mode, enableAt), lightDeviceSettings (isIndicatorEnabled, pirDuration, pirSensitivity, ledLevel), isDark, isLightOn, isLightForceEnabled, lastMotion, isPirMotionDetected, camera (7.3.
|
|
25
|
+
returns: "id, modelKey, name, mac, state, type, guid, lightModeSettings (mode, enableAt), lightDeviceSettings (isIndicatorEnabled, pirDuration, pirSensitivity, ledLevel), isDark, isLightOn, isLightForceEnabled, lastMotion, isPirMotionDetected, camera (7.3.70 docs)",
|
|
25
26
|
},
|
|
26
27
|
{
|
|
27
28
|
urlPath: "sensors",
|
|
28
29
|
singular: "sensor",
|
|
29
30
|
plural: "sensors",
|
|
30
31
|
label: "Sensor",
|
|
31
|
-
hint: "Known fields: name (string),
|
|
32
|
-
returns: "id, modelKey, name, mac, state, type, guid, mountType, featureFlags (temperature, humidity, light, motion, waterLeak, open, tamper, smoke — each {channelCount}), batteryStatus (percentage, isLow), stats (light, humidity, temperature), lightSettings
|
|
32
|
+
hint: "Known fields: name (string), lightSettings / humiditySettings / temperatureSettings (objects: isEnabled, margin, lowThreshold, highThreshold), motionSettings / glassBreakSettings (objects: isEnabled, sensitivity, sensitivityWhenArmed), alarmSettings (object: isEnabled), scheduleMode (\"always\" | \"when_armed\"), armProfileIds (array of up to 32 strings, or null), hasCustomSensitivityWhenArmed (boolean). leakSettings is read-only. Returns HTTP 409 if a setting is not supported by this sensor",
|
|
33
|
+
returns: "id, modelKey, name, mac, state, type, guid, mountType, featureFlags (temperature, humidity, light, motion, waterLeak, open, tamper, smoke — each {channelCount}), batteryStatus (percentage, isLow), stats (light, humidity, temperature — each {value, status}), lightSettings / humiditySettings / temperatureSettings (isEnabled, margin, lowThreshold, highThreshold), isOpened, openStatusChangedAt, isMotionDetected, motionDetectedAt, motionSettings / glassBreakSettings (isEnabled, sensitivity, sensitivityWhenArmed), scheduleMode, armProfileIds, hasCustomSensitivityWhenArmed, alarmTriggeredAt, alarmSettings, leakDetectedAt, externalLeakDetectedAt, leakSettings (isInternalEnabled, isExternalEnabled), tamperingDetectedAt, wirelessConnectionState (signalState: signalQuality, signalStrength; batteryStatus: percentage, isLow; bridge) (7.3.70 docs)",
|
|
33
34
|
},
|
|
34
35
|
{
|
|
35
36
|
urlPath: "chimes",
|
|
36
37
|
singular: "chime",
|
|
37
38
|
plural: "chimes",
|
|
38
39
|
label: "Chime",
|
|
39
|
-
hint: "Known fields: name (string), cameraIds (array of camera IDs linked to the chime), ringSettings (array of objects: cameraId, volume, ringtoneId, repeatTimes)",
|
|
40
|
-
returns: "id, modelKey, name, mac, state, type, guid, cameraIds (array of camera IDs), ringSettings (array of objects: cameraId, volume, ringtoneId, repeatTimes) — verified live 7.3.
|
|
40
|
+
hint: "Known fields: name (string), cameraIds (array of doorbell camera IDs linked to the chime), ringSettings (array of objects: cameraId, volume, ringtoneId, repeatTimes)",
|
|
41
|
+
returns: "id, modelKey, name, mac, state, type, guid, cameraIds (array of camera IDs), ringSettings (array of objects: cameraId, volume, ringtoneId, repeatTimes) — verified live 7.3.70",
|
|
41
42
|
},
|
|
42
43
|
{
|
|
43
44
|
urlPath: "viewers",
|
|
@@ -45,7 +46,7 @@ const DEVICES = [
|
|
|
45
46
|
plural: "viewers",
|
|
46
47
|
label: "Viewer",
|
|
47
48
|
hint: "Known fields: name (string), liveview (string, liveview ID to display, or null)",
|
|
48
|
-
returns: "id, modelKey, name, mac, state, type, guid, liveview, streamLimit (7.3.
|
|
49
|
+
returns: "id, modelKey, name, mac, state, type, guid, liveview, streamLimit (7.3.70 docs)",
|
|
49
50
|
},
|
|
50
51
|
{
|
|
51
52
|
urlPath: "sirens",
|
|
@@ -53,7 +54,7 @@ const DEVICES = [
|
|
|
53
54
|
plural: "sirens",
|
|
54
55
|
label: "Siren",
|
|
55
56
|
hint: "Known fields: name (string), volume (integer 1-100), ledSettings (object with isEnabled boolean)",
|
|
56
|
-
returns: "id, modelKey, name, mac, state, type, guid, volume, ledSettings (isEnabled), sirenStatus (isActive, activatedAt, duration), connectionType, wirelessConnectionState (7.3.
|
|
57
|
+
returns: "id, modelKey, name, mac, state, type, guid, volume, ledSettings (isEnabled), sirenStatus (isActive, activatedAt, duration), connectionType, wirelessConnectionState (signalState: signalQuality, signalStrength; batteryStatus: percentage, isLow; bridge) (7.3.70 docs)",
|
|
57
58
|
},
|
|
58
59
|
{
|
|
59
60
|
urlPath: "fobs",
|
|
@@ -61,7 +62,7 @@ const DEVICES = [
|
|
|
61
62
|
plural: "fobs",
|
|
62
63
|
label: "Fob",
|
|
63
64
|
hint: "Known fields: name (string)",
|
|
64
|
-
returns: "id, modelKey, name, mac, state, type, guid, awayState, buttonLabels, featureFlags (buttons[]
|
|
65
|
+
returns: "id, modelKey, name, mac, state, type, guid, awayState, buttonLabels, featureFlags (buttons[], hasKeypad), armControlSettings (enabled, armProfileId, nightProfileId), keypadSettings (beepEnabled, beepVolume), wirelessConnectionState (signalState: signalQuality, signalStrength; batteryStatus: percentage, isLow; bridge) (7.3.70 docs)",
|
|
65
66
|
},
|
|
66
67
|
{
|
|
67
68
|
urlPath: "relays",
|
|
@@ -69,7 +70,7 @@ const DEVICES = [
|
|
|
69
70
|
plural: "relays",
|
|
70
71
|
label: "Relay",
|
|
71
72
|
hint: "Known fields: name (string), ledSettings (object with isEnabled boolean)",
|
|
72
|
-
returns: "id, modelKey, name, mac, state, type, guid, ledSettings (isEnabled), outputs (array), inputs (array), wirelessConnectionState (7.3.
|
|
73
|
+
returns: "id, modelKey, name, mac, state, type, guid, ledSettings (isEnabled), outputs (array of objects: id, name, type, delay, pulseDuration, state, rebootState — id is the outputId for protect_activate_relay_output), inputs (array of objects: id, name, state, actionTrigger, actionType, actionOutputId), wirelessConnectionState (signalState: signalQuality, signalStrength; batteryStatus: percentage, isLow; bridge) (7.3.70 docs)",
|
|
73
74
|
},
|
|
74
75
|
{
|
|
75
76
|
urlPath: "speakers",
|
|
@@ -77,7 +78,7 @@ const DEVICES = [
|
|
|
77
78
|
plural: "speakers",
|
|
78
79
|
label: "Speaker",
|
|
79
80
|
hint: "Known fields: name (string), volume (integer 0-100), micVolume (integer 0-100), isMicEnabled (boolean)",
|
|
80
|
-
returns: "id, modelKey, name, mac, state, type, guid, volume, micVolume, isMicEnabled, speakerState (status, mode), featureFlags (hasMic) (7.3.
|
|
81
|
+
returns: "id, modelKey, name, mac, state, type, guid, volume, micVolume, isMicEnabled, speakerState (status, mode), featureFlags (hasMic) (7.3.70 docs)",
|
|
81
82
|
},
|
|
82
83
|
{
|
|
83
84
|
urlPath: "bridges",
|
|
@@ -85,15 +86,16 @@ const DEVICES = [
|
|
|
85
86
|
plural: "bridges",
|
|
86
87
|
label: "Bridge",
|
|
87
88
|
hint: "Known fields: name (string)",
|
|
88
|
-
returns: "id, modelKey, name, mac, state, type, guid, platform, clients (array of MACs), maxClients (7.3.
|
|
89
|
+
returns: "id, modelKey, name, mac, state, type, guid, platform, clients (array of MACs), maxClients (7.3.70 docs)",
|
|
89
90
|
},
|
|
90
91
|
{
|
|
91
92
|
urlPath: "link-stations",
|
|
92
93
|
singular: "link_station",
|
|
93
94
|
plural: "link_stations",
|
|
94
95
|
label: "Link station",
|
|
96
|
+
note: "Link stations are non-alarm-hub gateways (use protect_list_alarm_hubs for alarm hubs).",
|
|
95
97
|
hint: "Known fields: name (string)",
|
|
96
|
-
returns: "id, modelKey (\"linkstation\"), name, mac, state, type, guid, isAlarmHub, ledSettings (isEnabled), lastEvent, alarmHub (
|
|
98
|
+
returns: "id, modelKey (\"linkstation\"), name, mac, state, type, guid, isAlarmHub, ledSettings (isEnabled), lastEvent, alarmHub (armed, battery, buckboost, connector, cover, deviceTamperStatus (e.g. \"tampered\"), currentMeterChannelStatus, currentMeterStatus, inputPower, poeout, powerMeter, output and input (maps keyed by channel), inputTerminalStatus, outputTerminalStatus, emergencyTerminalStatus, auxiliaryPowerTerminalStatus), threadState (network: status, role, networkName, channel, panId, extendedPanId, joinedDeviceCount, errorReason, lastUpdatedAt) (7.3.70 docs)",
|
|
97
99
|
},
|
|
98
100
|
{
|
|
99
101
|
urlPath: "alarm-hubs",
|
|
@@ -101,13 +103,14 @@ const DEVICES = [
|
|
|
101
103
|
plural: "alarm_hubs",
|
|
102
104
|
label: "Alarm hub",
|
|
103
105
|
hint: "Known fields: name (string)",
|
|
104
|
-
returns: "id, modelKey (\"linkstation\"), name, mac, state, type, guid, isAlarmHub, ledSettings (isEnabled), lastEvent, alarmHub (
|
|
106
|
+
returns: "id, modelKey (\"linkstation\"), name, mac, state, type, guid, isAlarmHub, ledSettings (isEnabled), lastEvent, alarmHub (armed, battery, buckboost, connector, cover, deviceTamperStatus (e.g. \"tampered\"), currentMeterChannelStatus, currentMeterStatus, inputPower, poeout, powerMeter, output and input (maps keyed by channel), inputTerminalStatus, outputTerminalStatus, emergencyTerminalStatus, auxiliaryPowerTerminalStatus), threadState (network: status, role, networkName, channel, panId, extendedPanId, joinedDeviceCount, errorReason, lastUpdatedAt) (7.3.70 docs)",
|
|
105
107
|
},
|
|
106
108
|
];
|
|
107
109
|
function registerDeviceCrud(server, client, cfg, readOnly) {
|
|
108
110
|
const schemaKey = cfg.singular;
|
|
111
|
+
const note = cfg.note ? `${cfg.note} ` : "";
|
|
109
112
|
server.registerTool(`protect_list_${cfg.plural}`, {
|
|
110
|
-
description: `List all ${cfg.label.toLowerCase()}s managed by UniFi Protect. Returns array; each ${cfg.label.toLowerCase()} includes: ${cfg.returns}.`,
|
|
113
|
+
description: `List all ${cfg.label.toLowerCase()}s managed by UniFi Protect. ${note}Returns array; each ${cfg.label.toLowerCase()} includes: ${cfg.returns}.`,
|
|
111
114
|
outputSchema: (0, devices_js_1.listOutputShape)(schemaKey),
|
|
112
115
|
annotations: safety_js_1.READ_ONLY,
|
|
113
116
|
}, async () => {
|
|
@@ -120,7 +123,7 @@ function registerDeviceCrud(server, client, cfg, readOnly) {
|
|
|
120
123
|
}
|
|
121
124
|
});
|
|
122
125
|
server.registerTool(`protect_get_${cfg.singular}`, {
|
|
123
|
-
description: `Get full details for a specific ${cfg.label.toLowerCase()} by ID. Returns: ${cfg.returns}.`,
|
|
126
|
+
description: `Get full details for a specific ${cfg.label.toLowerCase()} by ID. ${note}Returns: ${cfg.returns}.`,
|
|
124
127
|
inputSchema: { id: zod_1.z.string().describe(`${cfg.label} ID`) },
|
|
125
128
|
outputSchema: (0, devices_js_1.itemOutputShape)(schemaKey),
|
|
126
129
|
annotations: safety_js_1.READ_ONLY,
|
|
@@ -140,6 +143,7 @@ function registerDeviceCrud(server, client, cfg, readOnly) {
|
|
|
140
143
|
id: zod_1.z.string().describe(`${cfg.label} ID`),
|
|
141
144
|
settings: zod_1.z
|
|
142
145
|
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
146
|
+
.refine((o) => Object.keys(o).length > 0, "settings must include at least one field")
|
|
143
147
|
.describe(`Partial ${cfg.label.toLowerCase()} settings to update. ${cfg.hint}`),
|
|
144
148
|
dryRun: zod_1.z
|
|
145
149
|
.boolean()
|
package/dist/tools/files.js
CHANGED
|
@@ -6,14 +6,23 @@ const responses_js_1 = require("../utils/responses.js");
|
|
|
6
6
|
const safety_js_1 = require("../utils/safety.js");
|
|
7
7
|
const url_js_1 = require("../utils/url.js");
|
|
8
8
|
const misc_js_1 = require("../schemas/misc.js");
|
|
9
|
+
// The only asset file type the API accepts (path param `assetFileType`).
|
|
10
|
+
const FILE_TYPE = zod_1.z
|
|
11
|
+
.literal("animations")
|
|
12
|
+
.describe("Asset file type (only 'animations' is supported)");
|
|
13
|
+
const UPLOAD_CONTENT_TYPES = [
|
|
14
|
+
"image/gif",
|
|
15
|
+
"image/jpeg",
|
|
16
|
+
"image/png",
|
|
17
|
+
"audio/mpeg",
|
|
18
|
+
"audio/mp4",
|
|
19
|
+
"audio/wave",
|
|
20
|
+
"audio/x-caf",
|
|
21
|
+
];
|
|
9
22
|
function registerFileTools(server, client, readOnly) {
|
|
10
23
|
server.registerTool("protect_list_files", {
|
|
11
|
-
description: "List
|
|
12
|
-
inputSchema: {
|
|
13
|
-
fileType: zod_1.z
|
|
14
|
-
.string()
|
|
15
|
-
.describe("File type to list (only 'animations' is currently supported)"),
|
|
16
|
-
},
|
|
24
|
+
description: "List device asset files (only 'animations' is supported — clips uploadable to G4 Doorbell screens). Returns array; each record (verified live 7.3.70): id, name (server-generated), type, originalName, path, size, createdAt, updatedAt, metadata. An image upload (verified for GIF) later gains a derived <name>.gif preview record, created asynchronously, so a listing taken right after the upload may show only the stored image.",
|
|
25
|
+
inputSchema: { fileType: FILE_TYPE },
|
|
17
26
|
outputSchema: misc_js_1.fileListOutputSchema,
|
|
18
27
|
annotations: safety_js_1.READ_ONLY,
|
|
19
28
|
}, async ({ fileType }) => {
|
|
@@ -27,52 +36,39 @@ function registerFileTools(server, client, readOnly) {
|
|
|
27
36
|
});
|
|
28
37
|
if (readOnly)
|
|
29
38
|
return;
|
|
30
|
-
server.registerTool("protect_trigger_alarm_webhook", {
|
|
31
|
-
description: "Trigger an alarm manager webhook by ID. This fires an external alarm action.",
|
|
32
|
-
inputSchema: {
|
|
33
|
-
id: zod_1.z.string().describe("User-defined webhook ID matching an alarm manager configuration"),
|
|
34
|
-
confirm: zod_1.z
|
|
35
|
-
.literal(true)
|
|
36
|
-
.describe("Must be true to confirm triggering the alarm webhook"),
|
|
37
|
-
},
|
|
38
|
-
annotations: safety_js_1.DESTRUCTIVE,
|
|
39
|
-
}, async ({ id, confirm }) => {
|
|
40
|
-
const denied = (0, safety_js_1.requireConfirmation)(confirm, "trigger the alarm webhook");
|
|
41
|
-
if (denied)
|
|
42
|
-
return denied;
|
|
43
|
-
try {
|
|
44
|
-
await client.post((0, url_js_1.safePath) `/alarm-manager/webhook/${id}`);
|
|
45
|
-
return (0, responses_js_1.formatSuccess)({ triggered: true, webhookId: id });
|
|
46
|
-
}
|
|
47
|
-
catch (err) {
|
|
48
|
-
return (0, responses_js_1.formatError)(err);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
39
|
server.registerTool("protect_upload_file", {
|
|
52
|
-
description: "Upload a base64-encoded file to
|
|
40
|
+
description: "Upload a base64-encoded device asset file (sent as multipart/form-data). Observed for GIF image uploads: Protect stores the image as PNG (appending the stored extension to originalName) and a few seconds later creates a separate <name>.gif preview asset, which protect_list_files shows once it exists. Audio uploads have not been observed. Returns: id, name (server-generated), type, originalName, path, size, createdAt, updatedAt, metadata.",
|
|
53
41
|
inputSchema: {
|
|
54
|
-
fileType:
|
|
42
|
+
fileType: FILE_TYPE,
|
|
55
43
|
base64Data: zod_1.z.string().describe("Base64-encoded file content"),
|
|
56
44
|
contentType: zod_1.z
|
|
57
|
-
.
|
|
45
|
+
.enum(UPLOAD_CONTENT_TYPES)
|
|
58
46
|
.default("image/gif")
|
|
59
|
-
.describe("MIME type of the file
|
|
47
|
+
.describe("MIME type of the file"),
|
|
48
|
+
fileName: zod_1.z
|
|
49
|
+
.string()
|
|
50
|
+
.min(1)
|
|
51
|
+
.optional()
|
|
52
|
+
.describe("Original file name to record. Reported back as originalName, with the stored extension appended when Protect converts the file (e.g. welcome.gif → welcome.gif.png)"),
|
|
60
53
|
dryRun: zod_1.z
|
|
61
54
|
.boolean()
|
|
62
55
|
.optional()
|
|
63
56
|
.describe("If true, return what would happen without making changes"),
|
|
64
57
|
},
|
|
58
|
+
outputSchema: misc_js_1.fileOutputSchema,
|
|
65
59
|
annotations: safety_js_1.WRITE,
|
|
66
|
-
}, async ({ fileType, base64Data, contentType, dryRun }) => {
|
|
60
|
+
}, async ({ fileType, base64Data, contentType, fileName, dryRun }) => {
|
|
67
61
|
try {
|
|
62
|
+
const name = fileName ?? defaultFileName(contentType);
|
|
68
63
|
if (dryRun) {
|
|
69
64
|
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/files/${fileType}`, {
|
|
70
65
|
contentType,
|
|
66
|
+
fileName: name,
|
|
71
67
|
dataLength: base64Data.length,
|
|
72
68
|
});
|
|
73
69
|
}
|
|
74
70
|
const buffer = Buffer.from(base64Data, "base64");
|
|
75
|
-
const data = await client.
|
|
71
|
+
const data = await client.postFile((0, url_js_1.safePath) `/files/${fileType}`, buffer, contentType, name);
|
|
76
72
|
return (0, responses_js_1.formatSuccess)(data);
|
|
77
73
|
}
|
|
78
74
|
catch (err) {
|
|
@@ -80,3 +76,15 @@ function registerFileTools(server, client, readOnly) {
|
|
|
80
76
|
}
|
|
81
77
|
});
|
|
82
78
|
}
|
|
79
|
+
const EXTENSIONS = {
|
|
80
|
+
"image/gif": "gif",
|
|
81
|
+
"image/jpeg": "jpg",
|
|
82
|
+
"image/png": "png",
|
|
83
|
+
"audio/mpeg": "mp3",
|
|
84
|
+
"audio/mp4": "m4a",
|
|
85
|
+
"audio/wave": "wav",
|
|
86
|
+
"audio/x-caf": "caf",
|
|
87
|
+
};
|
|
88
|
+
function defaultFileName(contentType) {
|
|
89
|
+
return `upload.${EXTENSIONS[contentType]}`;
|
|
90
|
+
}
|
package/dist/tools/index.js
CHANGED
|
@@ -7,6 +7,7 @@ const devices_js_1 = require("./devices.js");
|
|
|
7
7
|
const device_actions_js_1 = require("./device-actions.js");
|
|
8
8
|
const liveviews_js_1 = require("./liveviews.js");
|
|
9
9
|
const files_js_1 = require("./files.js");
|
|
10
|
+
const alarm_manager_js_1 = require("./alarm-manager.js");
|
|
10
11
|
const subscriptions_js_1 = require("./subscriptions.js");
|
|
11
12
|
const arm_profiles_js_1 = require("./arm-profiles.js");
|
|
12
13
|
const users_js_1 = require("./users.js");
|
|
@@ -19,6 +20,7 @@ function registerAllTools(server, client, readOnly) {
|
|
|
19
20
|
(0, device_actions_js_1.registerDeviceActionTools)(server, client, readOnly);
|
|
20
21
|
(0, liveviews_js_1.registerLiveviewTools)(server, client, readOnly);
|
|
21
22
|
(0, files_js_1.registerFileTools)(server, client, readOnly);
|
|
23
|
+
(0, alarm_manager_js_1.registerAlarmManagerTools)(server, client, readOnly);
|
|
22
24
|
(0, arm_profiles_js_1.registerArmProfileTools)(server, client, readOnly);
|
|
23
25
|
(0, users_js_1.registerUserTools)(server, client);
|
|
24
26
|
(0, pos_js_1.registerPosTools)(server, client, readOnly);
|
package/dist/tools/liveviews.js
CHANGED
|
@@ -6,9 +6,31 @@ const responses_js_1 = require("../utils/responses.js");
|
|
|
6
6
|
const safety_js_1 = require("../utils/safety.js");
|
|
7
7
|
const url_js_1 = require("../utils/url.js");
|
|
8
8
|
const misc_js_1 = require("../schemas/misc.js");
|
|
9
|
+
// POST and PATCH both take the full liveview object; every field is required
|
|
10
|
+
// (7.3.70 docs). Slot item schema is not rendered in the docs, so slots pass
|
|
11
|
+
// through beyond the observed { cameras, cycleMode, cycleInterval }.
|
|
12
|
+
const liveviewSlotInput = zod_1.z
|
|
13
|
+
.object({
|
|
14
|
+
cameras: zod_1.z.array(zod_1.z.string()).describe("Camera IDs shown in this slot"),
|
|
15
|
+
cycleMode: zod_1.z.string().describe('Cycle mode, e.g. "time" or "motion"'),
|
|
16
|
+
cycleInterval: zod_1.z.number().describe("Cycle interval in seconds"),
|
|
17
|
+
})
|
|
18
|
+
.passthrough();
|
|
19
|
+
const liveviewBody = zod_1.z
|
|
20
|
+
.object({
|
|
21
|
+
id: zod_1.z.string().describe("Liveview ID"),
|
|
22
|
+
modelKey: zod_1.z.literal("liveview").describe('Must be "liveview"'),
|
|
23
|
+
name: zod_1.z.string().describe("Liveview name"),
|
|
24
|
+
isDefault: zod_1.z.boolean().describe("Whether this is the default liveview for all viewers"),
|
|
25
|
+
isGlobal: zod_1.z.boolean().describe("Whether this liveview is available to all users"),
|
|
26
|
+
owner: zod_1.z.string().describe("Owning user ID"),
|
|
27
|
+
layout: zod_1.z.number().min(1).max(26).describe("Number of slots (1-26)"),
|
|
28
|
+
slots: zod_1.z.array(liveviewSlotInput).describe("Per-slot cameras and cycling settings"),
|
|
29
|
+
})
|
|
30
|
+
.passthrough();
|
|
9
31
|
function registerLiveviewTools(server, client, readOnly) {
|
|
10
32
|
server.registerTool("protect_list_liveviews", {
|
|
11
|
-
description: "List all live views (camera grid layouts shown on viewers / in the Protect UI). Returns array; each liveview includes (Integration API 7.
|
|
33
|
+
description: "List all live views (camera grid layouts shown on viewers / in the Protect UI). Returns array; each liveview includes (Integration API 7.3.70-verified): id, modelKey, name, isDefault, isGlobal, layout (number, slot count), owner (user ID), slots (array of {cameras: string[], cycleMode, cycleInterval}). NOTE: slots use a `cameras` string-array, not a single `cameraId`.",
|
|
12
34
|
outputSchema: misc_js_1.liveviewListOutputSchema,
|
|
13
35
|
annotations: safety_js_1.READ_ONLY,
|
|
14
36
|
}, async () => {
|
|
@@ -37,11 +59,9 @@ function registerLiveviewTools(server, client, readOnly) {
|
|
|
37
59
|
if (readOnly)
|
|
38
60
|
return;
|
|
39
61
|
server.registerTool("protect_create_liveview", {
|
|
40
|
-
description: "Create a new live view",
|
|
62
|
+
description: "Create a new live view. Returns: the created liveview (same fields as protect_get_liveview).",
|
|
41
63
|
inputSchema: {
|
|
42
|
-
settings:
|
|
43
|
-
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
44
|
-
.describe("Liveview configuration. Required fields: id (string), modelKey (string, must be 'liveview'), owner (string, user ID), name (string), layout (number of slots), slots (array of {cameras: string[], cycleMode: string, cycleInterval: number}), isDefault (boolean), isGlobal (boolean)"),
|
|
64
|
+
settings: liveviewBody.describe("Full liveview object (all fields required)"),
|
|
45
65
|
dryRun: zod_1.z
|
|
46
66
|
.boolean()
|
|
47
67
|
.optional()
|
|
@@ -62,12 +82,10 @@ function registerLiveviewTools(server, client, readOnly) {
|
|
|
62
82
|
}
|
|
63
83
|
});
|
|
64
84
|
server.registerTool("protect_update_liveview", {
|
|
65
|
-
description: "Update an existing live view (full replacement via PUT-style PATCH — include all fields)",
|
|
85
|
+
description: "Update an existing live view (full replacement via PUT-style PATCH — include all fields). Returns: the updated liveview.",
|
|
66
86
|
inputSchema: {
|
|
67
87
|
id: zod_1.z.string().describe("Liveview ID"),
|
|
68
|
-
settings:
|
|
69
|
-
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
70
|
-
.describe("Full liveview object for update (all fields required). Known fields: id (string), modelKey (string, must be 'liveview'), owner (string, user ID), name (string), layout (number), slots (array of {cameras: string[], cycleMode: string, cycleInterval: number}), isDefault (boolean), isGlobal (boolean)"),
|
|
88
|
+
settings: liveviewBody.describe("Full liveview object (all fields required — fetch it with protect_get_liveview first)"),
|
|
71
89
|
dryRun: zod_1.z
|
|
72
90
|
.boolean()
|
|
73
91
|
.optional()
|
package/dist/tools/pos.d.ts
CHANGED
|
@@ -3,8 +3,10 @@ import { ProtectClient } from "../client.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* Point-of-sale event ingestion (added in Protect 7.3.x).
|
|
5
5
|
*
|
|
6
|
-
* `lineItems` entries use `.passthrough()` because the 7.3.
|
|
7
|
-
* item schema collapsed — the request sample shows `{ title, quantity }`, but
|
|
6
|
+
* `lineItems` entries use `.passthrough()` because the 7.3.70 docs render the
|
|
7
|
+
* item schema collapsed — the request sample shows `{ title, quantity: 1 }`, but
|
|
8
8
|
* an undocumented extra field should reach the API rather than be stripped.
|
|
9
|
+
* Since the item constraints are not published, `quantity` is only required
|
|
10
|
+
* to be positive (fractional quantities such as weights are allowed).
|
|
9
11
|
*/
|
|
10
12
|
export declare function registerPosTools(server: McpServer, client: ProtectClient, readOnly: boolean): void;
|
package/dist/tools/pos.js
CHANGED
|
@@ -9,9 +9,11 @@ const pos_js_1 = require("../schemas/pos.js");
|
|
|
9
9
|
/**
|
|
10
10
|
* Point-of-sale event ingestion (added in Protect 7.3.x).
|
|
11
11
|
*
|
|
12
|
-
* `lineItems` entries use `.passthrough()` because the 7.3.
|
|
13
|
-
* item schema collapsed — the request sample shows `{ title, quantity }`, but
|
|
12
|
+
* `lineItems` entries use `.passthrough()` because the 7.3.70 docs render the
|
|
13
|
+
* item schema collapsed — the request sample shows `{ title, quantity: 1 }`, but
|
|
14
14
|
* an undocumented extra field should reach the API rather than be stripped.
|
|
15
|
+
* Since the item constraints are not published, `quantity` is only required
|
|
16
|
+
* to be positive (fractional quantities such as weights are allowed).
|
|
15
17
|
*/
|
|
16
18
|
function registerPosTools(server, client, readOnly) {
|
|
17
19
|
if (readOnly)
|
|
@@ -19,7 +21,7 @@ function registerPosTools(server, client, readOnly) {
|
|
|
19
21
|
const lineItemSchema = zod_1.z
|
|
20
22
|
.object({
|
|
21
23
|
title: zod_1.z.string().describe("Line item name"),
|
|
22
|
-
quantity: zod_1.z.number().
|
|
24
|
+
quantity: zod_1.z.number().positive().describe("Quantity purchased (> 0)"),
|
|
23
25
|
})
|
|
24
26
|
.passthrough();
|
|
25
27
|
server.registerTool("protect_ingest_pos_transaction", {
|