rhombus-node-mcp 0.1.26 → 0.1.27

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.
@@ -45,14 +45,15 @@ function buildDateRangeFilter(filter) {
45
45
  localEndDateRangeEnd,
46
46
  };
47
47
  }
48
- async function verifyExceptionConfig(exception, requestModifiers, sessionId) {
48
+ async function verifyExceptionConfig(exception, options, requestModifiers, sessionId) {
49
49
  const verified = {
50
50
  ...exception,
51
51
  };
52
- const cleanedDoorUuids = (Array.isArray(exception.doorUuids)
53
- ? exception.doorUuids.filter((doorUuid) => !!doorUuid)
54
- : []) ?? [];
55
- if (cleanedDoorUuids.length > 0) {
52
+ const doorUuidsProvided = Array.isArray(exception.doorUuids);
53
+ const cleanedDoorUuids = doorUuidsProvided
54
+ ? (exception.doorUuids?.filter((doorUuid) => !!doorUuid) ?? [])
55
+ : undefined;
56
+ if (cleanedDoorUuids !== undefined) {
56
57
  verified.doorUuids = cleanedDoorUuids;
57
58
  }
58
59
  if ((!Array.isArray(exception.intervals) || exception.intervals.length === 0) &&
@@ -67,27 +68,33 @@ async function verifyExceptionConfig(exception, requestModifiers, sessionId) {
67
68
  },
68
69
  ];
69
70
  }
70
- // construct locationToDoorsMap
71
- const locationToDoorsMap = {};
72
- if (cleanedDoorUuids.length > 0) {
73
- const doors = await getAccessControlledDoors(requestModifiers, sessionId);
74
- const doorsMap = await createUuidMap(doors.accessControlledDoors ?? [], "uuid");
75
- for (const doorUuid of cleanedDoorUuids) {
76
- const door = doorsMap.get(doorUuid);
77
- const locationUuid = door?.locationUuid;
78
- if (locationUuid) {
79
- if (!locationToDoorsMap[locationUuid]) {
80
- locationToDoorsMap[locationUuid] = [];
71
+ // construct locationToDoorsMap only when doors are explicitly provided.
72
+ // For update, omitting doorUuids should preserve the existing map.
73
+ if (cleanedDoorUuids !== undefined) {
74
+ const locationToDoorsMap = {};
75
+ if (cleanedDoorUuids.length > 0) {
76
+ const doors = await getAccessControlledDoors(requestModifiers, sessionId);
77
+ const doorsMap = await createUuidMap(doors.accessControlledDoors ?? [], "uuid");
78
+ for (const doorUuid of cleanedDoorUuids) {
79
+ const door = doorsMap.get(doorUuid);
80
+ const locationUuid = door?.locationUuid;
81
+ if (locationUuid) {
82
+ if (!locationToDoorsMap[locationUuid]) {
83
+ locationToDoorsMap[locationUuid] = [];
84
+ }
85
+ locationToDoorsMap[locationUuid].push(doorUuid);
81
86
  }
82
- locationToDoorsMap[locationUuid].push(doorUuid);
83
87
  }
84
88
  }
89
+ verified.locationToDoorsMap = locationToDoorsMap;
90
+ }
91
+ else if (!options.isUpdate) {
92
+ verified.locationToDoorsMap = {};
85
93
  }
86
- verified.locationToDoorsMap = locationToDoorsMap;
87
94
  return verified;
88
95
  }
89
96
  export async function createDoorScheduleException(exception, requestModifiers, sessionId) {
90
- const normalizedException = await verifyExceptionConfig(exception, requestModifiers, sessionId);
97
+ const normalizedException = await verifyExceptionConfig(exception, { isUpdate: false }, requestModifiers, sessionId);
91
98
  const res = await postApi({
92
99
  route: "/accesscontrol/doorScheduleException/createExceptionV2",
93
100
  body: {
@@ -103,7 +110,7 @@ export async function createDoorScheduleException(exception, requestModifiers, s
103
110
  };
104
111
  }
105
112
  export async function updateDoorScheduleException(exception, requestModifiers, sessionId) {
106
- const normalizedException = await verifyExceptionConfig(exception, requestModifiers, sessionId);
113
+ const normalizedException = await verifyExceptionConfig(exception, { isUpdate: true }, requestModifiers, sessionId);
107
114
  const res = await postApi({
108
115
  route: "/accesscontrol/doorScheduleException/updateExceptionV2",
109
116
  body: {
@@ -1,5 +1,5 @@
1
1
  import { createDoorScheduleException, deleteDoorScheduleException, findDoorScheduleExceptions, findDoorScheduleExceptionsForDoor, findDoorScheduleExceptionsForLocation, getDoorScheduleException, updateDoorScheduleException, } from "../api/door-schedule-exception-tool-api.js";
2
- import { DoorScheduleExceptionRequestType, OUTPUT_SCHEMA, TOOL_ARGS, } from "../types/door-schedule-exception-tool-types.js";
2
+ import { CREATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA, DoorScheduleExceptionRequestType, OUTPUT_SCHEMA, TOOL_ARGS, UPDATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA, } from "../types/door-schedule-exception-tool-types.js";
3
3
  import { createToolStructuredContent, createToolTextContent, extractFromToolExtra, } from "../util.js";
4
4
  const TOOL_NAME = "door-schedule-exception-tool";
5
5
  const TOOL_DESCRIPTION = `
@@ -9,6 +9,10 @@ If a lock/unlock exception is enabled, it will overwrite the existing lock/unloc
9
9
  A schedule exception allows you to create a custom schedule that is only active for the specified dates/times.
10
10
  Once the date/time a schedule exception is set for passes, the original schedule will resume.
11
11
 
12
+ Door schedule exceptions can be either expired or not expired. If its scheduled date is in the past, then it is expired.
13
+ Users through the web console can toggle whether to see expired door schedule exceptions or not. Please mirror this behavior
14
+ when responding to the user.
15
+
12
16
  It has the following modes of operation, determined by the "requestType" parameter:
13
17
  - ${DoorScheduleExceptionRequestType.CREATE_EXCEPTION}: Create a door schedule exception. Requires exception (DoorScheduleExceptionType object). If locationUuid is missing but doorUuids are provided, the tool will resolve the location automatically.
14
18
  - ${DoorScheduleExceptionRequestType.DELETE_EXCEPTION}: Delete a door schedule exception. Requires exceptionUuid.
@@ -38,7 +42,13 @@ const TOOL_HANDLER = async (args, _extra) => {
38
42
  error: "exception is required for create-exception.",
39
43
  }));
40
44
  }
41
- const created = await createDoorScheduleException(args.exception, requestModifiers, sessionId);
45
+ const parsedException = CREATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA.safeParse(args.exception);
46
+ if (!parsedException.success) {
47
+ return createToolTextContent(JSON.stringify({
48
+ error: parsedException.error.issues[0]?.message ?? "Invalid exception payload for create-exception.",
49
+ }));
50
+ }
51
+ const created = await createDoorScheduleException(parsedException.data, requestModifiers, sessionId);
42
52
  return createToolStructuredContent(created);
43
53
  }
44
54
  case DoorScheduleExceptionRequestType.DELETE_EXCEPTION: {
@@ -87,7 +97,14 @@ const TOOL_HANDLER = async (args, _extra) => {
87
97
  error: "exception is required for update-exception.",
88
98
  }));
89
99
  }
90
- const updated = await updateDoorScheduleException(args.exception, requestModifiers, sessionId);
100
+ const parsedException = UPDATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA.safeParse(args.exception);
101
+ if (!parsedException.success) {
102
+ return createToolTextContent(JSON.stringify({
103
+ error: parsedException.error.issues[0]?.message ??
104
+ "Invalid exception payload for update-exception.",
105
+ }));
106
+ }
107
+ const updated = await updateDoorScheduleException(parsedException.data, requestModifiers, sessionId);
91
108
  return createToolStructuredContent(updated);
92
109
  }
93
110
  }
@@ -11,29 +11,29 @@ export var DoorScheduleExceptionRequestType;
11
11
  DoorScheduleExceptionRequestType["GET_EXCEPTION"] = "get-exception";
12
12
  DoorScheduleExceptionRequestType["UPDATE_EXCEPTION"] = "update-exception";
13
13
  })(DoorScheduleExceptionRequestType || (DoorScheduleExceptionRequestType = {}));
14
- export const DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA = z
14
+ const DOOR_INTERVAL_SCHEMA = z.object({
15
+ localEndDateTime: z.string().nullable(),
16
+ localStartDateTime: z.string().nullable(),
17
+ state: z.enum(AccessControlledDoorStateEnumType),
18
+ });
19
+ const DOOR_SCHEDULE_EXCEPTION_BASE_SCHEMA = z
15
20
  .object({
16
- createdAtMillis: z.number().nullable(),
17
- defaultState: z.enum(AccessControlledDoorStateEnumType).nullable(),
18
- description: z.string().nullable(),
21
+ uuid: z.string().nullable().optional(),
22
+ createdAtMillis: z.number().nullable().optional(),
23
+ defaultState: z.enum(AccessControlledDoorStateEnumType).nullable().optional(),
24
+ description: z.string().nullable().optional(),
19
25
  doorUuids: z.array(z.string().nullable()).nullable().optional(),
20
- intervals: z
21
- .array(z.object({
22
- localEndDateTime: z.string().nullable(),
23
- localStartDateTime: z.string().nullable(),
24
- state: z.enum(AccessControlledDoorStateEnumType),
25
- }))
26
- .nullable(),
27
- localEndDate: z.string().nullable(),
28
- localStartDate: z.string().nullable(),
26
+ intervals: z.array(DOOR_INTERVAL_SCHEMA).nullable().optional(),
27
+ localEndDate: z.string().nullable().optional(),
28
+ localStartDate: z.string().nullable().optional(),
29
29
  locationToDoorsMap: z
30
30
  .record(z.string(), z.array(z.string().nullable()).nullable())
31
31
  .nullable()
32
32
  .optional(),
33
- name: z.string().nullable(),
34
- updatedAtMillis: z.number().nullable(),
35
- })
36
- .superRefine((value, ctx) => {
33
+ name: z.string().nullable().optional(),
34
+ updatedAtMillis: z.number().nullable().optional(),
35
+ });
36
+ export const CREATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA = DOOR_SCHEDULE_EXCEPTION_BASE_SCHEMA.superRefine((value, ctx) => {
37
37
  if (!value.name) {
38
38
  ctx.addIssue({
39
39
  code: z.ZodIssueCode.custom,
@@ -74,6 +74,36 @@ export const DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA = z
74
74
  });
75
75
  }
76
76
  });
77
+ export const UPDATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA = DOOR_SCHEDULE_EXCEPTION_BASE_SCHEMA.superRefine((value, ctx) => {
78
+ if (!value.uuid) {
79
+ ctx.addIssue({
80
+ code: z.ZodIssueCode.custom,
81
+ message: "exception.uuid is required for update-exception.",
82
+ path: ["uuid"],
83
+ });
84
+ }
85
+ const hasMutableField = [
86
+ value.name,
87
+ value.description,
88
+ value.defaultState,
89
+ value.localStartDate,
90
+ value.localEndDate,
91
+ value.intervals,
92
+ value.doorUuids,
93
+ value.locationToDoorsMap,
94
+ ].some((field) => field !== undefined);
95
+ if (!hasMutableField) {
96
+ ctx.addIssue({
97
+ code: z.ZodIssueCode.custom,
98
+ message: "exception update must provide at least one mutable field (for example name, description, dates, intervals, doorUuids, or locationToDoorsMap).",
99
+ path: [],
100
+ });
101
+ }
102
+ });
103
+ export const DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA = z.union([
104
+ CREATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA,
105
+ UPDATE_DOOR_SCHEDULE_EXCEPTION_INPUT_SCHEMA,
106
+ ]);
77
107
  export const TOOL_ARGS = {
78
108
  requestType: z
79
109
  .nativeEnum(DoorScheduleExceptionRequestType)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rhombus-node-mcp",
3
- "version": "0.1.26",
3
+ "version": "0.1.27",
4
4
  "description": "MCP server for Rhombus API",
5
5
  "keywords": [
6
6
  "ai",