rhombus-node-mcp 0.1.29 → 0.1.30

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.
@@ -4,9 +4,20 @@ import { postApi } from "../network/network.js";
4
4
  import { formatTimestamp } from "../util.js";
5
5
  import { tempFunc, TempUnit } from "../utils/temp.js";
6
6
  // Type definitions
7
- export const HumanEvent = z.object({
8
- timestamp: z.number(),
9
- id: z.number(),
7
+ /** One entry from the camera VOD footage seekpoint index (many activity types). */
8
+ export const CameraFootageEvent = z.object({
9
+ activity: z
10
+ .string()
11
+ .describe("Activity type on the recording timeline (e.g. MOTION_HUMAN, MOTION_CAR, LICENSEPLATE_IDENTIFIED—exact set depends on the camera and analytics)."),
12
+ timestamp: z.number().describe("Unix timestamp in milliseconds."),
13
+ id: z.number().optional().describe("Seekpoint id when the API provides one."),
14
+ licensePlate: z
15
+ .string()
16
+ .nullable()
17
+ .optional()
18
+ .describe("Plate text on this seekpoint when present. For org LPR saved vehicles, labels, and plate search APIs, use lpr-tool."),
19
+ vehicleName: z.string().nullable().optional().describe("Vehicle display name on this seekpoint when present."),
20
+ faceNames: z.string().nullable().optional().describe("Face names on this seekpoint when present."),
10
21
  });
11
22
  const ACCESS_CONTROL_EVENT_BATCH_SIZE = 1000;
12
23
  const ACCESS_CONTROL_EVENT_TYPE_FILTER = ["CredentialReceivedEvent"];
@@ -186,7 +197,7 @@ export async function getAccessControlEvents(doorUuids, startTime, endTime, time
186
197
  console.error(`componentEvents: ${JSON.stringify(accessControlEvents)}`);
187
198
  return accessControlEvents;
188
199
  }
189
- export async function getHumanMotionEvents(cameraUuid, duration, startTime, requestModifiers, sessionId) {
200
+ export async function getCameraFootageSeekpointEvents(cameraUuid, duration, startTime, requestModifiers, sessionId) {
190
201
  const body = {
191
202
  cameraUuid,
192
203
  duration,
@@ -197,25 +208,31 @@ export async function getHumanMotionEvents(cameraUuid, duration, startTime, requ
197
208
  body,
198
209
  modifiers: requestModifiers,
199
210
  sessionId,
200
- }).then(response => {
201
- const seekPoints = response.footageSeekPoints || [];
202
- const uniqueHumanEvents = seekPoints
203
- .reduceRight((acc, point) => {
204
- if (point.a === "MOTION_HUMAN" &&
205
- typeof point.ts === "number" &&
206
- typeof point.id === "number" &&
207
- !acc.some(existing => existing.id === point.id)) {
208
- acc.unshift({ timestamp: point.ts, id: point.id });
209
- }
210
- return acc;
211
- }, [])
212
- .map(event => ({
213
- timestamp: event.timestamp,
214
- id: event.id,
215
- }));
216
- return { cameraUuid, uniqueHumanEvents };
217
211
  });
218
- return response;
212
+ const seekPoints = response.footageSeekPoints ?? [];
213
+ const seen = new Set();
214
+ const cameraFootageEvents = [];
215
+ for (const point of seekPoints) {
216
+ if (typeof point.ts !== "number" || point.a == null) {
217
+ continue;
218
+ }
219
+ const idPart = point.id != null ? String(point.id) : "noid";
220
+ const dedupeKey = `${idPart}_${point.ts}_${point.a}`;
221
+ if (seen.has(dedupeKey)) {
222
+ continue;
223
+ }
224
+ seen.add(dedupeKey);
225
+ cameraFootageEvents.push({
226
+ activity: String(point.a),
227
+ timestamp: point.ts,
228
+ ...(point.id != null ? { id: point.id } : {}),
229
+ ...(point.lp !== undefined ? { licensePlate: point.lp } : {}),
230
+ ...(point.vn !== undefined ? { vehicleName: point.vn } : {}),
231
+ ...(point.fn !== undefined ? { faceNames: point.fn } : {}),
232
+ });
233
+ }
234
+ cameraFootageEvents.sort((a, b) => b.timestamp - a.timestamp);
235
+ return { cameraUuid, cameraFootageEvents };
219
236
  }
220
237
  const EVENT_COUNT_MAX_PER_RESPONSE = 2000;
221
238
  export async function getEventsForEnvironmentalGateway(deviceUuid, startTime, endTime, timeZone, tempUnit, requestModifiers, sessionId) {
@@ -1,4 +1,4 @@
1
- import { getAccessControlEvents, getBrivoAccessControlEvents, getEventsForEnvironmentalGateway, getClimateEventsForSensor, getComponentEventsByLocation, getHumanMotionEvents, getButtonPressEvents, getOccupancyEvents, getProximityEvents, getDoorbellEvents, } from "../api/events-tool-api.js";
1
+ import { getAccessControlEvents, getBrivoAccessControlEvents, getEventsForEnvironmentalGateway, getClimateEventsForSensor, getComponentEventsByLocation, getCameraFootageSeekpointEvents, getButtonPressEvents, getOccupancyEvents, getProximityEvents, getDoorbellEvents, } from "../api/events-tool-api.js";
2
2
  import { EventsToolRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/events-tools-types.js";
3
3
  import { createToolStructuredContent } from "../util.js";
4
4
  import { getLogger } from "../logger.js";
@@ -9,7 +9,9 @@ const TOOL_NAME = "events-tool";
9
9
  const TOOL_DESCRIPTION = `
10
10
  **Scope:** This tool returns **raw, event-level data** (individual events with timestamps). Use **report-tool** when you need aggregated counts, time-series summaries, or analytics over intervals.
11
11
 
12
- This tool has multiple modes, set by "eventType": access-control, brivo-access-control, environmental-gateway, climate-sensor, component-events, camera. Use it when the user asks for specific events (unlocks, badge ins, credentials, arrivals, environmental readings, climate data, camera motion, or other component events). It can return large result sets; keep time ranges narrow. For ranges spanning more than ~24 hours, prefer report-tool for aggregates. For maximum flexibility across event types at a location, use eventType "component-events".
12
+ **Vehicles vs lpr-tool:** **eventType "camera"** returns **footage seekpoints** from that camera's recording timeline—**every activity type** the API returns for the window (human motion, vehicle motion, and others depending on the camera and analytics). Some rows may include plate or vehicle metadata on the seekpoint. **lpr-tool** is still the right choice for **org LPR workflows**: saved vehicles, vehicle labels, fuzzy plate search, and vehicle event APIs—not a replacement for "everything this camera logged on its timeline."
13
+
14
+ This tool has multiple modes, set by "eventType": access-control, brivo-access-control, environmental-gateway, climate-sensor, component-events, camera. Use it when the user asks for specific events (unlocks, badge ins, credentials, arrivals, environmental readings, climate data, camera timeline activity, or other component events). It can return large result sets; keep time ranges narrow. For ranges spanning more than ~24 hours, prefer report-tool for aggregates. For maximum flexibility across event types at a location, use eventType "component-events".
13
15
 
14
16
  ---
15
17
 
@@ -100,7 +102,7 @@ Valid event types include:
100
102
 
101
103
  When eventType is "camera":
102
104
 
103
- Retrieves human motion events for a camera in a time range. Timestamps in milliseconds.
105
+ Retrieves **footage seekpoints** for one camera: **all activity types** returned for the search window (not limited to human motion). Each item includes an **activity** string plus **timestamp**; plate/vehicle/face fields appear when the API provides them. Use **lpr-tool** for org LPR saved vehicles, labels, and dedicated plate search.
104
106
 
105
107
  Arguments:
106
108
  * **cameraUuid (string):** UUID of the camera.
@@ -175,8 +177,8 @@ const TOOL_HANDLER = async (args, extra) => {
175
177
  });
176
178
  }
177
179
  else {
178
- const events = await getHumanMotionEvents(cameraUuid, duration ?? 3600, startTime ? new Date(startTime).getTime() : Date.now() - 3600000, extra._meta?.requestModifiers, extra.sessionId);
179
- return createToolStructuredContent({ eventType: "camera", cameraEvents: events.uniqueHumanEvents });
180
+ const events = await getCameraFootageSeekpointEvents(cameraUuid, duration ?? 3600, startTime ? new Date(startTime).getTime() : Date.now() - 3600000, extra._meta?.requestModifiers, extra.sessionId);
181
+ return createToolStructuredContent({ eventType: "camera", cameraEvents: events.cameraFootageEvents });
180
182
  }
181
183
  }
182
184
  case EventsToolRequestType.BUTTON_PRESS: {
@@ -5,6 +5,8 @@ const TOOL_NAME = "lpr-tool";
5
5
  const TOOL_DESCRIPTION = `
6
6
  This tool interacts with the Rhombus LPR system to retrieve information about license plate recognition events and registered license plates.
7
7
 
8
+ **Vs events-tool (camera):** **events-tool** with eventType **camera** returns that camera’s **VOD footage seekpoints** (many activity types on the timeline, including vehicle-related activity when present). **lpr-tool** is for the **LPR product surface**: plate events, **saved vehicles**, **labels**, and plate **search** APIs across the org—use it when the user needs registry, labeling, or org-wide LPR queries, not only “what showed up on this camera’s timeline.”
9
+
8
10
  The system's cameras may have LPR enabled, and when it is enabled, it will detect "license plate recognition" events when it sees a license plate
9
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.
10
12
  Users will be able to save license plates into the system, and then additionally label them with a name.
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
  import { ISOTimestampFormatDescription } from "../utils/timestampInput.js";
3
3
  import { ComponentEventEnumType } from "./schema.js";
4
- import { HumanEvent } from "../api/events-tool-api.js";
4
+ import { CameraFootageEvent } from "../api/events-tool-api.js";
5
5
  import { TempUnit } from "../utils/temp.js";
6
6
  export var EventsToolRequestType;
7
7
  (function (EventsToolRequestType) {
@@ -25,7 +25,7 @@ export const TOOL_ARGS = {
25
25
  "environmental-gateway: Environmental gateway events with sensor readings and derived values. " +
26
26
  "climate-sensor: Climate sensor events with temperature, humidity, air quality readings. " +
27
27
  "component-events: All types of component events for a location (most flexible option). " +
28
- "camera: Human motion events detected by cameras. " +
28
+ "camera: Footage seekpoints for one camera—all timeline activity types that camera recorded (human motion, vehicle motion, etc., depending on device/analytics). For org LPR saved vehicles, labels, and plate search APIs, use lpr-tool. " +
29
29
  "button-press: Button press events from button sensors. " +
30
30
  "occupancy: Occupancy sensor events with people count. " +
31
31
  "proximity: Proximity tag events with RSSI readings. " +
@@ -86,7 +86,7 @@ export const TOOL_ARGS = {
86
86
  .int()
87
87
  .positive()
88
88
  .nullable()
89
- .describe("Duration in seconds to search for human motion events. Required when eventType is 'camera'. Default is 3600 (1 hour)."),
89
+ .describe("Duration in seconds to search footage seekpoints. Required when eventType is 'camera'. Default is 3600 (1 hour)."),
90
90
  buttonSensorUuid: z
91
91
  .string()
92
92
  .nullable()
@@ -330,9 +330,9 @@ export const OUTPUT_SCHEMA = z.object({
330
330
  .nullable()
331
331
  .describe("Component events data for all types of access control events at a location, sorted by timestamp (newest first)")),
332
332
  cameraEvents: z
333
- .array(HumanEvent)
333
+ .array(CameraFootageEvent)
334
334
  .optional()
335
- .describe(`Camera events data, as requested with requestType ${EventsToolRequestType.CAMERA}`),
335
+ .describe(`Footage timeline seekpoints for the camera (all activity types returned in the window). Use with eventType "${EventsToolRequestType.CAMERA}".`),
336
336
  buttonPressEvents: z
337
337
  .array(z.object({
338
338
  timestampMs: z.number().optional(),
@@ -45,7 +45,9 @@ export const VehicleEventsArgs = z.object({
45
45
  ISOTimestampFormatDescription),
46
46
  });
47
47
  export const TOOL_ARGS = {
48
- requestType: z.nativeEnum(LprToolRequestType).describe("The type of request to make."),
48
+ requestType: z
49
+ .nativeEnum(LprToolRequestType)
50
+ .describe("Org LPR operation (vehicle events, saved vehicles, labels, plate search, save vehicle). Per-camera VOD timeline seekpoints use events-tool (eventType camera)."),
49
51
  vehicleEventsArgs: VehicleEventsArgs.nullable().describe("Only necessary for requestType 'get-vehicle-events'"),
50
52
  timeZone: z
51
53
  .string()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhombus-node-mcp",
3
- "version": "0.1.29",
3
+ "version": "0.1.30",
4
4
  "description": "MCP server for Rhombus API",
5
5
  "keywords": [
6
6
  "ai",