rhombus-node-mcp 0.1.56 → 0.1.57

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 (102) hide show
  1. package/dist/api/access-control-tool-api.js +231 -32
  2. package/dist/api/alarm-monitoring-tool-api.js +50 -1
  3. package/dist/api/automated-prompts-tool-api.js +9 -17
  4. package/dist/api/camera-policy-tool-api.js +122 -0
  5. package/dist/api/camera-tool-api.js +3 -5
  6. package/dist/api/camera-uptime-tool-api.js +62 -25
  7. package/dist/api/clips-tool-api.js +45 -9
  8. package/dist/api/create-tool-api.js +55 -7
  9. package/dist/api/door-tool-api.js +74 -13
  10. package/dist/api/entity-lookup-tool-api.js +8 -1
  11. package/dist/api/events-tool-api.js +79 -6
  12. package/dist/api/faces-tool-api.js +112 -7
  13. package/dist/api/get-accessible-apps.js +68 -6
  14. package/dist/api/get-entity-tool-api.js +12 -12
  15. package/dist/api/get-org-information-tool-api.js +2 -2
  16. package/dist/api/guest-management-tool-api.js +75 -1
  17. package/dist/api/location-tool-api.js +120 -28
  18. package/dist/api/lpr-tool-api.js +71 -16
  19. package/dist/api/policy-alerts-tool-api.js +7 -3
  20. package/dist/api/report-tool-api.js +4 -1
  21. package/dist/api/rules-tool-api.js +6 -16
  22. package/dist/api/schedule-tool-api.js +107 -0
  23. package/dist/api/search-tool-api.js +5 -13
  24. package/dist/api/update-tool-api.js +96 -1
  25. package/dist/api/user-tool-api.js +58 -13
  26. package/dist/createServer.js +18 -6
  27. package/dist/filtering-utils.js +421 -29
  28. package/dist/index.js +1 -1
  29. package/dist/network/network.js +122 -10
  30. package/dist/network/org-reference-cache.js +74 -0
  31. package/dist/telemetry/tracingProxy.js +4 -0
  32. package/dist/tools/entity-lookup-tool.js +6 -4
  33. package/dist/tools/get-entity-tool.js +40 -20
  34. package/dist/tools/get-org-information-tool.js +30 -2
  35. package/dist/tools/time-conversion-tool.js +59 -36
  36. package/dist/tools/time-tool.js +12 -3
  37. package/dist/tools/user-tool.js +100 -2
  38. package/dist/tools-console/access-anomaly-tool.js +2 -19
  39. package/dist/tools-console/access-control-tool.js +328 -2
  40. package/dist/tools-console/alarm-monitoring-tool.js +72 -2
  41. package/dist/tools-console/analytics-tool.js +2 -2
  42. package/dist/tools-console/badge-timeline-tool.js +2 -21
  43. package/dist/tools-console/camera-policy-tool.js +252 -0
  44. package/dist/tools-console/camera-tool.js +111 -89
  45. package/dist/tools-console/clips-tool.js +21 -31
  46. package/dist/tools-console/count-tool.js +8 -0
  47. package/dist/tools-console/create-camera-policy-tool.js +302 -122
  48. package/dist/tools-console/door-schedule-exception-tool.js +2 -33
  49. package/dist/tools-console/door-tool.js +130 -2
  50. package/dist/tools-console/elements-access-anomaly-tool.js +2 -19
  51. package/dist/tools-console/elements-badge-timeline-tool.js +2 -21
  52. package/dist/tools-console/elements-lost-badge-tool.js +2 -17
  53. package/dist/tools-console/elements-tool.js +2 -29
  54. package/dist/tools-console/events-tool.js +22 -101
  55. package/dist/tools-console/faces-tool.js +254 -28
  56. package/dist/tools-console/guest-management-tool.js +127 -2
  57. package/dist/tools-console/location-tool.js +156 -35
  58. package/dist/tools-console/lost-badge-tool.js +2 -17
  59. package/dist/tools-console/lpr-tool.js +89 -23
  60. package/dist/tools-console/netbox-access-anomaly-tool.js +2 -19
  61. package/dist/tools-console/netbox-badge-timeline-tool.js +2 -21
  62. package/dist/tools-console/netbox-lost-badge-tool.js +2 -17
  63. package/dist/tools-console/netbox-tool.js +2 -29
  64. package/dist/tools-console/onguard-tool.js +2 -30
  65. package/dist/tools-console/person-tracking-tool.js +2 -2
  66. package/dist/tools-console/policy-alerts-tool.js +5 -29
  67. package/dist/tools-console/reboot-cameras-tool.js +17 -2
  68. package/dist/tools-console/report-tool.js +10 -40
  69. package/dist/tools-console/schedule-tool.js +190 -0
  70. package/dist/tools-console/update-tool.js +298 -168
  71. package/dist/tools-console/video-walls-tool.js +93 -21
  72. package/dist/types/access-anomaly-tool-types.js +22 -2
  73. package/dist/types/access-control-tool-types.js +116 -1
  74. package/dist/types/alarm-monitoring-tool-types.js +42 -1
  75. package/dist/types/badge-timeline-tool-types.js +18 -3
  76. package/dist/types/camera-policy-tool-types.js +104 -0
  77. package/dist/types/camera-tool-types.js +80 -1
  78. package/dist/types/clips-tool-types.js +27 -3
  79. package/dist/types/create-camera-policy-tool-types.js +275 -26
  80. package/dist/types/door-schedule-exception-tool-types.js +20 -6
  81. package/dist/types/door-tool-types.js +36 -4
  82. package/dist/types/entity-lookup-tool-types.js +6 -1
  83. package/dist/types/events-tools-types.js +24 -16
  84. package/dist/types/faces-tools-types.js +106 -3
  85. package/dist/types/get-entity-tool-types.js +76 -12
  86. package/dist/types/guest-management-tool-types.js +103 -1
  87. package/dist/types/location-tool-types.js +100 -14
  88. package/dist/types/lost-badge-tool-types.js +17 -2
  89. package/dist/types/lpr-tool-types.js +47 -8
  90. package/dist/types/onguard-tool-types.js +21 -1
  91. package/dist/types/policy-alerts-tool-types.js +1 -1
  92. package/dist/types/report-tool-types.js +29 -1
  93. package/dist/types/schedule-tool-types.js +370 -0
  94. package/dist/types/schema.js +479 -9
  95. package/dist/types/time-conversion-tool-types.js +3 -4
  96. package/dist/types/update-tool-types.js +40 -18
  97. package/dist/types/user-tool-types.js +44 -1
  98. package/dist/types/video-walls-tool-types.js +30 -2
  99. package/dist/types/zod-schemas.js +5128 -996
  100. package/dist/util.js +62 -3
  101. package/dist/utils/entity-name-match.js +11 -0
  102. package/package.json +3 -1
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  import { isConfirmed, requireConfirmation } from "../utils/confirmation.js";
2
3
  import { rebootCameras } from "../api/reboot-cameras-tool-api.js";
3
4
  import { TOOL_ARGS } from "../types/reboot-cameras-tool-types.js";
@@ -10,12 +11,15 @@ const TOOL_HANDLER = async (args, extra) => {
10
11
  return confirmation;
11
12
  }
12
13
  const cameraRebootData = await rebootCameras(cameraUuids, extra._meta?.requestModifiers, extra.sessionId);
13
- if (!cameraRebootData) {
14
+ if (!cameraRebootData || ("error" in cameraRebootData && cameraRebootData.error)) {
14
15
  return {
16
+ isError: true,
15
17
  content: [
16
18
  {
17
19
  type: "text",
18
- text: "Failed to reboot cameras",
20
+ text: cameraRebootData && "status" in cameraRebootData && cameraRebootData.status
21
+ ? String(cameraRebootData.status)
22
+ : "Failed to reboot cameras",
19
23
  },
20
24
  ],
21
25
  };
@@ -27,6 +31,7 @@ const TOOL_HANDLER = async (args, extra) => {
27
31
  text: JSON.stringify(cameraRebootData),
28
32
  },
29
33
  ],
34
+ structuredContent: cameraRebootData,
30
35
  };
31
36
  };
32
37
  export function createTool(server) {
@@ -34,6 +39,16 @@ export function createTool(server) {
34
39
  title: "Reboot Cameras",
35
40
  description: TOOL_DESCRIPTION,
36
41
  inputSchema: TOOL_ARGS,
42
+ // The confirmation-needed result goes through createToolTextContent,
43
+ // which sets isError: true — so it legitimately skips output validation.
44
+ outputSchema: {
45
+ status: z
46
+ .string()
47
+ .optional()
48
+ .describe("SUCCESS when every camera rebooted, PARTIAL_SUCCESS when some did, ERROR when none did"),
49
+ successCount: z.number().optional(),
50
+ errorCount: z.number().optional(),
51
+ },
37
52
  annotations: { readOnlyHint: false, destructiveHint: true },
38
53
  }, TOOL_HANDLER);
39
54
  }
@@ -3,50 +3,20 @@ import { logger } from "../logger.js";
3
3
  import { OUTPUT_SCHEMA, RequestType, TOOL_ARGS, } from "../types/report-tool-types.js";
4
4
  import { extractFromToolExtra } from "../util.js";
5
5
  const TOOL_NAME = "report-tool";
6
+ // Tool descriptions are billed on EVERY LLM call even while the tool is
7
+ // deferred behind hosted tool_search, so this string carries only what the
8
+ // model needs to CHOOSE this tool. Per-requestType behaviour, call ordering,
9
+ // and the people/occupancy strategy live on the requestType parameter
10
+ // description (unbilled until the tool is loaded, still in front of the model
11
+ // when it builds the call). See PERF_MASTER_PLAN P2 #4a.
6
12
  const TOOL_DESCRIPTION = `
7
- **Scope:** This tool returns **aggregated counts and time-series summaries** over specified intervals and scopes. Use **events-tool** when you need raw, event-level data (individual events with timestamps). Use this tool for high-level reports, analytics, and trends—especially over periods of a day or more.
13
+ **Scope:** **aggregated counts and time-series summaries** over specified intervals and scopes (device, location, region, org). Use **events-tool** when you need raw, event-level data (individual events with timestamps). Use this tool for high-level reports, analytics, and trends—especially over periods of a day or more.
8
14
 
9
- **Interval guidance:** A shorter interval (HOURLY instead of DAILY) gives a better representation of data over time. Balance interval and range so you don't request too much data. For ranges spanning a week or so, HOURLY is appropriate.
10
-
11
- ---
12
-
13
- **People / occupancy counting strategy**
14
-
15
- When asked to count people on a camera or at a location, follow this strategy:
16
- 1. **Always call GET_OCCUPANCY_ENABLED_CAMERAS first** to discover which cameras have occupancy counting enabled.
17
- 2. If the target camera IS in the list, call **GET_OCCUPANCY_COUNT_REPORT** for that device. The response will automatically include a \`faceCountEnrichment\` field with the number of unique individuals identified by face recognition in the same time range. Present both data sources: occupancy estimate and unique face count.
18
- 3. If the target camera is NOT in the list, **tell the user** that camera does not have occupancy counting enabled, and list the cameras that do. You can still call GET_SUMMARY_COUNT_REPORT with PEOPLE type — its response will also include \`faceCountEnrichment\` with unique face data as a fallback. If the PEOPLE count returns zero, the response will also include the list of occupancy-enabled cameras and a hint.
19
- 4. When both occupancy data and face recognition data are available, **synthesize both** in your answer (e.g., "Occupancy estimates ~15 people. Face recognition identified 9 unique individuals during this period.").
20
-
21
- **PEOPLE type (in GET_SUMMARY_COUNT_REPORT):** Not a unique person count; it counts people-detection events. Requires people detection to be enabled on the camera. Use for high-level activity trends, not for deduplicated head counts.
22
-
23
- ---
15
+ Covers: people counting and occupancy reports (enriched with unique-face counts), line-crossing / threshold ingress-egress counts, custom LLM event reports and their prompt configurations (e.g. "black dog sightings", "delivery truck arrivals", "parking availability %"), the org audit log of user and admin actions, device diagnostic feeds, and the most recent people-count readings for a device.
24
16
 
25
- **Summary and occupancy**
26
- - **GET_SUMMARY_COUNT_REPORT:** Aggregated counts (people, faces, motion, vehicles, etc.) over time at device, location, or org scope. Interval: minutely, hourly, daily, weekly, monthly, yearly. When called with PEOPLE type at DEVICE scope, the response is automatically enriched with face recognition data.
27
- - **GET_OCCUPANCY_ENABLED_CAMERAS:** List of cameras with occupancy reporting enabled. **Always call this first** before any people/occupancy counting request to verify camera support.
28
- - **GET_OCCUPANCY_COUNT_REPORT:** Occupancy count time series for a specific device over a time range. Response is automatically enriched with face recognition data. If the device does not support occupancy, the response will include a hint and the list of cameras that do.
17
+ Pick one with "requestType". Each type's arguments, response enrichment, and required call ordering (some types must be called first to discover which cameras support a feature) are documented on the requestType parameter.
29
18
 
30
- ---
31
-
32
- **Line crossing**
33
- - **GET_LINE_CROSSING_ENABLED_CAMERAS:** Cameras at a location with line crossing enabled, plus their configs. Call first to see which cameras support threshold crossing reports.
34
- - **GET_THRESHOLD_CROSSING_COUNT_REPORT:** Ingress/egress counts for line crossings over time. Supports human and vehicle detection; bucket size: quarter hour, hour, day, week. Response includes computed metrics: average entries/exits per hour, hour with most entries/exits, busiest hour (with breakdown).
35
-
36
- ---
37
-
38
- **Custom LLM events**
39
- - **FIND_PROMPT_CONFIGURATIONS:** All custom event prompt configurations (e.g. "black dog sightings", "delivery truck arrivals", "parking availability %"). Each has prompt text, UUID, and promptType (COUNT, PERCENT, BOOLEAN). Call first to discover available custom events.
40
- - **GET_CUSTOM_LLM_REPORT:** **This is the PRIMARY way to get custom event reports.** Aggregated time-series for one custom event by prompt UUID. Automatically selects the correct API based on promptType: COUNT (numeric counts), PERCENT (percentages), BOOLEAN (true/false). Intervals: minutely, quarter-hourly, hourly, daily, weekly, monthly. **Always use this for custom event reports, trends, and analytics.** Use FIND_PROMPT_CONFIGURATIONS first to get the promptUuid and promptType.
41
- - **GET_CUSTOM_EVENTS_REPORT:** Raw individual event values only (not aggregated). Use only when you need per-event granularity, not for reports or trends.
42
-
43
- ---
44
-
45
- **Audit and diagnostics**
46
- - **GET_AUDIT_FEED:** Audit log of all user/admin actions in the org over a time range. Returns who did what and when (principalName, targetName, action, displayText).
47
- - **GET_DIAGNOSTIC_FEED:** Device diagnostic events over a time range.
48
- - **GET_THRESHOLD_CROSSING_EVENTS:** Individual line-crossing events (not aggregated counts).
49
- - **GET_PEOPLE_COUNT_EVENTS:** Most recent people count readings for specified devices.
19
+ **Interval guidance:** A shorter interval (HOURLY instead of DAILY) gives a better representation of data over time. Balance interval and range so you don't request too much data. For ranges spanning a week or so, HOURLY is appropriate.
50
20
  `;
51
21
  const TOOL_HANDLER = async (args, extra) => {
52
22
  const { requestType } = args;
@@ -0,0 +1,190 @@
1
+ import { createSchedule, deleteSchedule, findSchedules, getPoliciesUsingSchedule, updateSchedule, } from "../api/schedule-tool-api.js";
2
+ import { INTERVALS_EXAMPLE, OUTPUT_SCHEMA, ScheduleToolRequestType, TOOL_ARGS, WEEKLY_REPEATING_STRATEGY, describeIntervals, parseWeeklyIntervals, } from "../types/schedule-tool-types.js";
3
+ import { createToolStructuredContent, createToolTextContent, extractFromToolExtra, } from "../util.js";
4
+ const TOOL_NAME = "schedule-tool";
5
+ const TOOL_DESCRIPTION = `
6
+ This tool manages Rhombus schedules — the named weekly time windows that policies attach to, e.g. "Business Hours" or "After Hours".
7
+
8
+ Use it to resolve a schedule NAME to the scheduleUuid that other tools require. create-camera-policy-tool needs a scheduleUuid and has no other legitimate source for one, so start here rather than guessing a UUID.
9
+
10
+ It has the following modes of operation, determined by the "requestType" parameter:
11
+ - ${ScheduleToolRequestType.LIST}: List every schedule in the organization with its UUID, name and active windows.
12
+ - ${ScheduleToolRequestType.GET}: Get one schedule by scheduleUuid.
13
+ - ${ScheduleToolRequestType.CREATE}: Create a weekly schedule. Requires name and intervals, e.g. intervals=${INTERVALS_EXAMPLE}.
14
+ - ${ScheduleToolRequestType.UPDATE}: Rename a schedule and/or replace its windows. Requires scheduleUuid plus name and/or intervals. Fields you omit are left unchanged; passing intervals REPLACES all existing windows.
15
+ - ${ScheduleToolRequestType.DELETE}: Delete a schedule. Requires scheduleUuid. Refuses (without deleting) when policies still reference the schedule, and lists them — see confirmDeleteInUse.
16
+ - ${ScheduleToolRequestType.GET_POLICIES_USING}: List the policies that reference a schedule. Requires scheduleUuid.
17
+
18
+ Schedules are org-wide and shared: editing one changes the behaviour of every policy attached to it. Schedules reported with "mutable": false are system-managed and cannot be updated or deleted.
19
+ `;
20
+ /** Look a schedule up in the org list; the API has no get-one endpoint. */
21
+ async function findScheduleByUuid(scheduleUuid, requestModifiers, sessionId) {
22
+ const { schedules } = await findSchedules(requestModifiers, sessionId);
23
+ return schedules.find(schedule => schedule.uuid === scheduleUuid);
24
+ }
25
+ const TOOL_HANDLER = async (args, extra) => {
26
+ const { requestModifiers, sessionId } = extractFromToolExtra(extra);
27
+ try {
28
+ switch (args.requestType) {
29
+ case ScheduleToolRequestType.LIST: {
30
+ const { schedules, warningMsg } = await findSchedules(requestModifiers, sessionId);
31
+ return createToolStructuredContent({
32
+ schedules,
33
+ warningMsg,
34
+ // A bare [] reads as "no schedules exist" indistinguishably from
35
+ // "this org has none configured yet"; say which it is.
36
+ note: schedules.length === 0
37
+ ? "This organization has no schedules configured. A camera or door policy cannot be scheduled until one is created — offer to create one with requestType 'create'."
38
+ : undefined,
39
+ });
40
+ }
41
+ case ScheduleToolRequestType.GET: {
42
+ if (!args.scheduleUuid) {
43
+ return createToolTextContent("scheduleUuid is required for 'get'.");
44
+ }
45
+ const schedule = await findScheduleByUuid(args.scheduleUuid, requestModifiers, sessionId);
46
+ if (!schedule) {
47
+ return createToolTextContent(`No schedule in this organization has the uuid "${args.scheduleUuid}". Use requestType 'list' to see the schedules that exist — do not guess a uuid.`);
48
+ }
49
+ return createToolStructuredContent({ schedule });
50
+ }
51
+ case ScheduleToolRequestType.CREATE: {
52
+ if (!args.name?.trim()) {
53
+ return createToolTextContent("name is required for 'create'.");
54
+ }
55
+ if (!args.intervals) {
56
+ return createToolTextContent(`intervals is required for 'create'. Example: ${INTERVALS_EXAMPLE}`);
57
+ }
58
+ const parsed = parseWeeklyIntervals(args.intervals);
59
+ if (!parsed.ok) {
60
+ // Pre-mutation: nothing was created, so a corrected retry is safe.
61
+ return createToolTextContent(`RETRYABLE — nothing was created. ${parsed.message}`);
62
+ }
63
+ const scheduleInput = {
64
+ name: args.name.trim(),
65
+ strategy: WEEKLY_REPEATING_STRATEGY,
66
+ intervalList: parsed.value,
67
+ };
68
+ const { scheduleUuid, warningMsg } = await createSchedule(scheduleInput, requestModifiers, sessionId);
69
+ return createToolStructuredContent({
70
+ created: { success: true, scheduleUuid },
71
+ warningMsg,
72
+ note: `Created "${args.name.trim()}" active ${describeIntervals(parsed.value)}.`,
73
+ });
74
+ }
75
+ case ScheduleToolRequestType.UPDATE: {
76
+ if (!args.scheduleUuid) {
77
+ return createToolTextContent("scheduleUuid is required for 'update'.");
78
+ }
79
+ if (!args.name?.trim() && !args.intervals) {
80
+ return createToolTextContent("'update' needs at least one of name or intervals — both were empty, so there is nothing to change.");
81
+ }
82
+ // api2's updateSchedule takes the WHOLE schedule, so sending only the
83
+ // changed field would blank the other one. Read first and merge.
84
+ const existing = await findScheduleByUuid(args.scheduleUuid, requestModifiers, sessionId);
85
+ if (!existing) {
86
+ return createToolTextContent(`No schedule in this organization has the uuid "${args.scheduleUuid}". Use requestType 'list' to see the schedules that exist.`);
87
+ }
88
+ if (existing.mutable === false) {
89
+ return createToolTextContent(`Schedule "${existing.name ?? args.scheduleUuid}" is system-managed (mutable: false) and cannot be edited. Tell the user this schedule is read-only; creating a new schedule is the alternative.`);
90
+ }
91
+ let intervalList = existing.intervals
92
+ ?.flatMap(interval => typeof interval.minuteOfWeekStart === "number" &&
93
+ typeof interval.minuteOfWeekStop === "number"
94
+ ? [
95
+ {
96
+ minuteOfWeekStart: interval.minuteOfWeekStart,
97
+ minuteOfWeekStop: interval.minuteOfWeekStop,
98
+ },
99
+ ]
100
+ : [])
101
+ .slice();
102
+ let replacedWindows = false;
103
+ if (args.intervals) {
104
+ const parsed = parseWeeklyIntervals(args.intervals);
105
+ if (!parsed.ok) {
106
+ return createToolTextContent(`RETRYABLE — nothing was changed. ${parsed.message}`);
107
+ }
108
+ intervalList = parsed.value;
109
+ replacedWindows = true;
110
+ }
111
+ const { updated, warningMsg } = await updateSchedule({
112
+ uuid: args.scheduleUuid,
113
+ name: args.name?.trim() || existing.name,
114
+ strategy: WEEKLY_REPEATING_STRATEGY,
115
+ intervalList,
116
+ }, requestModifiers, sessionId);
117
+ return createToolStructuredContent({
118
+ updated,
119
+ warningMsg,
120
+ note: replacedWindows
121
+ ? `Replaced all windows on "${args.name?.trim() || existing.name}" — it is now active ${describeIntervals(intervalList ?? [])}. Every policy using this schedule now follows the new windows.`
122
+ : `Renamed to "${args.name?.trim()}". Its windows are unchanged.`,
123
+ });
124
+ }
125
+ case ScheduleToolRequestType.DELETE: {
126
+ if (!args.scheduleUuid) {
127
+ return createToolTextContent("scheduleUuid is required for 'delete'.");
128
+ }
129
+ const existing = await findScheduleByUuid(args.scheduleUuid, requestModifiers, sessionId);
130
+ if (!existing) {
131
+ return createToolTextContent(`No schedule in this organization has the uuid "${args.scheduleUuid}". Use requestType 'list' to see the schedules that exist.`);
132
+ }
133
+ if (existing.mutable === false) {
134
+ return createToolTextContent(`Schedule "${existing.name ?? args.scheduleUuid}" is system-managed (mutable: false) and cannot be deleted.`);
135
+ }
136
+ // Deleting a schedule silently rewires every policy attached to it, so
137
+ // check usage first and make the caller confirm.
138
+ const { usedBy } = await getPoliciesUsingSchedule(args.scheduleUuid, requestModifiers, sessionId);
139
+ if (usedBy.length > 0 && !args.confirmDeleteInUse) {
140
+ return createToolStructuredContent({
141
+ usedBy,
142
+ note: `NOT DELETED — nothing was changed. "${existing.name ?? args.scheduleUuid}" is still used by ` +
143
+ `${usedBy.length} ${usedBy.length === 1 ? "policy" : "policies"}: ` +
144
+ `${usedBy.map(policy => `${policy.name ?? policy.uuid} (${policy.policyType})`).join(", ")}. ` +
145
+ `Deleting it changes how those policies behave. Tell the user which policies are affected and ask them to ` +
146
+ `confirm, then call this tool again with confirmDeleteInUse: true.`,
147
+ });
148
+ }
149
+ const { deleted, warningMsg } = await deleteSchedule(args.scheduleUuid, requestModifiers, sessionId);
150
+ return createToolStructuredContent({
151
+ deleted,
152
+ warningMsg,
153
+ note: usedBy.length > 0
154
+ ? `Deleted "${existing.name ?? args.scheduleUuid}", which was in use by ${usedBy.length} ${usedBy.length === 1 ? "policy" : "policies"}: ${usedBy.map(policy => policy.name ?? policy.uuid).join(", ")}. Those policies no longer have this schedule.`
155
+ : `Deleted "${existing.name ?? args.scheduleUuid}". No policies referenced it.`,
156
+ });
157
+ }
158
+ case ScheduleToolRequestType.GET_POLICIES_USING: {
159
+ if (!args.scheduleUuid) {
160
+ return createToolTextContent("scheduleUuid is required for 'get-policies-using'.");
161
+ }
162
+ const { usedBy, warningMsg } = await getPoliciesUsingSchedule(args.scheduleUuid, requestModifiers, sessionId);
163
+ return createToolStructuredContent({
164
+ usedBy,
165
+ warningMsg,
166
+ note: usedBy.length === 0
167
+ ? "No policies reference this schedule, so it can be deleted or edited without affecting anything."
168
+ : undefined,
169
+ });
170
+ }
171
+ }
172
+ }
173
+ catch (error) {
174
+ return createToolStructuredContent({
175
+ error: error instanceof Error ? error.message : "Unknown error",
176
+ });
177
+ }
178
+ return createToolStructuredContent({
179
+ error: `Invalid requestType. Valid values are: ${Object.values(ScheduleToolRequestType).join(", ")}.`,
180
+ });
181
+ };
182
+ export function createTool(server) {
183
+ server.registerTool(TOOL_NAME, {
184
+ title: "Schedules",
185
+ description: TOOL_DESCRIPTION,
186
+ inputSchema: TOOL_ARGS,
187
+ outputSchema: OUTPUT_SCHEMA.shape,
188
+ annotations: { readOnlyHint: false, destructiveHint: true },
189
+ }, TOOL_HANDLER);
190
+ }