rhombus-node-mcp 0.1.16 → 0.1.18

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.
@@ -2,6 +2,7 @@ import { getLogger } from "../logger.js";
2
2
  import { appendQueryParams, AUTH_HEADERS, postApi, STATIC_HEADERS } from "../network.js";
3
3
  import { removeNullFields } from "../util.js";
4
4
  const logger = getLogger("camera-tool");
5
+ const MILLISECONDS_PER_DAY = 24 * 60 * 60 * 1000;
5
6
  export async function getImageForCameraAtTime(cameraUuid, timestampMs, requestModifiers, sessionId) {
6
7
  const body = {
7
8
  cameraUuid: cameraUuid,
@@ -60,6 +61,54 @@ export async function getImageForCameraAtTime(cameraUuid, timestampMs, requestMo
60
61
  imageData: base64Image,
61
62
  };
62
63
  }
64
+ async function getCameraStorageData(cameraUuid, requestModifiers, sessionId) {
65
+ // First, get the camera's full state to determine the time range and cloud archive days
66
+ const stateResponse = await postApi({
67
+ route: "/camera/getFullCameraState",
68
+ body: {
69
+ cameraUuid: cameraUuid,
70
+ },
71
+ modifiers: requestModifiers,
72
+ sessionId,
73
+ });
74
+ const cloudArchiveDays = stateResponse.fullCameraState?.onCloudState?.cloud_archive_days ?? null;
75
+ // Get the oldest segment time (in seconds) to use as start time
76
+ const oldestSegmentSecs = stateResponse.fullCameraState?.onCameraState?.oldest_segment_secs ?? 0;
77
+ const endTimeSec = Math.floor(Date.now() / 1000);
78
+ const startTimeSec = oldestSegmentSecs || 0;
79
+ const durationSec = endTimeSec - startTimeSec;
80
+ const presenceResponse = await postApi({
81
+ route: "/camera/getPresenceWindows",
82
+ body: {
83
+ cameraUuid: cameraUuid,
84
+ startTimeSec: startTimeSec,
85
+ durationSec: durationSec,
86
+ },
87
+ modifiers: requestModifiers,
88
+ sessionId,
89
+ });
90
+ const calculateTotalDays = (timeWindows) => {
91
+ if (!timeWindows || timeWindows.length === 0) {
92
+ return 0;
93
+ }
94
+ let totalMilliseconds = 0;
95
+ timeWindows.forEach(window => {
96
+ if (window.startSeconds && window.durationSeconds) {
97
+ const durationMs = window.durationSeconds * 1000;
98
+ totalMilliseconds += durationMs;
99
+ }
100
+ });
101
+ return Math.floor(totalMilliseconds / MILLISECONDS_PER_DAY);
102
+ };
103
+ const presenceWindows = presenceResponse.presenceWindows;
104
+ const daysOnCamera = calculateTotalDays(presenceWindows?.VideoLocal);
105
+ const daysInCloud = calculateTotalDays(presenceWindows?.VideoCloud);
106
+ return {
107
+ daysInCloud,
108
+ daysOnCamera,
109
+ cloudArchiveDays,
110
+ };
111
+ }
63
112
  export async function getCameraSettings(cameraUuid, requestModifiers, sessionId) {
64
113
  const res = await postApi({
65
114
  route: "/camera/getFacetedConfig",
@@ -75,9 +124,13 @@ export async function getCameraSettings(cameraUuid, requestModifiers, sessionId)
75
124
  status: "failed to fetch camera settings",
76
125
  };
77
126
  }
127
+ const storageData = await getCameraStorageData(cameraUuid, requestModifiers, sessionId);
78
128
  return {
79
129
  success: true,
80
130
  config: res.config,
131
+ daysInCloud: storageData.daysInCloud,
132
+ daysOnCamera: storageData.daysOnCamera,
133
+ cloudArchiveDays: storageData.cloudArchiveDays,
81
134
  status: "fetched camera settings",
82
135
  };
83
136
  }
@@ -1,4 +1,20 @@
1
+ import { postApi } from "../network.js";
1
2
  import { getAccessControlledDoors, getAudioGateways, getBadgeReaders, getButtons, getCameraList, getDoorbellCameras, getDoorSensors, getEnvironmentalGateways, getEnvironmentalSensors, getKeypads, getMotionSensors, } from "./get-entity-tool-api.js";
3
+ async function getDeviceFeatures(deviceUuid, requestModifiers, sessionId) {
4
+ const response = await postApi({
5
+ route: "/feature/getDeviceFeatures",
6
+ body: { deviceUuid },
7
+ modifiers: requestModifiers,
8
+ sessionId,
9
+ });
10
+ if (response.error || !response.features) {
11
+ return null;
12
+ }
13
+ return {
14
+ assignedLicense: response.features.assignedLicense,
15
+ featureMap: response.features.featureMap,
16
+ };
17
+ }
2
18
  export async function getAllEntities(deviceUuids, timeZone, tempUnit, requestModifiers, sessionId) {
3
19
  const promises = [
4
20
  getCameraList(requestModifiers, sessionId),
@@ -14,7 +30,7 @@ export async function getAllEntities(deviceUuids, timeZone, tempUnit, requestMod
14
30
  getEnvironmentalGateways(timeZone, requestModifiers, sessionId),
15
31
  ];
16
32
  const responses = await Promise.all(promises);
17
- // Filter each response to only include devices with matching UUIDs
33
+ // Filter each response to only include devices with matching UUIDs and fetch device features
18
34
  for (let i = 0; i < responses.length; i++) {
19
35
  const response = responses[i];
20
36
  // Look through keys and find any with an array
@@ -22,10 +38,22 @@ export async function getAllEntities(deviceUuids, timeZone, tempUnit, requestMod
22
38
  const value = response[key];
23
39
  if (Array.isArray(value)) {
24
40
  // Filter to only include devices with matching UUIDs
25
- response[key] = value.filter((item) => {
41
+ const filteredDevices = value.filter((item) => {
26
42
  return deviceUuids.includes(item.uuid);
27
43
  });
28
- response[`${key}Count`] = response[key].length;
44
+ // Fetch device features for each filtered device
45
+ const devicesWithFeatures = await Promise.all(filteredDevices.map(async (device) => {
46
+ const features = await getDeviceFeatures(device.uuid, requestModifiers, sessionId);
47
+ return {
48
+ ...device,
49
+ ...(features && {
50
+ assignedLicense: features.assignedLicense,
51
+ featureMap: features.featureMap,
52
+ }),
53
+ };
54
+ }));
55
+ response[key] = devicesWithFeatures;
56
+ response[`${key}Count`] = devicesWithFeatures.length;
29
57
  }
30
58
  }
31
59
  }
@@ -22,7 +22,7 @@ or situational assessment. When invoked, the tool provides the following:
22
22
 
23
23
  What follows is a description of the behavior of this tool given the requestType "get-settings"
24
24
 
25
- This tool retrieves the current configuration for a specified camera or associated device (e.g., sensor, access controller). The returned JSON object can include detailed camera settings (e.g., resolution, bitrate) and various device-specific configurations.
25
+ This tool retrieves the current configuration for a specified camera or associated device (e.g., sensor, access controller). The returned JSON object can include detailed camera settings (e.g., resolution, bitrate) and various device-specific configurations (e.g. storage settings).
26
26
 
27
27
  NOTE: To update camera settings, use the update-tool instead.
28
28
  `;
@@ -4,7 +4,8 @@ import { createToolStructuredContent, extractFromToolExtra } from "../util.js";
4
4
  const TOOL_NAME = "entity-lookup-tool";
5
5
  const TOOL_DESCRIPTION = `
6
6
  Retrieves specific entities (or devices) by their UUIDs.
7
- Takes a list of device UUIDs and returns the device information for those specific devices.
7
+ Takes a list of device UUIDs and returns the device information for those specific devices.
8
+ Use this tool when the user asks for details on devices' states and details about their licenses and features.
8
9
  The return structure is a JSON object that contains the states of the requested entities.
9
10
  This data is exact. Only devices with matching UUIDs will be returned.
10
11
  `;
@@ -2,7 +2,7 @@ import { getLocations } from "../api/location-tool-api.js";
2
2
  import { TOOL_ARGS } from "../types/location-tool-types.js";
3
3
  const TOOL_NAME = "location-tool";
4
4
  const TOOL_DESCRIPTION = `This tool performs operations on locations.
5
- - 'get': Retrieves all locations.`;
5
+ - 'get': Retrieves all locations. When generating reports with location details, use location names not uuids.`;
6
6
  const TOOL_HANDLER = async (args, extra) => {
7
7
  const { action } = args;
8
8
  let ret;
@@ -9,6 +9,14 @@ The system's cameras may have LPR enabled, and when it is enabled, it will detec
9
9
  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.
10
10
  Users will be able to save license plates into the system, and then additionally label them with a name.
11
11
 
12
+ Regarding vehicle labels: Users in the Rhombus LPR system can assign labels to vehicles. When a vehicle (license plate) is assigned a label, and then later
13
+ 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}).
14
+
15
+ You should use the location-tool if trying to pair vehicle events to a particular location. Never use location UUIDs in reports, use names.
16
+
17
+ 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}
18
+ or ${LprToolRequestType.GET_VEHICLE_EVENTS}.
19
+
12
20
  This tool has 3 modes of operation, determined by the "requestType" parameter:
13
21
  - ${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*
14
22
  to return a lot of data. However, 7 days should be a reasonable time range to start from if the user is not specific.
@@ -10,6 +10,8 @@ policies configured in the Rhombus Console. These policies trigger alerts when s
10
10
  * Physical or Visual Tamper: Detection of physical movement of a device or obstruction of a camera's field of view.
11
11
  * Access Control Events: Such as unauthorized access attempts in restricted areas.
12
12
 
13
+ Alerts are generated on triggers, but are NOT the same as notifications. Only certain alerts generate notifications based on user settings.
14
+
13
15
  Can inquire about labels that have been seen.
14
16
 
15
17
  Please note, this is not an exhaustive list, and there may be other types of triggers or events that generate
@@ -1,5 +1,44 @@
1
1
  import { z } from "zod";
2
2
  import { ISOTimestampFormatDescription } from "../utils/timestampInput.js";
3
+ export const TimeWindowSecondsSchema = z.object({
4
+ startSeconds: z.number(),
5
+ durationSeconds: z.number(),
6
+ });
7
+ export const PresenceWindowsResponseSchema = z.object({
8
+ presenceWindows: z
9
+ .object({
10
+ VideoLocal: z.array(TimeWindowSecondsSchema).nullable().optional(),
11
+ VideoCloud: z.array(TimeWindowSecondsSchema).nullable().optional(),
12
+ })
13
+ .nullable(),
14
+ });
15
+ export const CameraDaysResultSchema = z.object({
16
+ daysInCloud: z.number(),
17
+ daysOnCamera: z.number(),
18
+ });
19
+ export const CameraFullStateResponseSchema = z.object({
20
+ fullCameraState: z
21
+ .object({
22
+ onCloudState: z
23
+ .object({
24
+ cloud_archive_days: z.number().optional(),
25
+ })
26
+ .nullable()
27
+ .optional(),
28
+ onCameraState: z
29
+ .object({
30
+ oldest_segment_secs: z.number().optional(),
31
+ })
32
+ .nullable()
33
+ .optional(),
34
+ })
35
+ .nullable(),
36
+ });
37
+ export const CameraStorageDataSchema = z.object({
38
+ daysInCloud: z.number(),
39
+ daysOnCamera: z.number(),
40
+ cloudArchiveDays: z.number().nullable(),
41
+ });
3
42
  export const VideoFacetSettings = z
4
43
  .object({
5
44
  // blocked_debounce_time_ms: z.number().int().nullable(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhombus-node-mcp",
3
- "version": "0.1.16",
3
+ "version": "0.1.18",
4
4
  "description": "MCP server for Rhombus API",
5
5
  "keywords": [
6
6
  "ai",