rhombus-node-mcp 0.1.9 โ 0.1.11
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/README.md +54 -18
- package/dist/index.js +129 -4
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,20 +1,39 @@
|
|
|
1
|
-
# Rhombus MCP Server
|
|
1
|
+
# ๐ Rhombus MCP Server - Supercharge Your AI Tools!
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Unleash the power of the Rhombus API with this cutting-edge MCP server implementation! Transform your chatbot experience with advanced security and surveillance capabilities at your fingertips.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## ๐ About Rhombus
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Rhombus is the #1 rated commercial security system, built to protect and designed to adapt! We deliver enterprise-grade physical security with a cybersecurity focus, combining:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
* ๐ก **Smart Cameras** - Innovative dome, fisheye, bullet, and multisensor security cameras
|
|
10
|
+
* ๐ช **Access Control** - Secure door controllers, readers, and intercoms with mobile, card, and fob access
|
|
11
|
+
* ๐ก **IoT Sensors** - Advanced audio, environmental, motion, entry, and multipurpose panic sensors
|
|
12
|
+
* ๐ **Alarm Monitoring** - TMA 5-diamond certified live agent verification and dispatch
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
Our platform is powered by actionable AI insights that help you respond faster with real-time detections, all while being rooted in strong cybersecurity principles.
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
## ๐งช Join Our Beta Test!
|
|
16
17
|
|
|
17
|
-
|
|
18
|
+
**We're thrilled to be part of the AI Generation!** Help us shape the future of AI-powered security by testing our MCP server implementation and providing valuable feedback.
|
|
19
|
+
|
|
20
|
+
๐ **[Fill out our beta test form here](https://rhmbs.link/beta_test)** ๐
|
|
21
|
+
|
|
22
|
+
Your insights will directly influence our development roadmap and help us create the most powerful AI security tools on the market!
|
|
23
|
+
|
|
24
|
+
## ๐ง Quick Setup
|
|
25
|
+
|
|
26
|
+
### Step 1: Give Your Rhombus Console Superpowers
|
|
27
|
+
|
|
28
|
+
1. Login to your Rhombus Console - [Login Here](https://console.rhombus.com)! ๐
|
|
29
|
+
2. Generate your magic key at [API Key Settings](https://console.rhombus.com/settings/api-management) โจ
|
|
30
|
+
3. Activate your powers by setting `RHOMBUS_API_KEY` in your environment ๐ช
|
|
31
|
+
|
|
32
|
+
### Step 2: Supercharge Claude Desktop
|
|
33
|
+
|
|
34
|
+
1. Grab the incredible Claude desktop [right here](https://claude.ai/download)! ๐ฅ
|
|
35
|
+
|
|
36
|
+
2. Unleash the magic by adding this to your `claude_desktop_config.json`:
|
|
18
37
|
|
|
19
38
|
### DOCKER
|
|
20
39
|
|
|
@@ -61,18 +80,35 @@ You can access the file using:
|
|
|
61
80
|
vim ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
62
81
|
```
|
|
63
82
|
|
|
64
|
-
### Step 3:
|
|
83
|
+
### Step 3: Take It For A Spin! ๐ก
|
|
84
|
+
|
|
85
|
+
Time to see the magic happen! Let's verify that Claude for Desktop is detecting our awesome `rhombus` server tools:
|
|
86
|
+
|
|
87
|
+
1. Look for the powerful hammer icon in Claude's interface โ๏ธ
|
|
88
|
+
2. Click it to reveal your new arsenal of tools
|
|
89
|
+
3. Spot the Filesystem MCP Server tools in the list
|
|
90
|
+
|
|
91
|
+
If they're visible, YOU'VE DONE IT! ๐ Your integration is live and ready to rock! Claude can now communicate directly with Rhombus systems, giving you the same incredible capabilities as the Rhombus web app, but with the added power of Claude's intelligence!
|
|
92
|
+
|
|
93
|
+
### Hitting a Snag? We've Got You! ๐
|
|
94
|
+
|
|
95
|
+
Check out Claude's fantastic [troubleshooting guide](https://modelcontextprotocol.io/docs/tools/debugging) for quick fixes! Still stuck? Our team of experts is ready to help!
|
|
96
|
+
|
|
97
|
+
* Email us: developer@rhombus.com ๐ง
|
|
98
|
+
* Share your feedback via our [beta test form](https://rhmbs.link/beta_test) ๐
|
|
65
99
|
|
|
66
|
-
|
|
100
|
+
We're committed to making your experience absolutely seamless and are SUPER excited to hear what amazing applications you're building with our tools!
|
|
67
101
|
|
|
68
|
-
|
|
102
|
+
## ๐ค We Want Your Feedback!
|
|
69
103
|
|
|
70
|
-
|
|
104
|
+
As pioneers in the AI Generation, we're passionate about pushing the boundaries of what's possible when combining advanced physical security with cutting-edge AI tools. Your feedback is invaluable as we continue to innovate and improve.
|
|
71
105
|
|
|
72
|
-
|
|
106
|
+
* What features would make your workflow even better?
|
|
107
|
+
* How are you using our MCP server in your projects?
|
|
108
|
+
* What integrations would you like to see next?
|
|
73
109
|
|
|
74
|
-
|
|
110
|
+
Share your thoughts, ideas, and success stories with us! Together, we're creating the future of intelligent security.
|
|
75
111
|
|
|
76
|
-
## License
|
|
112
|
+
## ๐ License
|
|
77
113
|
|
|
78
|
-
This MCP server
|
|
114
|
+
This awesome MCP server comes with the freedom of the MIT License! โ
Use it, โ
modify it, โ
share it - the possibilities are endless! Just remember to follow the MIT License terms and conditions. Check out the LICENSE file in our repository for all the details.
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { CreateVideoWallOptions } from "./types.js";
|
|
6
|
+
import { parse } from "chrono-node";
|
|
7
|
+
import { DateTime } from "luxon";
|
|
5
8
|
const THREE_HOURS_MS = 3 * 60 * 60 * 1000;
|
|
6
9
|
const FIVE_SECONDS_MS = 5 * 1000;
|
|
7
10
|
const RHOMBUS_API_KEY = process.env.RHOMBUS_API_KEY;
|
|
@@ -69,6 +72,7 @@ async function postApi(url, body, modifiers) {
|
|
|
69
72
|
const response = await fetch(url, { method: "POST", headers: requestHeaders, body });
|
|
70
73
|
log(`[POSTAPI] RESPONSE - ${JSON.stringify(response)}`);
|
|
71
74
|
if (!response.ok) {
|
|
75
|
+
log(`โ RESPONSE - ${response.ok} - ${response.status}`);
|
|
72
76
|
if (response.status === 401 || response.status === 403) {
|
|
73
77
|
return {
|
|
74
78
|
error: true,
|
|
@@ -77,7 +81,9 @@ async function postApi(url, body, modifiers) {
|
|
|
77
81
|
}
|
|
78
82
|
throw new Error(`HTTP error! status: ${response.status}`);
|
|
79
83
|
}
|
|
80
|
-
|
|
84
|
+
const ret = await response.json();
|
|
85
|
+
log(`โ RESPONSE - ${response.ok} - ${JSON.stringify(ret)}`);
|
|
86
|
+
return ret;
|
|
81
87
|
}
|
|
82
88
|
catch (error) {
|
|
83
89
|
log(`[POSTAPI] ERROR - ${JSON.stringify(error || {})}`);
|
|
@@ -181,6 +187,25 @@ async function rebootCameras(cameraUuids, requestModifiers) {
|
|
|
181
187
|
return { status, successCount, errorCount };
|
|
182
188
|
}
|
|
183
189
|
}
|
|
190
|
+
async function createVideoWall(options, headers) {
|
|
191
|
+
const url = BASE_URL + "/camera/createVideoWall";
|
|
192
|
+
const body = JSON.stringify({
|
|
193
|
+
videoWall: {
|
|
194
|
+
displayName: options?.displayName,
|
|
195
|
+
deviceList: options?.deviceList,
|
|
196
|
+
othersCanEdit: true,
|
|
197
|
+
orgUuid: options?.orgUuid,
|
|
198
|
+
shared: true,
|
|
199
|
+
settings: {
|
|
200
|
+
gridSize: { width: options?.settings.columnCount, height: options?.settings.columnCount },
|
|
201
|
+
gridLayout: "1 2\n3 4",
|
|
202
|
+
intervalSeconds: options?.settings.intervalSeconds || 5,
|
|
203
|
+
},
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
const response = await postApi(url, body, headers);
|
|
207
|
+
return response;
|
|
208
|
+
}
|
|
184
209
|
async function getImageForCameraAtTime(cameraUuid, timestampMs, requestModifiers) {
|
|
185
210
|
const url = BASE_URL + "/video/getExactFrameUri";
|
|
186
211
|
const body = JSON.stringify({
|
|
@@ -231,6 +256,104 @@ server.tool("get-org-information", "Get general information about the organizati
|
|
|
231
256
|
],
|
|
232
257
|
};
|
|
233
258
|
});
|
|
259
|
+
async function handleCreateVideoWallRequest(videoWallCreateOptions, headers) {
|
|
260
|
+
let text = "Unable to create video wall!";
|
|
261
|
+
// console.error("๐จ Creating video wall");
|
|
262
|
+
if (!videoWallCreateOptions?.displayName) {
|
|
263
|
+
text = JSON.stringify({
|
|
264
|
+
needUserInput: true,
|
|
265
|
+
commandForUser: "What should the name of the video wall be?",
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
else if ((videoWallCreateOptions?.deviceList || []).length === 0) {
|
|
269
|
+
text = JSON.stringify({
|
|
270
|
+
needUserInput: true,
|
|
271
|
+
commandForUser: "Which cameras would you like on this video wall?",
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
// console.error("Creating video wall with options: ", JSON.stringify(videoWallCreateOptions));
|
|
276
|
+
text = JSON.stringify(await createVideoWall(videoWallCreateOptions, headers));
|
|
277
|
+
}
|
|
278
|
+
return Promise.resolve({
|
|
279
|
+
content: [
|
|
280
|
+
{
|
|
281
|
+
type: "text",
|
|
282
|
+
text,
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
server.tool("create-tool", "Tool for creating many entity types such as video walls.", {
|
|
288
|
+
entityType: z.enum(["video-wall"]).describe("The entity type to create. Example: video wall."),
|
|
289
|
+
videoWallCreateOptions: CreateVideoWallOptions,
|
|
290
|
+
...STATIC_ARGS,
|
|
291
|
+
}, async ({ entityType, videoWallCreateOptions, requestModifiers }) => {
|
|
292
|
+
switch (entityType) {
|
|
293
|
+
case "video-wall":
|
|
294
|
+
return await handleCreateVideoWallRequest(videoWallCreateOptions, requestModifiers);
|
|
295
|
+
default:
|
|
296
|
+
}
|
|
297
|
+
return {
|
|
298
|
+
content: [
|
|
299
|
+
{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: "",
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
};
|
|
305
|
+
});
|
|
306
|
+
function nullToUndefined(value) {
|
|
307
|
+
return value === null ? undefined : value;
|
|
308
|
+
}
|
|
309
|
+
server.tool("time-tool", "Tool for converting a natural language time description into a timestamp in milliseconds.", {
|
|
310
|
+
time_description: z
|
|
311
|
+
.string()
|
|
312
|
+
.describe("A natural language description of the time (e.g., '2pm today', 'tomorrow at noon')."),
|
|
313
|
+
timezone: z
|
|
314
|
+
.optional(z.string())
|
|
315
|
+
.describe("Optional IANA timezone string (e.g., 'America/Los_Angeles', 'UTC'). Defaults to system timezone."),
|
|
316
|
+
}, async ({ time_description, timezone }) => {
|
|
317
|
+
// console.error(`๐ handling tool call for time ${time_description} using timezone ${timezone}`);
|
|
318
|
+
const now = DateTime.now()
|
|
319
|
+
.setZone(timezone || undefined)
|
|
320
|
+
.toJSDate();
|
|
321
|
+
const parsed = parse(time_description, now, { forwardDate: true });
|
|
322
|
+
if (!parsed || parsed.length === 0) {
|
|
323
|
+
throw new Error(`Could not parse time description: ${time_description}`);
|
|
324
|
+
}
|
|
325
|
+
const dateComponents = parsed[0].start;
|
|
326
|
+
if (!dateComponents) {
|
|
327
|
+
throw new Error("Parsed time has no start component");
|
|
328
|
+
}
|
|
329
|
+
const dt = DateTime.fromObject({
|
|
330
|
+
year: nullToUndefined(dateComponents.get("year")),
|
|
331
|
+
month: nullToUndefined(dateComponents.get("month")),
|
|
332
|
+
day: nullToUndefined(dateComponents.get("day")),
|
|
333
|
+
hour: nullToUndefined(dateComponents.get("hour")),
|
|
334
|
+
minute: nullToUndefined(dateComponents.get("minute")),
|
|
335
|
+
second: nullToUndefined(dateComponents.get("second")),
|
|
336
|
+
millisecond: 0,
|
|
337
|
+
}, {
|
|
338
|
+
zone: timezone || "local",
|
|
339
|
+
});
|
|
340
|
+
if (!dt.isValid) {
|
|
341
|
+
throw new Error(`Could not construct valid DateTime: ${dt.invalidReason}`);
|
|
342
|
+
}
|
|
343
|
+
const timestamp = dt.toMillis();
|
|
344
|
+
return {
|
|
345
|
+
content: [
|
|
346
|
+
{
|
|
347
|
+
type: "text",
|
|
348
|
+
text: JSON.stringify({
|
|
349
|
+
timestamp,
|
|
350
|
+
iso: dt.toISO(),
|
|
351
|
+
timezone: dt.zoneName,
|
|
352
|
+
}),
|
|
353
|
+
},
|
|
354
|
+
],
|
|
355
|
+
};
|
|
356
|
+
});
|
|
234
357
|
server.tool("get-entity-tool", "get a list of entities like cameras, access controlled doors, sensors, etc", {
|
|
235
358
|
entityType: z
|
|
236
359
|
.enum(["camera", "access-controlled-doors"])
|
|
@@ -258,9 +381,11 @@ server.tool("get-entity-tool", "get a list of entities like cameras, access cont
|
|
|
258
381
|
],
|
|
259
382
|
};
|
|
260
383
|
});
|
|
261
|
-
server.tool("camera-tool",
|
|
384
|
+
server.tool("camera-tool", 'This tool captures and returns a real-time snapshot from a designated security camera. The image reflects the current scene in the camera\'s field of view and serves as a contextual input source for downstream tasks such as object recognition, anomaly detection, incident investigation, or situational assessment. When invoked, the tool provides the following: \n โข Visual Scene Capture: A high-resolution image of what the camera is actively observing, including people, vehicles, license plates, and any detectable objects. \nโข Scene Context: Metadata such as camera ID, location name, timestamp, and motion detection status if available. \nโข Enriched Data Potential: The image can be paired with AI models or downstream analytics to extract insights such as: \nโข Number and type of objects in frame (e.g., humans, cars, packages) \nโข Unusual behaviors (e.g., loitering, unauthorized access) \nโข Environmental conditions (e.g., lighting, obstruction, cleanliness) \nUse Cases: \nโข Verify what triggered a motion alert or analytic rule. \nโข Provide visual context for access events or alarms. \nโข Support live incident triage or retrospective investigations. \nโข Feed contextual imagery to agents making security or operational decisions. \nInvocation Notes: \nTo use this tool correctly, the agent should provide the specific camera identifier or location name. If possible, include the intent (e.g., "verify unauthorized access", "identify vehicle", "check for obstructions") to enhance downstream processing or summarization.', {
|
|
262
385
|
requestType: z.enum(["image"]),
|
|
263
|
-
timestampMs: z
|
|
386
|
+
timestampMs: z
|
|
387
|
+
.optional(z.number())
|
|
388
|
+
.describe("the timestamp in milliseconds which should always be obtained using time-tool"),
|
|
264
389
|
cameraUuid: z.optional(z.string()).describe("the camera uuid requested"),
|
|
265
390
|
...STATIC_ARGS,
|
|
266
391
|
}, async ({ cameraUuid, timestampMs, requestType, requestModifiers }) => {
|
|
@@ -378,7 +503,7 @@ server.tool("events-tool", "event data for certain types of information like fac
|
|
|
378
503
|
};
|
|
379
504
|
});
|
|
380
505
|
server.tool("location-tool", "contains basic operations for locations and response in JSON format.", {
|
|
381
|
-
action: z.enum(["get"]),
|
|
506
|
+
action: z.enum(["get", "update"]),
|
|
382
507
|
locationUpdate: z.optional(z.object({ uuid: z.string(), name: z.optional(z.string()) })),
|
|
383
508
|
...STATIC_ARGS,
|
|
384
509
|
}, async ({ action, locationUpdate, requestModifiers }) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rhombus-node-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"description": "MCP server for Rhombus API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -33,9 +33,12 @@
|
|
|
33
33
|
],
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@modelcontextprotocol/sdk": "^1.9.0",
|
|
36
|
+
"chrono-node": "^2.8.0",
|
|
37
|
+
"luxon": "^3.6.1",
|
|
36
38
|
"zod": "^3.24.2"
|
|
37
39
|
},
|
|
38
40
|
"devDependencies": {
|
|
41
|
+
"@types/luxon": "^3.6.2",
|
|
39
42
|
"@types/node": "^22.14.0",
|
|
40
43
|
"prettier": "3.5.3",
|
|
41
44
|
"typescript": "^5.8.3"
|