rhombus-node-mcp 0.1.4 → 0.1.6
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/index.js +51 -35
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,14 +7,16 @@ const FIVE_SECONDS_MS = 5 * 1000;
|
|
|
7
7
|
const RHOMBUS_API_KEY = process.env.RHOMBUS_API_KEY;
|
|
8
8
|
if (!RHOMBUS_API_KEY) {
|
|
9
9
|
console.error("Missing RHOMBUS_API_KEY");
|
|
10
|
-
process.exit(1);
|
|
11
10
|
}
|
|
12
11
|
const BASE_URL = "https://api2.rhombussystems.com/api";
|
|
13
|
-
const
|
|
12
|
+
const STATIC_HEADERS = {
|
|
14
13
|
"Content-Type": "application/json",
|
|
14
|
+
accept: "application/json",
|
|
15
|
+
};
|
|
16
|
+
const AUTH_HEADERS = {
|
|
15
17
|
"x-auth-apikey": RHOMBUS_API_KEY,
|
|
16
18
|
"x-auth-scheme": "api-token",
|
|
17
|
-
|
|
19
|
+
"x-rhombus-agent": "chatbot",
|
|
18
20
|
};
|
|
19
21
|
const server = new McpServer({
|
|
20
22
|
name: "rhombus",
|
|
@@ -24,7 +26,16 @@ const server = new McpServer({
|
|
|
24
26
|
tools: {},
|
|
25
27
|
},
|
|
26
28
|
});
|
|
27
|
-
|
|
29
|
+
const STATIC_ARGS = {
|
|
30
|
+
headers: z
|
|
31
|
+
.optional(z.any())
|
|
32
|
+
.describe("Optional headers accepted by tools. LLM should never ever use this. 😅"),
|
|
33
|
+
};
|
|
34
|
+
async function postApi(url, body, customHeaders) {
|
|
35
|
+
let headers = {
|
|
36
|
+
...(customHeaders || AUTH_HEADERS),
|
|
37
|
+
...STATIC_HEADERS,
|
|
38
|
+
};
|
|
28
39
|
try {
|
|
29
40
|
const response = await fetch(url, { method: "POST", headers, body });
|
|
30
41
|
if (!response.ok) {
|
|
@@ -45,27 +56,27 @@ async function postApi(url, body) {
|
|
|
45
56
|
};
|
|
46
57
|
}
|
|
47
58
|
}
|
|
48
|
-
async function getOrg() {
|
|
59
|
+
async function getOrg(headers) {
|
|
49
60
|
const url = BASE_URL + "/org/getOrgV2";
|
|
50
|
-
return await postApi(url, "{}");
|
|
61
|
+
return await postApi(url, "{}", headers);
|
|
51
62
|
}
|
|
52
|
-
async function getLocations() {
|
|
63
|
+
async function getLocations(headers) {
|
|
53
64
|
const url = BASE_URL + "/location/getLocationsV2";
|
|
54
|
-
return await postApi(url, "{}");
|
|
65
|
+
return await postApi(url, "{}", headers);
|
|
55
66
|
}
|
|
56
|
-
async function getCameraList() {
|
|
67
|
+
async function getCameraList(headers) {
|
|
57
68
|
const url = BASE_URL + "/camera/getMinimalCameraStateList";
|
|
58
|
-
return await postApi(url, "{}").then(response => {
|
|
69
|
+
return await postApi(url, "{}", headers).then(response => {
|
|
59
70
|
return {
|
|
60
71
|
cameraStates: response.cameraStates.filter((camera) => !!camera.locationUuid),
|
|
61
72
|
};
|
|
62
73
|
});
|
|
63
74
|
}
|
|
64
|
-
async function getAccessControlledDoors() {
|
|
75
|
+
async function getAccessControlledDoors(headers) {
|
|
65
76
|
const url = BASE_URL + "/component/findAccessControlledDoors";
|
|
66
|
-
return await postApi(url, "{}");
|
|
77
|
+
return await postApi(url, "{}", headers);
|
|
67
78
|
}
|
|
68
|
-
async function getFaceEvents(_locationUuid) {
|
|
79
|
+
async function getFaceEvents(_locationUuid, headers) {
|
|
69
80
|
const nowMs = Date.now();
|
|
70
81
|
const rangeStartMs = nowMs - THREE_HOURS_MS;
|
|
71
82
|
const rangeEndMs = nowMs - FIVE_SECONDS_MS;
|
|
@@ -86,7 +97,7 @@ async function getFaceEvents(_locationUuid) {
|
|
|
86
97
|
},
|
|
87
98
|
},
|
|
88
99
|
});
|
|
89
|
-
const response = await postApi(BASE_URL + "/faceRecognition/faceEvent/findFaceEventsByOrg", body).then(response => {
|
|
100
|
+
const response = await postApi(BASE_URL + "/faceRecognition/faceEvent/findFaceEventsByOrg", body, headers).then(response => {
|
|
90
101
|
return {
|
|
91
102
|
faceEvents: (response.faceEvents || []).map((event) => ({
|
|
92
103
|
...event,
|
|
@@ -96,13 +107,13 @@ async function getFaceEvents(_locationUuid) {
|
|
|
96
107
|
});
|
|
97
108
|
return response;
|
|
98
109
|
}
|
|
99
|
-
async function getAccessControlEvents(doorUuid) {
|
|
110
|
+
async function getAccessControlEvents(doorUuid, headers) {
|
|
100
111
|
const url = BASE_URL + "/component/findComponentEventsByAccessControlledDoor";
|
|
101
112
|
const body = JSON.stringify({
|
|
102
113
|
limit: 50,
|
|
103
114
|
accessControlledDoorUuid: doorUuid,
|
|
104
115
|
});
|
|
105
|
-
const response = await postApi(url, body).then(response => ({
|
|
116
|
+
const response = await postApi(url, body, headers).then(response => ({
|
|
106
117
|
componentEvents: (response.componentEvents || []).map((event) => ({
|
|
107
118
|
...event,
|
|
108
119
|
timestamp: new Date(event.timestampMs).toString(),
|
|
@@ -110,14 +121,14 @@ async function getAccessControlEvents(doorUuid) {
|
|
|
110
121
|
}));
|
|
111
122
|
return response;
|
|
112
123
|
}
|
|
113
|
-
async function rebootCameras(cameraUuids) {
|
|
124
|
+
async function rebootCameras(cameraUuids, headers) {
|
|
114
125
|
const url = BASE_URL + "/camera/reboot";
|
|
115
126
|
let successCount = 0;
|
|
116
127
|
let errorCount = 0;
|
|
117
128
|
for (const cameraUuid in cameraUuids) {
|
|
118
129
|
try {
|
|
119
130
|
const body = JSON.stringify({ cameraUuid: cameraUuid });
|
|
120
|
-
const response = await postApi(url, body);
|
|
131
|
+
const response = await postApi(url, body, headers);
|
|
121
132
|
if (response.error) {
|
|
122
133
|
errorCount++;
|
|
123
134
|
}
|
|
@@ -139,7 +150,7 @@ async function rebootCameras(cameraUuids) {
|
|
|
139
150
|
return { status, successCount, errorCount };
|
|
140
151
|
}
|
|
141
152
|
}
|
|
142
|
-
async function getImageForCameraAtTime(cameraUuid, timestampMs) {
|
|
153
|
+
async function getImageForCameraAtTime(cameraUuid, timestampMs, headers) {
|
|
143
154
|
const url = BASE_URL + "/video/getExactFrameUri";
|
|
144
155
|
const body = JSON.stringify({
|
|
145
156
|
cameraUuid: cameraUuid,
|
|
@@ -151,7 +162,7 @@ async function getImageForCameraAtTime(cameraUuid, timestampMs) {
|
|
|
151
162
|
permyriadCropY: 0,
|
|
152
163
|
timestampMs: timestampMs,
|
|
153
164
|
});
|
|
154
|
-
const base64Image = await postApi(url, body).then(async (res) => {
|
|
165
|
+
const base64Image = await postApi(url, body, headers).then(async (res) => {
|
|
155
166
|
return await fetch(res.frameUri, { method: "GET", headers: headers }).then(async (res) => {
|
|
156
167
|
if (!res.ok)
|
|
157
168
|
return null;
|
|
@@ -174,8 +185,8 @@ async function getImageForCameraAtTime(cameraUuid, timestampMs) {
|
|
|
174
185
|
imageData: base64Image,
|
|
175
186
|
};
|
|
176
187
|
}
|
|
177
|
-
server.tool("get-org-information", "Get general information about the organization including org name, camera configuration defaults, contact information, and org settings.", {}, async ({}) => {
|
|
178
|
-
const org = await getOrg();
|
|
188
|
+
server.tool("get-org-information", "Get general information about the organization including org name, camera configuration defaults, contact information, and org settings.", { ...STATIC_ARGS }, async ({ headers }) => {
|
|
189
|
+
const org = await getOrg(headers);
|
|
179
190
|
return {
|
|
180
191
|
content: [
|
|
181
192
|
{
|
|
@@ -189,14 +200,15 @@ server.tool("get-entity-tool", "get a list of entities like cameras, access cont
|
|
|
189
200
|
entityType: z
|
|
190
201
|
.enum(["camera", "access-controlled-doors"])
|
|
191
202
|
.describe("The entity type to retreive. Example: cameras."),
|
|
192
|
-
|
|
203
|
+
...STATIC_ARGS,
|
|
204
|
+
}, async ({ entityType, headers }) => {
|
|
193
205
|
let ret;
|
|
194
206
|
switch (entityType) {
|
|
195
207
|
case "camera":
|
|
196
|
-
ret = await getCameraList();
|
|
208
|
+
ret = await getCameraList(headers);
|
|
197
209
|
break;
|
|
198
210
|
case "access-controlled-doors":
|
|
199
|
-
ret = await getAccessControlledDoors();
|
|
211
|
+
ret = await getAccessControlledDoors(headers);
|
|
200
212
|
break;
|
|
201
213
|
default:
|
|
202
214
|
ret = {};
|
|
@@ -211,11 +223,12 @@ server.tool("get-entity-tool", "get a list of entities like cameras, access cont
|
|
|
211
223
|
],
|
|
212
224
|
};
|
|
213
225
|
});
|
|
214
|
-
server.tool("camera-tool", "get specific requested information about a camera such as an image snapshot, or detailed analytics info", {
|
|
226
|
+
server.tool("camera-tool", "get specific requested information about a camera such as an image snapshot, or detailed analytics info. this can be used to answer questions about tracking people across cameras", {
|
|
215
227
|
requestType: z.enum(["image"]),
|
|
216
228
|
timestampMs: z.optional(z.number()).describe("the timestamp in milliseconds"),
|
|
217
229
|
cameraUuid: z.optional(z.string()).describe("the camera uuid requested"),
|
|
218
|
-
|
|
230
|
+
...STATIC_ARGS,
|
|
231
|
+
}, async ({ cameraUuid, timestampMs, requestType, headers }) => {
|
|
219
232
|
if (!cameraUuid) {
|
|
220
233
|
return {
|
|
221
234
|
content: [
|
|
@@ -245,7 +258,7 @@ server.tool("camera-tool", "get specific requested information about a camera su
|
|
|
245
258
|
let response;
|
|
246
259
|
switch (requestType) {
|
|
247
260
|
case "image":
|
|
248
|
-
response = await getImageForCameraAtTime(cameraUuid, timestampMs);
|
|
261
|
+
response = await getImageForCameraAtTime(cameraUuid, timestampMs, headers);
|
|
249
262
|
if (!response.success || !response.imageData) {
|
|
250
263
|
return {
|
|
251
264
|
content: [{ type: "text", text: JSON.stringify(response) }],
|
|
@@ -281,9 +294,10 @@ server.tool("events-tool", "event data for certain types of information like fac
|
|
|
281
294
|
eventType: z.enum(["faces", "people", "access-control"]),
|
|
282
295
|
locationUuid: z.optional(z.string()),
|
|
283
296
|
accessControlledDoorUuid: z.optional(z.string()),
|
|
284
|
-
|
|
297
|
+
...STATIC_ARGS,
|
|
298
|
+
}, async ({ eventType, locationUuid, accessControlledDoorUuid, headers }) => {
|
|
285
299
|
if (eventType === "faces" || eventType === "people") {
|
|
286
|
-
const response = await getFaceEvents(locationUuid);
|
|
300
|
+
const response = await getFaceEvents(locationUuid, headers);
|
|
287
301
|
return {
|
|
288
302
|
content: [
|
|
289
303
|
{
|
|
@@ -308,7 +322,7 @@ server.tool("events-tool", "event data for certain types of information like fac
|
|
|
308
322
|
};
|
|
309
323
|
}
|
|
310
324
|
else {
|
|
311
|
-
const events = await getAccessControlEvents(accessControlledDoorUuid);
|
|
325
|
+
const events = await getAccessControlEvents(accessControlledDoorUuid, headers);
|
|
312
326
|
return {
|
|
313
327
|
content: [
|
|
314
328
|
{
|
|
@@ -331,11 +345,12 @@ server.tool("events-tool", "event data for certain types of information like fac
|
|
|
331
345
|
server.tool("location-tool", "contains basic operations for locations and response in JSON format.", {
|
|
332
346
|
action: z.enum(["get"]),
|
|
333
347
|
locationUpdate: z.optional(z.object({ uuid: z.string(), name: z.optional(z.string()) })),
|
|
334
|
-
|
|
348
|
+
...STATIC_ARGS,
|
|
349
|
+
}, async ({ action, locationUpdate, headers }) => {
|
|
335
350
|
let ret;
|
|
336
351
|
switch (action) {
|
|
337
352
|
case "get":
|
|
338
|
-
ret = await getLocations();
|
|
353
|
+
ret = await getLocations(headers);
|
|
339
354
|
break;
|
|
340
355
|
default:
|
|
341
356
|
ret = { error: true, status: `unsupported location tool call: ${action}` };
|
|
@@ -349,8 +364,9 @@ server.tool("reboot-cameras", "this tool is for rebooting one or more cameras ca
|
|
|
349
364
|
cameraUuids: z
|
|
350
365
|
.array(z.string())
|
|
351
366
|
.describe("An array of camera UUID strings which are unique identifiers for cameras"),
|
|
352
|
-
|
|
353
|
-
|
|
367
|
+
...STATIC_ARGS,
|
|
368
|
+
}, async ({ cameraUuids, headers }) => {
|
|
369
|
+
const cameraRebootData = await rebootCameras(cameraUuids, headers);
|
|
354
370
|
if (!cameraRebootData) {
|
|
355
371
|
return {
|
|
356
372
|
content: [
|