master
c 376 lines 16.8 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 /**
4 * MCP Tools Namespace
5 *
6 * The MCP Tools namespace provides methods for discovering and executing tools offered by the server.
7 * In the MCP protocol, tools are discrete operations that clients can invoke to perform specific actions.
8 *
9 * Tools are model-controlled actions - meaning the AI decides when and how to use them based on context.
10 * Each tool has a defined input schema that specifies required and optional parameters.
11 *
12 * Standard methods in the MCP specification:
13 *
14 * 1. tools/list - Lists available tools with their schemas and metadata
15 * - Returns a list of all available tools
16 * - Includes each tool's name, description, and input schema
17 *
18 * 2. tools/call - Executes a specific tool with provided parameters
19 * - Takes a tool name and arguments
20 * - Returns the result of the tool execution
21 * - May include resource references, text, images, or other content types
22 *
23 * In the Netdata context, tools provide access to operations like:
24 * - Exploring metrics and their relationships
25 * - Analyzing time-series data patterns
26 * - Finding correlations between metrics
27 * - Root cause analysis for anomalies
28 * - Summarizing system health
29 */
30
31 #include "mcp-tools.h"
32
33 // Include tool-specific header files
34 #include "mcp-tools-list-metadata.h"
35 #include "mcp-tools-execute-function.h"
36 #include "mcp-tools-query-metrics.h"
37 #include "mcp-tools-weights.h"
38 #include "mcp-tools-alert-transitions.h"
39 #include "mcp-tools-configured-alerts.h"
40
41 // Tool handler function prototypes
42 typedef MCP_RETURN_CODE (*mcp_tool_execute_t)(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id);
43 typedef void (*mcp_tool_schema_t)(BUFFER *schema_buffer);
44
45 // Tool definition structure
46 typedef struct {
47 const char *name; // Tool name
48 const char *description; // Tool description
49 mcp_tool_execute_t execute_callback; // Tool execution callback
50 mcp_tool_schema_t schema_callback; // Tool schema definition callback
51
52 // UI/UX annotations
53 const char *title; // Human-readable title
54 bool read_only_hint; // If true, the tool doesn't modify a state
55 bool open_world_hint; // If true, the tool interacts with the external world
56 } MCP_TOOL_DEF;
57
58 // Wrapper functions for unified list tools
59 static MCP_RETURN_CODE unified_list_tool_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id, const char *tool_name) {
60 const MCP_LIST_TOOL_CONFIG *config = mcp_get_list_tool_config(tool_name);
61 if (!config) {
62 buffer_sprintf(mcpc->error, "Unknown list tool: %s", tool_name);
63 return MCP_RC_INTERNAL_ERROR;
64 }
65 return mcp_unified_list_tool_execute(mcpc, config, params, id);
66 }
67
68 static void unified_list_tool_schema(BUFFER *buffer, const char *tool_name) {
69 const MCP_LIST_TOOL_CONFIG *config = mcp_get_list_tool_config(tool_name);
70 if (!config) return;
71 mcp_unified_list_tool_schema(buffer, config);
72 }
73
74 // Specific wrappers for each list tool
75 static MCP_RETURN_CODE list_metrics_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
76 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_LIST_METRICS);
77 }
78 static void list_metrics_schema(BUFFER *buffer) {
79 unified_list_tool_schema(buffer, MCP_TOOL_LIST_METRICS);
80 }
81
82 static MCP_RETURN_CODE get_metrics_details_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
83 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_GET_METRICS_DETAILS);
84 }
85 static void get_metrics_details_schema(BUFFER *buffer) {
86 unified_list_tool_schema(buffer, MCP_TOOL_GET_METRICS_DETAILS);
87 }
88
89 static MCP_RETURN_CODE list_nodes_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
90 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_LIST_NODES);
91 }
92 static void list_nodes_schema(BUFFER *buffer) {
93 unified_list_tool_schema(buffer, MCP_TOOL_LIST_NODES);
94 }
95
96 static MCP_RETURN_CODE list_functions_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
97 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_LIST_FUNCTIONS);
98 }
99 static void list_functions_schema(BUFFER *buffer) {
100 unified_list_tool_schema(buffer, MCP_TOOL_LIST_FUNCTIONS);
101 }
102
103 static MCP_RETURN_CODE get_nodes_details_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
104 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_GET_NODES_DETAILS);
105 }
106 static void get_nodes_details_schema(BUFFER *buffer) {
107 unified_list_tool_schema(buffer, MCP_TOOL_GET_NODES_DETAILS);
108 }
109
110 static MCP_RETURN_CODE list_raised_alerts_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
111 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_LIST_RAISED_ALERTS);
112 }
113 static void list_raised_alerts_schema(BUFFER *buffer) {
114 unified_list_tool_schema(buffer, MCP_TOOL_LIST_RAISED_ALERTS);
115 }
116
117 static MCP_RETURN_CODE list_all_alerts_execute(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id) {
118 return unified_list_tool_execute(mcpc, params, id, MCP_TOOL_LIST_ALL_ALERTS);
119 }
120 static void list_all_alerts_schema(BUFFER *buffer) {
121 unified_list_tool_schema(buffer, MCP_TOOL_LIST_ALL_ALERTS);
122 }
123
124
125 // Static array of tool definitions
126 static const MCP_TOOL_DEF mcp_tools[] = {
127 // List tools (using unified implementation)
128 {
129 .name = MCP_TOOL_LIST_METRICS,
130 .title = "Discover available metrics",
131 .description = "Lists available metrics (contexts) with time-aware filtering. Returns metric names matching search patterns, filtered by nodes and time window. Supports full-text search across names, titles, instances, dimensions, and labels.\n",
132 .execute_callback = list_metrics_execute,
133 .schema_callback = list_metrics_schema,
134 .read_only_hint = true,
135 .open_world_hint = false
136 },
137 {
138 .name = MCP_TOOL_GET_METRICS_DETAILS,
139 .title = "Get detailed information about specific metrics",
140 .description = "Gets comprehensive metadata for specific metrics. Returns titles, units, dimensions, instances, labels, and collection status.\n",
141 .execute_callback = get_metrics_details_execute,
142 .schema_callback = get_metrics_details_schema,
143 .read_only_hint = true,
144 .open_world_hint = false
145 },
146 {
147 .name = MCP_TOOL_LIST_NODES,
148 .title = "List all monitored nodes in the Netdata ecosystem",
149 .description = "Lists all monitored nodes in the infrastructure. Returns node IDs, hostnames, connection status, and parent-child relationships. Use this to discover available nodes before querying metrics or executing functions.\n",
150 .execute_callback = list_nodes_execute,
151 .schema_callback = list_nodes_schema,
152 .read_only_hint = true,
153 .open_world_hint = false
154 },
155 {
156 .name = MCP_TOOL_LIST_FUNCTIONS,
157 .title = "List available functions",
158 .description = "Lists all available Netdata functions that can be executed on nodes. Returns function names, descriptions, and execution requirements. Use this to discover what functions are available before executing them.\n",
159 .execute_callback = list_functions_execute,
160 .schema_callback = list_functions_schema,
161 .read_only_hint = true,
162 .open_world_hint = false
163 },
164 {
165 .name = MCP_TOOL_GET_NODES_DETAILS,
166 .title = "Get detailed information about monitored nodes",
167 .description = "Gets comprehensive node information including hardware specs, OS details, capabilities, health status, available functions, and monitoring configuration. Essential for understanding node capabilities before executing functions.\n",
168 .execute_callback = get_nodes_details_execute,
169 .schema_callback = get_nodes_details_schema,
170 .read_only_hint = true,
171 .open_world_hint = false
172 },
173
174 // Non-list tools (keep their original implementation)
175 {
176 .name = MCP_TOOL_EXECUTE_FUNCTION,
177 .title = "Execute a function on a specific node",
178 .description = "Executes live data collection functions on nodes. Common functions: 'processes' (running processes), 'network-connections' (active connections), 'mount-points' (disk mounts), 'systemd-services' (service status). Returns tabular data with filtering and sorting options.\n",
179 .execute_callback = mcp_tool_execute_function_execute,
180 .schema_callback = mcp_tool_execute_function_schema,
181 .read_only_hint = true, // Currently read-only (will change when dynamic config is added)
182 .open_world_hint = true // Routes requests to remote nodes in the Netdata ecosystem
183 },
184 {
185 .name = MCP_TOOL_QUERY_METRICS,
186 .title = "Query metrics data",
187 .description = "Queries time-series metrics data with powerful aggregation options. Specify context, time range, and grouping (by dimension, instance, node, or label). Returns data points with statistics and contribution analysis.\n",
188 .execute_callback = mcp_tool_query_metrics_execute,
189 .schema_callback = mcp_tool_query_metrics_schema,
190 .read_only_hint = true,
191 .open_world_hint = false
192 },
193
194 // Weights/correlation tools
195 {
196 .name = MCP_TOOL_FIND_CORRELATED_METRICS,
197 .title = "Find metrics that changed during an incident",
198 .description = "Finds metrics that changed significantly during an incident by comparing a problem time period with a normal baseline period. Essential for root cause analysis. IMPORTANT: For large infrastructures, use filters (metrics, nodes, instances, dimensions, or labels) to narrow the scope and avoid timeouts.",
199 .execute_callback = mcp_tool_find_correlated_metrics_execute,
200 .schema_callback = mcp_tool_find_correlated_metrics_schema,
201 .read_only_hint = true,
202 .open_world_hint = false
203 },
204 {
205 .name = MCP_TOOL_FIND_ANOMALOUS_METRICS,
206 .title = "Find metrics with highest anomaly rates",
207 .description = "Finds metrics that were behaving anomalously according to Netdata's ML models. Returns metrics ranked by their anomaly rates (0 to 1, representing 0-100% of time anomalous). IMPORTANT: For large infrastructures, use filters (metrics, nodes, instances, dimensions, or labels) to narrow the scope and avoid timeouts.",
208 .execute_callback = mcp_tool_find_anomalous_metrics_execute,
209 .schema_callback = mcp_tool_find_anomalous_metrics_schema,
210 .read_only_hint = true,
211 .open_world_hint = false
212 },
213 {
214 .name = MCP_TOOL_FIND_UNSTABLE_METRICS,
215 .title = "Find metrics with high variability",
216 .description = "Finds metrics with the highest variability using coefficient of variation (standard deviation as % of mean). Useful for identifying unstable or fluctuating metrics. IMPORTANT: For large infrastructures, use filters (metrics, nodes, instances, dimensions, or labels) to narrow the scope and avoid timeouts.",
217 .execute_callback = mcp_tool_find_unstable_metrics_execute,
218 .schema_callback = mcp_tool_find_unstable_metrics_schema,
219 .read_only_hint = true,
220 .open_world_hint = false
221 },
222
223 // Alert tools
224 {
225 .name = MCP_TOOL_LIST_RAISED_ALERTS,
226 .title = "List raised alerts",
227 .description = "List currently active alerts (WARNING and CRITICAL status) across all nodes",
228 .execute_callback = list_raised_alerts_execute,
229 .schema_callback = list_raised_alerts_schema,
230 .read_only_hint = true,
231 .open_world_hint = false
232 },
233 {
234 .name = MCP_TOOL_LIST_ALL_ALERTS,
235 .title = "List all alerts",
236 .description = "List all alerts including cleared, undefined, and uninitialized alerts across all nodes",
237 .execute_callback = list_all_alerts_execute,
238 .schema_callback = list_all_alerts_schema,
239 .read_only_hint = true,
240 .open_world_hint = false
241 },
242 {
243 .name = MCP_TOOL_LIST_ALERT_TRANSITIONS,
244 .title = "List alert transitions",
245 .description = "List recent alert state transitions showing how alerts changed over time",
246 .execute_callback = mcp_tool_list_alert_transitions_execute,
247 .schema_callback = mcp_tool_list_alert_transitions_schema,
248 .read_only_hint = true,
249 .open_world_hint = false
250 },
251
252 // Commented for the moment - probably dyncfg is a better way to do this
253 // {
254 // .name = MCP_TOOL_LIST_CONFIGURED_ALERTS,
255 // .title = "List configured alert prototypes",
256 // .description = "Lists all configured alert prototypes (templates) that define how alerts are created. Shows matching criteria, conditions, thresholds, and actions. Useful for understanding what Netdata is configured to monitor.",
257 // .execute_callback = mcp_tool_list_configured_alerts_execute,
258 // .schema_callback = mcp_tool_list_configured_alerts_schema,
259 // .read_only_hint = true,
260 // .open_world_hint = false
261 // },
262
263 // Add more tools here
264
265 // Terminator
266 {
267 .name = NULL
268 }
269 };
270
271 // Return a list of available tools
272 static MCP_RETURN_CODE mcp_tools_method_list(MCP_CLIENT *mcpc, struct json_object *params __maybe_unused, MCP_REQUEST_ID id __maybe_unused) {
273 if (!mcpc)
274 return MCP_RC_ERROR;
275
276 // Initialize success response
277 mcp_init_success_result(mcpc, id);
278
279 // Create tool-array
280 buffer_json_member_add_array(mcpc->result, "tools");
281
282 // Iterate through all defined tools and add them to the response
283 for (size_t i = 0; mcp_tools[i].name != NULL; i++) {
284 const MCP_TOOL_DEF *tool = &mcp_tools[i];
285
286 // Add a tool object
287 buffer_json_add_array_item_object(mcpc->result);
288
289 // Add basic properties
290 buffer_json_member_add_string(mcpc->result, "name", tool->name);
291 buffer_json_member_add_string(mcpc->result, "description", tool->description);
292
293 // Add schema using the tool's schema callback
294 tool->schema_callback(mcpc->result);
295
296 // Add annotations if available
297 buffer_json_member_add_object(mcpc->result, "annotations");
298 if (tool->title) {
299 buffer_json_member_add_string(mcpc->result, "title", tool->title);
300 }
301 buffer_json_member_add_boolean(mcpc->result, "readOnlyHint", tool->read_only_hint);
302 buffer_json_member_add_boolean(mcpc->result, "openWorldHint", tool->open_world_hint);
303 buffer_json_object_close(mcpc->result); // Close annotations
304
305 buffer_json_object_close(mcpc->result); // Close tool object
306 }
307
308 buffer_json_array_close(mcpc->result); // Close tools array
309 buffer_json_finalize(mcpc->result); // Finalize the JSON
310
311 return MCP_RC_OK;
312 }
313
314 // Main execute method that routes to specific tool handlers
315 static MCP_RETURN_CODE mcp_tools_method_call(MCP_CLIENT *mcpc, struct json_object *params, MCP_REQUEST_ID id __maybe_unused) {
316 if (!mcpc || !params)
317 return MCP_RC_ERROR;
318
319 // Extract tool name
320 struct json_object *name_obj = NULL;
321 if (!json_object_object_get_ex(params, "name", &name_obj)) {
322 buffer_sprintf(mcpc->error, "Missing required parameter 'name'");
323 return MCP_RC_BAD_REQUEST;
324 }
325
326 if (!json_object_is_type(name_obj, json_type_string)) {
327 buffer_sprintf(mcpc->error, "Parameter 'name' must be a string");
328 return MCP_RC_BAD_REQUEST;
329 }
330
331 const char *tool_name = json_object_get_string(name_obj);
332
333 // Get arguments if present
334 struct json_object *args_obj = NULL;
335 json_object_object_get_ex(params, "arguments", &args_obj);
336
337 // Search for the tool in our static array
338 for (size_t i = 0; mcp_tools[i].name != NULL; i++) {
339 if (strcmp(tool_name, mcp_tools[i].name) == 0) {
340 // Found the tool, execute its callback
341 return mcp_tools[i].execute_callback(mcpc, args_obj, id);
342 }
343 }
344
345 // Tool not found
346 buffer_sprintf(mcpc->error, "Unknown tool: %s", tool_name);
347 return MCP_RC_BAD_REQUEST;
348 }
349
350 // The MCP specification only defines list and call methods for tools
351 // Other methods are not part of the standard specification
352
353 // Tools namespace method dispatcher (transport-agnostic)
354 MCP_RETURN_CODE mcp_tools_route(MCP_CLIENT *mcpc, const char *method, struct json_object *params, MCP_REQUEST_ID id) {
355 if (!mcpc || !method) return MCP_RC_INTERNAL_ERROR;
356
357 netdata_log_debug(D_MCP, "MCP tools method: %s", method);
358
359 MCP_RETURN_CODE rc;
360
361 if (strcmp(method, "list") == 0) {
362 // List available tools - standard method in MCP specification
363 rc = mcp_tools_method_list(mcpc, params, id);
364 }
365 else if (strcmp(method, "call") == 0) {
366 // Execute a tool; standard method in MCP specification
367 rc = mcp_tools_method_call(mcpc, params, id);
368 }
369 else {
370 // Method not found in tools namespace
371 buffer_sprintf(mcpc->error, "Method 'tools/%s' not supported. The MCP specification only defines 'list' and 'call' methods.", method);
372 rc = MCP_RC_NOT_IMPLEMENTED;
373 }
374
375 return rc;
376 }