@owine/unifi-protect-mcp 2.14.2 → 3.0.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 +248 -92
- package/dist/client.d.ts +43 -3
- package/dist/client.js +255 -85
- package/dist/config.d.ts +7 -1
- package/dist/config.js +114 -6
- package/dist/index.js +14 -6
- package/dist/instructions.d.ts +6 -0
- package/dist/instructions.js +24 -0
- package/dist/schemas/cameras.d.ts +32 -30
- package/dist/schemas/cameras.js +7 -6
- package/dist/schemas/common.d.ts +31 -6
- package/dist/schemas/common.js +45 -6
- package/dist/schemas/devices.d.ts +2 -10
- package/dist/schemas/devices.js +35 -26
- package/dist/schemas/misc.d.ts +50 -18
- package/dist/schemas/misc.js +23 -15
- package/dist/schemas/pos.d.ts +1 -1
- package/dist/schemas/pos.js +1 -1
- package/dist/tls.d.ts +41 -0
- package/dist/tls.js +75 -0
- package/dist/tools/alarm-manager.d.ts +3 -0
- package/dist/tools/alarm-manager.js +32 -0
- package/dist/tools/arm-profiles.d.ts +8 -2
- package/dist/tools/arm-profiles.js +98 -95
- package/dist/tools/cameras.d.ts +2 -2
- package/dist/tools/cameras.js +156 -181
- package/dist/tools/device-actions.d.ts +2 -2
- package/dist/tools/device-actions.js +84 -97
- package/dist/tools/devices.d.ts +6 -3
- package/dist/tools/devices.js +50 -51
- package/dist/tools/files.d.ts +2 -2
- package/dist/tools/files.js +82 -57
- package/dist/tools/index.d.ts +2 -2
- package/dist/tools/index.js +15 -10
- package/dist/tools/liveviews.d.ts +2 -2
- package/dist/tools/liveviews.js +52 -52
- package/dist/tools/pos.d.ts +6 -4
- package/dist/tools/pos.js +15 -20
- package/dist/tools/registrar.d.ts +34 -0
- package/dist/tools/registrar.js +103 -0
- package/dist/tools/subscriptions.d.ts +13 -2
- package/dist/tools/subscriptions.js +148 -27
- package/dist/tools/system.d.ts +2 -2
- package/dist/tools/system.js +5 -15
- package/dist/tools/users.d.ts +2 -2
- package/dist/tools/users.js +18 -35
- package/dist/utils/limits.d.ts +33 -0
- package/dist/utils/limits.js +74 -0
- package/dist/utils/paging.d.ts +15 -0
- package/dist/utils/paging.js +28 -0
- package/dist/utils/params.d.ts +13 -0
- package/dist/utils/params.js +40 -0
- package/dist/utils/responses.js +2 -3
- package/dist/utils/safety.d.ts +39 -3
- package/dist/utils/safety.js +45 -3
- package/dist/utils/url.d.ts +4 -1
- package/dist/utils/url.js +4 -1
- package/dist/version.d.ts +6 -0
- package/dist/version.js +11 -0
- package/package.json +14 -12
package/dist/tools/cameras.js
CHANGED
|
@@ -5,44 +5,41 @@ const zod_1 = require("zod");
|
|
|
5
5
|
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
|
+
const paging_js_1 = require("../utils/paging.js");
|
|
9
|
+
const params_js_1 = require("../utils/params.js");
|
|
8
10
|
const cameras_js_1 = require("../schemas/cameras.js");
|
|
11
|
+
// Each gate's confirm input and handler check share one action text.
|
|
12
|
+
const OPEN_STREAM = (0, safety_js_1.confirmGate)("open an RTSPS stream (its URL is a credential for live video)");
|
|
13
|
+
const OPEN_TALKBACK = (0, safety_js_1.confirmGate)("open a two-way audio session");
|
|
14
|
+
const DISABLE_MIC = (0, safety_js_1.confirmGate)("permanently disable the microphone");
|
|
9
15
|
function registerCameraTools(server, client, readOnly) {
|
|
10
16
|
// --- Read-only tools (always registered) ---
|
|
11
17
|
server.registerTool("protect_list_cameras", {
|
|
12
|
-
description: "List all cameras managed by UniFi Protect. Returns array; each camera includes (Integration API 7.3.
|
|
18
|
+
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." + paging_js_1.PAGING_NOTE,
|
|
19
|
+
inputSchema: paging_js_1.pagingInput,
|
|
13
20
|
outputSchema: cameras_js_1.cameraListOutputSchema,
|
|
14
21
|
annotations: safety_js_1.READ_ONLY,
|
|
15
|
-
}, async () => {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
19
|
-
}
|
|
20
|
-
catch (err) {
|
|
21
|
-
return (0, responses_js_1.formatError)(err);
|
|
22
|
-
}
|
|
22
|
+
}, async ({ limit, offset }) => {
|
|
23
|
+
const data = await client.get("/cameras");
|
|
24
|
+
return (0, responses_js_1.formatSuccess)((0, paging_js_1.pageOf)(data, limit, offset));
|
|
23
25
|
});
|
|
24
26
|
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.
|
|
26
|
-
inputSchema: { id:
|
|
27
|
+
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.",
|
|
28
|
+
inputSchema: { id: (0, params_js_1.resourceId)().describe("Camera ID") },
|
|
27
29
|
outputSchema: cameras_js_1.cameraOutputSchema,
|
|
28
30
|
annotations: safety_js_1.READ_ONLY,
|
|
29
31
|
}, async ({ id }) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
33
|
-
}
|
|
34
|
-
catch (err) {
|
|
35
|
-
return (0, responses_js_1.formatError)(err);
|
|
36
|
-
}
|
|
32
|
+
const data = await client.get((0, url_js_1.safePath) `/cameras/${id}`);
|
|
33
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
37
34
|
});
|
|
38
35
|
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
|
|
36
|
+
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. A snapshot too large for one MCP message is refused with an error; try without highQuality.",
|
|
40
37
|
inputSchema: {
|
|
41
|
-
id:
|
|
38
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
42
39
|
highQuality: zod_1.z
|
|
43
40
|
.boolean()
|
|
44
41
|
.optional()
|
|
45
|
-
.describe("If true,
|
|
42
|
+
.describe("If true, force a 1080p-or-higher resolution snapshot"),
|
|
46
43
|
channel: zod_1.z
|
|
47
44
|
.enum(["main", "package"])
|
|
48
45
|
.optional()
|
|
@@ -50,237 +47,215 @@ function registerCameraTools(server, client, readOnly) {
|
|
|
50
47
|
},
|
|
51
48
|
annotations: safety_js_1.READ_ONLY,
|
|
52
49
|
}, async ({ id, highQuality, channel }) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
return (0, responses_js_1.formatError)(err);
|
|
76
|
-
}
|
|
50
|
+
const params = new URLSearchParams();
|
|
51
|
+
if (highQuality)
|
|
52
|
+
params.set("highQuality", "true");
|
|
53
|
+
if (channel)
|
|
54
|
+
params.set("channel", channel);
|
|
55
|
+
const query = params.toString();
|
|
56
|
+
const url = query
|
|
57
|
+
? (0, url_js_1.safePath) `/cameras/${id}/snapshot` + `?${query}`
|
|
58
|
+
: (0, url_js_1.safePath) `/cameras/${id}/snapshot`;
|
|
59
|
+
const { data, mimeType } = await client.getBinary(url);
|
|
60
|
+
return {
|
|
61
|
+
content: [
|
|
62
|
+
{
|
|
63
|
+
type: "image",
|
|
64
|
+
data: data.toString("base64"),
|
|
65
|
+
mimeType,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
77
69
|
});
|
|
78
70
|
server.registerTool("protect_get_rtsp_streams", {
|
|
79
|
-
description: "Get active RTSPS stream sessions for a camera. Returns
|
|
80
|
-
inputSchema: { id:
|
|
71
|
+
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. Each URL is a bearer credential for live video: do not repeat it unless the user asked for it.",
|
|
72
|
+
inputSchema: { id: (0, params_js_1.resourceId)().describe("Camera ID") },
|
|
81
73
|
outputSchema: cameras_js_1.rtspStreamOutputSchema,
|
|
82
74
|
annotations: safety_js_1.READ_ONLY,
|
|
83
75
|
}, async ({ id }) => {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
return (0, responses_js_1.formatError)(err);
|
|
90
|
-
}
|
|
76
|
+
const data = await client.get((0, url_js_1.safePath) `/cameras/${id}/rtsps-stream`);
|
|
77
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
91
78
|
});
|
|
92
79
|
// --- Write tools (skipped in read-only mode) ---
|
|
93
80
|
if (readOnly)
|
|
94
81
|
return;
|
|
95
82
|
server.registerTool("protect_update_camera", {
|
|
96
|
-
description: "Update camera settings (partial update via PATCH)",
|
|
83
|
+
description: "Update camera settings (partial update via PATCH). Returns: the full updated camera (same fields as protect_get_camera).",
|
|
97
84
|
inputSchema: {
|
|
98
|
-
id:
|
|
85
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
99
86
|
settings: zod_1.z
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
87
|
+
.object({
|
|
88
|
+
name: zod_1.z.string().optional().describe("Camera name"),
|
|
89
|
+
osdSettings: zod_1.z
|
|
90
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
91
|
+
.optional()
|
|
92
|
+
.describe("On-screen display (isNameEnabled, isDateEnabled, isLogoEnabled, isDebugEnabled, overlayLocation)"),
|
|
93
|
+
ledSettings: zod_1.z
|
|
94
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
95
|
+
.optional()
|
|
96
|
+
.describe("LED settings (isEnabled, welcomeLed, floodLed)"),
|
|
97
|
+
lcdMessage: zod_1.z
|
|
98
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
99
|
+
.optional()
|
|
100
|
+
.describe('Doorbell LCD message (type, resetAt, text), e.g. { type: "DO_NOT_DISTURB", resetAt: 0 }'),
|
|
101
|
+
micVolume: zod_1.z.number().min(0).max(100).optional().describe("Mic volume (0-100)"),
|
|
102
|
+
videoMode: zod_1.z
|
|
103
|
+
.enum(["default", "highFps", "sport", "slowShutter", "lprReflex", "lprNoneReflex"])
|
|
104
|
+
.optional()
|
|
105
|
+
.describe("Video mode"),
|
|
106
|
+
hdrType: zod_1.z.enum(["auto", "on", "off"]).optional().describe("HDR mode"),
|
|
107
|
+
smartDetectSettings: zod_1.z
|
|
108
|
+
.record(zod_1.z.string(), zod_1.z.unknown())
|
|
109
|
+
.optional()
|
|
110
|
+
.describe("Smart detection (objectTypes[], audioTypes[])"),
|
|
111
|
+
})
|
|
112
|
+
.loose()
|
|
113
|
+
.refine((o) => Object.keys(o).length > 0, "settings must include at least one field")
|
|
114
|
+
.describe("Partial camera settings to update"),
|
|
115
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
106
116
|
},
|
|
107
117
|
outputSchema: cameras_js_1.cameraOutputSchema,
|
|
108
|
-
annotations: safety_js_1.
|
|
118
|
+
annotations: safety_js_1.DESTRUCTIVE,
|
|
109
119
|
}, async ({ id, settings, dryRun }) => {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return (0, safety_js_1.formatDryRun)("PATCH", (0, url_js_1.safePath) `/cameras/${id}`, settings);
|
|
113
|
-
}
|
|
114
|
-
const data = await client.patch((0, url_js_1.safePath) `/cameras/${id}`, settings);
|
|
115
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
116
|
-
}
|
|
117
|
-
catch (err) {
|
|
118
|
-
return (0, responses_js_1.formatError)(err);
|
|
120
|
+
if (dryRun) {
|
|
121
|
+
return (0, safety_js_1.formatDryRun)("PATCH", (0, url_js_1.safePath) `/cameras/${id}`, settings);
|
|
119
122
|
}
|
|
123
|
+
const data = await client.patch((0, url_js_1.safePath) `/cameras/${id}`, settings);
|
|
124
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
120
125
|
});
|
|
121
126
|
server.registerTool("protect_create_rtsp_stream", {
|
|
122
|
-
description: "Create an RTSPS stream session for a camera",
|
|
127
|
+
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. Requires confirm: true: the returned URLs are bearer credentials for live video.",
|
|
123
128
|
inputSchema: {
|
|
124
|
-
id:
|
|
129
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
125
130
|
qualities: zod_1.z
|
|
126
131
|
.array(zod_1.z.enum(["high", "medium", "low", "package"]))
|
|
127
|
-
.
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
.describe("If true, return what would happen without making changes"),
|
|
132
|
+
.min(1)
|
|
133
|
+
.describe("Quality levels to include in the RTSPS stream (at least one)"),
|
|
134
|
+
confirm: OPEN_STREAM.input,
|
|
135
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
132
136
|
},
|
|
133
137
|
outputSchema: cameras_js_1.rtspStreamOutputSchema,
|
|
134
|
-
annotations: safety_js_1.
|
|
135
|
-
}, async ({ id, qualities, dryRun }) => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/rtsps-stream`, { qualities });
|
|
139
|
-
}
|
|
140
|
-
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/rtsps-stream`, {
|
|
141
|
-
qualities,
|
|
142
|
-
});
|
|
143
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
return (0, responses_js_1.formatError)(err);
|
|
138
|
+
annotations: safety_js_1.DESTRUCTIVE,
|
|
139
|
+
}, async ({ id, qualities, confirm, dryRun }) => {
|
|
140
|
+
if (dryRun) {
|
|
141
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/rtsps-stream`, { qualities });
|
|
147
142
|
}
|
|
143
|
+
const denied = OPEN_STREAM.check(confirm);
|
|
144
|
+
if (denied)
|
|
145
|
+
return denied;
|
|
146
|
+
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/rtsps-stream`, {
|
|
147
|
+
qualities,
|
|
148
|
+
});
|
|
149
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
148
150
|
});
|
|
149
151
|
server.registerTool("protect_delete_rtsp_stream", {
|
|
150
152
|
description: "Stop and delete an active RTSPS stream session for a camera",
|
|
151
153
|
inputSchema: {
|
|
152
|
-
id:
|
|
154
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
153
155
|
qualities: zod_1.z
|
|
154
156
|
.array(zod_1.z.enum(["high", "medium", "low", "package"]))
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
.optional()
|
|
159
|
-
.describe("If true, return what would happen without making changes"),
|
|
157
|
+
.min(1)
|
|
158
|
+
.describe("Quality levels to remove from the RTSPS stream (at least one)"),
|
|
159
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
160
160
|
},
|
|
161
161
|
annotations: safety_js_1.DESTRUCTIVE,
|
|
162
162
|
}, async ({ id, qualities, dryRun }) => {
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return (0, responses_js_1.formatError)(err);
|
|
163
|
+
// Protect expects repeated keys (qualities=high&qualities=medium), not
|
|
164
|
+
// a comma-joined value — safePath would encode the comma as %2C.
|
|
165
|
+
const params = new URLSearchParams();
|
|
166
|
+
for (const quality of qualities)
|
|
167
|
+
params.append("qualities", quality);
|
|
168
|
+
const url = (0, url_js_1.safePath) `/cameras/${id}/rtsps-stream` + `?${params.toString()}`;
|
|
169
|
+
if (dryRun) {
|
|
170
|
+
return (0, safety_js_1.formatDryRun)("DELETE", url);
|
|
172
171
|
}
|
|
172
|
+
const data = await client.delete(url);
|
|
173
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
173
174
|
});
|
|
174
175
|
server.registerTool("protect_create_talkback", {
|
|
175
|
-
description: "Create a talkback (two-way audio) session for a camera. Returns: url, codec, samplingRate, bitsPerSample (the audio config for encoding outbound audio).",
|
|
176
|
+
description: "Create a talkback (two-way audio) session for a camera. Returns: url, codec, samplingRate, bitsPerSample (the audio config for encoding outbound audio). Requires confirm: true: the session opens two-way audio to the camera, and the returned url lets anyone holding it talk through the camera.",
|
|
176
177
|
inputSchema: {
|
|
177
|
-
id:
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.optional()
|
|
181
|
-
.describe("If true, return what would happen without making changes"),
|
|
178
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
179
|
+
confirm: OPEN_TALKBACK.input,
|
|
180
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
182
181
|
},
|
|
183
182
|
outputSchema: cameras_js_1.talkbackSessionOutputSchema,
|
|
184
|
-
annotations: safety_js_1.
|
|
185
|
-
}, async ({ id, dryRun }) => {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/talkback-session`);
|
|
189
|
-
}
|
|
190
|
-
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/talkback-session`);
|
|
191
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
192
|
-
}
|
|
193
|
-
catch (err) {
|
|
194
|
-
return (0, responses_js_1.formatError)(err);
|
|
183
|
+
annotations: safety_js_1.DESTRUCTIVE,
|
|
184
|
+
}, async ({ id, confirm, dryRun }) => {
|
|
185
|
+
if (dryRun) {
|
|
186
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/talkback-session`);
|
|
195
187
|
}
|
|
188
|
+
const denied = OPEN_TALKBACK.check(confirm);
|
|
189
|
+
if (denied)
|
|
190
|
+
return denied;
|
|
191
|
+
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/talkback-session`);
|
|
192
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
196
193
|
});
|
|
197
194
|
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.",
|
|
195
|
+
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). Requires confirm: true.",
|
|
199
196
|
inputSchema: {
|
|
200
|
-
id:
|
|
201
|
-
confirm:
|
|
202
|
-
|
|
203
|
-
.describe("Must be true to confirm this irreversible action"),
|
|
197
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
198
|
+
confirm: DISABLE_MIC.input,
|
|
199
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
204
200
|
},
|
|
201
|
+
outputSchema: cameras_js_1.cameraOutputSchema,
|
|
205
202
|
annotations: safety_js_1.DESTRUCTIVE,
|
|
206
|
-
}, async ({ id, confirm }) => {
|
|
207
|
-
|
|
203
|
+
}, async ({ id, confirm, dryRun }) => {
|
|
204
|
+
if (dryRun) {
|
|
205
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/disable-mic-permanently`);
|
|
206
|
+
}
|
|
207
|
+
const denied = DISABLE_MIC.check(confirm);
|
|
208
208
|
if (denied)
|
|
209
209
|
return denied;
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
213
|
-
}
|
|
214
|
-
catch (err) {
|
|
215
|
-
return (0, responses_js_1.formatError)(err);
|
|
216
|
-
}
|
|
210
|
+
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/disable-mic-permanently`);
|
|
211
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
217
212
|
});
|
|
218
213
|
server.registerTool("protect_start_ptz_patrol", {
|
|
219
214
|
description: "Start PTZ patrol on a camera at a given slot",
|
|
220
215
|
inputSchema: {
|
|
221
|
-
id:
|
|
216
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
222
217
|
slot: zod_1.z.number().int().min(0).max(4).describe("Patrol slot number (0-4)"),
|
|
223
|
-
dryRun:
|
|
224
|
-
.boolean()
|
|
225
|
-
.optional()
|
|
226
|
-
.describe("If true, return what would happen without making changes"),
|
|
218
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
227
219
|
},
|
|
228
220
|
annotations: safety_js_1.WRITE,
|
|
229
221
|
}, async ({ id, slot, dryRun }) => {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/start/${slot}`);
|
|
233
|
-
}
|
|
234
|
-
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/start/${slot}`);
|
|
235
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
236
|
-
}
|
|
237
|
-
catch (err) {
|
|
238
|
-
return (0, responses_js_1.formatError)(err);
|
|
222
|
+
if (dryRun) {
|
|
223
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/start/${slot}`);
|
|
239
224
|
}
|
|
225
|
+
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/start/${slot}`);
|
|
226
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
240
227
|
});
|
|
241
228
|
server.registerTool("protect_stop_ptz_patrol", {
|
|
242
229
|
description: "Stop PTZ patrol on a camera",
|
|
243
230
|
inputSchema: {
|
|
244
|
-
id:
|
|
245
|
-
dryRun:
|
|
246
|
-
.boolean()
|
|
247
|
-
.optional()
|
|
248
|
-
.describe("If true, return what would happen without making changes"),
|
|
231
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
232
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
249
233
|
},
|
|
250
234
|
annotations: safety_js_1.WRITE,
|
|
251
235
|
}, async ({ id, dryRun }) => {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/stop`);
|
|
255
|
-
}
|
|
256
|
-
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/stop`);
|
|
257
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
258
|
-
}
|
|
259
|
-
catch (err) {
|
|
260
|
-
return (0, responses_js_1.formatError)(err);
|
|
236
|
+
if (dryRun) {
|
|
237
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/stop`);
|
|
261
238
|
}
|
|
239
|
+
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/ptz/patrol/stop`);
|
|
240
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
262
241
|
});
|
|
263
242
|
server.registerTool("protect_goto_ptz_preset", {
|
|
264
243
|
description: "Move camera PTZ to a preset position",
|
|
265
244
|
inputSchema: {
|
|
266
|
-
id:
|
|
267
|
-
slot: zod_1.z
|
|
268
|
-
|
|
269
|
-
.
|
|
270
|
-
.
|
|
271
|
-
.describe("
|
|
245
|
+
id: (0, params_js_1.resourceId)().describe("Camera ID"),
|
|
246
|
+
slot: zod_1.z
|
|
247
|
+
.number()
|
|
248
|
+
.int()
|
|
249
|
+
.min(-1)
|
|
250
|
+
.describe("PTZ preset slot: -1 for the home position, 0 or higher for saved presets"),
|
|
251
|
+
dryRun: (0, safety_js_1.dryRunInput)(),
|
|
272
252
|
},
|
|
273
253
|
annotations: safety_js_1.WRITE,
|
|
274
254
|
}, async ({ id, slot, dryRun }) => {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/ptz/goto/${slot}`);
|
|
278
|
-
}
|
|
279
|
-
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/ptz/goto/${slot}`);
|
|
280
|
-
return (0, responses_js_1.formatSuccess)(data);
|
|
281
|
-
}
|
|
282
|
-
catch (err) {
|
|
283
|
-
return (0, responses_js_1.formatError)(err);
|
|
255
|
+
if (dryRun) {
|
|
256
|
+
return (0, safety_js_1.formatDryRun)("POST", (0, url_js_1.safePath) `/cameras/${id}/ptz/goto/${slot}`);
|
|
284
257
|
}
|
|
258
|
+
const data = await client.post((0, url_js_1.safePath) `/cameras/${id}/ptz/goto/${slot}`);
|
|
259
|
+
return (0, responses_js_1.formatSuccess)(data);
|
|
285
260
|
});
|
|
286
261
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { ToolRegistrar } from "./registrar.js";
|
|
2
2
|
import { ProtectClient } from "../client.js";
|
|
3
|
-
export declare function registerDeviceActionTools(server:
|
|
3
|
+
export declare function registerDeviceActionTools(server: ToolRegistrar, client: ProtectClient, readOnly: boolean): void;
|