rhombus-node-mcp 0.1.10 → 0.1.12

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/util.js ADDED
@@ -0,0 +1,111 @@
1
+ import { z } from "zod";
2
+ import fs from "fs";
3
+ import path from "path";
4
+ export function generateRandomString(length) {
5
+ const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
6
+ let result = "";
7
+ const charactersLength = characters.length;
8
+ for (let i = 0; i < length; i++) {
9
+ result += characters.charAt(Math.floor(Math.random() * charactersLength));
10
+ }
11
+ return result;
12
+ }
13
+ const STATIC_ARGS = {
14
+ requestModifiers: z
15
+ .optional(z.object({
16
+ headers: z.nullable(z.record(z.string(), z.string())),
17
+ query: z.nullable(z.record(z.string(), z.string())),
18
+ }))
19
+ .describe("Optional headers accepted by tools. LLM should never ever use this. 😅"),
20
+ };
21
+ /**
22
+ * Get all file paths in a directory in a directory
23
+ *
24
+ * i.e.
25
+ * foo:
26
+ * - folder1:
27
+ * - har
28
+ * - gar
29
+ * - bar
30
+ * - lar
31
+ *
32
+ * returns: ["folder1/har", "folder1/gar", "bar", "lar"]
33
+ */
34
+ export function getFilePathsInDirectory(dirPath) {
35
+ const filePaths = [];
36
+ const fileNames = fs.readdirSync(dirPath);
37
+ for (const fileName of fileNames) {
38
+ const pathToAdd = path.join(dirPath, fileName);
39
+ const stats = fs.lstatSync(pathToAdd);
40
+ if (stats.isDirectory()) {
41
+ filePaths.push(...getFilePathsInDirectory(pathToAdd));
42
+ }
43
+ else if (stats.isFile()) {
44
+ filePaths.push(pathToAdd);
45
+ }
46
+ }
47
+ return filePaths;
48
+ }
49
+ export function createToolArgs(args) {
50
+ return {
51
+ ...args,
52
+ ...STATIC_ARGS,
53
+ };
54
+ }
55
+ /**
56
+ * Returns an object in the form expected by `server.tool`
57
+ */
58
+ export function createToolTextContent(content) {
59
+ return {
60
+ content: [
61
+ {
62
+ type: "text",
63
+ text: content,
64
+ },
65
+ ],
66
+ };
67
+ }
68
+ /**
69
+ * Recursively removes fields with null values from a JavaScript object.
70
+ * If a nested object becomes empty after removing nulls, it will also be removed.
71
+ *
72
+ * @param {unknown} obj The object or array to clean.
73
+ * @returns {object | unknown[] | undefined} The cleaned object/array, or undefined if the input was null/undefined or an empty object/array resulted.
74
+ */
75
+ export function removeNullFields(obj) {
76
+ if (obj === null || obj === undefined) {
77
+ return undefined;
78
+ }
79
+ if (Array.isArray(obj)) {
80
+ const cleanedArray = [];
81
+ for (const item of obj) {
82
+ const cleanedItem = removeNullFields(item);
83
+ if (cleanedItem !== undefined) {
84
+ cleanedArray.push(cleanedItem);
85
+ }
86
+ }
87
+ return cleanedArray.length > 0 ? cleanedArray : undefined;
88
+ }
89
+ if (typeof obj !== "object") {
90
+ return obj; // Cast to any for primitive types
91
+ }
92
+ const cleanedObject = {};
93
+ for (const key in obj) {
94
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
95
+ const value = obj[key];
96
+ if (value === null) {
97
+ continue;
98
+ }
99
+ if (typeof value === "object") {
100
+ const cleanedValue = removeNullFields(value);
101
+ if (cleanedValue !== undefined) {
102
+ cleanedObject[key] = cleanedValue;
103
+ }
104
+ }
105
+ else {
106
+ cleanedObject[key] = value;
107
+ }
108
+ }
109
+ }
110
+ return Object.keys(cleanedObject).length > 0 ? cleanedObject : undefined;
111
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Confirmation is an important flow for our MCP, as letting an AI perform tools that change and mutate can be potentially dangerous
3
+ */
4
+ import { z } from "zod";
5
+ import { createToolTextContent, generateRandomString } from "../util.js";
6
+ import { logger } from "../logger.js";
7
+ // export function createToolArgs<TArgs extends object>(args: TArgs): TArgs & typeof STATIC_ARGS {
8
+ // return {
9
+ // ...args,
10
+ // ...STATIC_ARGS,
11
+ // };
12
+ // }
13
+ export const CONFIRMATION_ARGS = {
14
+ confirmationId: z.string().nullable().optional(),
15
+ };
16
+ export function addConfirmationParams(args) {
17
+ return {
18
+ ...args,
19
+ ...CONFIRMATION_ARGS,
20
+ };
21
+ }
22
+ // IN MEMORY
23
+ const confirmationStore = new Map();
24
+ const CONFIRMATION_MAX_AGE = 15 * 60 * 1000; // 15 minutes
25
+ export function requireConfirmation(confirmationId) {
26
+ // remove any old confirmations
27
+ for (const entry of confirmationStore) {
28
+ if (Date.now() - entry[1] > CONFIRMATION_MAX_AGE)
29
+ confirmationStore.delete(entry[0]);
30
+ }
31
+ logger.log("Checking for confirmationId:", confirmationId);
32
+ if (!confirmationId || !confirmationStore.has(confirmationId ?? "")) {
33
+ const newId = generateRandomString(8);
34
+ confirmationStore.set(newId, Date.now());
35
+ return createToolTextContent(JSON.stringify({
36
+ needUserInput: true,
37
+ description: `Please request confirmation for the user for performing this action. Upon confirmation, call this tool again with the confirmation id: ${newId}`,
38
+ }));
39
+ }
40
+ confirmationStore.delete(confirmationId);
41
+ return true;
42
+ }
43
+ export function isConfirmed(confirmation) {
44
+ return confirmation === true;
45
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhombus-node-mcp",
3
- "version": "0.1.10",
3
+ "version": "0.1.12",
4
4
  "description": "MCP server for Rhombus API",
5
5
  "keywords": [
6
6
  "ai",
@@ -34,6 +34,8 @@
34
34
  "dependencies": {
35
35
  "@modelcontextprotocol/sdk": "^1.9.0",
36
36
  "chrono-node": "^2.8.0",
37
+ "dotenv": "^16.5.0",
38
+ "log4js": "^6.9.1",
37
39
  "luxon": "^3.6.1",
38
40
  "zod": "^3.24.2"
39
41
  },