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.
Files changed (3) hide show
  1. package/README.md +9 -29
  2. package/dist/index.js +109 -2
  3. 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. Follow the account setup instructions and generate your API key from the developer dashboard.
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 3: Configure Claude Desktop
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", "RHOMBUS-API-KEY", "mcp/rhombus"],
26
+ "args": ["run", "-i", "--rm", "-e", "RHOMBUS_API_KEY", "mcp-server-rhombus"],
39
27
  "env": {
40
- "RHOMBUS_API_KEY": "YOUR_API_KEY_HERE"
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 4: Build the Docker Image
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.0",
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
- "typescript": "^5.8.3",
41
- "prettier": "3.5.3"
40
+ "prettier": "3.5.3",
41
+ "typescript": "^5.8.3"
42
42
  },
43
43
  "engines": {
44
44
  "node": ">=18"