rhombus-node-mcp 0.1.32 → 0.1.33

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.
@@ -0,0 +1,195 @@
1
+ import { postApi } from "../network/network.js";
2
+ import { ChatFollowUpActionEnum, } from "../types/automated-prompts-tool-types.js";
3
+ import { formatIsoWithOffset } from "../util.js";
4
+ function extractNotifyUserUuids(followUpActions) {
5
+ if (!followUpActions)
6
+ return undefined;
7
+ const notifyAction = followUpActions.find((action) => action?.type === ChatFollowUpActionEnum.NOTIFY_USERS);
8
+ if (!notifyAction)
9
+ return undefined;
10
+ const userUuids = notifyAction.userUuids;
11
+ return userUuids?.filter((u) => u !== null) ?? [];
12
+ }
13
+ function toSummary(prompt, timeZone) {
14
+ if (!prompt)
15
+ return undefined;
16
+ return {
17
+ uuid: prompt.uuid ?? undefined,
18
+ prompt: prompt.prompt ?? undefined,
19
+ responseTemplate: prompt.responseTemplate ?? undefined,
20
+ frequency: prompt.frequency
21
+ ? {
22
+ frequency: prompt.frequency.frequency ?? undefined,
23
+ unit: prompt.frequency.unit ?? undefined,
24
+ }
25
+ : undefined,
26
+ invokeAt: formatIsoWithOffset(prompt.invokeAtMs, timeZone),
27
+ permissionGroupUuid: prompt.permissionGroupUuid ?? undefined,
28
+ notifyUserUuids: extractNotifyUserUuids(prompt.followUpActions),
29
+ scheduleUuid: prompt.scheduleUuid ?? undefined,
30
+ orgUuid: prompt.orgUuid ?? undefined,
31
+ };
32
+ }
33
+ function toHistoryEntry(record, timeZone) {
34
+ if (!record)
35
+ return undefined;
36
+ return {
37
+ uuid: record.uuid ?? undefined,
38
+ automatedPromptUuid: record.automatedPromptUuid ?? undefined,
39
+ query: record.query ?? undefined,
40
+ response: record.response ?? undefined,
41
+ queriedAt: formatIsoWithOffset(record.queriedAtMs, timeZone),
42
+ respondedAt: formatIsoWithOffset(record.respondedAtMs, timeZone),
43
+ responseType: record.responseType ?? undefined,
44
+ };
45
+ }
46
+ function buildSettingsBody(input, includeUuid) {
47
+ const settings = {};
48
+ if (includeUuid && "promptUuid" in input && input.promptUuid) {
49
+ settings.uuid = input.promptUuid;
50
+ }
51
+ if (input.prompt !== undefined && input.prompt !== null) {
52
+ settings.prompt = input.prompt;
53
+ }
54
+ if (input.responseTemplate !== undefined && input.responseTemplate !== null) {
55
+ settings.responseTemplate = input.responseTemplate;
56
+ }
57
+ if (input.invokeAtMs !== undefined && input.invokeAtMs !== null) {
58
+ settings.invokeAtMs = input.invokeAtMs;
59
+ }
60
+ if (input.frequencyValue !== undefined &&
61
+ input.frequencyValue !== null &&
62
+ input.frequencyUnit !== undefined &&
63
+ input.frequencyUnit !== null) {
64
+ settings.frequency = {
65
+ frequency: input.frequencyValue,
66
+ unit: input.frequencyUnit,
67
+ };
68
+ }
69
+ if (input.permissionGroupUuid !== undefined && input.permissionGroupUuid !== null) {
70
+ settings.permissionGroupUuid = input.permissionGroupUuid;
71
+ }
72
+ if (input.notifyUserUuids !== undefined && input.notifyUserUuids !== null) {
73
+ settings.followUpActions = input.notifyUserUuids.length
74
+ ? [
75
+ {
76
+ type: ChatFollowUpActionEnum.NOTIFY_USERS,
77
+ userUuids: input.notifyUserUuids,
78
+ },
79
+ ]
80
+ : [];
81
+ }
82
+ return settings;
83
+ }
84
+ export async function listAutomatedPrompts(pageRequest, requestModifiers, sessionId, timeZone) {
85
+ const res = await postApi({
86
+ route: "/chatbot/automation/getAutomatedPromptsForOrg",
87
+ body: pageRequest,
88
+ modifiers: requestModifiers,
89
+ sessionId,
90
+ });
91
+ if (res.error)
92
+ throw new Error(JSON.stringify(res));
93
+ const settingsList = (res.settingsList ?? [])
94
+ .map((p) => toSummary(p, timeZone))
95
+ .filter((s) => s !== undefined);
96
+ return {
97
+ settingsList,
98
+ lastEvaluatedKey: res.lastEvaluatedKey ?? undefined,
99
+ };
100
+ }
101
+ export async function getAutomatedPrompt(promptUuid, requestModifiers, sessionId, timeZone) {
102
+ const res = await postApi({
103
+ route: "/chatbot/automation/getAutomatedPrompt",
104
+ body: { promptUuid },
105
+ modifiers: requestModifiers,
106
+ sessionId,
107
+ });
108
+ if (res.error)
109
+ throw new Error(JSON.stringify(res));
110
+ return toSummary(res.settings, timeZone);
111
+ }
112
+ export async function createAutomatedPrompt(input, requestModifiers, sessionId, timeZone) {
113
+ const settings = buildSettingsBody(input, /*includeUuid*/ false);
114
+ const res = await postApi({
115
+ route: "/chatbot/automation/createAutomatedPrompt",
116
+ body: { settings },
117
+ modifiers: requestModifiers,
118
+ sessionId,
119
+ });
120
+ if (res.error)
121
+ throw new Error(JSON.stringify(res));
122
+ return toSummary(res.settings, timeZone);
123
+ }
124
+ export async function updateAutomatedPrompt(input, requestModifiers, sessionId, timeZone) {
125
+ const selectiveUpdate = buildSettingsBody(input, /*includeUuid*/ true);
126
+ const res = await postApi({
127
+ route: "/chatbot/automation/updateAutomatedPrompt",
128
+ body: { selectiveUpdate },
129
+ modifiers: requestModifiers,
130
+ sessionId,
131
+ });
132
+ if (res.error)
133
+ throw new Error(JSON.stringify(res));
134
+ return toSummary(res.settings, timeZone);
135
+ }
136
+ export async function deleteAutomatedPrompt(promptUuid, requestModifiers, sessionId) {
137
+ const res = await postApi({
138
+ route: "/chatbot/automation/deleteAutomatedPrompt",
139
+ body: { promptUuid },
140
+ modifiers: requestModifiers,
141
+ sessionId,
142
+ });
143
+ if (res.error)
144
+ throw new Error(JSON.stringify(res));
145
+ return { ok: true, action: "deleted" };
146
+ }
147
+ export async function getAutomatedPromptChatHistory(promptUuid, pageRequest, requestModifiers, sessionId, timeZone) {
148
+ const res = await postApi({
149
+ route: "/chatbot/automation/getAutomatedPromptChatHistory",
150
+ body: {
151
+ promptUuid,
152
+ lastEvaluatedKey: pageRequest.lastEvaluatedKey ?? undefined,
153
+ maxPageSize: pageRequest.maxPageSize ?? undefined,
154
+ },
155
+ modifiers: requestModifiers,
156
+ sessionId,
157
+ });
158
+ if (res.error)
159
+ throw new Error(JSON.stringify(res));
160
+ const chatHistory = (res.chatHistory ?? [])
161
+ .map((r) => toHistoryEntry(r, timeZone))
162
+ .filter((e) => e !== undefined);
163
+ return {
164
+ chatHistory,
165
+ lastEvaluatedKey: res.lastEvaluatedKey ?? undefined,
166
+ };
167
+ }
168
+ export async function shareAutomatedPromptResponse(chatUuid, visibility, requestModifiers, sessionId) {
169
+ const res = await postApi({
170
+ route: "/chatbot/automation/shareAutomatedPromptResponse",
171
+ body: {
172
+ chatUuid,
173
+ privacy: { visibility },
174
+ },
175
+ modifiers: requestModifiers,
176
+ sessionId,
177
+ });
178
+ if (res.error)
179
+ throw new Error(JSON.stringify(res));
180
+ return { ok: true, action: `visibility set to ${visibility}` };
181
+ }
182
+ export async function verifyJobScheduled(promptUuid, requestModifiers, sessionId) {
183
+ const res = await postApi({
184
+ route: "/chatbot/automation/verifyJobScheduled",
185
+ body: { promptUuid },
186
+ modifiers: requestModifiers,
187
+ sessionId,
188
+ });
189
+ if (res.error)
190
+ throw new Error(JSON.stringify(res));
191
+ return {
192
+ scheduleExpression: res.scheduleExpression ?? undefined,
193
+ scheduleTimezone: res.scheduleTimezone ?? undefined,
194
+ };
195
+ }
@@ -58,6 +58,39 @@ export async function getPermissionsForCurrentUser(requestModifiers, sessionId)
58
58
  : undefined,
59
59
  };
60
60
  }
61
+ /**
62
+ * Keep only non-null string entries from a nullable string[] coming back from
63
+ * the generated schema types.
64
+ */
65
+ function cleanStringArray(arr) {
66
+ if (!arr)
67
+ return undefined;
68
+ const cleaned = arr.filter((v) => typeof v === "string");
69
+ return cleaned.length > 0 ? cleaned : undefined;
70
+ }
71
+ /**
72
+ * Map a `{ [k]: PermissionEnum }` to a `Record<string, string>` dropping null
73
+ * values. Returns undefined when the input is nullish so the field is omitted
74
+ * from the JSON payload entirely (and the filtering proxy's `includeFields`
75
+ * can omit it without ambiguity).
76
+ */
77
+ function toStringMap(map) {
78
+ if (!map)
79
+ return undefined;
80
+ const entries = Object.entries(map).filter((pair) => typeof pair[1] === "string");
81
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
82
+ }
83
+ function toNestedStringMap(map) {
84
+ if (!map)
85
+ return undefined;
86
+ const result = {};
87
+ for (const [outer, inner] of Object.entries(map)) {
88
+ const cleanedInner = toStringMap(inner);
89
+ if (cleanedInner)
90
+ result[outer] = cleanedInner;
91
+ }
92
+ return Object.keys(result).length > 0 ? result : undefined;
93
+ }
61
94
  export async function getPermissionGroups(requestModifiers, sessionId) {
62
95
  const res = await postApi({
63
96
  route: "/permission/getPermissionGroupsForOrg",
@@ -71,7 +104,23 @@ export async function getPermissionGroups(requestModifiers, sessionId) {
71
104
  return (res.permissionGroups?.map(group => ({
72
105
  uuid: group.uuid ?? undefined,
73
106
  name: group.name ?? undefined,
107
+ description: group.description ?? undefined,
74
108
  orgUuid: group.orgUuid ?? undefined,
75
- role: group.role ?? undefined,
109
+ mutable: group.mutable ?? undefined,
110
+ superAdmin: group.superAdmin ?? undefined,
111
+ installer: group.installer ?? undefined,
112
+ inLine: group.inLine ?? undefined,
113
+ storedInS3: group.storedInS3 ?? undefined,
114
+ defaultPermissionForNewLocations: group.defaultPermissionForNewLocations ?? undefined,
115
+ defaultAccessControlPermissionForNewLocations: group.defaultAccessControlPermissionForNewLocations ?? undefined,
116
+ functionalityList: group.functionalityList ?? undefined,
117
+ accessibleLocations: cleanStringArray(group.accessibleLocations),
118
+ assignablePermissionGroups: cleanStringArray(group.assignablePermissionGroups),
119
+ // Heavy maps — callers should typically prune these out via includeFields.
120
+ locationAccessMap: toStringMap(group.locationAccessMap),
121
+ accessControlLocationAccessMap: toStringMap(group.accessControlLocationAccessMap),
122
+ deviceAccessMap: toStringMap(group.deviceAccessMap),
123
+ userPermissionGroupAccessMap: toStringMap(group.userPermissionGroupAccessMap),
124
+ locationGranularAccessMap: toNestedStringMap(group.locationGranularAccessMap),
76
125
  })) ?? []);
77
126
  }
@@ -0,0 +1,145 @@
1
+ import { createAutomatedPrompt, deleteAutomatedPrompt, getAutomatedPrompt, getAutomatedPromptChatHistory, listAutomatedPrompts, shareAutomatedPromptResponse, updateAutomatedPrompt, verifyJobScheduled, } from "../api/automated-prompts-tool-api.js";
2
+ import { AutomatedPromptsRequestType, CreateRequestSchema, DeleteRequestSchema, GetHistoryRequestSchema, GetRequestSchema, ListRequestSchema, OUTPUT_SCHEMA, ShareResponseRequestSchema, TOOL_ARGS, UpdateRequestSchema, VerifyScheduledRequestSchema, } from "../types/automated-prompts-tool-types.js";
3
+ import { createToolStructuredContent, createToolTextContent, extractFromToolExtra, } from "../util.js";
4
+ const TOOL_NAME = "automated-prompts-tool";
5
+ const TOOL_DESCRIPTION = `
6
+ This tool manages Rhombus MIND automated prompts - scheduled chatbot jobs that run a prompt at a recurring interval and store each response. Use it to list, inspect, create, update, delete, page through past responses for, share, or re-verify the schedule of an automated prompt.
7
+
8
+ Modes (set "requestType"):
9
+ - ${AutomatedPromptsRequestType.LIST}: List all automated prompts in the org. Optional 'lastEvaluatedKey' / 'maxPageSize' for pagination.
10
+ - ${AutomatedPromptsRequestType.GET}: Get a single automated prompt's settings. Requires 'promptUuid'.
11
+ - ${AutomatedPromptsRequestType.CREATE}: Create a new automated prompt. Requires 'prompt', 'invokeAt' (ISO 8601 with offset, must be at least 15 minutes in the future), 'frequencyValue', 'frequencyUnit', and 'permissionGroupUuid'. Optional 'responseTemplate' and 'notifyUserUuids'.
12
+ - ${AutomatedPromptsRequestType.UPDATE}: Selectively update an automated prompt. Requires 'promptUuid'; only the fields you set will be changed. To change the role, the caller must have access to both the current and new role. Pass an empty 'notifyUserUuids' array to clear notifyees.
13
+ - ${AutomatedPromptsRequestType.DELETE}: Delete an automated prompt and all of its stored responses. Requires 'promptUuid'.
14
+ - ${AutomatedPromptsRequestType.GET_HISTORY}: Page through responses generated by an automated prompt. Requires 'promptUuid'. Optional 'lastEvaluatedKey' / 'maxPageSize'.
15
+ - ${AutomatedPromptsRequestType.SHARE_RESPONSE}: Update the visibility of one stored response. Requires 'chatUuid' and 'visibility' (PUBLIC, ORG_WIDE, SELECT_USERS, PRIVATE).
16
+ - ${AutomatedPromptsRequestType.VERIFY_SCHEDULED}: Re-verify that the job is scheduled to trigger; the server reschedules it if missing. Requires 'promptUuid'.
17
+
18
+ Notes:
19
+ - Use 'user-tool' to look up user UUIDs and resolve them to names/emails. 'notifyUserUuids' must be members of the chosen 'permissionGroupUuid'.
20
+ - 'invokeAt' is an ISO 8601 timestamp; both 'Z' and '+/-HH:mm' offsets are accepted. The tool converts it to milliseconds for the API.
21
+ - Timestamps in the output are ISO 8601 strings with timezone offset (defaults to America/Los_Angeles when no org timezone is available).
22
+ - 'submitTestPrompt' is intentionally not exposed by this tool.
23
+ `;
24
+ function formatZodError(err) {
25
+ return err.issues
26
+ .map(issue => {
27
+ const path = issue.path.join(".");
28
+ return path ? `${path}: ${issue.message}` : issue.message;
29
+ })
30
+ .join("; ");
31
+ }
32
+ function validationErrorResponse(err) {
33
+ return createToolTextContent(JSON.stringify({ error: formatZodError(err) }));
34
+ }
35
+ const TOOL_HANDLER = async (args, _extra) => {
36
+ const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
37
+ try {
38
+ switch (args.requestType) {
39
+ case AutomatedPromptsRequestType.LIST: {
40
+ const parsed = ListRequestSchema.safeParse(args);
41
+ if (!parsed.success)
42
+ return validationErrorResponse(parsed.error);
43
+ const { lastEvaluatedKey, maxPageSize } = parsed.data;
44
+ const result = await listAutomatedPrompts({
45
+ lastEvaluatedKey: lastEvaluatedKey ?? undefined,
46
+ maxPageSize: maxPageSize ?? undefined,
47
+ }, requestModifiers, sessionId);
48
+ return createToolStructuredContent({
49
+ settingsList: result.settingsList,
50
+ lastEvaluatedKey: result.lastEvaluatedKey,
51
+ });
52
+ }
53
+ case AutomatedPromptsRequestType.GET: {
54
+ const parsed = GetRequestSchema.safeParse(args);
55
+ if (!parsed.success)
56
+ return validationErrorResponse(parsed.error);
57
+ const settings = await getAutomatedPrompt(parsed.data.promptUuid, requestModifiers, sessionId);
58
+ return createToolStructuredContent({ settings });
59
+ }
60
+ case AutomatedPromptsRequestType.CREATE: {
61
+ const parsed = CreateRequestSchema.safeParse(args);
62
+ if (!parsed.success)
63
+ return validationErrorResponse(parsed.error);
64
+ const data = parsed.data;
65
+ const settings = await createAutomatedPrompt({
66
+ prompt: data.prompt,
67
+ invokeAtMs: new Date(data.invokeAt).getTime(),
68
+ frequencyValue: data.frequencyValue,
69
+ frequencyUnit: data.frequencyUnit,
70
+ permissionGroupUuid: data.permissionGroupUuid,
71
+ responseTemplate: data.responseTemplate ?? undefined,
72
+ notifyUserUuids: data.notifyUserUuids ?? undefined,
73
+ }, requestModifiers, sessionId);
74
+ return createToolStructuredContent({ settings });
75
+ }
76
+ case AutomatedPromptsRequestType.UPDATE: {
77
+ const parsed = UpdateRequestSchema.safeParse(args);
78
+ if (!parsed.success)
79
+ return validationErrorResponse(parsed.error);
80
+ const data = parsed.data;
81
+ const settings = await updateAutomatedPrompt({
82
+ promptUuid: data.promptUuid,
83
+ prompt: data.prompt ?? undefined,
84
+ invokeAtMs: data.invokeAt ? new Date(data.invokeAt).getTime() : undefined,
85
+ frequencyValue: data.frequencyValue ?? undefined,
86
+ frequencyUnit: data.frequencyUnit ?? undefined,
87
+ permissionGroupUuid: data.permissionGroupUuid ?? undefined,
88
+ responseTemplate: data.responseTemplate ?? undefined,
89
+ notifyUserUuids: data.notifyUserUuids ?? undefined,
90
+ }, requestModifiers, sessionId);
91
+ return createToolStructuredContent({ settings });
92
+ }
93
+ case AutomatedPromptsRequestType.DELETE: {
94
+ const parsed = DeleteRequestSchema.safeParse(args);
95
+ if (!parsed.success)
96
+ return validationErrorResponse(parsed.error);
97
+ const success = await deleteAutomatedPrompt(parsed.data.promptUuid, requestModifiers, sessionId);
98
+ return createToolStructuredContent({ success });
99
+ }
100
+ case AutomatedPromptsRequestType.GET_HISTORY: {
101
+ const parsed = GetHistoryRequestSchema.safeParse(args);
102
+ if (!parsed.success)
103
+ return validationErrorResponse(parsed.error);
104
+ const data = parsed.data;
105
+ const result = await getAutomatedPromptChatHistory(data.promptUuid, {
106
+ lastEvaluatedKey: data.lastEvaluatedKey ?? undefined,
107
+ maxPageSize: data.maxPageSize ?? undefined,
108
+ }, requestModifiers, sessionId);
109
+ return createToolStructuredContent({
110
+ chatHistory: result.chatHistory,
111
+ lastEvaluatedKey: result.lastEvaluatedKey,
112
+ });
113
+ }
114
+ case AutomatedPromptsRequestType.SHARE_RESPONSE: {
115
+ const parsed = ShareResponseRequestSchema.safeParse(args);
116
+ if (!parsed.success)
117
+ return validationErrorResponse(parsed.error);
118
+ const { chatUuid, visibility } = parsed.data;
119
+ const success = await shareAutomatedPromptResponse(chatUuid, visibility, requestModifiers, sessionId);
120
+ return createToolStructuredContent({ success });
121
+ }
122
+ case AutomatedPromptsRequestType.VERIFY_SCHEDULED: {
123
+ const parsed = VerifyScheduledRequestSchema.safeParse(args);
124
+ if (!parsed.success)
125
+ return validationErrorResponse(parsed.error);
126
+ const verifyResult = await verifyJobScheduled(parsed.data.promptUuid, requestModifiers, sessionId);
127
+ return createToolStructuredContent({ verifyResult });
128
+ }
129
+ }
130
+ }
131
+ catch (error) {
132
+ if (error instanceof Error) {
133
+ return createToolStructuredContent({ error: error.message });
134
+ }
135
+ return createToolStructuredContent({ error: "Unknown error" });
136
+ }
137
+ return createToolStructuredContent({ error: "Invalid request type" });
138
+ };
139
+ export function createTool(server) {
140
+ server.registerTool(TOOL_NAME, {
141
+ description: TOOL_DESCRIPTION,
142
+ inputSchema: TOOL_ARGS,
143
+ outputSchema: OUTPUT_SCHEMA.shape,
144
+ }, TOOL_HANDLER);
145
+ }
@@ -2,7 +2,7 @@ import { getOrg } from "../api/get-org-information-tool-api.js";
2
2
  import { TOOL_ARGS } from "../types/get-org-information-tool-types.js";
3
3
  const TOOL_NAME = "get-org-information";
4
4
  const TOOL_DESCRIPTION = "Get general information about the organization including org name, camera configuration defaults, contact information, and org settings.";
5
- const TOOL_HANDLER = async (args, extra) => {
5
+ const TOOL_HANDLER = async (_, extra) => {
6
6
  const org = await getOrg(extra._meta?.requestModifiers, extra.sessionId);
7
7
  return {
8
8
  content: [
@@ -9,9 +9,16 @@ It has the following modes of operation, determined by the "requestType" paramet
9
9
  - ${UserToolRequestType.LIST_USERS}: List all users in the organization with their details and roles.
10
10
  - ${UserToolRequestType.FIND_BY_EMAIL}: Find a specific user by their email address. Requires the email parameter.
11
11
  - ${UserToolRequestType.GET_PERMISSIONS}: Get the permissions for the current API user/token.
12
- - ${UserToolRequestType.GET_PERMISSION_GROUPS}: List all permission groups defined in the organization.
12
+ - ${UserToolRequestType.GET_PERMISSION_GROUPS}: List all permission groups defined in the organization. Each row can be very large — see below.
13
13
 
14
14
  User UUIDs returned here can be used with the access-control-tool to look up credentials.
15
+
16
+ IMPORTANT for '${UserToolRequestType.GET_PERMISSION_GROUPS}':
17
+ Each permission group row includes five access maps whose size scales with the org's locations, devices, and other permission groups. The total payload for 'userPermissionGroupAccessMap' across all rows grows O(N^2) in the number of permission groups. Before calling, decide which fields you actually need and pass them via 'includeFields':
18
+ - Safe/small fields (O(1) per row): 'permissionGroups.uuid', 'permissionGroups.name', 'permissionGroups.description', 'permissionGroups.mutable', 'permissionGroups.superAdmin', 'permissionGroups.installer', 'permissionGroups.defaultPermissionForNewLocations', 'permissionGroups.defaultAccessControlPermissionForNewLocations'.
19
+ - Bounded fields (O(K) per row): 'permissionGroups.functionalityList', 'permissionGroups.accessibleLocations', 'permissionGroups.assignablePermissionGroups'.
20
+ - Heavy fields (O(locations) / O(devices) / O(groups) per row): 'permissionGroups.locationAccessMap', 'permissionGroups.accessControlLocationAccessMap', 'permissionGroups.deviceAccessMap', 'permissionGroups.userPermissionGroupAccessMap', 'permissionGroups.locationGranularAccessMap'. Only request these when you specifically need them for a user.
21
+ Typical usage when just picking a role uuid: 'includeFields: ["permissionGroups.uuid", "permissionGroups.name", "permissionGroups.description"]'.
15
22
  `;
16
23
  const TOOL_HANDLER = async (args, _extra) => {
17
24
  const { requestModifiers, sessionId } = extractFromToolExtra(_extra);
@@ -0,0 +1,203 @@
1
+ import { z } from "zod";
2
+ import { ISOTimestampFormatDescription } from "../utils/timestampInput.js";
3
+ import { ChatFollowUpActionEnum, ChatVisibilityEnum, FrequencyUnitEnum } from "./schema.js";
4
+ export var AutomatedPromptsRequestType;
5
+ (function (AutomatedPromptsRequestType) {
6
+ AutomatedPromptsRequestType["LIST"] = "list";
7
+ AutomatedPromptsRequestType["GET"] = "get";
8
+ AutomatedPromptsRequestType["CREATE"] = "create";
9
+ AutomatedPromptsRequestType["UPDATE"] = "update";
10
+ AutomatedPromptsRequestType["DELETE"] = "delete";
11
+ AutomatedPromptsRequestType["GET_HISTORY"] = "get-history";
12
+ AutomatedPromptsRequestType["SHARE_RESPONSE"] = "share-response";
13
+ AutomatedPromptsRequestType["VERIFY_SCHEDULED"] = "verify-scheduled";
14
+ })(AutomatedPromptsRequestType || (AutomatedPromptsRequestType = {}));
15
+ export const TOOL_ARGS = {
16
+ requestType: z
17
+ .enum(AutomatedPromptsRequestType)
18
+ .describe("The type of automated prompt request to make."),
19
+ promptUuid: z
20
+ .string()
21
+ .nullable()
22
+ .describe("UUID of the automated prompt. Required for 'get', 'update', 'delete', 'get-history', and 'verify-scheduled'."),
23
+ chatUuid: z
24
+ .string()
25
+ .nullable()
26
+ .describe("UUID of a chat record (an individual response generated by an automated prompt). Required for 'share-response'."),
27
+ visibility: z
28
+ .enum(ChatVisibilityEnum)
29
+ .nullable()
30
+ .describe("Visibility for a shared chat response. Required for 'share-response'. One of PUBLIC, ORG_WIDE, SELECT_USERS, PRIVATE."),
31
+ prompt: z
32
+ .string()
33
+ .nullable()
34
+ .describe("The prompt MIND will execute every time the job runs. Required for 'create', recommended for 'update'."),
35
+ responseTemplate: z
36
+ .string()
37
+ .nullable()
38
+ .describe("Optional template that guides the shape of MIND's response. Used by 'create' and 'update'."),
39
+ frequencyValue: z
40
+ .number()
41
+ .int()
42
+ .positive()
43
+ .nullable()
44
+ .describe("How many time units between runs (e.g. 1 for 'every 1 day'). Required for 'create' alongside frequencyUnit."),
45
+ frequencyUnit: z
46
+ .enum(FrequencyUnitEnum)
47
+ .nullable()
48
+ .describe("Unit for the recurrence (HOURS, DAYS, WEEKS, MONTHS). Required for 'create' alongside frequencyValue."),
49
+ invokeAt: z.iso
50
+ .datetime({
51
+ message: "Invalid datetime string. Expected ISO 8601 format.",
52
+ offset: true,
53
+ })
54
+ .nullable()
55
+ .describe("When the automated prompt should first run. Required for 'create'. Must be at least 15 minutes in the future. " +
56
+ ISOTimestampFormatDescription),
57
+ permissionGroupUuid: z
58
+ .string()
59
+ .nullable()
60
+ .describe("UUID of the permission group/role that owns the job. Users in this role (or a higher role) can view and edit the job, and MIND uses this role's permissions when running the prompt. Required for 'create'."),
61
+ notifyUserUuids: z
62
+ .array(z.string())
63
+ .nullable()
64
+ .describe("User UUIDs to email-notify when MIND finishes a run. These users must be members of the permission group set above. Used by 'create' and 'update'. Pass an empty array to clear existing notifyees on 'update'."),
65
+ lastEvaluatedKey: z
66
+ .string()
67
+ .nullable()
68
+ .describe("Pagination cursor returned by a previous 'list' or 'get-history' call. Pass to fetch the next page."),
69
+ maxPageSize: z
70
+ .number()
71
+ .int()
72
+ .positive()
73
+ .nullable()
74
+ .describe("Maximum number of items per page for 'list' and 'get-history'."),
75
+ };
76
+ const TOOL_ARGS_SCHEMA = z.object(TOOL_ARGS);
77
+ /**
78
+ * Below is the individual request types schema
79
+ */
80
+ const invokeAtDatetime = z.iso.datetime({
81
+ message: "invokeAt must be a valid ISO 8601 timestamp with offset (e.g. 2026-04-22T10:57:00-07:00).",
82
+ offset: true,
83
+ });
84
+ export const ListRequestSchema = z.object({
85
+ requestType: z.literal(AutomatedPromptsRequestType.LIST),
86
+ lastEvaluatedKey: z.string().nullish(),
87
+ maxPageSize: z.number().int().positive().nullish(),
88
+ });
89
+ export const GetRequestSchema = z.object({
90
+ requestType: z.literal(AutomatedPromptsRequestType.GET),
91
+ promptUuid: z.string().min(1, "promptUuid is required."),
92
+ });
93
+ export const CreateRequestSchema = z.object({
94
+ requestType: z.literal(AutomatedPromptsRequestType.CREATE),
95
+ prompt: z.string().min(1, "prompt is required."),
96
+ invokeAt: invokeAtDatetime,
97
+ frequencyValue: z.number().int().positive(),
98
+ frequencyUnit: z.enum(FrequencyUnitEnum),
99
+ permissionGroupUuid: z.string().min(1, "permissionGroupUuid is required."),
100
+ responseTemplate: z.string().nullish(),
101
+ notifyUserUuids: z.array(z.string()).nullish(),
102
+ });
103
+ export const UpdateRequestSchema = z
104
+ .object({
105
+ requestType: z.literal(AutomatedPromptsRequestType.UPDATE),
106
+ promptUuid: z.string().min(1, "promptUuid is required."),
107
+ prompt: z.string().min(1).nullish(),
108
+ invokeAt: invokeAtDatetime.nullish(),
109
+ frequencyValue: z.number().int().positive().nullish(),
110
+ frequencyUnit: z.enum(FrequencyUnitEnum).nullish(),
111
+ permissionGroupUuid: z.string().min(1).nullish(),
112
+ responseTemplate: z.string().nullish(),
113
+ notifyUserUuids: z.array(z.string()).nullish(),
114
+ })
115
+ .refine(data => (data.frequencyValue == null) === (data.frequencyUnit == null), {
116
+ message: "frequencyValue and frequencyUnit must be provided together when updating recurrence.",
117
+ path: ["frequencyValue"],
118
+ });
119
+ export const DeleteRequestSchema = z.object({
120
+ requestType: z.literal(AutomatedPromptsRequestType.DELETE),
121
+ promptUuid: z.string().min(1, "promptUuid is required."),
122
+ });
123
+ export const GetHistoryRequestSchema = z.object({
124
+ requestType: z.literal(AutomatedPromptsRequestType.GET_HISTORY),
125
+ promptUuid: z.string().min(1, "promptUuid is required."),
126
+ lastEvaluatedKey: z.string().nullish(),
127
+ maxPageSize: z.number().int().positive().nullish(),
128
+ });
129
+ export const ShareResponseRequestSchema = z.object({
130
+ requestType: z.literal(AutomatedPromptsRequestType.SHARE_RESPONSE),
131
+ chatUuid: z.string().min(1, "chatUuid is required."),
132
+ visibility: z.enum(ChatVisibilityEnum),
133
+ });
134
+ export const VerifyScheduledRequestSchema = z.object({
135
+ requestType: z.literal(AutomatedPromptsRequestType.VERIFY_SCHEDULED),
136
+ promptUuid: z.string().min(1, "promptUuid is required."),
137
+ });
138
+ /* More shared types */
139
+ const FrequencySchema = z
140
+ .object({
141
+ frequency: z.number().optional(),
142
+ unit: z.enum(FrequencyUnitEnum).optional(),
143
+ })
144
+ .describe("How often the prompt runs.");
145
+ const AutomatedPromptSummarySchema = z
146
+ .object({
147
+ uuid: z.string().optional(),
148
+ prompt: z.string().optional(),
149
+ responseTemplate: z.string().optional(),
150
+ frequency: FrequencySchema.optional(),
151
+ invokeAt: z
152
+ .string()
153
+ .optional()
154
+ .describe("Next run time as an ISO 8601 string with timezone offset."),
155
+ permissionGroupUuid: z.string().optional(),
156
+ notifyUserUuids: z.array(z.string()).optional(),
157
+ scheduleUuid: z.string().optional(),
158
+ orgUuid: z.string().optional(),
159
+ })
160
+ .describe("Summary of an automated prompt's settings.");
161
+ const ChatHistoryEntrySchema = z
162
+ .object({
163
+ uuid: z.string().optional(),
164
+ automatedPromptUuid: z.string().optional(),
165
+ query: z.string().optional(),
166
+ response: z.string().optional(),
167
+ queriedAt: z
168
+ .string()
169
+ .optional()
170
+ .describe("When the run started, as an ISO 8601 string with offset."),
171
+ respondedAt: z
172
+ .string()
173
+ .optional()
174
+ .describe("When the response was generated, as an ISO 8601 string with offset."),
175
+ responseType: z.string().optional(),
176
+ })
177
+ .describe("One MIND response generated by an automated prompt run.");
178
+ export const OUTPUT_SCHEMA = z.object({
179
+ settings: AutomatedPromptSummarySchema.optional().describe("Returned by 'get', 'create', and 'update'."),
180
+ settingsList: z.array(AutomatedPromptSummarySchema).optional().describe("Returned by 'list'."),
181
+ lastEvaluatedKey: z
182
+ .string()
183
+ .optional()
184
+ .describe("Pagination cursor for the next page; absent when no more pages."),
185
+ chatHistory: z.array(ChatHistoryEntrySchema).optional().describe("Returned by 'get-history'."),
186
+ verifyResult: z
187
+ .object({
188
+ scheduleExpression: z.string().optional(),
189
+ scheduleTimezone: z.string().optional(),
190
+ })
191
+ .optional()
192
+ .describe("Returned by 'verify-scheduled'. Schedule expression and timezone for the next trigger."),
193
+ success: z
194
+ .object({
195
+ ok: z.boolean(),
196
+ action: z.string(),
197
+ })
198
+ .optional()
199
+ .describe("Returned by 'delete' and 'share-response'."),
200
+ error: z.string().optional().describe("An error message if the request failed."),
201
+ });
202
+ // Re-export for the api/tool layers so they can build ChatPrivacy follow-ups.
203
+ export { ChatFollowUpActionEnum, ChatVisibilityEnum, FrequencyUnitEnum };
@@ -1,5 +1,4 @@
1
1
  import { z } from "zod";
2
- import { INCLUDE_FIELDS_ARG, FILTER_BY_ARG } from "../util.js";
3
2
  export var UserToolRequestType;
4
3
  (function (UserToolRequestType) {
5
4
  UserToolRequestType["LIST_USERS"] = "list-users";
@@ -7,16 +6,71 @@ export var UserToolRequestType;
7
6
  UserToolRequestType["GET_PERMISSIONS"] = "get-permissions";
8
7
  UserToolRequestType["GET_PERMISSION_GROUPS"] = "get-permission-groups";
9
8
  })(UserToolRequestType || (UserToolRequestType = {}));
9
+ // NOTE: `includeFields` and `filterBy` are NOT declared here — the
10
+ // `createFilteringProxy` wrapper automatically injects them into every tool's
11
+ // inputSchema and post-processes the handler's output. Declaring them here
12
+ // would be dead code (the proxy overwrites them).
10
13
  export const TOOL_ARGS = {
11
14
  requestType: z.nativeEnum(UserToolRequestType).describe("The type of user request to make."),
12
15
  email: z
13
16
  .string()
14
17
  .nullable()
15
18
  .describe("The email address of the user to find. Required for 'find-by-email'."),
16
- includeFields: INCLUDE_FIELDS_ARG,
17
- filterBy: FILTER_BY_ARG,
18
19
  };
19
20
  const TOOL_ARGS_SCHEMA = z.object(TOOL_ARGS);
21
+ // Permission level assigned to a resource for a role. Typed loosely as string
22
+ // because the upstream schema enum (READONLY/ADMIN/LIVEONLY) may grow.
23
+ const PermissionEnumLoose = z.string();
24
+ const PermissionGroupSchema = z
25
+ .object({
26
+ uuid: z.string().optional(),
27
+ name: z.string().optional(),
28
+ description: z.string().optional(),
29
+ orgUuid: z.string().optional(),
30
+ mutable: z.boolean().optional(),
31
+ superAdmin: z.boolean().optional(),
32
+ installer: z.boolean().optional(),
33
+ inLine: z.boolean().optional(),
34
+ storedInS3: z.boolean().optional(),
35
+ defaultPermissionForNewLocations: PermissionEnumLoose.optional(),
36
+ defaultAccessControlPermissionForNewLocations: PermissionEnumLoose.optional(),
37
+ functionalityList: z
38
+ .array(z.string())
39
+ .optional()
40
+ .describe("List of functionality flags this role unlocks."),
41
+ accessibleLocations: z
42
+ .array(z.string())
43
+ .optional()
44
+ .describe("UUIDs of locations this role has any access to."),
45
+ assignablePermissionGroups: z
46
+ .array(z.string())
47
+ .optional()
48
+ .describe("UUIDs of other permission groups a member of this role may assign to users."),
49
+ // The five maps below grow with the number of locations / devices /
50
+ // other permission groups in the org. For large orgs a single row can be
51
+ // hundreds of KB; use `includeFields` to request only the maps you need.
52
+ locationAccessMap: z
53
+ .record(z.string(), PermissionEnumLoose)
54
+ .optional()
55
+ .describe("locationUuid -> permission level. Size = O(#locations)."),
56
+ accessControlLocationAccessMap: z
57
+ .record(z.string(), PermissionEnumLoose)
58
+ .optional()
59
+ .describe("locationUuid -> access-control permission level. Size = O(#locations)."),
60
+ deviceAccessMap: z
61
+ .record(z.string(), PermissionEnumLoose)
62
+ .optional()
63
+ .describe("deviceUuid -> permission level. Size = O(#devices)."),
64
+ userPermissionGroupAccessMap: z
65
+ .record(z.string(), PermissionEnumLoose)
66
+ .optional()
67
+ .describe("otherPermissionGroupUuid -> permission level. Total payload across all rows is O(N^2) in the number of permission groups — trim with includeFields."),
68
+ locationGranularAccessMap: z
69
+ .record(z.string(), z.record(z.string(), PermissionEnumLoose))
70
+ .optional()
71
+ .describe("locationUuid -> (subResourceUuid -> permission level). Size = O(#locations * avg #sub-resources)."),
72
+ })
73
+ .describe("A permission group (role) definition.");
20
74
  export const OUTPUT_SCHEMA = z.object({
21
75
  users: z
22
76
  .array(z.object({
@@ -49,13 +103,8 @@ export const OUTPUT_SCHEMA = z.object({
49
103
  .optional()
50
104
  .describe("Current user permissions"),
51
105
  permissionGroups: z
52
- .array(z.object({
53
- uuid: z.string().optional(),
54
- name: z.string().optional(),
55
- orgUuid: z.string().optional(),
56
- role: z.string().optional(),
57
- }))
106
+ .array(PermissionGroupSchema)
58
107
  .optional()
59
- .describe("List of permission groups"),
108
+ .describe("List of permission groups in the organization."),
60
109
  error: z.string().optional().describe("An error message if the request failed."),
61
110
  });
package/dist/util.js CHANGED
@@ -168,3 +168,21 @@ export function formatTimestamp(timestampMs, timeZone) {
168
168
  locale: "en-US",
169
169
  });
170
170
  }
171
+ /**
172
+ * Formats a timestamp in milliseconds to an ISO 8601 string with timezone offset.
173
+ * Format: "2025-04-21T10:57:00.000-07:00"
174
+ *
175
+ * Use this when returning timestamps in tool output schemas so the offset is preserved
176
+ * (rather than the bare "Z" produced by Date.prototype.toISOString()).
177
+ *
178
+ * @param timestampMs - Timestamp in milliseconds
179
+ * @param timeZone - Optional IANA timezone string (defaults to "America/Los_Angeles")
180
+ * @returns ISO 8601 string with offset, or undefined if input is null/undefined
181
+ */
182
+ export function formatIsoWithOffset(timestampMs, timeZone) {
183
+ if (timestampMs === null || timestampMs === undefined)
184
+ return undefined;
185
+ return (DateTime.fromMillis(timestampMs)
186
+ .setZone(timeZone || "America/Los_Angeles")
187
+ .toISO() ?? undefined);
188
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhombus-node-mcp",
3
- "version": "0.1.32",
3
+ "version": "0.1.33",
4
4
  "description": "MCP server for Rhombus API",
5
5
  "keywords": [
6
6
  "ai",