rhombus-node-mcp 0.1.0 → 0.1.2
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 +9 -29
- package/dist/index.js +109 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -4,40 +4,28 @@ An MCP server implementation that integrates the Rhombus API to provide Chatbot
|
|
|
4
4
|
|
|
5
5
|
## Configuration
|
|
6
6
|
|
|
7
|
-
### Step 1:
|
|
8
|
-
|
|
9
|
-
Clone this repository:
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
git clone git@github.com:RhombusSystems/rhombus-node-mcp.git
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
Navigate to the `rhombus-node-mcp` directory and install the necessary dependencies:
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
cd rhombus-node-mcp && npm install
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
### Step 2: Get a Rhombus API Key
|
|
7
|
+
### Step 1: Get a Rhombus API Key
|
|
22
8
|
|
|
23
9
|
1. Sign up for a [Rhombus Account](https://console.rhombus.com).
|
|
24
|
-
2.
|
|
10
|
+
2. Create an API Key at [API Key Settings](https://console.rhombus.com/settings/api-management)
|
|
25
11
|
3. Set the API key in your environment as `RHOMBUS_API_KEY`.
|
|
26
12
|
|
|
27
|
-
### Step
|
|
13
|
+
### Step 2: Configure Claude Desktop
|
|
28
14
|
|
|
29
15
|
1. Download Claude desktop [here](https://claude.ai/download).
|
|
30
16
|
|
|
31
17
|
2. Add this to your `claude_desktop_config.json`:
|
|
32
18
|
|
|
19
|
+
### DOCKER
|
|
20
|
+
|
|
33
21
|
```json
|
|
34
22
|
{
|
|
35
23
|
"mcpServers": {
|
|
36
24
|
"rhombus": {
|
|
37
25
|
"command": "docker",
|
|
38
|
-
"args": ["run", "-i", "--rm", "-e", "
|
|
26
|
+
"args": ["run", "-i", "--rm", "-e", "RHOMBUS_API_KEY", "mcp-server-rhombus"],
|
|
39
27
|
"env": {
|
|
40
|
-
"RHOMBUS_API_KEY": "
|
|
28
|
+
"RHOMBUS_API_KEY": "YOUR_API_KEY"
|
|
41
29
|
}
|
|
42
30
|
}
|
|
43
31
|
}
|
|
@@ -66,17 +54,9 @@ You can access the file using:
|
|
|
66
54
|
vim ~/Library/Application\ Support/Claude/claude_desktop_config.json
|
|
67
55
|
```
|
|
68
56
|
|
|
69
|
-
### Step
|
|
70
|
-
|
|
71
|
-
Docker build:
|
|
72
|
-
|
|
73
|
-
```bash
|
|
74
|
-
docker build -t mcp/rhombus:latest -f Dockerfile .
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
### Step 5: Testing
|
|
57
|
+
### Step 3: Testing
|
|
78
58
|
|
|
79
|
-
Let's make sure Claude for Desktop is picking up the tools we've exposed in our `rhombus` server. You can do this by looking for the hammer icon
|
|
59
|
+
Let's make sure Claude for Desktop is picking up the tools we've exposed in our `rhombus` server. You can do this by looking for the hammer icon.
|
|
80
60
|
|
|
81
61
|
After clicking on the hammer icon, you should see the tools that come with the Filesystem MCP Server:
|
|
82
62
|
|
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,9 +185,9 @@ 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, images from cameras, etc.", {
|
|
148
189
|
entityType: z
|
|
149
|
-
.enum(["camera", "access-controlled-doors"])
|
|
190
|
+
.enum(["camera", "access-controlled-doors", "image-frame"])
|
|
150
191
|
.describe("The entity type to retreive. Example: cameras."),
|
|
151
192
|
}, async ({ entityType }) => {
|
|
152
193
|
let ret;
|
|
@@ -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.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "MCP server for Rhombus API",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/node": "^22.14.0",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
40
|
+
"prettier": "3.5.3",
|
|
41
|
+
"typescript": "^5.8.3"
|
|
42
42
|
},
|
|
43
43
|
"engines": {
|
|
44
44
|
"node": ">=18"
|