rhombus-node-mcp 0.1.13 → 0.1.16

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.
Files changed (81) hide show
  1. package/README.md +11 -3
  2. package/dist/api/camera-tool-api.js +108 -0
  3. package/dist/api/clips-tool-api.js +36 -0
  4. package/dist/api/create-camera-policy-tool-api.js +9 -0
  5. package/dist/api/create-tool-api.js +53 -0
  6. package/dist/api/entity-lookup-tool-api.js +37 -0
  7. package/dist/api/events-tool-api.js +320 -0
  8. package/dist/api/faces-tool-api.js +110 -0
  9. package/dist/api/get-entity-tool-api.js +176 -0
  10. package/dist/api/get-org-information-tool-api.js +9 -0
  11. package/dist/api/location-tool-api.js +9 -0
  12. package/dist/api/lpr-tool-api.js +68 -0
  13. package/dist/api/policy-alerts-tool-api.js +42 -0
  14. package/dist/api/reboot-cameras-tool-api.js +34 -0
  15. package/dist/api/report-tool-api.js +426 -0
  16. package/dist/api/time-tool-api.js +90 -0
  17. package/dist/api/update-tool-api.js +148 -0
  18. package/dist/createServer.js +50 -0
  19. package/dist/disabled-tools/endpoint-to-keys-tool.js +84 -0
  20. package/dist/disabled-tools/semantic-search-tool.js +90 -0
  21. package/dist/index.js +25 -32
  22. package/dist/logger.js +5 -4
  23. package/dist/network.js +45 -14
  24. package/dist/resources/routes.json.js +1 -1
  25. package/dist/services/embedding-service.js +153 -0
  26. package/dist/services/faiss-search-service.js +261 -0
  27. package/dist/tools/camera-tool.js +100 -0
  28. package/dist/tools/clips-tool.js +23 -38
  29. package/dist/tools/count-tool.js +25 -0
  30. package/dist/tools/create-camera-policy-tool.js +214 -0
  31. package/dist/tools/create-tool.js +25 -75
  32. package/dist/tools/entity-lookup-tool.js +35 -0
  33. package/dist/tools/events-tool.js +188 -93
  34. package/dist/tools/faces-tool.js +59 -133
  35. package/dist/tools/get-entity-tool.js +78 -0
  36. package/dist/tools/get-org-information-tool.js +18 -0
  37. package/dist/tools/location-tool.js +24 -32
  38. package/dist/tools/lpr-tool.js +71 -0
  39. package/dist/tools/policy-alerts-tool.js +32 -43
  40. package/dist/tools/reboot-cameras-tool.js +34 -0
  41. package/dist/tools/report-tool.js +190 -0
  42. package/dist/tools/time-conversion-tool.js +43 -0
  43. package/dist/tools/time-tool.js +17 -55
  44. package/dist/tools/update-tool.js +262 -0
  45. package/dist/transports/stdio.js +10 -0
  46. package/dist/transports/streamable-http.js +201 -0
  47. package/dist/{tools/devices/camera-tool/types.js → types/camera-tool-types.js} +13 -0
  48. package/dist/types/clips-tool-types.js +41 -0
  49. package/dist/types/create-camera-policy-tool-types.js +44 -0
  50. package/dist/types/create-tool-types.js +8 -0
  51. package/dist/types/deviceType.js +1 -0
  52. package/dist/types/endpoint-to-keys-tool-types.js +7 -0
  53. package/dist/types/entity-lookup-tool-types.js +70 -0
  54. package/dist/types/events-tools-types.js +257 -0
  55. package/dist/types/faces-tools-types.js +143 -0
  56. package/dist/types/get-entity-tool-types.js +25 -0
  57. package/dist/types/get-org-information-tool-types.js +3 -0
  58. package/dist/types/location-tool-types.js +11 -0
  59. package/dist/types/lpr-tool-types.js +97 -0
  60. package/dist/types/policy-alerts-tool-types.js +74 -0
  61. package/dist/types/reboot-cameras-tool-types.js +8 -0
  62. package/dist/types/report-tool-types.js +268 -0
  63. package/dist/types/schema-components.js +7093 -0
  64. package/dist/types/schema.js +1 -0
  65. package/dist/types/semantic-search-tool-types.js +5 -0
  66. package/dist/types/time-conversion-tool-types.js +8 -0
  67. package/dist/types/time-tool-types.js +11 -0
  68. package/dist/types/update-tool-types.js +186 -0
  69. package/dist/types/zod-schemas.js +21315 -0
  70. package/dist/types.js +17 -7
  71. package/dist/util.js +96 -14
  72. package/dist/utils/confirmation.js +1 -7
  73. package/dist/utils/reduce-output.js +35 -0
  74. package/dist/utils/remove-nulls.js +28 -0
  75. package/dist/utils/temp.js +8 -0
  76. package/dist/utils/timestampInput.js +12 -0
  77. package/package.json +28 -3
  78. package/dist/tools/devices/camera-tool/camera-tool.js +0 -218
  79. package/dist/tools/devices/get-entity-tool.js +0 -117
  80. package/dist/tools/get-org-information.js +0 -18
  81. package/dist/tools/reboot-cameras.js +0 -63
@@ -0,0 +1,110 @@
1
+ import { postApi } from "../network.js";
2
+ import { formatTimestamp } from "../util.js";
3
+ import { removeNulls } from "../utils/remove-nulls.js";
4
+ export async function getFaceEvents(args, timeZone, requestModifiers, sessionId) {
5
+ let allFaceEvents = [];
6
+ let currentArgs = { ...args };
7
+ let hasMore = true;
8
+ let lastResponse = null;
9
+ while (hasMore) {
10
+ // Filter out empty/undefined fields from currentArgs
11
+ // OptionalNullable so that we can remove some fields before returning them to the MCP client
12
+ let filteredArgs = { ...currentArgs };
13
+ if (filteredArgs.pageRequest) {
14
+ if (filteredArgs.pageRequest.lastEvaluatedKey === "") {
15
+ delete filteredArgs.pageRequest.lastEvaluatedKey;
16
+ }
17
+ }
18
+ if (filteredArgs.searchFilter) {
19
+ // Remove empty arrays
20
+ // if (
21
+ // filteredArgs.searchFilter.deviceUuids &&
22
+ // filteredArgs.searchFilter.deviceUuids.length === 0
23
+ // ) {
24
+ // // @ts-expect-error - we can break typing
25
+ // delete filteredArgs.searchFilter.deviceUuids;
26
+ // }
27
+ if (filteredArgs.searchFilter.faceNames && filteredArgs.searchFilter.faceNames.length === 0) {
28
+ // @ts-expect-error - we can break typing
29
+ delete filteredArgs.searchFilter.faceNames;
30
+ }
31
+ if (filteredArgs.searchFilter.labels && filteredArgs.searchFilter.labels.length === 0) {
32
+ // @ts-expect-error - we can break typing
33
+ delete filteredArgs.searchFilter.labels;
34
+ }
35
+ // Remove empty strings
36
+ // if (filteredArgs.searchFilter.faceNameContains === "") {
37
+ // delete filteredArgs.searchFilter.faceNameContains;
38
+ // }
39
+ // Remove false booleans
40
+ if (filteredArgs.searchFilter.hasEmbedding === false) {
41
+ delete filteredArgs.searchFilter.hasEmbedding;
42
+ }
43
+ if (filteredArgs.searchFilter.hasName === false) {
44
+ delete filteredArgs.searchFilter.hasName;
45
+ }
46
+ // if there is no timestampFilter, we can remove it
47
+ // rangeEnd and rangeStart are ISO 8601 strings
48
+ if (filteredArgs.searchFilter.timestampFilter) {
49
+ if (filteredArgs.searchFilter.timestampFilter.rangeEnd === null) {
50
+ delete filteredArgs.searchFilter.timestampFilter.rangeEnd;
51
+ }
52
+ if (filteredArgs.searchFilter.timestampFilter.rangeStart === null) {
53
+ delete filteredArgs.searchFilter.timestampFilter.rangeStart;
54
+ }
55
+ }
56
+ }
57
+ // one last sweep of null values
58
+ console.log("filteredArgs", filteredArgs);
59
+ filteredArgs = removeNulls(filteredArgs);
60
+ const response = await postApi({
61
+ route: "/faceRecognition/faceEvent/findFaceEventsByOrg",
62
+ body: filteredArgs,
63
+ modifiers: requestModifiers,
64
+ sessionId,
65
+ });
66
+ lastResponse = response;
67
+ // Filter the faceEvents to only include the specified fields
68
+ if (response && response.faceEvents) {
69
+ const filteredEvents = response.faceEvents.map((event) => ({
70
+ deviceUuid: event.deviceUuid,
71
+ eventTimestampMs: event.eventTimestamp,
72
+ eventTimestamp: formatTimestamp(event.eventTimestamp, timeZone),
73
+ faceName: event.faceName,
74
+ locationUuid: event.locationUuid,
75
+ personUuid: event.personUuid,
76
+ // selectedPersonMatch: event.selectedPersonMatch,
77
+ thumbnailS3Key: event.thumbnailS3Key,
78
+ // topPersonMatches: event.topPersonMatches,
79
+ uuid: event.uuid,
80
+ }));
81
+ allFaceEvents.push(...filteredEvents);
82
+ // Check if we should continue pagination
83
+ if (response.faceEvents.length === 100 && response.lastEvaluatedKey) {
84
+ // Update the pageRequest for the next iteration
85
+ currentArgs = {
86
+ ...currentArgs,
87
+ pageRequest: {
88
+ maxPageSize: currentArgs.pageRequest?.maxPageSize || 100,
89
+ lastEvaluatedKey: response.lastEvaluatedKey,
90
+ },
91
+ };
92
+ }
93
+ else {
94
+ hasMore = false;
95
+ }
96
+ }
97
+ else {
98
+ hasMore = false;
99
+ }
100
+ }
101
+ return allFaceEvents;
102
+ }
103
+ export async function getRegisteredFaces(_args, requestModifiers, sessionId) {
104
+ return await postApi({
105
+ route: "/faceRecognition/person/findPeopleByOrg",
106
+ body: {},
107
+ modifiers: requestModifiers,
108
+ sessionId,
109
+ });
110
+ }
@@ -0,0 +1,176 @@
1
+ import { logger } from "../logger.js";
2
+ import { postApi } from "../network.js";
3
+ import { formatTimestamp } from "../util.js";
4
+ import { tempFunc } from "../utils/temp.js";
5
+ export async function getCameraList(requestModifiers, sessionId) {
6
+ return {
7
+ cameras: (await postApi({
8
+ route: "/camera/getMinimalCameraStateList",
9
+ body: {},
10
+ modifiers: requestModifiers,
11
+ sessionId,
12
+ })).cameraStates.filter((camera) => !!camera.locationUuid),
13
+ };
14
+ }
15
+ export async function getDoorbellCameras(requestModifiers, sessionId) {
16
+ return {
17
+ doorbellCameras: (await postApi({
18
+ route: "/doorbellcamera/getMinimalStateList",
19
+ body: {},
20
+ modifiers: requestModifiers,
21
+ sessionId,
22
+ })).minimalStates.filter((doorbellCamera) => !!doorbellCamera.locationUuid),
23
+ };
24
+ }
25
+ export async function getBadgeReaders(requestModifiers, sessionId) {
26
+ return await postApi({
27
+ route: "/badgereader/getMinimalStateList",
28
+ body: {},
29
+ modifiers: requestModifiers,
30
+ sessionId,
31
+ }).then(response => {
32
+ return {
33
+ badgeReaders: response.minimalStates.filter((badgeReader) => !!badgeReader.locationUuid),
34
+ };
35
+ });
36
+ }
37
+ export async function getAccessControlledDoors(requestModifiers, sessionId) {
38
+ return await postApi({
39
+ route: "/component/findAccessControlledDoors",
40
+ body: {},
41
+ modifiers: requestModifiers,
42
+ sessionId,
43
+ }).then(response => {
44
+ return {
45
+ accessControlledDoors: (response.accessControlledDoors || []).map((door) => {
46
+ return {
47
+ geofenceEnabled: door.geofenceEnabled,
48
+ locationUuid: door.locationUuid,
49
+ policyUuid: door.policyUuid,
50
+ name: door.name,
51
+ uuid: door.uuid,
52
+ };
53
+ }),
54
+ };
55
+ });
56
+ }
57
+ export async function getAudioGateways(timeZone, requestModifiers, sessionId) {
58
+ return await postApi({
59
+ route: "/audiogateway/getMinimalAudioGatewayStateList",
60
+ body: {},
61
+ modifiers: requestModifiers,
62
+ sessionId,
63
+ }).then(response => {
64
+ return {
65
+ audioGateways: response.audioGatewayStates
66
+ .filter((camera) => !!camera.locationUuid)
67
+ .map((gateway) => ({
68
+ ...gateway,
69
+ createdAtString: gateway.createdAtMillis
70
+ ? formatTimestamp(gateway.createdAtMillis, timeZone)
71
+ : undefined,
72
+ })),
73
+ };
74
+ });
75
+ }
76
+ export async function getDoorSensors(requestModifiers, sessionId) {
77
+ return await postApi({
78
+ route: "/door/getMinimalDoorStateList",
79
+ body: {},
80
+ modifiers: requestModifiers,
81
+ sessionId,
82
+ }).then(response => {
83
+ return {
84
+ doorStates: response.doorStates.filter((door) => !!door.locationUuid),
85
+ };
86
+ });
87
+ }
88
+ export async function getEnvironmentalSensors(timeZone, tempUnit, requestModifiers, sessionId) {
89
+ return await postApi({
90
+ route: "/climate/getMinimalClimateStateList",
91
+ body: {},
92
+ modifiers: requestModifiers,
93
+ sessionId,
94
+ }).then(response => {
95
+ logger.info("Using tempUnit: ", tempUnit);
96
+ return {
97
+ climateStates: response.climateStates
98
+ .filter((sensor) => !!sensor.locationUuid)
99
+ .map((_sensor) => {
100
+ const { temperatureCelcius, ...sensor } = _sensor;
101
+ return {
102
+ ...sensor,
103
+ temperature: tempFunc(temperatureCelcius ?? 0, tempUnit),
104
+ createdAtString: sensor.createdAtMillis
105
+ ? formatTimestamp(sensor.createdAtMillis, timeZone)
106
+ : undefined,
107
+ };
108
+ }),
109
+ };
110
+ });
111
+ }
112
+ export async function getMotionSensors(requestModifiers, sessionId) {
113
+ return await postApi({
114
+ route: "/occupancy/getMinimalOccupancySensorStateList",
115
+ body: {},
116
+ modifiers: requestModifiers,
117
+ sessionId,
118
+ }).then(response => {
119
+ return {
120
+ occupancySensorStates: response.occupancySensorStates.filter((occupancySensor) => !!occupancySensor.locationUuid),
121
+ };
122
+ });
123
+ }
124
+ export async function getButtons(timeZone, requestModifiers, sessionId) {
125
+ return await postApi({
126
+ route: "/button/getMinimalButtonStateList",
127
+ body: {},
128
+ modifiers: requestModifiers,
129
+ sessionId,
130
+ }).then(response => {
131
+ return {
132
+ buttonStates: response.states
133
+ .filter((button) => !!button.locationUuid)
134
+ .map((button) => ({
135
+ ...button,
136
+ createdAtString: button.createdAtMillis
137
+ ? formatTimestamp(button.createdAtMillis, timeZone)
138
+ : undefined,
139
+ })),
140
+ };
141
+ });
142
+ }
143
+ export async function getKeypads(requestModifiers, sessionId) {
144
+ return await postApi({
145
+ route: "/keypad/getKeypadsForOrg",
146
+ body: {},
147
+ modifiers: requestModifiers,
148
+ sessionId,
149
+ }).then(response => {
150
+ return {
151
+ keypadStates: response.keypads.filter((keypad) => !!keypad.locationUuid),
152
+ };
153
+ });
154
+ }
155
+ export async function getEnvironmentalGateways(timeZone, requestModifiers, sessionId) {
156
+ return await postApi({
157
+ route: "/climate/getMinimalEnvironmentalGatewayStates",
158
+ body: {},
159
+ modifiers: requestModifiers,
160
+ sessionId,
161
+ }).then(response => {
162
+ return {
163
+ minimalEnvironmentalGatewayStates: response.minimalEnvironmentalGatewayStates
164
+ .filter((gateway) => !!gateway.locationUuid)
165
+ .map((gateway) => ({
166
+ ...gateway,
167
+ createdAtString: gateway.createdAtMillis
168
+ ? formatTimestamp(gateway.createdAtMillis, timeZone)
169
+ : undefined,
170
+ timestampString: gateway.timestampMs
171
+ ? formatTimestamp(gateway.timestampMs, timeZone)
172
+ : undefined,
173
+ })),
174
+ };
175
+ });
176
+ }
@@ -0,0 +1,9 @@
1
+ import { postApi } from "../network.js";
2
+ export async function getOrg(requestModifiers, sessionId) {
3
+ return await postApi({
4
+ route: "/org/getOrgV2",
5
+ body: {},
6
+ modifiers: requestModifiers,
7
+ sessionId,
8
+ });
9
+ }
@@ -0,0 +1,9 @@
1
+ import { postApi } from "../network.js";
2
+ export async function getLocations(requestModifiers, sessionId) {
3
+ return await postApi({
4
+ route: "/location/getLocationsV2",
5
+ body: {},
6
+ modifiers: requestModifiers,
7
+ sessionId,
8
+ });
9
+ }
@@ -0,0 +1,68 @@
1
+ import { postApi } from "../network.js";
2
+ import { formatTimestamp } from "../util.js";
3
+ export async function getVehicleEvents(args, timeZone, requestModifiers, sessionId) {
4
+ const requestArgs = {
5
+ deviceUuidFilter: args.deviceUuidFilter,
6
+ locationUuidFilter: args.locationUuidFilter,
7
+ vehicleLabelQuery: args.vehicleLabelQuery,
8
+ licensePlateFuzzyQuery: args.licensePlateFuzzyQuery,
9
+ startTimeMs: args.startTime ? new Date(args.startTime).getTime() : undefined,
10
+ endTimeMs: args.endTime ? new Date(args.endTime).getTime() : undefined,
11
+ };
12
+ const res = await postApi({
13
+ route: "/vehicle/getVehicleEvents",
14
+ body: requestArgs,
15
+ modifiers: requestModifiers,
16
+ sessionId,
17
+ });
18
+ if (res.error) {
19
+ throw new Error(JSON.stringify(res));
20
+ }
21
+ return (res.events?.map(event => ({
22
+ uuid: event.uuid ?? "",
23
+ deviceUuid: event.deviceUuid ?? undefined,
24
+ eventTimestamp: formatTimestamp(event.eventTimestamp ?? Date.now(), timeZone),
25
+ imageS3Key: event.imageS3Key ?? undefined,
26
+ thumbnailS3Key: event.thumbnailS3Key ?? undefined,
27
+ locationUuid: event.locationUuid ?? undefined,
28
+ vehicleLicensePlate: event.vehicleLicensePlate ?? "No License Plate",
29
+ })) ?? []);
30
+ }
31
+ export async function getSavedVehicles(timeZone, requestModifiers, sessionId) {
32
+ const res = await postApi({
33
+ route: "/vehicle/getVehicles",
34
+ body: {},
35
+ modifiers: requestModifiers,
36
+ sessionId,
37
+ });
38
+ if (res.error) {
39
+ throw new Error(JSON.stringify(res));
40
+ }
41
+ return (res.vehicles?.map(vehicle => ({
42
+ createdTimestamp: formatTimestamp(vehicle.createdAtMillis ?? Date.now(), timeZone),
43
+ name: vehicle.name ?? "No Name",
44
+ description: vehicle.description ?? "No Description",
45
+ licensePlate: vehicle.licensePlate ?? "No License Plate",
46
+ orgUuid: vehicle.orgUuid ?? "",
47
+ })) ?? []);
48
+ }
49
+ export async function getVehicleLabels(requestModifiers, sessionId) {
50
+ const res = await postApi({
51
+ route: "/vehicle/getVehicleLabelsForOrg",
52
+ body: {},
53
+ modifiers: requestModifiers,
54
+ sessionId,
55
+ });
56
+ if (res.error) {
57
+ throw new Error(JSON.stringify(res));
58
+ }
59
+ const processedVehicleLabels = {};
60
+ for (const [licensePlate, labels] of Object.entries(res.vehicleLabels ?? {})) {
61
+ const filteredLabels = labels?.filter(label => label !== null) ?? [];
62
+ if (!filteredLabels || filteredLabels.length === 0) {
63
+ continue;
64
+ }
65
+ processedVehicleLabels[licensePlate] = filteredLabels;
66
+ }
67
+ return processedVehicleLabels;
68
+ }
@@ -0,0 +1,42 @@
1
+ import { postApi } from "../network.js";
2
+ import { formatTimestamp } from "../util.js";
3
+ export async function getPolicyAlerts(args, requestModifiers, sessionId) {
4
+ return await postApi({
5
+ route: "/event/getPolicyAlerts",
6
+ body: args,
7
+ modifiers: requestModifiers,
8
+ sessionId,
9
+ }).then(response => {
10
+ return {
11
+ ...response,
12
+ policyAlerts: (response.policyAlerts || []).map((alert) => ({
13
+ ...alert,
14
+ labels: [
15
+ ...(alert.alertingEventFaces || []),
16
+ ...(alert.alertingEventVehicles || []),
17
+ ].flatMap(alert => alert.labels),
18
+ createdOnString: alert.timestampMs
19
+ ? formatTimestamp(alert.timestampMs, args.timeZone)
20
+ : undefined,
21
+ })),
22
+ };
23
+ });
24
+ }
25
+ export async function getExpiringPolicyAlerts(args, requestModifiers, sessionId) {
26
+ return await postApi({
27
+ route: "/event/getExpiringPolicyAlerts",
28
+ body: args,
29
+ modifiers: requestModifiers,
30
+ sessionId,
31
+ }).then(response => {
32
+ return {
33
+ ...response,
34
+ policyAlerts: (response.policyAlerts || []).map((alert) => ({
35
+ ...alert,
36
+ createdOnString: alert.timestampMs
37
+ ? formatTimestamp(alert.timestampMs)
38
+ : undefined,
39
+ })),
40
+ };
41
+ });
42
+ }
@@ -0,0 +1,34 @@
1
+ import { postApi } from "../network.js";
2
+ export async function rebootCameras(cameraUuids, requestModifiers, sessionId) {
3
+ let successCount = 0;
4
+ let errorCount = 0;
5
+ for (const cameraUuid of cameraUuids) {
6
+ try {
7
+ const body = { cameraUuid };
8
+ const response = await postApi({
9
+ route: "/camera/reboot",
10
+ body,
11
+ modifiers: requestModifiers,
12
+ sessionId,
13
+ });
14
+ if (response.error) {
15
+ errorCount++;
16
+ }
17
+ else {
18
+ successCount++;
19
+ }
20
+ }
21
+ catch (error) {
22
+ const ret = `Error rebooting cameras: ${error}`;
23
+ return { error: true, status: ret };
24
+ }
25
+ }
26
+ let status;
27
+ if (successCount === cameraUuids.length)
28
+ status = "SUCCESS";
29
+ else if (successCount > 0 && successCount < cameraUuids.length)
30
+ status = "PARTIAL_SUCCESS";
31
+ else
32
+ status = "ERROR";
33
+ return { status, successCount, errorCount };
34
+ }