rhombus-node-mcp 0.1.1 → 0.1.3

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 (2) hide show
  1. package/dist/index.js +108 -1
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -28,6 +28,12 @@ async function postApi(url, body) {
28
28
  try {
29
29
  const response = await fetch(url, { method: "POST", headers, body });
30
30
  if (!response.ok) {
31
+ if (response.status === 401 || response.status === 403) {
32
+ return {
33
+ error: true,
34
+ status: "Sorry, I don't have permission to help with this request. Consider upgrading my permissions by changing the role of the API Key I am using.",
35
+ };
36
+ }
31
37
  throw new Error(`HTTP error! status: ${response.status}`);
32
38
  }
33
39
  return await response.json();
@@ -133,6 +139,41 @@ async function rebootCameras(cameraUuids) {
133
139
  return { status, successCount, errorCount };
134
140
  }
135
141
  }
142
+ async function getImageForCameraAtTime(cameraUuid, timestampMs) {
143
+ const url = BASE_URL + "/video/getExactFrameUri";
144
+ const body = JSON.stringify({
145
+ cameraUuid: cameraUuid,
146
+ downscaleFactor: 10,
147
+ jpgQuality: 70,
148
+ permyriadCropHeight: 10000,
149
+ permyriadCropWidth: 5625,
150
+ permyriadCropX: 2188,
151
+ permyriadCropY: 0,
152
+ timestampMs: timestampMs,
153
+ });
154
+ const base64Image = await postApi(url, body).then(async (res) => {
155
+ return await fetch(res.frameUri, { method: "GET", headers: headers }).then(async (res) => {
156
+ if (!res.ok)
157
+ return null;
158
+ const arrayBuffer = await res.arrayBuffer();
159
+ const buffer = Buffer.from(arrayBuffer);
160
+ const base64 = buffer.toString("base64");
161
+ return base64;
162
+ });
163
+ });
164
+ if (!base64Image) {
165
+ return {
166
+ success: false,
167
+ status: "failed to fetch image",
168
+ };
169
+ }
170
+ return {
171
+ success: true,
172
+ status: "successfully fetched image",
173
+ imageType: "base64",
174
+ imageData: base64Image,
175
+ };
176
+ }
136
177
  server.tool("get-org-information", "Get general information about the organization including org name, camera configuration defaults, contact information, and org settings.", {}, async ({}) => {
137
178
  const org = await getOrg();
138
179
  return {
@@ -144,7 +185,7 @@ server.tool("get-org-information", "Get general information about the organizati
144
185
  ],
145
186
  };
146
187
  });
147
- server.tool("get-entity-tool", "get a list of entities like cameras, access controlled doors, sensors, etc.", {
188
+ server.tool("get-entity-tool", "get a list of entities like cameras, access controlled doors, sensors, etc", {
148
189
  entityType: z
149
190
  .enum(["camera", "access-controlled-doors"])
150
191
  .describe("The entity type to retreive. Example: cameras."),
@@ -170,6 +211,72 @@ server.tool("get-entity-tool", "get a list of entities like cameras, access cont
170
211
  ],
171
212
  };
172
213
  });
214
+ server.tool("camera-tool", "get specific requested information about a camera such as an image snapshot, or detailed analytics info", {
215
+ requestType: z.enum(["image"]),
216
+ timestampMs: z.optional(z.number()).describe("the timestamp in milliseconds"),
217
+ cameraUuid: z.optional(z.string()).describe("the camera uuid requested"),
218
+ }, async ({ cameraUuid, timestampMs, requestType }) => {
219
+ if (!cameraUuid) {
220
+ return {
221
+ content: [
222
+ {
223
+ type: "text",
224
+ text: JSON.stringify({
225
+ needUserInput: true,
226
+ commandForUser: "Which camera are you talking about?",
227
+ }),
228
+ },
229
+ ],
230
+ };
231
+ }
232
+ if (!timestampMs) {
233
+ return {
234
+ content: [
235
+ {
236
+ type: "text",
237
+ text: JSON.stringify({
238
+ needUserInput: true,
239
+ commandForUser: "At what time?",
240
+ }),
241
+ },
242
+ ],
243
+ };
244
+ }
245
+ let response;
246
+ switch (requestType) {
247
+ case "image":
248
+ response = await getImageForCameraAtTime(cameraUuid, timestampMs);
249
+ if (!response.success || !response.imageData) {
250
+ return {
251
+ content: [{ type: "text", text: JSON.stringify(response) }],
252
+ };
253
+ }
254
+ return {
255
+ content: [
256
+ {
257
+ type: "image",
258
+ data: response.imageData,
259
+ mimeType: "image/jpeg",
260
+ },
261
+ ],
262
+ };
263
+ break;
264
+ default:
265
+ response = {
266
+ error: true,
267
+ status: "mising unknown type from tool call",
268
+ };
269
+ break;
270
+ }
271
+ return {
272
+ content: [
273
+ {
274
+ type: "text",
275
+ text: JSON.stringify({ response }),
276
+ },
277
+ ],
278
+ };
279
+ });
173
280
  server.tool("events-tool", "event data for certain types of information like faces and license plates", {
174
281
  eventType: z.enum(["faces", "people", "access-control"]),
175
282
  locationUuid: z.optional(z.string()),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhombus-node-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "MCP server for Rhombus API",
5
5
  "keywords": [
6
6
  "ai",