UNPKG

rhombus-node-mcp

Version:
250 lines (218 loc) 14.3 kB
import { getAccessControlEvents, getBrivoAccessControlEvents, getEventsForEnvironmentalGateway, getClimateEventsForSensor, getComponentEventsByLocation, getCameraFootageSeekpointEvents, getButtonPressEvents, getOccupancyEvents, getProximityEvents, getDoorbellEvents, } from "../api/events-tool-api.js"; import { EventsToolRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/events-tools-types.js"; import { createToolStructuredContent } from "../util.js"; import { getLogger } from "../logger.js"; import { TempUnit } from "../utils/temp.js"; const logger = getLogger("events-tool"); const TOOL_NAME = "events-tool"; // "faces" | "people" | "human" | "access-control" const TOOL_DESCRIPTION = ` **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. **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." 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". --- When eventType is "brivo-access-control": Retrieves badge/credential events from Brivo-integrated doors. Automatically fetches the Brivo integration configuration to determine which locations have Brivo doors mapped. No door UUIDs are required. Use this when the user asks specifically about Brivo events, Brivo badge ins, Brivo access control, or events from Brivo doors. Arguments: * **startTime (string):** Start of the time range (ISO 8601). * **endTime (string):** End of the time range (ISO 8601). Returns: * **integrationEnabled:** Whether the Brivo integration is currently enabled. * **brivoDoorsConfigured:** Number of Brivo doors configured in the integration. * **brivoDoors:** List of Brivo doors with their IDs, names, and associated Rhombus location UUIDs. * **events:** Credential received events from all locations that have Brivo doors configured, sorted newest first. Note: Events are fetched at the location level, so results may include events from all access-controlled doors at locations where Brivo is configured. --- When eventType is "access-control": Retrieves access control events (arrivals, badge ins, credentials, unlocks) for the given door(s). Can return a lot of data—use a narrow time range. Arguments: * **accessControlledDoorUuids (array of strings):** UUIDs of the access-controlled doors. * **startTime (string):** Start of the time range (ISO 8601). * **endTime (string):** End of the time range (ISO 8601). The \`credSource\` field indicates how the event was triggered: * **REMOTE:** Rhombus Key app remote unlock. * **REMOTE (Admin):** Unlock via Rhombus console or browser/mobile app. * **BLE_WAVE:** User waved hand over the reader. * **NFC:** User tapped badge or phone on the reader. --- Retrieves environmental gateway events (sensor readings, derived values) for a device in a time range. Timestamps are in the **device** timezone, not necessarily UTC. Arguments: * **deviceUuid (string):** UUID of the environmental gateway device. * **startTime (string):** Start of range (ISO 8601). * **endTime (string):** End of range (ISO 8601). --- When eventType is "climate-sensor": Retrieves climate sensor events (temperature, humidity, air quality, etc.) for a sensor in a time range. Timestamps are in the **sensor** timezone, not necessarily UTC. Arguments: * **sensorUuid (string):** UUID of the climate sensor. * **startTime (string):** Start of range (ISO 8601). * **endTime (string):** End of range (ISO 8601). * **limit (number, optional):** Max events to return. Default 1000. --- When eventType is "component-events": Retrieves all component event types for a location in a time range. Most flexible option; filter by event type via componentEventTypes. Timestamps are in the **location** timezone, not necessarily UTC. Arguments: * **locationUuid (string):** UUID of the location. * **componentEventTypes (array, optional):** Event types to include. If empty or omitted, returns all types. * **startTime (string):** Start of range (ISO 8601). * **endTime (string):** End of range (ISO 8601). Valid event types include: * **DoorbellEvent:** Doorbell button press events * **CredentialReceivedEvent:** Badge/credential scans (NFC, BLE_WAVE, REMOTE unlocks) * **DoorStateChangeEvent:** Door state changes (locked/unlocked) * **ButtonEvent:** Generic button press events * **PanicButtonEvent:** Panic/emergency button activations * **DoorReaderStateChangeEvent:** Changes in door reader state * **DoorRelayStateChangeEvent:** Changes in door relay state * **AccessControlUnitTamperEvent:** Tamper detection events * **AccessControlUnitBatteryStateChangeEvent:** Battery state changes * **WaveToUnlockIntentExpiredEvent:** Wave-to-unlock timeout events * **DoorAuthFirstInStateEvent:** First-in authentication state events * **DoorScheduleFirstInStateEvent:** First-in schedule state events * And more (see input schema for full list). --- When eventType is "camera": 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. Arguments: * **cameraUuid (string):** UUID of the camera. * **startTime (string):** Start of range (ISO 8601). * **duration (number):** Search window in seconds. Default 3600 (1 hour). `; const TOOL_HANDLER = async (args, extra) => { const { eventType, accessControlledDoorUuids, deviceUuid, sensorUuid, locationUuid, componentEventTypes, startTime, endTime, limit, timeZone, tempUnit, cameraUuid, duration, buttonSensorUuid, occupancySensorUuid, proximityTagUuids, doorbellCameraUuid, } = args; logger.debug(`eventType: ${eventType}`); switch (eventType) { case EventsToolRequestType.BRIVO_ACCESS_CONTROL: { const result = await getBrivoAccessControlEvents(startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "brivo-access-control", brivoAccessControlEvents: result, }); } case "access-control": { if (!accessControlledDoorUuids || accessControlledDoorUuids.length === 0) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which door are you asking about?", }); } else { const events = await getAccessControlEvents(accessControlledDoorUuids, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "access-control", accessControlEvents: events }); } } case "environmental-gateway": { if (!deviceUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which environmental gateway device are you asking about?", }); } else { const events = await getEventsForEnvironmentalGateway(deviceUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, tempUnit ?? TempUnit.CELSIUS, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "environmental-gateway", environmentalGatewayEvents: events }); } } case "climate-sensor": { if (!sensorUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which climate sensor are you asking about?", }); } else { const events = await getClimateEventsForSensor(sensorUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, limit ?? null, timeZone, tempUnit ?? TempUnit.CELSIUS, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "climate-sensor", climateSensorEvents: events }); } } case "component-events": { if (!locationUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which location are you asking about?", }); } else { const events = await getComponentEventsByLocation(locationUuid, componentEventTypes || [], startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "component-events", componentEvents: events }); } } case EventsToolRequestType.CAMERA: { if (!cameraUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which camera are you asking about?", }); } else { const events = await getCameraFootageSeekpointEvents(cameraUuid, duration ?? 3600, startTime ? new Date(startTime).getTime() : Date.now() - 3600000, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "camera", cameraEvents: events.cameraFootageEvents }); } } case EventsToolRequestType.BUTTON_PRESS: { const bSensorUuid = args.buttonSensorUuid; if (!bSensorUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which button sensor are you asking about?", }); } const buttonEvents = await getButtonPressEvents(bSensorUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "button-press", buttonPressEvents: buttonEvents }); } case EventsToolRequestType.OCCUPANCY: { const occSensorUuid = args.occupancySensorUuid; if (!occSensorUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which occupancy sensor are you asking about?", }); } const occupancyEvts = await getOccupancyEvents(occSensorUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "occupancy", occupancyEvents: occupancyEvts }); } case EventsToolRequestType.PROXIMITY: { const tagUuids = args.proximityTagUuids; if (!tagUuids || tagUuids.length === 0) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which proximity tags are you asking about?", }); } const proxEvents = await getProximityEvents(tagUuids, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "proximity", proximityEvents: proxEvents }); } case EventsToolRequestType.DOORBELL: { const dbCamUuid = args.doorbellCameraUuid; if (!dbCamUuid) { return createToolStructuredContent({ needUserInput: true, commandForUser: "Which doorbell camera are you asking about?", }); } const doorbellEvts = await getDoorbellEvents(dbCamUuid, startTime ? new Date(startTime).getTime() : undefined, endTime ? new Date(endTime).getTime() : undefined, timeZone, extra._meta?.requestModifiers, extra.sessionId); return createToolStructuredContent({ eventType: "doorbell", doorbellEvents: doorbellEvts }); } } // This should not happen, but return empty result if eventType is unknown const result = {}; return { content: [ { type: "text", text: JSON.stringify(result), }, ], structuredContent: result, }; }; export function createTool(server) { server.registerTool(TOOL_NAME, { title: "Events", description: TOOL_DESCRIPTION, inputSchema: TOOL_ARGS, outputSchema: OUTPUT_SCHEMA.shape, annotations: { readOnlyHint: true }, }, TOOL_HANDLER); }