rhombus-node-mcp 0.1.56 → 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 +6 -4
- package/dist/tools/get-entity-tool.js +40 -20
- package/dist/tools/get-org-information-tool.js +30 -2
- package/dist/tools/time-conversion-tool.js +59 -36
- package/dist/tools/time-tool.js +12 -3
- package/dist/tools/user-tool.js +100 -2
- package/dist/tools-console/access-anomaly-tool.js +2 -19
- package/dist/tools-console/access-control-tool.js +328 -2
- package/dist/tools-console/alarm-monitoring-tool.js +72 -2
- package/dist/tools-console/analytics-tool.js +2 -2
- package/dist/tools-console/badge-timeline-tool.js +2 -21
- package/dist/tools-console/camera-policy-tool.js +252 -0
- package/dist/tools-console/camera-tool.js +111 -89
- package/dist/tools-console/clips-tool.js +21 -31
- package/dist/tools-console/count-tool.js +8 -0
- package/dist/tools-console/create-camera-policy-tool.js +302 -122
- package/dist/tools-console/door-schedule-exception-tool.js +2 -33
- package/dist/tools-console/door-tool.js +130 -2
- package/dist/tools-console/elements-access-anomaly-tool.js +2 -19
- package/dist/tools-console/elements-badge-timeline-tool.js +2 -21
- package/dist/tools-console/elements-lost-badge-tool.js +2 -17
- package/dist/tools-console/elements-tool.js +2 -29
- package/dist/tools-console/events-tool.js +22 -101
- package/dist/tools-console/faces-tool.js +254 -28
- package/dist/tools-console/guest-management-tool.js +127 -2
- package/dist/tools-console/location-tool.js +156 -35
- package/dist/tools-console/lost-badge-tool.js +2 -17
- package/dist/tools-console/lpr-tool.js +89 -23
- package/dist/tools-console/netbox-access-anomaly-tool.js +2 -19
- package/dist/tools-console/netbox-badge-timeline-tool.js +2 -21
- package/dist/tools-console/netbox-lost-badge-tool.js +2 -17
- package/dist/tools-console/netbox-tool.js +2 -29
- package/dist/tools-console/onguard-tool.js +2 -30
- package/dist/tools-console/person-tracking-tool.js +2 -2
- package/dist/tools-console/policy-alerts-tool.js +5 -29
- package/dist/tools-console/reboot-cameras-tool.js +17 -2
- package/dist/tools-console/report-tool.js +10 -40
- package/dist/tools-console/schedule-tool.js +190 -0
- package/dist/tools-console/update-tool.js +298 -168
- package/dist/tools-console/video-walls-tool.js +93 -21
- 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
|
@@ -1,50 +1,171 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { matchesEntityName } from "../utils/entity-name-match.js";
|
|
2
|
+
import { addLocationLabel, createLocation, deleteLocation, findLocation, getLocationLabels, getLocations, removeLocationLabel, updateLocation, } from "../api/location-tool-api.js";
|
|
3
|
+
import { LocationToolAction, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/location-tool-types.js";
|
|
4
|
+
import { createToolStructuredContent, createToolTextContent, extractFromToolExtra, } from "../util.js";
|
|
3
5
|
const TOOL_NAME = "location-tool";
|
|
4
6
|
const TOOL_DESCRIPTION = `This tool performs operations on locations.
|
|
5
|
-
- '
|
|
6
|
-
- '
|
|
7
|
-
- '
|
|
8
|
-
- '
|
|
7
|
+
- '${LocationToolAction.GET}': Retrieves locations with their address, timezone and labels. Pass locationName to search names ignoring case, spaces and punctuation ("iceblocks" matches "Ice Blocks - Headquarters"), or omit it to list all locations. All matches are returned; disambiguate when more than one location matches. When generating reports with location details, use location names not uuids.
|
|
8
|
+
- '${LocationToolAction.CREATE}': Creates a new location with a name and optional address.
|
|
9
|
+
- '${LocationToolAction.UPDATE}': Updates an existing location. Requires locationUuid plus at least one of locationName, locationAddress, postalCode, countryCode, timezoneId. Fields you omit are left unchanged.
|
|
10
|
+
- '${LocationToolAction.DELETE}': Deletes a location. Requires locationUuid. Refuses by default — see confirmDelete.
|
|
11
|
+
- '${LocationToolAction.GET_LABELS}': Retrieves all location labels for the organization.
|
|
12
|
+
- '${LocationToolAction.ADD_LABEL}' / '${LocationToolAction.REMOVE_LABEL}': Attaches or detaches one label on one location. Requires locationUuid and label.`;
|
|
9
13
|
const TOOL_HANDLER = async (args, extra) => {
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
const { requestModifiers, sessionId } = extractFromToolExtra(extra);
|
|
15
|
+
try {
|
|
16
|
+
switch (args.action) {
|
|
17
|
+
case LocationToolAction.GET: {
|
|
18
|
+
const { locations, warningMsg } = await getLocations(requestModifiers, sessionId);
|
|
19
|
+
const query = args.locationName?.trim();
|
|
20
|
+
const matches = query
|
|
21
|
+
? locations.filter(location => location.name && matchesEntityName(location.name, query))
|
|
22
|
+
: locations;
|
|
23
|
+
return createToolStructuredContent({
|
|
24
|
+
locations: matches,
|
|
25
|
+
warningMsg,
|
|
26
|
+
note: locations.length === 0
|
|
27
|
+
? "This organization has no locations configured, so the empty result is not a failed lookup."
|
|
28
|
+
: query && matches.length === 0
|
|
29
|
+
? `No locations matched "${query}". Retry 'get' with locationName omitted to inspect available names.`
|
|
30
|
+
: query && matches.length > 1
|
|
31
|
+
? `Multiple locations matched "${query}". Resolve the intended location before applying a location filter.`
|
|
32
|
+
: undefined,
|
|
33
|
+
});
|
|
20
34
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
35
|
+
case LocationToolAction.CREATE: {
|
|
36
|
+
if (!args.locationName?.trim()) {
|
|
37
|
+
return createToolTextContent("locationName is required for 'create'.");
|
|
38
|
+
}
|
|
39
|
+
const created = await createLocation(args.locationName.trim(), args.locationAddress ?? undefined, requestModifiers, sessionId);
|
|
40
|
+
return createToolStructuredContent({
|
|
41
|
+
createdLocation: { uuid: created.uuid, success: created.success },
|
|
42
|
+
warningMsg: created.warningMsg,
|
|
43
|
+
note: `Created "${args.locationName.trim()}". Its timezone has not been set — schedules and reports at a location are interpreted in its timezone, so set one with 'update' (timezoneId) if it is not the org default.`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
case LocationToolAction.UPDATE: {
|
|
47
|
+
if (!args.locationUuid) {
|
|
48
|
+
return createToolTextContent("locationUuid is required for 'update'.");
|
|
49
|
+
}
|
|
50
|
+
const hasChange = !!args.locationName?.trim() ||
|
|
51
|
+
!!args.locationAddress?.trim() ||
|
|
52
|
+
!!args.postalCode?.trim() ||
|
|
53
|
+
!!args.countryCode?.trim() ||
|
|
54
|
+
!!args.timezoneId?.trim();
|
|
55
|
+
if (!hasChange) {
|
|
56
|
+
return createToolTextContent("'update' needs at least one of locationName, locationAddress, postalCode, countryCode or timezoneId — all were empty, so there is nothing to change.");
|
|
57
|
+
}
|
|
58
|
+
const existing = await findLocation(args.locationUuid, requestModifiers, sessionId);
|
|
59
|
+
if (!existing) {
|
|
60
|
+
return createToolTextContent(`No location in this organization has the uuid "${args.locationUuid}". Use action 'get' to see the locations that exist — do not guess a uuid.`);
|
|
61
|
+
}
|
|
62
|
+
const { updated, warningMsg } = await updateLocation({
|
|
63
|
+
locationUuid: args.locationUuid,
|
|
64
|
+
// selectiveUpdateLocation leaves omitted fields alone, so only the
|
|
65
|
+
// fields the caller actually set are sent.
|
|
66
|
+
name: args.locationName?.trim() || undefined,
|
|
67
|
+
address1: args.locationAddress?.trim() || undefined,
|
|
68
|
+
postalCode: args.postalCode?.trim() || undefined,
|
|
69
|
+
countryCode: args.countryCode?.trim() || undefined,
|
|
70
|
+
timezoneId: args.timezoneId?.trim() || undefined,
|
|
71
|
+
}, requestModifiers, sessionId);
|
|
72
|
+
const notes = [];
|
|
73
|
+
if (args.timezoneId?.trim() && args.timezoneId.trim() !== existing.timezone) {
|
|
74
|
+
notes.push(`The timezone changed from ${existing.timezone ?? "unset"} to ${args.timezoneId.trim()}. Every schedule and report at "${existing.name ?? args.locationUuid}" is now interpreted in the new zone, which shifts when they take effect.`);
|
|
75
|
+
}
|
|
76
|
+
return createToolStructuredContent({
|
|
77
|
+
updated,
|
|
78
|
+
warningMsg,
|
|
79
|
+
note: notes.length > 0 ? notes.join(" ") : undefined,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
case LocationToolAction.DELETE: {
|
|
83
|
+
if (!args.locationUuid) {
|
|
84
|
+
return createToolTextContent("locationUuid is required for 'delete'.");
|
|
85
|
+
}
|
|
86
|
+
const existing = await findLocation(args.locationUuid, requestModifiers, sessionId);
|
|
87
|
+
if (!existing) {
|
|
88
|
+
return createToolTextContent(`No location in this organization has the uuid "${args.locationUuid}". Use action 'get' to see the locations that exist.`);
|
|
89
|
+
}
|
|
90
|
+
if (!args.confirmDelete) {
|
|
91
|
+
// Deleting a location is not reversible and cascades to every device
|
|
92
|
+
// assigned to it, so never do it on an inferred intent.
|
|
93
|
+
return createToolStructuredContent({
|
|
94
|
+
locations: [existing],
|
|
95
|
+
note: `NOT DELETED — nothing was changed. Deleting the location "${existing.name ?? args.locationUuid}" cannot be undone and ` +
|
|
96
|
+
`affects every camera, door and sensor assigned to it, plus its schedules and reports. Confirm with the user ` +
|
|
97
|
+
`explicitly, then call this tool again with confirmDelete: true.`,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
const { deleted, warningMsg } = await deleteLocation(args.locationUuid, requestModifiers, sessionId);
|
|
101
|
+
return createToolStructuredContent({
|
|
102
|
+
deleted,
|
|
103
|
+
warningMsg,
|
|
104
|
+
note: `Deleted the location "${existing.name ?? args.locationUuid}".`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
case LocationToolAction.GET_LABELS: {
|
|
108
|
+
const locationLabels = await getLocationLabels(requestModifiers, sessionId);
|
|
109
|
+
return createToolStructuredContent({
|
|
110
|
+
locationLabels,
|
|
111
|
+
note: locationLabels.length === 0
|
|
112
|
+
? "This organization has no location labels defined. 'add-label' creates a label by using it."
|
|
113
|
+
: undefined,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
case LocationToolAction.ADD_LABEL:
|
|
117
|
+
case LocationToolAction.REMOVE_LABEL: {
|
|
118
|
+
const removing = args.action === LocationToolAction.REMOVE_LABEL;
|
|
119
|
+
if (!args.locationUuid) {
|
|
120
|
+
return createToolTextContent(`locationUuid is required for '${args.action}'.`);
|
|
121
|
+
}
|
|
122
|
+
if (!args.label?.trim()) {
|
|
123
|
+
return createToolTextContent(`label is required for '${args.action}'.`);
|
|
124
|
+
}
|
|
125
|
+
const existing = await findLocation(args.locationUuid, requestModifiers, sessionId);
|
|
126
|
+
if (!existing) {
|
|
127
|
+
return createToolTextContent(`No location in this organization has the uuid "${args.locationUuid}". Use action 'get' to see the locations that exist.`);
|
|
128
|
+
}
|
|
129
|
+
const label = args.label.trim();
|
|
130
|
+
const alreadyHas = existing.labels?.includes(label) ?? false;
|
|
131
|
+
if (removing && !alreadyHas) {
|
|
132
|
+
return createToolStructuredContent({
|
|
133
|
+
locations: [existing],
|
|
134
|
+
note: `Nothing was changed — "${existing.name ?? args.locationUuid}" does not have the label "${label}". Its labels are: ${existing.labels?.length ? existing.labels.join(", ") : "(none)"}.`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (!removing && alreadyHas) {
|
|
138
|
+
return createToolStructuredContent({
|
|
139
|
+
locations: [existing],
|
|
140
|
+
note: `Nothing was changed — "${existing.name ?? args.locationUuid}" already has the label "${label}".`,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
const { labelChanged, warningMsg } = removing
|
|
144
|
+
? await removeLocationLabel(args.locationUuid, label, requestModifiers, sessionId)
|
|
145
|
+
: await addLocationLabel(args.locationUuid, label, requestModifiers, sessionId);
|
|
146
|
+
return createToolStructuredContent({
|
|
147
|
+
labelChanged,
|
|
148
|
+
warningMsg,
|
|
149
|
+
note: `${removing ? "Removed" : "Added"} the label "${label}" ${removing ? "from" : "to"} "${existing.name ?? args.locationUuid}".`,
|
|
150
|
+
});
|
|
28
151
|
}
|
|
29
|
-
ret = await updateLocation(args.locationUuid, args.locationUpdate?.name, args.locationAddress ?? undefined, extra._meta?.requestModifiers, extra.sessionId);
|
|
30
|
-
break;
|
|
31
152
|
}
|
|
32
|
-
case "get-labels":
|
|
33
|
-
ret = await getLocationLabels(extra._meta?.requestModifiers, extra.sessionId);
|
|
34
|
-
break;
|
|
35
|
-
default:
|
|
36
|
-
ret = { error: true, status: `unsupported location tool call: ${action}` };
|
|
37
|
-
break;
|
|
38
153
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
154
|
+
catch (error) {
|
|
155
|
+
return createToolStructuredContent({
|
|
156
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return createToolTextContent(`Invalid action. Valid values are: ${Object.values(LocationToolAction).join(", ")}.`);
|
|
42
160
|
};
|
|
43
161
|
export function createTool(server) {
|
|
44
162
|
server.registerTool(TOOL_NAME, {
|
|
45
163
|
title: "Locations",
|
|
46
164
|
description: TOOL_DESCRIPTION,
|
|
47
165
|
inputSchema: TOOL_ARGS,
|
|
48
|
-
|
|
166
|
+
// Previously declared but never passed, so results came back as raw text
|
|
167
|
+
// and the filtering proxy had nothing to project.
|
|
168
|
+
outputSchema: OUTPUT_SCHEMA.shape,
|
|
169
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
49
170
|
}, TOOL_HANDLER);
|
|
50
171
|
}
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import { getLostBadgeResponse } from "../api/lost-badge-tool-api.js";
|
|
2
2
|
import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/lost-badge-tool-types.js";
|
|
3
3
|
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
|
+
import { buildLostBadgeToolDescription } from "../types/lost-badge-tool-types.js";
|
|
4
5
|
const TOOL_NAME = "lost-badge-tool";
|
|
5
|
-
const TOOL_DESCRIPTION =
|
|
6
|
-
Lost / stolen-badge live response for Honeywell OnGuard (Lenel) access. Use for "a lost badge was just used —
|
|
7
|
-
who is it and where did they go?", or to review lost/inactive-badge use over a window.
|
|
8
|
-
|
|
9
|
-
For each lost/inactive-badge use it returns:
|
|
10
|
-
- cardholderOfRecord (who the badge belongs to — may NOT be who used it) and badgeStatus
|
|
11
|
-
- the door deviceUuid + time, plus clipHint / stillHint for the door footage
|
|
12
|
-
- facesAtDoor: the face(s) captured at the door at that moment — a recognized name, or UNIDENTIFIED (an unknown
|
|
13
|
-
face on a valid badge is the strongest stolen/shared-badge signal), with a thumbnail
|
|
14
|
-
- sightings: that same face tracked across cameras, ordered in time, ending in lastKnownSighting (last-known location)
|
|
15
|
-
|
|
16
|
-
Resolve relative times (e.g. "in the last hour") to ISO 8601 first via the timestamp tool.
|
|
17
|
-
|
|
18
|
-
To present: show the door still/clip (camera-tool image / clips-tool createClip with the hints), the face at the
|
|
19
|
-
door, and the cross-camera track to last-known location. Detection + door evidence are reliable; the face track
|
|
20
|
-
depends on face-recognition coverage, so treat it as investigative, not proof.
|
|
21
|
-
`;
|
|
6
|
+
const TOOL_DESCRIPTION = buildLostBadgeToolDescription("Honeywell OnGuard (Lenel)");
|
|
22
7
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
23
8
|
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
24
9
|
try {
|
|
@@ -1,31 +1,15 @@
|
|
|
1
|
-
import { getSavedVehicles, getVehicleEvents, getVehicleLabels,
|
|
1
|
+
import { addVehicleLabel, deleteVehicle, findSavedVehicle, getSavedVehicles, getVehicleEvents, getVehicleLabels, removeVehicleLabel, saveVehicle, searchLicensePlates, } from "../api/lpr-tool-api.js";
|
|
2
2
|
import { LprToolRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/lpr-tool-types.js";
|
|
3
3
|
import { createToolStructuredContent, createToolTextContent, extractFromToolExtra, } from "../util.js";
|
|
4
4
|
const TOOL_NAME = "lpr-tool";
|
|
5
5
|
const TOOL_DESCRIPTION = `
|
|
6
|
-
|
|
6
|
+
Rhombus LPR (license plate recognition): plate/vehicle events, saved vehicles, vehicle labels, and org-wide plate search. Users say "vehicle", "car" and "license plate" interchangeably — all route here.
|
|
7
7
|
|
|
8
|
-
**Vs events-tool (camera):**
|
|
8
|
+
**Vs events-tool (camera):** events-tool with eventType camera returns that camera's VOD timeline seekpoints (which include vehicle activity). lpr-tool is the LPR product surface — the plate registry, labeling, and org-wide LPR queries. Use lpr-tool when the user needs plates/vehicles as entities, not just "what showed up on this camera's timeline".
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
come into view. However, it is possible that the recognized license is only a partial match, so keep that in mind when using this tool.
|
|
12
|
-
Users will be able to save license plates into the system, and then additionally label them with a name.
|
|
10
|
+
Modes via "requestType": ${LprToolRequestType.GET_VEHICLE_EVENTS}, ${LprToolRequestType.GET_SAVED_VEHICLES}, ${LprToolRequestType.GET_VEHICLE_LABELS}, ${LprToolRequestType.SEARCH_LICENSE_PLATES}, ${LprToolRequestType.SAVE_VEHICLE}, ${LprToolRequestType.UPDATE_VEHICLE}, ${LprToolRequestType.DELETE_VEHICLE}, ${LprToolRequestType.ADD_VEHICLE_LABEL}, ${LprToolRequestType.REMOVE_VEHICLE_LABEL} — usage details, label workflow and defaults are documented on the parameters.
|
|
13
11
|
|
|
14
|
-
|
|
15
|
-
is recognized by a rhombus security camera, it will attach the label to the event and will be available on the events returned from (${LprToolRequestType.GET_SAVED_VEHICLES}).
|
|
16
|
-
|
|
17
|
-
You should use the location-tool if trying to pair vehicle events to a particular location. Never use location UUIDs in reports, use names.
|
|
18
|
-
|
|
19
|
-
As such, if the user is asking anything about a label or labels it would be best practice to first call ${LprToolRequestType.GET_VEHICLE_LABELS} and then ${LprToolRequestType.GET_VEHICLE_EVENTS}
|
|
20
|
-
or ${LprToolRequestType.GET_VEHICLE_EVENTS}.
|
|
21
|
-
|
|
22
|
-
This tool has 3 modes of operation, determined by the "requestType" parameter:
|
|
23
|
-
- ${LprToolRequestType.GET_VEHICLE_EVENTS}: Retrieves a list of vehicle events that have been detected by the system. Please keep in mind that this has the *potential*
|
|
24
|
-
to return a lot of data. However, 7 days should be a reasonable time range to start from if the user is not specific.
|
|
25
|
-
- ${LprToolRequestType.GET_SAVED_VEHICLES}: Retrieves a list of saved vehicles that have been saved in the organization.
|
|
26
|
-
- ${LprToolRequestType.GET_VEHICLE_LABELS}: Retrieves a list of vehicle labels that have been saved in the organization.
|
|
27
|
-
|
|
28
|
-
Its very likely that "vehicle", "car", and "license plates" are used interchangeably. Please keep this in mind.
|
|
12
|
+
A saved vehicle is identified by its PLATE, not a UUID, and the plate itself cannot be edited: correcting a mistyped plate means delete-vehicle then save-vehicle. Deleting a saved vehicle does not delete its past detection events.
|
|
29
13
|
`;
|
|
30
14
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
31
15
|
const responseType = args.requestType;
|
|
@@ -65,8 +49,90 @@ const TOOL_HANDLER = async (args, _extra) => {
|
|
|
65
49
|
error: "vehicleName and vehicleLicensePlate are required for save-vehicle.",
|
|
66
50
|
});
|
|
67
51
|
}
|
|
52
|
+
// The plate is the key, so saving one that already exists overwrites it.
|
|
53
|
+
// Say which happened rather than reporting a create either way.
|
|
54
|
+
const existing = await findSavedVehicle(args.vehicleLicensePlate, requestModifiers, sessionId);
|
|
68
55
|
const result = await saveVehicle(args.vehicleName, args.vehicleLicensePlate, args.vehicleDescription ?? undefined, requestModifiers, sessionId);
|
|
69
|
-
return createToolStructuredContent({
|
|
56
|
+
return createToolStructuredContent({
|
|
57
|
+
saveVehicleResult: { success: result.success, licensePlate: result.licensePlate },
|
|
58
|
+
warningMsg: result.warningMsg,
|
|
59
|
+
note: existing
|
|
60
|
+
? `Plate ${args.vehicleLicensePlate} was already saved as "${existing.name ?? "unnamed"}" and has been OVERWRITTEN with the new name and description, not added a second time.`
|
|
61
|
+
: undefined,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
case LprToolRequestType.UPDATE_VEHICLE: {
|
|
65
|
+
if (!args.vehicleLicensePlate) {
|
|
66
|
+
return createToolStructuredContent({
|
|
67
|
+
error: "vehicleLicensePlate is required for update-vehicle.",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
if (!args.vehicleName?.trim() && !args.vehicleDescription?.trim()) {
|
|
71
|
+
return createToolStructuredContent({
|
|
72
|
+
error: "update-vehicle needs at least one of vehicleName or vehicleDescription — both were empty, so there is nothing to change.",
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const existing = await findSavedVehicle(args.vehicleLicensePlate, requestModifiers, sessionId);
|
|
76
|
+
if (!existing) {
|
|
77
|
+
return createToolStructuredContent({
|
|
78
|
+
error: `No saved vehicle has the plate ${args.vehicleLicensePlate}. Use get-saved-vehicles to see the plates that are saved. To save a new one, use save-vehicle.`,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
// saveVehicle is a whole-record write keyed on the plate, so resend the
|
|
82
|
+
// fields the caller did not change.
|
|
83
|
+
const result = await saveVehicle(args.vehicleName?.trim() || existing.name || "", args.vehicleLicensePlate, args.vehicleDescription?.trim() || existing.description || undefined, requestModifiers, sessionId);
|
|
84
|
+
return createToolStructuredContent({
|
|
85
|
+
saveVehicleResult: { success: result.success, licensePlate: result.licensePlate },
|
|
86
|
+
warningMsg: result.warningMsg,
|
|
87
|
+
note: `Updated the saved vehicle for plate ${args.vehicleLicensePlate}. Fields you did not pass were left as they were. The plate itself cannot be changed — delete and re-save to correct one.`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
case LprToolRequestType.DELETE_VEHICLE: {
|
|
91
|
+
if (!args.vehicleLicensePlate) {
|
|
92
|
+
return createToolStructuredContent({
|
|
93
|
+
error: "vehicleLicensePlate is required for delete-vehicle.",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const existing = await findSavedVehicle(args.vehicleLicensePlate, requestModifiers, sessionId);
|
|
97
|
+
if (!existing) {
|
|
98
|
+
return createToolStructuredContent({
|
|
99
|
+
error: `No saved vehicle has the plate ${args.vehicleLicensePlate}, so there is nothing to delete. Use get-saved-vehicles to see the plates that are saved.`,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
const result = await deleteVehicle(args.vehicleLicensePlate, requestModifiers, sessionId);
|
|
103
|
+
return createToolStructuredContent({
|
|
104
|
+
deleteVehicleResult: { success: result.success, licensePlate: result.licensePlate },
|
|
105
|
+
warningMsg: result.warningMsg,
|
|
106
|
+
note: `Removed "${existing.name ?? "unnamed"}" (plate ${args.vehicleLicensePlate}) from the saved-vehicle registry, along with its labels. Past detection events for this plate are NOT deleted — they remain in the vehicle event history.`,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
case LprToolRequestType.ADD_VEHICLE_LABEL:
|
|
110
|
+
case LprToolRequestType.REMOVE_VEHICLE_LABEL: {
|
|
111
|
+
const removing = responseType === LprToolRequestType.REMOVE_VEHICLE_LABEL;
|
|
112
|
+
if (!args.vehicleLicensePlate || !args.vehicleLabel?.trim()) {
|
|
113
|
+
return createToolStructuredContent({
|
|
114
|
+
error: `vehicleLicensePlate and vehicleLabel are both required for ${responseType}.`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const existing = await findSavedVehicle(args.vehicleLicensePlate, requestModifiers, sessionId);
|
|
118
|
+
if (!existing) {
|
|
119
|
+
return createToolStructuredContent({
|
|
120
|
+
error: `No saved vehicle has the plate ${args.vehicleLicensePlate}. A label can only be attached to a SAVED vehicle — save it first with save-vehicle.`,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
const label = args.vehicleLabel.trim();
|
|
124
|
+
const result = removing
|
|
125
|
+
? await removeVehicleLabel(args.vehicleLicensePlate, label, requestModifiers, sessionId)
|
|
126
|
+
: await addVehicleLabel(args.vehicleLicensePlate, label, requestModifiers, sessionId);
|
|
127
|
+
return createToolStructuredContent({
|
|
128
|
+
vehicleLabelResult: {
|
|
129
|
+
success: result.success,
|
|
130
|
+
licensePlate: result.licensePlate,
|
|
131
|
+
label: result.label,
|
|
132
|
+
},
|
|
133
|
+
warningMsg: result.warningMsg,
|
|
134
|
+
note: `${removing ? "Removed" : "Added"} the label "${label}" ${removing ? "from" : "to"} plate ${args.vehicleLicensePlate}. Labels flow through to that plate's detection events, so ${removing ? "past events will stop showing" : "past and future events will show"} this label.`,
|
|
135
|
+
});
|
|
70
136
|
}
|
|
71
137
|
}
|
|
72
138
|
}
|
|
@@ -84,6 +150,6 @@ export function createTool(server) {
|
|
|
84
150
|
description: TOOL_DESCRIPTION,
|
|
85
151
|
inputSchema: TOOL_ARGS,
|
|
86
152
|
outputSchema: OUTPUT_SCHEMA.shape,
|
|
87
|
-
annotations: { readOnlyHint: false, destructiveHint:
|
|
153
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
88
154
|
}, TOOL_HANDLER);
|
|
89
155
|
}
|
|
@@ -1,26 +1,9 @@
|
|
|
1
1
|
import { getNetboxAccessAnomalies } from "../api/netbox-access-anomaly-tool-api.js";
|
|
2
2
|
import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/netbox-access-anomaly-tool-types.js";
|
|
3
3
|
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
|
+
import { buildAccessAnomalyToolDescription } from "../types/access-anomaly-tool-types.js";
|
|
4
5
|
const TOOL_NAME = "netbox-access-anomaly-tool";
|
|
5
|
-
const TOOL_DESCRIPTION =
|
|
6
|
-
Scans Lenel S2 NetBox (Honeywell NetBox) access events over a time window and flags anomalous badge activity. Use for
|
|
7
|
-
"find unusual badge activity", "anything suspicious in access this week", or proactive access review.
|
|
8
|
-
|
|
9
|
-
Runs deterministic rules and returns ranked findings (high severity first):
|
|
10
|
-
- lost_or_inactive_badge: a non-active badge (lost/suspended) was used
|
|
11
|
-
- entry_not_made: access granted but no entry made (possible tailgating)
|
|
12
|
-
- off_hours: entry outside business hours (configurable)
|
|
13
|
-
- impossible_travel: one cardholder at two different areas seconds apart
|
|
14
|
-
- area_novelty: a cardholder's first-ever entry to an area vs their prior history (needs a baseline window)
|
|
15
|
-
|
|
16
|
-
Each finding includes cardholderName, the rule, severity, datetime, area, the camera deviceUuid, a plain-language
|
|
17
|
-
rationale, and clip/still hints. Resolve relative times (e.g. "this week") to ISO 8601 first via the timestamp tool.
|
|
18
|
-
|
|
19
|
-
This is a triage aid: present findings grouped by severity, and for the notable ones call the camera-tool
|
|
20
|
-
(requestType "image", cameraUuid = finding.deviceUuid, timestamp = finding.timestampMs) and/or clips-tool
|
|
21
|
-
("createClip" using finding.clipHint) — in PARALLEL — so a human can confirm. Don't assert wrongdoing; surface the
|
|
22
|
-
evidence.
|
|
23
|
-
`;
|
|
6
|
+
const TOOL_DESCRIPTION = buildAccessAnomalyToolDescription("Lenel S2 NetBox (Honeywell NetBox)");
|
|
24
7
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
25
8
|
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
26
9
|
try {
|
|
@@ -1,28 +1,9 @@
|
|
|
1
1
|
import { getNetboxBadgeTimeline } from "../api/netbox-badge-timeline-tool-api.js";
|
|
2
2
|
import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/netbox-badge-timeline-tool-types.js";
|
|
3
3
|
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
|
+
import { buildBadgeTimelineToolDescription } from "../types/badge-timeline-tool-types.js";
|
|
4
5
|
const TOOL_NAME = "netbox-badge-timeline-tool";
|
|
5
|
-
const TOOL_DESCRIPTION =
|
|
6
|
-
Reconstructs one person's movements through a building from their Lenel S2 NetBox (Honeywell NetBox) badge taps.
|
|
7
|
-
Use this for incident reconstruction / "follow the badge" requests, e.g. "reconstruct Eve's movements
|
|
8
|
-
yesterday" or "where did this cardholder go".
|
|
9
|
-
|
|
10
|
-
Returns the cardholder's badge taps in CHRONOLOGICAL order (oldest first), each with:
|
|
11
|
-
- datetime / timestampMs and the area entered
|
|
12
|
-
- deviceUuid: the camera at that door
|
|
13
|
-
- clipHint (camera + start/end window) and stillHint (camera + timestamp)
|
|
14
|
-
- gapToNextSeconds: time until the next tap (a large gap = unobserved movement between doors)
|
|
15
|
-
plus a "path" array summarizing the areas traversed in order.
|
|
16
|
-
|
|
17
|
-
Resolve relative times like "yesterday" to ISO 8601 first (use the timestamp tool), then pass
|
|
18
|
-
startTime/endTime. cardholderQuery is a full-text name match; if "ambiguousCardholders" is returned the
|
|
19
|
-
query matched more than one person — ask the user which one before trusting the timeline.
|
|
20
|
-
|
|
21
|
-
IMPORTANT — to show the movement visually: for each stop (or the key transitions), call the camera-tool
|
|
22
|
-
(requestType "image", cameraUuid = stop.deviceUuid, timestamp = stop.timestampMs) for a still you can see,
|
|
23
|
-
and/or the clips-tool (requestType "createClip", using stop.clipHint) for video. Issue those per-stop
|
|
24
|
-
media calls in PARALLEL, then present the timeline as a chronological narrative.
|
|
25
|
-
`;
|
|
6
|
+
const TOOL_DESCRIPTION = buildBadgeTimelineToolDescription("Lenel S2 NetBox (Honeywell NetBox)");
|
|
26
7
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
27
8
|
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
28
9
|
try {
|
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import { getNetboxLostBadgeResponse } from "../api/netbox-lost-badge-tool-api.js";
|
|
2
2
|
import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/netbox-lost-badge-tool-types.js";
|
|
3
3
|
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
|
+
import { buildLostBadgeToolDescription } from "../types/lost-badge-tool-types.js";
|
|
4
5
|
const TOOL_NAME = "netbox-lost-badge-tool";
|
|
5
|
-
const TOOL_DESCRIPTION =
|
|
6
|
-
Lost / stolen-badge live response for Lenel S2 NetBox (Honeywell NetBox) access. Use for "a lost badge was just used —
|
|
7
|
-
who is it and where did they go?", or to review lost/inactive-badge use over a window.
|
|
8
|
-
|
|
9
|
-
For each lost/inactive-badge use it returns:
|
|
10
|
-
- cardholderOfRecord (who the badge belongs to — may NOT be who used it) and badgeStatus
|
|
11
|
-
- the door deviceUuid + time, plus clipHint / stillHint for the door footage
|
|
12
|
-
- facesAtDoor: the face(s) captured at the door at that moment — a recognized name, or UNIDENTIFIED (an unknown
|
|
13
|
-
face on a valid badge is the strongest stolen/shared-badge signal), with a thumbnail
|
|
14
|
-
- sightings: that same face tracked across cameras, ordered in time, ending in lastKnownSighting (last-known location)
|
|
15
|
-
|
|
16
|
-
Resolve relative times (e.g. "in the last hour") to ISO 8601 first via the timestamp tool.
|
|
17
|
-
|
|
18
|
-
To present: show the door still/clip (camera-tool image / clips-tool createClip with the hints), the face at the
|
|
19
|
-
door, and the cross-camera track to last-known location. Detection + door evidence are reliable; the face track
|
|
20
|
-
depends on face-recognition coverage, so treat it as investigative, not proof.
|
|
21
|
-
`;
|
|
6
|
+
const TOOL_DESCRIPTION = buildLostBadgeToolDescription("Lenel S2 NetBox (Honeywell NetBox)");
|
|
22
7
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
23
8
|
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
24
9
|
try {
|
|
@@ -1,36 +1,9 @@
|
|
|
1
1
|
import { searchNetboxEvents } from "../api/netbox-tool-api.js";
|
|
2
2
|
import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/netbox-tool-types.js";
|
|
3
|
+
import { buildBadgeEventsToolDescription } from "../types/onguard-tool-types.js";
|
|
3
4
|
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
5
|
const TOOL_NAME = "netbox-events-tool";
|
|
5
|
-
const TOOL_DESCRIPTION =
|
|
6
|
-
Searches Lenel S2 NetBox (Honeywell NetBox) badge / access-control events for the organization. Use this to answer
|
|
7
|
-
"who entered WHERE and WHEN" questions, e.g. "who entered the back office yesterday".
|
|
8
|
-
|
|
9
|
-
NOTE: an organization may run any combination of Honeywell OnGuard (Lenel), Honeywell Elements (LenelS2
|
|
10
|
-
Elements), and Lenel S2 NetBox badge integrations — each searched by its own sibling tool
|
|
11
|
-
(onguard-events-tool / elements-events-tool / netbox-events-tool), all taking identical arguments and
|
|
12
|
-
returning the same shape. For a general "who badged in / did anyone enter" question you usually do NOT
|
|
13
|
-
know which integration recorded the event, so call ALL THREE sibling tools (in parallel) and combine the
|
|
14
|
-
results — each returns an empty list when its integration isn't configured. Restrict to one vendor only
|
|
15
|
-
when the user explicitly names it.
|
|
16
|
-
|
|
17
|
-
Each returned event includes:
|
|
18
|
-
- cardholderName: the person's name
|
|
19
|
-
- deviceUuid: the camera that saw the event
|
|
20
|
-
- timestampMs / datetime: when it happened
|
|
21
|
-
- label: e.g. "NetBox: Badge Authorized" (a grant) or an anomaly label
|
|
22
|
-
- badgeStatus, badgeType, areaEntering, areaExiting, entryMade, isAnomaly
|
|
23
|
-
|
|
24
|
-
Filters (all optional): area, locationUuids, deviceUuids, cardholderQuery, badgeStatus, badgeType,
|
|
25
|
-
anomalyOnly, entryMade, startTime, endTime, limit. Resolve relative times like "yesterday" to ISO 8601
|
|
26
|
-
first (use the timestamp tool), then pass startTime/endTime.
|
|
27
|
-
|
|
28
|
-
IMPORTANT — to show pictures and video of each person so the user can visually identify them: after this
|
|
29
|
-
returns, for each event (or the most relevant ones) call the camera-tool (requestType "image",
|
|
30
|
-
cameraUuid = the event's deviceUuid, timestamp = the event's time) to get a still you can see, and/or the
|
|
31
|
-
clips-tool (requestType "createClip") with a short window around the timestamp for video. Issue those
|
|
32
|
-
per-event media calls in PARALLEL.
|
|
33
|
-
`;
|
|
6
|
+
const TOOL_DESCRIPTION = buildBadgeEventsToolDescription("Lenel S2 NetBox (Honeywell NetBox)", "NetBox");
|
|
34
7
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
35
8
|
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
36
9
|
try {
|
|
@@ -1,36 +1,8 @@
|
|
|
1
1
|
import { searchOnGuardEvents } from "../api/onguard-tool-api.js";
|
|
2
|
-
import { OUTPUT_SCHEMA, TOOL_ARGS } from "../types/onguard-tool-types.js";
|
|
2
|
+
import { buildBadgeEventsToolDescription, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/onguard-tool-types.js";
|
|
3
3
|
import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
|
|
4
4
|
const TOOL_NAME = "onguard-events-tool";
|
|
5
|
-
const TOOL_DESCRIPTION =
|
|
6
|
-
Searches Honeywell OnGuard (Lenel) badge / access-control events for the organization. Use this to answer
|
|
7
|
-
"who entered WHERE and WHEN" questions, e.g. "who entered the back office yesterday".
|
|
8
|
-
|
|
9
|
-
NOTE: an organization may run any combination of Honeywell OnGuard (Lenel), Honeywell Elements (LenelS2
|
|
10
|
-
Elements), and Lenel S2 NetBox badge integrations — each searched by its own sibling tool
|
|
11
|
-
(onguard-events-tool / elements-events-tool / netbox-events-tool), all taking identical arguments and
|
|
12
|
-
returning the same shape. For a general "who badged in / did anyone enter" question you usually do NOT
|
|
13
|
-
know which integration recorded the event, so call ALL THREE sibling tools (in parallel) and combine the
|
|
14
|
-
results — each returns an empty list when its integration isn't configured. Restrict to one vendor only
|
|
15
|
-
when the user explicitly names it.
|
|
16
|
-
|
|
17
|
-
Each returned event includes:
|
|
18
|
-
- cardholderName: the person's name
|
|
19
|
-
- deviceUuid: the camera that saw the event
|
|
20
|
-
- timestampMs / datetime: when it happened
|
|
21
|
-
- label: e.g. "OnGuard: Badge Authorized" (a grant) or an anomaly label
|
|
22
|
-
- badgeStatus, badgeType, areaEntering, areaExiting, entryMade, isAnomaly
|
|
23
|
-
|
|
24
|
-
Filters (all optional): area, locationUuids, deviceUuids, cardholderQuery, badgeStatus, badgeType,
|
|
25
|
-
anomalyOnly, entryMade, startTime, endTime, limit. Resolve relative times like "yesterday" to ISO 8601
|
|
26
|
-
first (use the timestamp tool), then pass startTime/endTime.
|
|
27
|
-
|
|
28
|
-
IMPORTANT — to show pictures and video of each person so the user can visually identify them: after this
|
|
29
|
-
returns, for each event (or the most relevant ones) call the camera-tool (requestType "image",
|
|
30
|
-
cameraUuid = the event's deviceUuid, timestamp = the event's time) to get a still you can see, and/or the
|
|
31
|
-
clips-tool (requestType "createClip") with a short window around the timestamp for video. Issue those
|
|
32
|
-
per-event media calls in PARALLEL.
|
|
33
|
-
`;
|
|
5
|
+
const TOOL_DESCRIPTION = buildBadgeEventsToolDescription("Honeywell OnGuard (Lenel)", "OnGuard");
|
|
34
6
|
const TOOL_HANDLER = async (args, _extra) => {
|
|
35
7
|
const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
|
|
36
8
|
try {
|
|
@@ -20,11 +20,11 @@ Returns:
|
|
|
20
20
|
- path (camera sequence) and lastKnownSighting (last-known location).
|
|
21
21
|
- note: set when no badge tap was found, or no re-id embedding existed on the door camera.
|
|
22
22
|
|
|
23
|
-
Resolve relative times like "yesterday" to ISO 8601 first (use
|
|
23
|
+
Resolve relative times like "yesterday" to ISO 8601 first (use time-tool), then pass
|
|
24
24
|
startTime/endTime. Re-id depends on human-detection coverage, so treat the track as investigative, not proof.
|
|
25
25
|
|
|
26
26
|
IMPORTANT — to show the movement visually: for each sighting (or the key transitions), call the camera-tool
|
|
27
|
-
(requestType "image", cameraUuid = sighting.deviceUuid,
|
|
27
|
+
(requestType "image", cameraUuid = sighting.deviceUuid, timestampISO = the sighting's time as ISO 8601 — convert sighting.timestampMs via time-conversion-tool) for a still, and/or
|
|
28
28
|
the clips-tool (requestType "createClip", using sighting.clipHint) for video. Issue those per-sighting media
|
|
29
29
|
calls in PARALLEL, then present the track as a chronological narrative.
|
|
30
30
|
`;
|
|
@@ -3,35 +3,10 @@ import { ApiPayloadSchema, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/policy-ale
|
|
|
3
3
|
import { extractFromToolExtra } from "../util.js";
|
|
4
4
|
const TOOL_NAME = "policy-alerts-tool";
|
|
5
5
|
const TOOL_DESCRIPTION = `
|
|
6
|
-
Retrieves Rhombus policy alerts
|
|
7
|
-
policies configured in the Rhombus Console. These policies trigger alerts when specific events occur, such as:
|
|
8
|
-
* AI & Computer Vision Events: Based on intelligent video analytics for motion, people, vehicles, facial recognition,
|
|
9
|
-
license plate recognition, or unusual behavior.
|
|
10
|
-
* Device Status Changes: Like camera disconnections or sensor low battery.
|
|
11
|
-
* Physical or Visual Tamper: Detection of physical movement of a device or obstruction of a camera's field of view.
|
|
12
|
-
* Access Control Events: Such as unauthorized access attempts in restricted areas.
|
|
6
|
+
Retrieves Rhombus policy alerts — alerts generated by user-defined alert policies when configured events occur: AI/computer-vision events (motion, people, vehicles, faces, license plates, unusual behavior), device status changes (disconnection, low battery), physical/visual tamper, and access-control events. Alerts are NOT the same as notifications (only some alerts notify, per user settings). Can also report which alert labels have been seen. Supports filtering by existing/expiring, time range, device UUIDs or location UUIDs — pagination and per-queryType requirements are documented on the parameters.
|
|
13
7
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
Can inquire about labels that have been seen.
|
|
17
|
-
|
|
18
|
-
Please note, this is not an exhaustive list, and there may be other types of triggers or events that generate
|
|
19
|
-
policy alerts within the Rhombus system.
|
|
20
|
-
|
|
21
|
-
This tool allows you to filter existing alerts by existing/expiring, a specific time range (before or after a timestamp in ISO 8601 format),
|
|
22
|
-
by a list of device UUIDs, or by a list of location UUIDs.
|
|
23
|
-
You can also specify the maximum number of results to return.
|
|
24
|
-
The output is provided in JSON format.
|
|
25
|
-
|
|
26
|
-
**Pagination**: Results are paginated and have a maximum page size. If the response includes a \`lastEvaluatedKey\` (for queryType "expiringSoon") or both \`lastTimestampISO\` and \`lastUuid\` (for queryTypes "existing" and "alert-groups"), more results are available. Pass these values back in the next call using the corresponding input parameters (lastEvaluatedKey, or lastTimestampISO and lastUuid) to retrieve the next page. Repeat until the response no longer includes these fields.
|
|
27
|
-
|
|
28
|
-
IMPORTANT: The "unhealthy-devices" queryType returns historical alert notifications that were triggered for device health issues.
|
|
29
|
-
It does NOT return live/real-time device connection status. If no device health alert policies are configured, or alerts were
|
|
30
|
-
dismissed, this may return empty even when devices are offline.
|
|
31
|
-
|
|
32
|
-
**To check which devices are currently online/offline, use the get-entity-tool instead.** The get-entity-tool returns the
|
|
33
|
-
current state of all devices including their live connection status (the "connected" field). Request all entity types
|
|
34
|
-
(CAMERA, DOORBELL_CAMERA, BADGE_READER, etc.) and check the "connected" field on each device to determine which are offline.`;
|
|
8
|
+
IMPORTANT: queryType "unhealthy-devices" returns HISTORICAL device-health alert notifications, not live status — it can be empty while devices are offline. To check which devices are currently online/offline, use get-entity-tool instead (filterBy on its "connected" field).
|
|
9
|
+
`;
|
|
35
10
|
const TOOL_HANDLER = async (args, extra) => {
|
|
36
11
|
const payload = ApiPayloadSchema.parse(args);
|
|
37
12
|
const { requestModifiers, sessionId } = extractFromToolExtra(extra);
|
|
@@ -75,6 +50,7 @@ export function createTool(server) {
|
|
|
75
50
|
description: TOOL_DESCRIPTION,
|
|
76
51
|
inputSchema: TOOL_ARGS,
|
|
77
52
|
outputSchema: OUTPUT_SCHEMA.shape,
|
|
78
|
-
|
|
53
|
+
// `dismiss` cannot be undone, which is what destructiveHint is for.
|
|
54
|
+
annotations: { readOnlyHint: false, destructiveHint: true },
|
|
79
55
|
}, TOOL_HANDLER);
|
|
80
56
|
}
|