master
c 353 lines 14.6 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "mcp-tools-alert-transitions.h"
4 #include "mcp-params.h"
5 #include "database/contexts/rrdcontext.h"
6
7 // Schema for alert transitions
8 void mcp_tool_list_alert_transitions_schema(BUFFER *buffer) {
9 // Tool metadata
10 buffer_json_member_add_object(buffer, "inputSchema");
11 buffer_json_member_add_string(buffer, "type", "object");
12 buffer_json_member_add_string(buffer, "title", "List alert transitions");
13
14 buffer_json_member_add_object(buffer, "properties");
15
16 // Time range
17 mcp_schema_add_time_params(
18 buffer,
19 "alert transitions",
20 false);
21
22 mcp_schema_add_array_param(
23 buffer, "alerts",
24 "Filter by alert names",
25 "Array of specific alert names to filter by. "
26 "Each alert name must be an exact match - no wildcards or patterns allowed. "
27 "Use '" MCP_TOOL_LIST_ALL_ALERTS "' to discover available alert names. "
28 "If not specified, all alerts are included. "
29 "Examples: [\"disk_space_usage\", \"cpu_iowait\", \"ram_in_use\"]");
30
31 // Nodes filter
32 mcp_schema_add_array_param(
33 buffer, "nodes", "Filter nodes",
34 "Show only alerts transitions for these nodes.\n"
35 "Use 'list_nodes' to discover available nodes.\n"
36 "If not specified, alerts transitions from all nodes are included. "
37 "Examples: [\"node1\", \"node2\"], [\"web-server-01\", \"db-server-01\"]");
38
39 mcp_schema_add_array_param(
40 buffer, "metrics",
41 "Filter by metrics",
42 "Array of specific metric names to filter by. "
43 "Each metric must be an exact match - no wildcards or patterns allowed. "
44 "Use '" MCP_TOOL_LIST_METRICS "' to discover available metrics. "
45 "If not specified, all metrics are included. "
46 "Examples: [\"system.cpu\", \"system.load\"], [\"disk.io\", \"disk.space\"]");
47
48 mcp_schema_add_array_param(
49 buffer, "instances",
50 "Filter by instances",
51 "Query only the given instances.\n"
52 "Use the '" MCP_TOOL_GET_METRICS_DETAILS "' tool to discover available instances for a metric.\n"
53 "If no instances are specified, all instances of the metric are queried.\n"
54 "Example: [\"instance1\", \"instance2\", \"instance3\"]\n."
55 "IMPORTANT: when you have a choice, prefer to filter by labels instead of instances, because many monitored "
56 "components may change instance names over time.");
57
58 // Status filter (required multi-select enum)
59 buffer_json_member_add_object(buffer, "status");
60 {
61 buffer_json_member_add_string(buffer, "type", "array");
62 buffer_json_member_add_string(buffer, "title", "Filter by status");
63 buffer_json_member_add_string(
64 buffer, "description",
65 "Select the alert statuses of interest. At least one status must be selected.\n"
66 " - CRITICAL: the highest severity, indicates a critical issue that needs immediate attention.\n"
67 " - WARNING: indicates a potential issue that should be monitored but is not critical.\n"
68 " - CLEAR: the normal state state for alerts, indicating that the alert is not triggered.\n"
69 " - UNDEFINED: the alerts failed to be evaluated (some variable of it is undefined, division by zero, etc).\n"
70 " - UNINITIALIZED: the alert has not been initialized for the first time yet, no data available.\n"
71 " - REMOVED: the alert was removed (happens during netdata shutdown, child disconnect, health reload).\n"
72 "Multiple statuses can be selected. Example: [\"CRITICAL\", \"WARNING\"]");
73
74 // Define items schema with enum values
75 buffer_json_member_add_object(buffer, "items");
76 {
77 buffer_json_member_add_string(buffer, "type", "string");
78 buffer_json_member_add_array(buffer, "enum");
79 buffer_json_add_array_item_string(buffer, "CRITICAL");
80 buffer_json_add_array_item_string(buffer, "WARNING");
81 buffer_json_add_array_item_string(buffer, "CLEAR");
82 buffer_json_add_array_item_string(buffer, "UNDEFINED");
83 buffer_json_add_array_item_string(buffer, "UNINITIALIZED");
84 buffer_json_add_array_item_string(buffer, "REMOVED");
85 buffer_json_array_close(buffer);
86 }
87 buffer_json_object_close(buffer); // items
88 }
89 buffer_json_object_close(buffer); // status
90
91 mcp_schema_add_array_param(
92 buffer, "classifications",
93 "Filter by classifications",
94 "Array of specific alert classifications to filter by. "
95 "Each classification must be an exact match - no wildcards or patterns allowed. "
96 "Use '" MCP_TOOL_LIST_ALL_ALERTS "' to discover available classifications. "
97 "If not specified, all classifications are included. "
98 "Examples: [\"Errors\", \"Latency\", \"Utilization\"]");
99
100 mcp_schema_add_array_param(
101 buffer, "types",
102 "Filter by types",
103 "Array of specific alert types to filter by. "
104 "Each type must be an exact match - no wildcards or patterns allowed. "
105 "Use '" MCP_TOOL_LIST_ALL_ALERTS "' to discover available types. "
106 "If not specified, all types are included. "
107 "Examples: [\"System\", \"Web Server\", \"Database\"]");
108
109 mcp_schema_add_array_param(
110 buffer, "components",
111 "Filter by components",
112 "Array of specific components to filter by. "
113 "Each component must be an exact match - no wildcards or patterns allowed. "
114 "Use '" MCP_TOOL_LIST_ALL_ALERTS "' to discover available components. "
115 "If not specified, all components are included. "
116 "Examples: [\"Network\", \"Disk\", \"Memory\"]");
117
118 mcp_schema_add_array_param(
119 buffer, "roles",
120 "Filter by roles",
121 "Array of specific roles to filter by. "
122 "Each role must be an exact match - no wildcards or patterns allowed. "
123 "Use '" MCP_TOOL_LIST_ALL_ALERTS "' to discover available roles. "
124 "If not specified, all roles are included. "
125 "Examples: [\"sysadmin\", \"webmaster\", \"dba\"]");
126
127 // Cardinality limit
128 mcp_schema_add_cardinality_limit(
129 buffer,
130 "Number of most recent alert transitions to return",
131 MCP_ALERTS_CARDINALITY_LIMIT, // default value
132 1, // minimum
133 MAX(MCP_ALERTS_CARDINALITY_LIMIT, MCP_ALERTS_CARDINALITY_LIMIT_MAX)); // max value
134
135 // Pagination cursor
136 mcp_schema_add_string_param(buffer, "cursor",
137 "Pagination cursor",
138 "Pagination cursor from previous response. Use the 'nextCursor' value from the previous response to get the next page of results.",
139 NULL, false);
140
141 // Timeout parameter
142 mcp_schema_add_timeout(buffer, "timeout",
143 "Query timeout",
144 "Maximum time to wait for the query to complete (in seconds)",
145 60, 1, 3600, false);
146
147 buffer_json_object_close(buffer); // properties
148
149 // Required fields
150 buffer_json_member_add_array(buffer, "required");
151 buffer_json_add_array_item_string(buffer, "status");
152 buffer_json_array_close(buffer);
153
154 buffer_json_object_close(buffer); // inputSchema
155 }
156
157 // Execute alert transitions query
158 MCP_RETURN_CODE mcp_tool_list_alert_transitions_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id __maybe_unused) {
159 if (!mcpc)
160 return MCP_RC_ERROR;
161
162 // Extract nodes array
163 const char *nodes_pattern = NULL;
164 CLEAN_BUFFER *nodes_buffer = NULL;
165
166 nodes_buffer = mcp_params_parse_array_to_pattern(params, "nodes", false, false, MCP_TOOL_LIST_NODES, mcpc->error);
167 if (buffer_strlen(mcpc->error) > 0) {
168 return MCP_RC_BAD_REQUEST;
169 }
170 if (nodes_buffer)
171 nodes_pattern = buffer_tostring(nodes_buffer);
172
173 // Extract time parameters
174 time_t after, before;
175 if (!mcp_params_parse_time_window(params, &after, &before,
176 MCP_DEFAULT_AFTER_TIME, MCP_DEFAULT_BEFORE_TIME,
177 false, mcpc->error)) {
178 return MCP_RC_BAD_REQUEST;
179 }
180
181 // Extract cardinality limit
182 size_t cardinality_limit = mcp_params_extract_size(params, "cardinality_limit", 1, 1, 100, mcpc->error);
183 if (buffer_strlen(mcpc->error) > 0) {
184 return MCP_RC_BAD_REQUEST;
185 }
186
187 // Extract pagination cursor (global_id_anchor)
188 usec_t global_id_anchor = 0;
189 const char *cursor = mcp_params_extract_string(params, "cursor", NULL);
190 if (cursor) {
191 // Parse cursor as usec_t
192 char *endptr;
193 unsigned long long value = strtoull(cursor, &endptr, 10);
194 if (*endptr != '\0' || endptr == cursor) {
195 buffer_sprintf(mcpc->error, "Invalid cursor value");
196 return MCP_RC_BAD_REQUEST;
197 }
198 global_id_anchor = (usec_t)value;
199 }
200
201 // Extract status array (required parameter)
202 CLEAN_BUFFER *status_buffer = NULL;
203 const char *status_pattern = NULL;
204
205 status_buffer = mcp_params_parse_array_to_pattern(params, "status", true, false, NULL, mcpc->error);
206 if (buffer_strlen(mcpc->error) > 0) {
207 buffer_strcat(mcpc->error, ". You must select at least one alert status to filter by.");
208 return MCP_RC_BAD_REQUEST;
209 }
210 if (status_buffer)
211 status_pattern = buffer_tostring(status_buffer);
212
213 // Extract instances array
214 const char *instances_pattern = NULL;
215 CLEAN_BUFFER *instances_buffer = NULL;
216
217 instances_buffer = mcp_params_parse_array_to_pattern(params, "instances", false, false, NULL, mcpc->error);
218 if (buffer_strlen(mcpc->error) > 0) {
219 return MCP_RC_BAD_REQUEST;
220 }
221 if (instances_buffer)
222 instances_pattern = buffer_tostring(instances_buffer);
223
224 // Extract metrics array
225 const char *metrics_pattern = NULL;
226 CLEAN_BUFFER *metrics_buffer = NULL;
227
228 metrics_buffer = mcp_params_parse_array_to_pattern(params, "metrics", false, false, MCP_TOOL_LIST_METRICS, mcpc->error);
229 if (buffer_strlen(mcpc->error) > 0) {
230 return MCP_RC_BAD_REQUEST;
231 }
232 if (metrics_buffer)
233 metrics_pattern = buffer_tostring(metrics_buffer);
234
235 // Extract alerts array
236 const char *alerts_pattern = NULL;
237 CLEAN_BUFFER *alerts_buffer = NULL;
238
239 alerts_buffer = mcp_params_parse_array_to_pattern(params, "alerts", false, false, MCP_TOOL_LIST_ALL_ALERTS, mcpc->error);
240 if (buffer_strlen(mcpc->error) > 0) {
241 return MCP_RC_BAD_REQUEST;
242 }
243 if (alerts_buffer)
244 alerts_pattern = buffer_tostring(alerts_buffer);
245
246 // Extract classifications array
247 const char *classifications_pattern = NULL;
248 CLEAN_BUFFER *classifications_buffer = NULL;
249
250 classifications_buffer = mcp_params_parse_array_to_pattern(params, "classifications", false, false, MCP_TOOL_LIST_ALL_ALERTS, mcpc->error);
251 if (buffer_strlen(mcpc->error) > 0) {
252 return MCP_RC_BAD_REQUEST;
253 }
254 if (classifications_buffer)
255 classifications_pattern = buffer_tostring(classifications_buffer);
256
257 // Extract types array
258 const char *types_pattern = NULL;
259 CLEAN_BUFFER *types_buffer = NULL;
260
261 types_buffer = mcp_params_parse_array_to_pattern(params, "types", false, false, MCP_TOOL_LIST_ALL_ALERTS, mcpc->error);
262 if (buffer_strlen(mcpc->error) > 0) {
263 return MCP_RC_BAD_REQUEST;
264 }
265 if (types_buffer)
266 types_pattern = buffer_tostring(types_buffer);
267
268 // Extract components array
269 const char *components_pattern = NULL;
270 CLEAN_BUFFER *components_buffer = NULL;
271
272 components_buffer = mcp_params_parse_array_to_pattern(params, "components", false, false, MCP_TOOL_LIST_ALL_ALERTS, mcpc->error);
273 if (buffer_strlen(mcpc->error) > 0) {
274 return MCP_RC_BAD_REQUEST;
275 }
276 if (components_buffer)
277 components_pattern = buffer_tostring(components_buffer);
278
279 // Extract roles array
280 const char *roles_pattern = NULL;
281 CLEAN_BUFFER *roles_buffer = NULL;
282
283 roles_buffer = mcp_params_parse_array_to_pattern(params, "roles", false, false, MCP_TOOL_LIST_ALL_ALERTS, mcpc->error);
284 if (buffer_strlen(mcpc->error) > 0) {
285 return MCP_RC_BAD_REQUEST;
286 }
287 if (roles_buffer)
288 roles_pattern = buffer_tostring(roles_buffer);
289
290 // Extract timeout parameter
291 int timeout = mcp_params_extract_timeout(params, "timeout", 60, 1, 3600, mcpc->error);
292 if (buffer_strlen(mcpc->error) > 0) {
293 return MCP_RC_BAD_REQUEST;
294 }
295
296 // Create request structure
297 struct api_v2_contexts_request req = {
298 .scope_nodes = nodes_pattern,
299 .scope_contexts = NULL, // Not used for alert transitions
300 .nodes = NULL,
301 .contexts = NULL,
302 .after = after,
303 .before = before,
304 .timeout_ms = timeout * 1000, // Convert seconds to milliseconds
305 .options = CONTEXTS_OPTION_CONFIGURATIONS | CONTEXTS_OPTION_MCP | CONTEXTS_OPTION_RFC3339 | CONTEXTS_OPTION_JSON_LONG_KEYS | CONTEXTS_OPTION_MINIFY,
306 .cardinality_limit = cardinality_limit,
307 .alerts = {
308 .last = cardinality_limit,
309 .global_id_anchor = global_id_anchor,
310 .facets = {
311 [ATF_STATUS] = status_pattern,
312 [ATF_CLASS] = classifications_pattern,
313 [ATF_TYPE] = types_pattern,
314 [ATF_COMPONENT] = components_pattern,
315 [ATF_ROLE] = roles_pattern,
316 [ATF_NODE] = NULL,
317 [ATF_ALERT_NAME] = alerts_pattern,
318 [ATF_CHART_NAME] = instances_pattern,
319 [ATF_CONTEXT] = metrics_pattern,
320 },
321 }
322 };
323
324 // Execute the query
325 CONTEXTS_V2_MODE mode = CONTEXTS_V2_NODES | CONTEXTS_V2_ALERT_TRANSITIONS;
326 CLEAN_BUFFER *t = buffer_create(0, NULL);
327 int response = rrdcontext_to_json_v2(t, &req, mode);
328
329 if (response != HTTP_RESP_OK) {
330 buffer_sprintf(mcpc->error, "Query failed with response code %d", response);
331 return MCP_RC_ERROR;
332 }
333
334 // Initialize success response
335 mcp_init_success_result(mcpc, id);
336
337 // Start building a content-array for the result
338 buffer_json_member_add_array(mcpc->result, "content");
339 {
340 // Return text content for LLM compatibility
341 buffer_json_add_array_item_object(mcpc->result);
342 {
343 buffer_json_member_add_string(mcpc->result, "type", "text");
344 buffer_json_member_add_string(mcpc->result, "text", buffer_tostring(t));
345 }
346 buffer_json_object_close(mcpc->result); // Close text content
347 }
348 buffer_json_array_close(mcpc->result); // Close content array
349 buffer_json_object_close(mcpc->result); // Close result object
350 buffer_json_finalize(mcpc->result); // Finalize the JSON
351
352 return MCP_RC_OK;
353 }