rhombus-node-mcp 0.1.55 → 0.1.57
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/dist/api/access-control-tool-api.js +231 -32
- package/dist/api/alarm-monitoring-tool-api.js +50 -1
- package/dist/api/automated-prompts-tool-api.js +9 -17
- package/dist/api/camera-policy-tool-api.js +122 -0
- package/dist/api/camera-tool-api.js +3 -5
- package/dist/api/camera-uptime-tool-api.js +62 -25
- package/dist/api/clips-tool-api.js +45 -9
- package/dist/api/create-tool-api.js +55 -7
- package/dist/api/door-tool-api.js +74 -13
- package/dist/api/entity-lookup-tool-api.js +8 -1
- package/dist/api/events-tool-api.js +79 -6
- package/dist/api/faces-tool-api.js +112 -7
- package/dist/api/get-accessible-apps.js +68 -6
- package/dist/api/get-entity-tool-api.js +12 -12
- package/dist/api/get-org-information-tool-api.js +2 -2
- package/dist/api/guest-management-tool-api.js +75 -1
- package/dist/api/location-tool-api.js +120 -28
- package/dist/api/lpr-tool-api.js +71 -16
- package/dist/api/policy-alerts-tool-api.js +7 -3
- package/dist/api/report-tool-api.js +4 -1
- package/dist/api/rules-tool-api.js +6 -16
- package/dist/api/schedule-tool-api.js +107 -0
- package/dist/api/search-tool-api.js +5 -13
- package/dist/api/update-tool-api.js +96 -1
- package/dist/api/user-tool-api.js +58 -13
- package/dist/createServer.js +18 -6
- package/dist/filtering-utils.js +421 -29
- package/dist/index.js +1 -1
- package/dist/network/network.js +122 -10
- package/dist/network/org-reference-cache.js +74 -0
- package/dist/telemetry/tracingProxy.js +4 -0
- package/dist/tools/entity-lookup-tool.js +8 -4
- package/dist/tools/get-entity-tool.js +46 -21
- package/dist/tools/get-org-information-tool.js +36 -3
- package/dist/tools/time-conversion-tool.js +65 -37
- package/dist/tools/time-tool.js +18 -4
- package/dist/tools/user-tool.js +101 -1
- package/dist/tools-console/access-anomaly-tool.js +4 -19
- package/dist/tools-console/access-control-tool.js +330 -2
- package/dist/tools-console/alarm-monitoring-tool.js +73 -1
- package/dist/tools-console/analytics-tool.js +4 -2
- package/dist/tools-console/automated-prompts-tool.js +2 -0
- package/dist/tools-console/badge-timeline-tool.js +4 -21
- package/dist/tools-console/camera-policy-tool.js +252 -0
- package/dist/tools-console/camera-tool.js +113 -89
- package/dist/tools-console/camera-uptime-tool.js +2 -0
- package/dist/tools-console/clips-tool.js +23 -31
- package/dist/tools-console/count-tool.js +19 -6
- package/dist/tools-console/create-camera-policy-tool.js +304 -122
- package/dist/tools-console/door-schedule-exception-tool.js +4 -33
- package/dist/tools-console/door-tool.js +131 -1
- package/dist/tools-console/elements-access-anomaly-tool.js +4 -19
- package/dist/tools-console/elements-badge-timeline-tool.js +4 -21
- package/dist/tools-console/elements-lost-badge-tool.js +4 -17
- package/dist/tools-console/elements-tool.js +4 -29
- package/dist/tools-console/events-tool.js +24 -101
- package/dist/tools-console/faces-tool.js +255 -27
- package/dist/tools-console/guest-management-tool.js +128 -1
- package/dist/tools-console/location-tool.js +161 -35
- package/dist/tools-console/lost-badge-tool.js +4 -17
- package/dist/tools-console/lpr-tool.js +90 -22
- package/dist/tools-console/netbox-access-anomaly-tool.js +4 -19
- package/dist/tools-console/netbox-badge-timeline-tool.js +4 -21
- package/dist/tools-console/netbox-lost-badge-tool.js +4 -17
- package/dist/tools-console/netbox-tool.js +4 -29
- package/dist/tools-console/onguard-tool.js +4 -30
- package/dist/tools-console/person-tracking-tool.js +4 -2
- package/dist/tools-console/policy-alerts-tool.js +6 -28
- package/dist/tools-console/reboot-cameras-tool.js +23 -3
- package/dist/tools-console/report-tool.js +12 -40
- package/dist/tools-console/rules-tool.js +2 -0
- package/dist/tools-console/schedule-tool.js +190 -0
- package/dist/tools-console/search-tool.js +2 -0
- package/dist/tools-console/update-tool.js +300 -168
- package/dist/tools-console/user-access-trail-tool.js +2 -0
- package/dist/tools-console/user-audit-tool.js +2 -0
- package/dist/tools-console/video-walls-tool.js +94 -20
- package/dist/types/access-anomaly-tool-types.js +22 -2
- package/dist/types/access-control-tool-types.js +116 -1
- package/dist/types/alarm-monitoring-tool-types.js +42 -1
- package/dist/types/badge-timeline-tool-types.js +18 -3
- package/dist/types/camera-policy-tool-types.js +104 -0
- package/dist/types/camera-tool-types.js +80 -1
- package/dist/types/clips-tool-types.js +27 -3
- package/dist/types/create-camera-policy-tool-types.js +275 -26
- package/dist/types/door-schedule-exception-tool-types.js +20 -6
- package/dist/types/door-tool-types.js +36 -4
- package/dist/types/entity-lookup-tool-types.js +6 -1
- package/dist/types/events-tools-types.js +24 -16
- package/dist/types/faces-tools-types.js +106 -3
- package/dist/types/get-entity-tool-types.js +76 -12
- package/dist/types/guest-management-tool-types.js +103 -1
- package/dist/types/location-tool-types.js +100 -14
- package/dist/types/lost-badge-tool-types.js +17 -2
- package/dist/types/lpr-tool-types.js +47 -8
- package/dist/types/onguard-tool-types.js +21 -1
- package/dist/types/policy-alerts-tool-types.js +1 -1
- package/dist/types/report-tool-types.js +29 -1
- package/dist/types/schedule-tool-types.js +370 -0
- package/dist/types/schema.js +479 -9
- package/dist/types/time-conversion-tool-types.js +3 -4
- package/dist/types/update-tool-types.js +40 -18
- package/dist/types/user-tool-types.js +44 -1
- package/dist/types/video-walls-tool-types.js +30 -2
- package/dist/types/zod-schemas.js +5128 -996
- package/dist/util.js +62 -3
- package/dist/utils/entity-name-match.js +11 -0
- package/package.json +3 -1
package/dist/api/lpr-tool-api.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { postApi } from "../network/network.js";
|
|
1
|
+
import { postApi, throwIfApiError } from "../network/network.js";
|
|
2
2
|
import { formatTimestamp } from "../util.js";
|
|
3
3
|
export async function getVehicleEvents(args, timeZone, requestModifiers, sessionId) {
|
|
4
4
|
const requestArgs = {
|
|
@@ -15,9 +15,7 @@ export async function getVehicleEvents(args, timeZone, requestModifiers, session
|
|
|
15
15
|
modifiers: requestModifiers,
|
|
16
16
|
sessionId,
|
|
17
17
|
});
|
|
18
|
-
|
|
19
|
-
throw new Error(JSON.stringify(res));
|
|
20
|
-
}
|
|
18
|
+
throwIfApiError(res);
|
|
21
19
|
return (res.events?.map(event => ({
|
|
22
20
|
uuid: event.uuid ?? "",
|
|
23
21
|
deviceUuid: event.deviceUuid ?? undefined,
|
|
@@ -35,9 +33,7 @@ export async function getSavedVehicles(timeZone, requestModifiers, sessionId) {
|
|
|
35
33
|
modifiers: requestModifiers,
|
|
36
34
|
sessionId,
|
|
37
35
|
});
|
|
38
|
-
|
|
39
|
-
throw new Error(JSON.stringify(res));
|
|
40
|
-
}
|
|
36
|
+
throwIfApiError(res);
|
|
41
37
|
return (res.vehicles?.map(vehicle => ({
|
|
42
38
|
createdTimestamp: formatTimestamp(vehicle.createdAtMillis ?? Date.now(), timeZone),
|
|
43
39
|
name: vehicle.name ?? "No Name",
|
|
@@ -53,9 +49,7 @@ export async function getVehicleLabels(requestModifiers, sessionId) {
|
|
|
53
49
|
modifiers: requestModifiers,
|
|
54
50
|
sessionId,
|
|
55
51
|
});
|
|
56
|
-
|
|
57
|
-
throw new Error(JSON.stringify(res));
|
|
58
|
-
}
|
|
52
|
+
throwIfApiError(res);
|
|
59
53
|
const processedVehicleLabels = {};
|
|
60
54
|
for (const [licensePlate, labels] of Object.entries(res.vehicleLabels ?? {})) {
|
|
61
55
|
const filteredLabels = labels?.filter(label => label !== null) ?? [];
|
|
@@ -73,22 +67,83 @@ export async function searchLicensePlates(query, timeZone, requestModifiers, ses
|
|
|
73
67
|
modifiers: requestModifiers,
|
|
74
68
|
sessionId,
|
|
75
69
|
});
|
|
76
|
-
|
|
77
|
-
throw new Error(JSON.stringify(res));
|
|
70
|
+
throwIfApiError(res);
|
|
78
71
|
return (res.licensePlateEvents || []).map((event) => ({
|
|
79
72
|
licensePlate: event.vehicleLicensePlate ?? undefined,
|
|
80
73
|
deviceUuid: event.deviceUuid ?? undefined,
|
|
81
74
|
timestampMs: event.eventTimestamp ?? undefined,
|
|
82
75
|
}));
|
|
83
76
|
}
|
|
77
|
+
/**
|
|
78
|
+
* Save (or overwrite) a vehicle. The plate is the key, so calling this for a
|
|
79
|
+
* plate that already exists replaces its name and description.
|
|
80
|
+
*
|
|
81
|
+
* NOTE the field-name asymmetry, which this used to get wrong: the REQUEST
|
|
82
|
+
* field is `vehicleLicensePlate` (`Vehicle_SaveVehicleWSRequest`) while the
|
|
83
|
+
* READ field is `licensePlate` (`VehicleV2Type`). Sending `licensePlate` here
|
|
84
|
+
* silently saved a vehicle with no plate attached.
|
|
85
|
+
*/
|
|
84
86
|
export async function saveVehicle(name, licensePlate, description, requestModifiers, sessionId) {
|
|
85
87
|
const res = await postApi({
|
|
86
88
|
route: "/vehicle/saveVehicle",
|
|
87
|
-
body: {
|
|
89
|
+
body: {
|
|
90
|
+
name,
|
|
91
|
+
vehicleLicensePlate: licensePlate,
|
|
92
|
+
description: description || "",
|
|
93
|
+
},
|
|
94
|
+
modifiers: requestModifiers,
|
|
95
|
+
sessionId,
|
|
96
|
+
});
|
|
97
|
+
throwIfApiError(res);
|
|
98
|
+
return { success: true, licensePlate, warningMsg: res.warningMsg ?? undefined };
|
|
99
|
+
}
|
|
100
|
+
export async function deleteVehicle(licensePlate, requestModifiers, sessionId) {
|
|
101
|
+
const res = await postApi({
|
|
102
|
+
route: "/vehicle/deleteVehicle",
|
|
103
|
+
body: {
|
|
104
|
+
vehicleLicensePlate: licensePlate,
|
|
105
|
+
},
|
|
106
|
+
modifiers: requestModifiers,
|
|
107
|
+
sessionId,
|
|
108
|
+
});
|
|
109
|
+
throwIfApiError(res);
|
|
110
|
+
return { success: true, licensePlate, warningMsg: res.warningMsg ?? undefined };
|
|
111
|
+
}
|
|
112
|
+
export async function addVehicleLabel(licensePlate, label, requestModifiers, sessionId) {
|
|
113
|
+
const res = await postApi({
|
|
114
|
+
route: "/vehicle/addVehicleLabel",
|
|
115
|
+
body: {
|
|
116
|
+
vehicleLicensePlate: licensePlate,
|
|
117
|
+
label,
|
|
118
|
+
},
|
|
119
|
+
modifiers: requestModifiers,
|
|
120
|
+
sessionId,
|
|
121
|
+
});
|
|
122
|
+
throwIfApiError(res);
|
|
123
|
+
return { success: true, licensePlate, label, warningMsg: res.warningMsg ?? undefined };
|
|
124
|
+
}
|
|
125
|
+
export async function removeVehicleLabel(licensePlate, label, requestModifiers, sessionId) {
|
|
126
|
+
const res = await postApi({
|
|
127
|
+
route: "/vehicle/removeVehicleLabel",
|
|
128
|
+
body: {
|
|
129
|
+
vehicleLicensePlate: licensePlate,
|
|
130
|
+
label,
|
|
131
|
+
},
|
|
132
|
+
modifiers: requestModifiers,
|
|
133
|
+
sessionId,
|
|
134
|
+
});
|
|
135
|
+
throwIfApiError(res);
|
|
136
|
+
return { success: true, licensePlate, label, warningMsg: res.warningMsg ?? undefined };
|
|
137
|
+
}
|
|
138
|
+
/** The raw saved vehicle, for the exists-checks the write paths need. */
|
|
139
|
+
export async function findSavedVehicle(licensePlate, requestModifiers, sessionId) {
|
|
140
|
+
const res = await postApi({
|
|
141
|
+
route: "/vehicle/getVehicles",
|
|
142
|
+
body: {},
|
|
88
143
|
modifiers: requestModifiers,
|
|
89
144
|
sessionId,
|
|
90
145
|
});
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
return
|
|
146
|
+
throwIfApiError(res);
|
|
147
|
+
const normalized = licensePlate.trim().toUpperCase();
|
|
148
|
+
return res.vehicles?.find(vehicle => vehicle?.licensePlate?.trim().toUpperCase() === normalized);
|
|
94
149
|
}
|
|
@@ -9,12 +9,14 @@ export async function getPolicyAlerts(args, requestModifiers, sessionId) {
|
|
|
9
9
|
}).then(response => {
|
|
10
10
|
return {
|
|
11
11
|
...response,
|
|
12
|
-
policyAlerts: (response.policyAlerts || [])
|
|
12
|
+
policyAlerts: (response.policyAlerts || [])
|
|
13
|
+
.filter((alert) => alert != null)
|
|
14
|
+
.map((alert) => ({
|
|
13
15
|
...alert,
|
|
14
16
|
labels: [
|
|
15
17
|
...(alert.alertingEventFaces || []),
|
|
16
18
|
...(alert.alertingEventVehicles || []),
|
|
17
|
-
].flatMap(
|
|
19
|
+
].flatMap(event => event?.labels ?? []),
|
|
18
20
|
createdOnString: alert.timestampMs
|
|
19
21
|
? formatTimestamp(alert.timestampMs, args.timeZone)
|
|
20
22
|
: undefined,
|
|
@@ -31,7 +33,9 @@ export async function getExpiringPolicyAlerts(args, requestModifiers, sessionId)
|
|
|
31
33
|
}).then(response => {
|
|
32
34
|
return {
|
|
33
35
|
...response,
|
|
34
|
-
policyAlerts: (response.policyAlerts || [])
|
|
36
|
+
policyAlerts: (response.policyAlerts || [])
|
|
37
|
+
.filter((alert) => alert != null)
|
|
38
|
+
.map((alert) => ({
|
|
35
39
|
...alert,
|
|
36
40
|
createdOnString: alert.timestampMs
|
|
37
41
|
? formatTimestamp(alert.timestampMs)
|
|
@@ -423,7 +423,10 @@ export async function getCustomLLMReport(promptUuid, promptType, startTimeMs, en
|
|
|
423
423
|
return result;
|
|
424
424
|
}
|
|
425
425
|
export async function getAuditFeed(startTimeMs, endTimeMs, requestModifiers, sessionId) {
|
|
426
|
-
const body = {
|
|
426
|
+
const body = {
|
|
427
|
+
timestampMsAfter: startTimeMs,
|
|
428
|
+
timestampMsBefore: endTimeMs,
|
|
429
|
+
};
|
|
427
430
|
const response = await postApi({
|
|
428
431
|
route: "/report/getAuditFeed",
|
|
429
432
|
body,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { postApi } from "../network/network.js";
|
|
1
|
+
import { postApi, throwIfApiError } from "../network/network.js";
|
|
2
2
|
export async function listRules(requestModifiers, sessionId) {
|
|
3
3
|
const res = await postApi({
|
|
4
4
|
route: "/rules/getRulesForOrg",
|
|
@@ -6,9 +6,7 @@ export async function listRules(requestModifiers, sessionId) {
|
|
|
6
6
|
modifiers: requestModifiers,
|
|
7
7
|
sessionId,
|
|
8
8
|
});
|
|
9
|
-
|
|
10
|
-
throw new Error(JSON.stringify(res));
|
|
11
|
-
}
|
|
9
|
+
throwIfApiError(res);
|
|
12
10
|
return (res.rules?.map((rule) => ({
|
|
13
11
|
uuid: rule.uuid ?? undefined,
|
|
14
12
|
name: rule.name ?? undefined,
|
|
@@ -26,9 +24,7 @@ export async function createRule(ruleConfig, requestModifiers, sessionId) {
|
|
|
26
24
|
modifiers: requestModifiers,
|
|
27
25
|
sessionId,
|
|
28
26
|
});
|
|
29
|
-
|
|
30
|
-
throw new Error(JSON.stringify(res));
|
|
31
|
-
}
|
|
27
|
+
throwIfApiError(res);
|
|
32
28
|
return {
|
|
33
29
|
uuid: res.ruleUuid ?? undefined,
|
|
34
30
|
success: true,
|
|
@@ -41,9 +37,7 @@ export async function updateRule(ruleConfig, requestModifiers, sessionId) {
|
|
|
41
37
|
modifiers: requestModifiers,
|
|
42
38
|
sessionId,
|
|
43
39
|
});
|
|
44
|
-
|
|
45
|
-
throw new Error(JSON.stringify(res));
|
|
46
|
-
}
|
|
40
|
+
throwIfApiError(res);
|
|
47
41
|
return { success: true };
|
|
48
42
|
}
|
|
49
43
|
export async function deleteRule(ruleUuid, requestModifiers, sessionId) {
|
|
@@ -53,9 +47,7 @@ export async function deleteRule(ruleUuid, requestModifiers, sessionId) {
|
|
|
53
47
|
modifiers: requestModifiers,
|
|
54
48
|
sessionId,
|
|
55
49
|
});
|
|
56
|
-
|
|
57
|
-
throw new Error(JSON.stringify(res));
|
|
58
|
-
}
|
|
50
|
+
throwIfApiError(res);
|
|
59
51
|
return { uuid: ruleUuid, success: true };
|
|
60
52
|
}
|
|
61
53
|
export async function getRuleRecords(ruleUuid, requestModifiers, sessionId) {
|
|
@@ -65,9 +57,7 @@ export async function getRuleRecords(ruleUuid, requestModifiers, sessionId) {
|
|
|
65
57
|
modifiers: requestModifiers,
|
|
66
58
|
sessionId,
|
|
67
59
|
});
|
|
68
|
-
|
|
69
|
-
throw new Error(JSON.stringify(res));
|
|
70
|
-
}
|
|
60
|
+
throwIfApiError(res);
|
|
71
61
|
return (res.rulesEventRecords?.map((record) => ({
|
|
72
62
|
uuid: record.uuid ?? undefined,
|
|
73
63
|
ruleUuid: record.ruleUuid ?? undefined,
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { postApi, throwIfApiError } from "../network/network.js";
|
|
2
|
+
import { describeWeeklyInterval } from "../util.js";
|
|
3
|
+
/**
|
|
4
|
+
* api2 models a weekly schedule as `{name, strategy, intervalList}` where every
|
|
5
|
+
* interval is a pair of "minute of week" integers. We return the raw integers
|
|
6
|
+
* *and* a rendered `window` string — see `describeWeeklyInterval`.
|
|
7
|
+
*/
|
|
8
|
+
function mapSchedule(raw) {
|
|
9
|
+
return {
|
|
10
|
+
uuid: raw.uuid ?? undefined,
|
|
11
|
+
name: raw.name ?? undefined,
|
|
12
|
+
strategy: raw.strategy ?? undefined,
|
|
13
|
+
mutable: raw.mutable ?? undefined,
|
|
14
|
+
oneTimeUse: raw.oneTimeUse ?? undefined,
|
|
15
|
+
intervals: raw.intervalList?.map(interval => ({
|
|
16
|
+
minuteOfWeekStart: interval.minuteOfWeekStart ?? undefined,
|
|
17
|
+
minuteOfWeekStop: interval.minuteOfWeekStop ?? undefined,
|
|
18
|
+
window: describeWeeklyInterval(interval.minuteOfWeekStart, interval.minuteOfWeekStop),
|
|
19
|
+
})) ?? [],
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export async function findSchedules(requestModifiers, sessionId) {
|
|
23
|
+
const res = await postApi({
|
|
24
|
+
route: "/policy/findSchedules",
|
|
25
|
+
body: {},
|
|
26
|
+
modifiers: requestModifiers,
|
|
27
|
+
sessionId,
|
|
28
|
+
});
|
|
29
|
+
throwIfApiError(res);
|
|
30
|
+
return {
|
|
31
|
+
schedules: res.schedules?.flatMap(raw => (raw ? [mapSchedule(raw)] : [])) ?? [],
|
|
32
|
+
warningMsg: res.warningMsg ?? undefined,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export async function createSchedule(scheduleInput, requestModifiers, sessionId) {
|
|
36
|
+
const res = await postApi({
|
|
37
|
+
route: "/policy/createSchedule",
|
|
38
|
+
body: {
|
|
39
|
+
schedule: scheduleInput,
|
|
40
|
+
},
|
|
41
|
+
modifiers: requestModifiers,
|
|
42
|
+
sessionId,
|
|
43
|
+
});
|
|
44
|
+
throwIfApiError(res);
|
|
45
|
+
return {
|
|
46
|
+
scheduleUuid: res.scheduleUuid ?? undefined,
|
|
47
|
+
warningMsg: res.warningMsg ?? undefined,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function updateSchedule(scheduleInput, requestModifiers, sessionId) {
|
|
51
|
+
const res = await postApi({
|
|
52
|
+
route: "/policy/updateSchedule",
|
|
53
|
+
body: {
|
|
54
|
+
schedule: scheduleInput,
|
|
55
|
+
},
|
|
56
|
+
modifiers: requestModifiers,
|
|
57
|
+
sessionId,
|
|
58
|
+
});
|
|
59
|
+
throwIfApiError(res);
|
|
60
|
+
return {
|
|
61
|
+
updated: { success: true, scheduleUuid: scheduleInput.uuid },
|
|
62
|
+
warningMsg: res.warningMsg ?? undefined,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function deleteSchedule(scheduleUuid, requestModifiers, sessionId) {
|
|
66
|
+
const res = await postApi({
|
|
67
|
+
route: "/policy/deleteSchedule",
|
|
68
|
+
body: { scheduleUuid },
|
|
69
|
+
modifiers: requestModifiers,
|
|
70
|
+
sessionId,
|
|
71
|
+
});
|
|
72
|
+
throwIfApiError(res);
|
|
73
|
+
return {
|
|
74
|
+
deleted: { success: true, scheduleUuid },
|
|
75
|
+
warningMsg: res.warningMsg ?? undefined,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Which policies reference a schedule. Deleting a schedule that is still in use
|
|
80
|
+
* silently changes the behaviour of every policy attached to it, so the delete
|
|
81
|
+
* path calls this first and refuses when the list is non-empty.
|
|
82
|
+
*/
|
|
83
|
+
export async function getPoliciesUsingSchedule(scheduleUuid, requestModifiers, sessionId) {
|
|
84
|
+
const res = await postApi({
|
|
85
|
+
route: "/policy/getPoliciesUsingSchedule",
|
|
86
|
+
body: {
|
|
87
|
+
scheduleUuid,
|
|
88
|
+
},
|
|
89
|
+
modifiers: requestModifiers,
|
|
90
|
+
sessionId,
|
|
91
|
+
});
|
|
92
|
+
throwIfApiError(res);
|
|
93
|
+
const collect = (list, policyType) => list?.flatMap(entry => entry ? [{ uuid: entry.uuid ?? undefined, name: entry.name ?? undefined, policyType }] : []) ?? [];
|
|
94
|
+
return {
|
|
95
|
+
usedBy: [
|
|
96
|
+
...collect(res.cameraPolicyList, "camera"),
|
|
97
|
+
...collect(res.doorPolicyList, "door"),
|
|
98
|
+
...collect(res.accessControlledDoorPolicyList, "access-controlled-door"),
|
|
99
|
+
...collect(res.audioPolicyList, "audio"),
|
|
100
|
+
...collect(res.climatePolicyList, "climate"),
|
|
101
|
+
...collect(res.occupancyPolicyList, "occupancy"),
|
|
102
|
+
...collect(res.proximityPolicyList, "proximity"),
|
|
103
|
+
...collect(res.videoIntercomPolicyList, "video-intercom"),
|
|
104
|
+
],
|
|
105
|
+
warningMsg: res.warningMsg ?? undefined,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { postApi } from "../network/network.js";
|
|
1
|
+
import { postApi, throwIfApiError } from "../network/network.js";
|
|
2
2
|
export async function searchLicensePlates(query, startTimeMs, endTimeMs, deviceUuids, locationUuids, requestModifiers, sessionId) {
|
|
3
3
|
const body = {
|
|
4
4
|
licensePlate: query,
|
|
@@ -13,9 +13,7 @@ export async function searchLicensePlates(query, startTimeMs, endTimeMs, deviceU
|
|
|
13
13
|
modifiers: requestModifiers,
|
|
14
14
|
sessionId,
|
|
15
15
|
});
|
|
16
|
-
|
|
17
|
-
throw new Error(JSON.stringify(res));
|
|
18
|
-
}
|
|
16
|
+
throwIfApiError(res);
|
|
19
17
|
return (res.vehicleEvents?.map((event) => ({
|
|
20
18
|
licensePlate: event.vehicleLicensePlate ?? undefined,
|
|
21
19
|
deviceUuid: event.deviceUuid ?? undefined,
|
|
@@ -36,9 +34,7 @@ export async function searchObjectsByColor(color, cameraUuid, startTimeMs, endTi
|
|
|
36
34
|
modifiers: requestModifiers,
|
|
37
35
|
sessionId,
|
|
38
36
|
});
|
|
39
|
-
|
|
40
|
-
throw new Error(JSON.stringify(res));
|
|
41
|
-
}
|
|
37
|
+
throwIfApiError(res);
|
|
42
38
|
return (res.objects?.map((result) => ({
|
|
43
39
|
deviceUuid: result.deviceUuid ?? undefined,
|
|
44
40
|
timestampMs: result.timestampMs ?? undefined,
|
|
@@ -62,9 +58,7 @@ export async function searchObjectsByText(textQuery, startTimeMs, endTimeMs, dev
|
|
|
62
58
|
modifiers: requestModifiers,
|
|
63
59
|
sessionId,
|
|
64
60
|
});
|
|
65
|
-
|
|
66
|
-
throw new Error(JSON.stringify(res));
|
|
67
|
-
}
|
|
61
|
+
throwIfApiError(res);
|
|
68
62
|
return (res.similarEmbeddings?.map((match) => ({
|
|
69
63
|
deviceUuid: match.embedding?.deviceUuid ?? undefined,
|
|
70
64
|
timestampMs: match.embedding?.timestamp ?? undefined,
|
|
@@ -84,9 +78,7 @@ export async function searchMotionGrid(cameraUuid, startTimeMs, endTimeMs, reque
|
|
|
84
78
|
modifiers: requestModifiers,
|
|
85
79
|
sessionId,
|
|
86
80
|
});
|
|
87
|
-
|
|
88
|
-
throw new Error(JSON.stringify(res));
|
|
89
|
-
}
|
|
81
|
+
throwIfApiError(res);
|
|
90
82
|
return (res.timeUtcSecsList?.map((ts) => ({
|
|
91
83
|
timestampSecs: ts,
|
|
92
84
|
})) ?? []);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { postApi } from "../network/network.js";
|
|
1
|
+
import { apiFailureMessage, postApi } from "../network/network.js";
|
|
2
2
|
/**
|
|
3
3
|
* Updates camera configuration using the faceted config API
|
|
4
4
|
*/
|
|
@@ -350,3 +350,98 @@ export function formatCameraSettings(settings) {
|
|
|
350
350
|
}
|
|
351
351
|
return sections.join("\n\n");
|
|
352
352
|
}
|
|
353
|
+
/**
|
|
354
|
+
* Apply settings to a DOORBELL camera.
|
|
355
|
+
*
|
|
356
|
+
* Doorbell cameras take the same setting NAMES as cameras but a different
|
|
357
|
+
* envelope: `/doorbellcamera/updateConfig` wants one FLAT `configUpdate` object,
|
|
358
|
+
* where `/camera/updateFacetedConfig` wants `videoFacetSettings` /
|
|
359
|
+
* `audioFacetSettings` / `deviceSettings` keyed by facet. Sending the faceted
|
|
360
|
+
* shape here is accepted and silently changes nothing, so the flattening has to
|
|
361
|
+
* happen on this side.
|
|
362
|
+
*/
|
|
363
|
+
export async function updateDoorbellCameraConfig(deviceUuid, settings, requestModifiers, sessionId) {
|
|
364
|
+
try {
|
|
365
|
+
const configUpdate = { deviceUuid, ...settings };
|
|
366
|
+
const result = await postApi({
|
|
367
|
+
route: "/doorbellcamera/updateConfig",
|
|
368
|
+
body: { configUpdate },
|
|
369
|
+
modifiers: requestModifiers,
|
|
370
|
+
sessionId,
|
|
371
|
+
});
|
|
372
|
+
if (result.error) {
|
|
373
|
+
return {
|
|
374
|
+
success: false,
|
|
375
|
+
error: result.errorMsg || "Failed to update doorbell camera configuration",
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return { success: true, updatedSettings: configUpdate };
|
|
379
|
+
}
|
|
380
|
+
catch (error) {
|
|
381
|
+
return {
|
|
382
|
+
success: false,
|
|
383
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Read a doorbell camera's current config. The response `config` is the same
|
|
389
|
+
* FLAT object `/doorbellcamera/updateConfig` writes, so a write can be verified
|
|
390
|
+
* by reading the changed keys back — which matters here because the update
|
|
391
|
+
* response only echoes the caller's own input (`updatedSettings`) and cannot
|
|
392
|
+
* distinguish a real write from a silent no-op.
|
|
393
|
+
*/
|
|
394
|
+
export async function getDoorbellCameraConfig(deviceUuid, requestModifiers, sessionId) {
|
|
395
|
+
try {
|
|
396
|
+
const result = await postApi({
|
|
397
|
+
route: "/doorbellcamera/getConfig",
|
|
398
|
+
body: { deviceUuid },
|
|
399
|
+
modifiers: requestModifiers,
|
|
400
|
+
sessionId,
|
|
401
|
+
});
|
|
402
|
+
if (result.error) {
|
|
403
|
+
return {
|
|
404
|
+
success: false,
|
|
405
|
+
error: apiFailureMessage(result) ?? "Failed to read the doorbell camera's configuration",
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
return { success: true, config: (result.config ?? {}) };
|
|
409
|
+
}
|
|
410
|
+
catch (error) {
|
|
411
|
+
return {
|
|
412
|
+
success: false,
|
|
413
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
/** Confirm a doorbell camera exists before writing to it. */
|
|
418
|
+
export async function getDoorbellCameraDetails(deviceUuid, requestModifiers, sessionId) {
|
|
419
|
+
try {
|
|
420
|
+
const result = await postApi({
|
|
421
|
+
route: "/doorbellcamera/getMinimalStateList",
|
|
422
|
+
body: {},
|
|
423
|
+
modifiers: requestModifiers,
|
|
424
|
+
sessionId,
|
|
425
|
+
});
|
|
426
|
+
if (result.error) {
|
|
427
|
+
return {
|
|
428
|
+
success: false,
|
|
429
|
+
error: result.errorMsg || "Failed to list doorbell cameras",
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
const match = result.minimalStates?.find(state => state?.uuid === deviceUuid);
|
|
433
|
+
if (!match) {
|
|
434
|
+
return {
|
|
435
|
+
success: false,
|
|
436
|
+
error: `No doorbell camera in this organization has the uuid "${deviceUuid}". Use get-entity-tool with entityType doorbell-camera to list them — do not guess a uuid.`,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
return { success: true, name: match.name ?? undefined };
|
|
440
|
+
}
|
|
441
|
+
catch (error) {
|
|
442
|
+
return {
|
|
443
|
+
success: false,
|
|
444
|
+
error: error instanceof Error ? error.message : "Unknown error occurred",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { postApi } from "../network/network.js";
|
|
1
|
+
import { apiWarning, postApi, throwIfApiError } from "../network/network.js";
|
|
2
2
|
export async function listUsers(requestModifiers, sessionId) {
|
|
3
3
|
const res = await postApi({
|
|
4
4
|
route: "/user/getUsersInOrg",
|
|
@@ -6,9 +6,7 @@ export async function listUsers(requestModifiers, sessionId) {
|
|
|
6
6
|
modifiers: requestModifiers,
|
|
7
7
|
sessionId,
|
|
8
8
|
});
|
|
9
|
-
|
|
10
|
-
throw new Error(JSON.stringify(res));
|
|
11
|
-
}
|
|
9
|
+
throwIfApiError(res);
|
|
12
10
|
return (res.users?.map(user => ({
|
|
13
11
|
uuid: user.uuid ?? undefined,
|
|
14
12
|
email: user.email ?? undefined,
|
|
@@ -23,9 +21,7 @@ export async function findUserByEmail(email, requestModifiers, sessionId) {
|
|
|
23
21
|
modifiers: requestModifiers,
|
|
24
22
|
sessionId,
|
|
25
23
|
});
|
|
26
|
-
|
|
27
|
-
throw new Error(JSON.stringify(res));
|
|
28
|
-
}
|
|
24
|
+
throwIfApiError(res);
|
|
29
25
|
const user = res.user;
|
|
30
26
|
if (!user)
|
|
31
27
|
return undefined;
|
|
@@ -48,9 +44,7 @@ export async function getPermissionsForCurrentUser(requestModifiers, sessionId)
|
|
|
48
44
|
modifiers: requestModifiers,
|
|
49
45
|
sessionId,
|
|
50
46
|
});
|
|
51
|
-
|
|
52
|
-
throw new Error(JSON.stringify(res));
|
|
53
|
-
}
|
|
47
|
+
throwIfApiError(res);
|
|
54
48
|
return {
|
|
55
49
|
role: res.role ?? undefined,
|
|
56
50
|
functionalityAccessMap: res.functionalityAccessMap
|
|
@@ -98,9 +92,7 @@ export async function getPermissionGroups(requestModifiers, sessionId) {
|
|
|
98
92
|
modifiers: requestModifiers,
|
|
99
93
|
sessionId,
|
|
100
94
|
});
|
|
101
|
-
|
|
102
|
-
throw new Error(JSON.stringify(res));
|
|
103
|
-
}
|
|
95
|
+
throwIfApiError(res);
|
|
104
96
|
return (res.permissionGroups?.map(group => ({
|
|
105
97
|
uuid: group.uuid ?? undefined,
|
|
106
98
|
name: group.name ?? undefined,
|
|
@@ -124,3 +116,56 @@ export async function getPermissionGroups(requestModifiers, sessionId) {
|
|
|
124
116
|
locationGranularAccessMap: toNestedStringMap(group.locationGranularAccessMap),
|
|
125
117
|
})) ?? []);
|
|
126
118
|
}
|
|
119
|
+
// ---------------------------------------------------------------------------
|
|
120
|
+
// User write paths
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
export async function createUser(input, requestModifiers, sessionId) {
|
|
123
|
+
const res = await postApi({
|
|
124
|
+
route: "/user/createUser",
|
|
125
|
+
body: {
|
|
126
|
+
name: input.name,
|
|
127
|
+
email: input.email,
|
|
128
|
+
permissionGroupUuid: input.permissionGroupUuid,
|
|
129
|
+
// Creating a user normally emails them an invitation. That is an
|
|
130
|
+
// outward-facing side effect, so the caller has to opt into suppressing it
|
|
131
|
+
// rather than us silently picking either behaviour.
|
|
132
|
+
suppressWelcomeEmail: input.suppressWelcomeEmail ?? false,
|
|
133
|
+
},
|
|
134
|
+
modifiers: requestModifiers,
|
|
135
|
+
sessionId,
|
|
136
|
+
});
|
|
137
|
+
throwIfApiError(res);
|
|
138
|
+
return {
|
|
139
|
+
success: true,
|
|
140
|
+
uuid: res.userUuid ?? undefined,
|
|
141
|
+
warningMsg: res.warningMsg ?? undefined,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Selective user update — omitted fields are left alone, so a rename cannot
|
|
146
|
+
* blank someone's notification settings or MFA flag.
|
|
147
|
+
*/
|
|
148
|
+
export async function updateUser(userUuid, changes, requestModifiers, sessionId) {
|
|
149
|
+
const res = await postApi({
|
|
150
|
+
route: "/user/updateUserSelective",
|
|
151
|
+
body: {
|
|
152
|
+
userUuid,
|
|
153
|
+
name: changes.name,
|
|
154
|
+
permissionGroupUuid: changes.permissionGroupUuid,
|
|
155
|
+
},
|
|
156
|
+
modifiers: requestModifiers,
|
|
157
|
+
sessionId,
|
|
158
|
+
});
|
|
159
|
+
throwIfApiError(res);
|
|
160
|
+
return { success: true, uuid: userUuid, warningMsg: apiWarning(res) };
|
|
161
|
+
}
|
|
162
|
+
export async function deleteUser(userUuid, requestModifiers, sessionId) {
|
|
163
|
+
const res = await postApi({
|
|
164
|
+
route: "/user/deleteUser",
|
|
165
|
+
body: { userUuid },
|
|
166
|
+
modifiers: requestModifiers,
|
|
167
|
+
sessionId,
|
|
168
|
+
});
|
|
169
|
+
throwIfApiError(res);
|
|
170
|
+
return { success: true, uuid: userUuid, warningMsg: apiWarning(res) };
|
|
171
|
+
}
|
package/dist/createServer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { createTracingProxy } from "./telemetry/tracingProxy.js";
|
|
4
|
-
import { resolveAccessibleApps } from "./api/get-accessible-apps.js";
|
|
4
|
+
import { resolveAccessibleApps, resolveSessionIdentity, } from "./api/get-accessible-apps.js";
|
|
5
5
|
import { logger } from "./logger.js";
|
|
6
6
|
import { createFilteringProxy } from "./util.js";
|
|
7
7
|
import getResources from "./resources/getResources.js";
|
|
@@ -42,6 +42,11 @@ export async function serverInit() {
|
|
|
42
42
|
*
|
|
43
43
|
* Otherwise (unresolved session, empty apps, or only other enums like
|
|
44
44
|
* RHOMBUS_KEY), fall back to the permissive union of console + partner sets.
|
|
45
|
+
*
|
|
46
|
+
* Support-authority sessions (getCurrentUser with no `user`, or
|
|
47
|
+
* `sessionType === "SUPPORT"`) are resolved to exactly `[CONSOLE]` upstream in
|
|
48
|
+
* `resolveAccessibleApps`, so they take the console branch here and never the
|
|
49
|
+
* permissive union.
|
|
45
50
|
*/
|
|
46
51
|
function pickToolsForSession(apps) {
|
|
47
52
|
if (apps !== null && apps.length > 0 && apps.includes(RhombusAppEnum.PARTNER)) {
|
|
@@ -59,7 +64,11 @@ function isNodeDevEnvironment() {
|
|
|
59
64
|
* Human-readable caller + tool-set summary for dev logs (must stay aligned with
|
|
60
65
|
* {@link pickToolsForSession}).
|
|
61
66
|
*/
|
|
62
|
-
function describeCallerForDevLogs(apps) {
|
|
67
|
+
function describeCallerForDevLogs(apps, identity) {
|
|
68
|
+
if (identity?.sessionType === "SUPPORT") {
|
|
69
|
+
const who = [identity.name, identity.email].filter(Boolean).join(" ");
|
|
70
|
+
return `caller=support session (${who || "no name"}, id=${identity.userId ?? "?"}); tool sets=shared + console`;
|
|
71
|
+
}
|
|
63
72
|
if (apps === null) {
|
|
64
73
|
return "caller=unknown (no session or unresolved apps); tool sets=shared + console + partner (permissive)";
|
|
65
74
|
}
|
|
@@ -77,11 +86,11 @@ function describeCallerForDevLogs(apps) {
|
|
|
77
86
|
}
|
|
78
87
|
return `caller=other (accessibleRhombusApps=[${apps.join(", ")}]); tool sets=shared + console + partner (permissive)`;
|
|
79
88
|
}
|
|
80
|
-
function logDevToolRegistration(sessionId, apps, toolsToRegister) {
|
|
89
|
+
function logDevToolRegistration(sessionId, apps, identity, toolsToRegister) {
|
|
81
90
|
if (!isNodeDevEnvironment())
|
|
82
91
|
return;
|
|
83
92
|
const names = toolsToRegister.map((t) => path.basename(t.name, ".js")).sort();
|
|
84
|
-
logger.info(`[dev MCP tools] session=${sessionId ?? "(none)"} — ${describeCallerForDevLogs(apps)} — registering ${names.length} tools`);
|
|
93
|
+
logger.info(`[dev MCP tools] session=${sessionId ?? "(none)"} — ${describeCallerForDevLogs(apps, identity)} — registering ${names.length} tools`);
|
|
85
94
|
logger.info(`[dev MCP tools] tool names: ${names.join(", ")}`);
|
|
86
95
|
}
|
|
87
96
|
export default async function createServer({ sessionId } = {}) {
|
|
@@ -103,9 +112,12 @@ export default async function createServer({ sessionId } = {}) {
|
|
|
103
112
|
}
|
|
104
113
|
logger.info(`📚 Registered ${resources.length} resources`);
|
|
105
114
|
const apps = await resolveAccessibleApps(sessionId);
|
|
115
|
+
// Same cached getCurrentUser payload — no second API call.
|
|
116
|
+
const identity = await resolveSessionIdentity(sessionId);
|
|
106
117
|
const toolsToRegister = pickToolsForSession(apps);
|
|
107
|
-
logDevToolRegistration(sessionId, apps, toolsToRegister);
|
|
108
|
-
|
|
118
|
+
logDevToolRegistration(sessionId, apps, identity, toolsToRegister);
|
|
119
|
+
const sessionKind = identity?.sessionType === "SUPPORT" ? " sessionType=SUPPORT" : "";
|
|
120
|
+
logger.info(`🔒 Session ${sessionId ?? "(none)"}:${sessionKind} apps=[${apps?.join(", ") ?? "unknown"}] — registering ${toolsToRegister.length} tools`);
|
|
109
121
|
// Tracing wraps every tool handler; filtering wraps on top so handlers are
|
|
110
122
|
// timed after includeFields/filterBy are stripped (keeping them out of
|
|
111
123
|
// mcp.tool.arg_keys). Spans are no-ops unless a host registers an OTel SDK.
|