mcp-instana 0.1.0__py3-none-any.whl

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,624 @@
1
+ """
2
+ Infrastructure Catalog MCP Tools Module
3
+
4
+ This module provides infrastructure catalog-specific MCP tools for Instana monitoring.
5
+ """
6
+
7
+ import sys
8
+ import traceback
9
+ from typing import Dict, Any, Optional, List, Union
10
+ from datetime import datetime
11
+
12
+ # Import the necessary classes from the SDK
13
+ try:
14
+ from instana_client.api.infrastructure_catalog_api import InfrastructureCatalogApi
15
+ from instana_client.api_client import ApiClient
16
+ from instana_client.configuration import Configuration
17
+ except ImportError as e:
18
+ print(f"Error importing Instana SDK: {e}", file=sys.stderr)
19
+ traceback.print_exc(file=sys.stderr)
20
+ raise
21
+
22
+ from .instana_client_base import BaseInstanaClient, register_as_tool
23
+
24
+ # Helper function for debug printing
25
+ def debug_print(*args, **kwargs):
26
+ """Print debug information to stderr instead of stdout"""
27
+ print(*args, file=sys.stderr, **kwargs)
28
+
29
+ class InfrastructureCatalogMCPTools(BaseInstanaClient):
30
+ """Tools for infrastructure catalog in Instana MCP."""
31
+
32
+ def __init__(self, read_token: str, base_url: str):
33
+ """Initialize the Infrastructure Catalog MCP tools client."""
34
+ super().__init__(read_token=read_token, base_url=base_url)
35
+
36
+ try:
37
+
38
+ # Configure the API client with the correct base URL and authentication
39
+ configuration = Configuration()
40
+ configuration.host = base_url
41
+ configuration.api_key['ApiKeyAuth'] = read_token
42
+ configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
43
+
44
+ # Create an API client with this configuration
45
+ api_client = ApiClient(configuration=configuration)
46
+
47
+ # Initialize the Instana SDK's InfrastructureCatalogApi with our configured client
48
+ self.catalog_api = InfrastructureCatalogApi(api_client=api_client)
49
+ except Exception as e:
50
+ debug_print(f"Error initializing InfrastructureCatalogApi: {e}")
51
+ traceback.print_exc(file=sys.stderr)
52
+ raise
53
+
54
+ @register_as_tool
55
+ async def get_available_payload_keys_by_plugin_id(self,
56
+ plugin_id: str,
57
+ ctx=None) -> Dict[str, Any]:
58
+ """
59
+ Get available payload keys for a specific plugin in Instana. This tool retrieves the list of payload keys that can be used to access detailed monitoring data
60
+ for a particular plugin type. Use this when you need to understand what data is available for a specific entity type, want to explore the monitoring capabilities
61
+ for a plugin, or need to find the correct payload key for accessing specific metrics or configuration data. This is particularly useful for preparing detailed
62
+ queries, understanding available monitoring data structures, or when building custom dashboards or integrations. For example, use this tool when asked about
63
+ 'what data is available for Java processes', 'payload keys for Kubernetes', 'what metrics can I access for MySQL', or when someone wants to
64
+ 'find out what monitoring data is collected for a specific technology'.
65
+
66
+ Args:
67
+ plugin_id: The ID of the plugin to get payload keys for
68
+ ctx: The MCP context (optional)
69
+
70
+ Returns:
71
+ Dictionary containing payload keys or error information
72
+ """
73
+ try:
74
+ debug_print(f"get_available_payload_keys_by_plugin_id called with plugin_id={plugin_id}")
75
+
76
+ if not plugin_id:
77
+ return {"error": "plugin_id parameter is required"}
78
+
79
+ # Try using the standard SDK method
80
+ try:
81
+ # Call the get_available_payload_keys_by_plugin_id method from the SDK
82
+ result = self.catalog_api.get_available_payload_keys_by_plugin_id(
83
+ plugin_id=plugin_id
84
+ )
85
+
86
+ # Convert the result to a dictionary
87
+ if hasattr(result, 'to_dict'):
88
+ result_dict = result.to_dict()
89
+ elif isinstance(result, dict):
90
+ result_dict = result
91
+ elif isinstance(result, list):
92
+ result_dict = [item.to_dict() if hasattr(item, 'to_dict') else item for item in result]
93
+ elif isinstance(result, str):
94
+ # Handle string response (special case for some plugins like db2Database)
95
+ debug_print(f"Received string response for plugin_id={plugin_id}: {result}")
96
+ result_dict = {"message": result, "plugin_id": plugin_id}
97
+ else:
98
+ # For any other type, convert to string representation
99
+ result_dict = {"data": str(result), "plugin_id": plugin_id}
100
+
101
+ debug_print(f"Result from get_available_payload_keys_by_plugin_id: {result_dict}")
102
+ return result_dict
103
+
104
+ except Exception as sdk_error:
105
+ debug_print(f"SDK method failed: {sdk_error}, trying fallback")
106
+
107
+ # Use the without_preload_content version to get the raw response
108
+ try:
109
+ response_data = self.catalog_api.get_available_payload_keys_by_plugin_id_without_preload_content(
110
+ plugin_id=plugin_id
111
+ )
112
+
113
+ # Check if the response was successful
114
+ if response_data.status != 200:
115
+ error_message = f"Failed to get payload keys: HTTP {response_data.status}"
116
+ debug_print(error_message)
117
+ return {"error": error_message}
118
+
119
+ # Read the response content
120
+ response_text = response_data.data.decode('utf-8')
121
+
122
+ # Try to parse as JSON first
123
+ import json
124
+ try:
125
+ result_dict = json.loads(response_text)
126
+ debug_print(f"Result from fallback method (JSON): {result_dict}")
127
+ return result_dict
128
+ except json.JSONDecodeError:
129
+ # If not valid JSON, return as string
130
+ debug_print(f"Result from fallback method (string): {response_text}")
131
+ return {"message": response_text, "plugin_id": plugin_id}
132
+
133
+ except Exception as fallback_error:
134
+ debug_print(f"Fallback method failed: {fallback_error}")
135
+ raise
136
+
137
+ except Exception as e:
138
+ debug_print(f"Error in get_available_payload_keys_by_plugin_id: {e}")
139
+ traceback.print_exc(file=sys.stderr)
140
+ return {"error": f"Failed to get payload keys: {str(e)}", "plugin_id": plugin_id}
141
+
142
+
143
+ @register_as_tool
144
+ async def get_infrastructure_catalog_metrics(self,
145
+ plugin: str,
146
+ filter: Optional[str] = None,
147
+ ctx=None) -> List[str]:
148
+ """
149
+ Get metric catalog for a specific plugin in Instana. This tool retrieves all available metric definitions for a requested plugin type.
150
+ Use this when you need to understand what metrics are available for a specific technology, want to explore the monitoring capabilities for a plugin,
151
+ or need to find the correct metric names for queries or dashboards. This is particularly useful for building custom dashboards, setting up alerts based on specific metrics,
152
+ or understanding the monitoring depth for a particular technology. For example, use this tool when asked about 'what metrics are available for hosts',
153
+ 'JVM metrics catalog', 'available metrics for Kubernetes', or when someone wants to 'see all metrics for a database'.
154
+
155
+ Returns the first 50 metrics to keep the response manageable.
156
+
157
+ Args:
158
+ plugin: The plugin ID to get metrics for
159
+ filter: Filter to restrict returned metric definitions ('custom' or 'builtin')
160
+ ctx: The MCP context (optional)
161
+
162
+ Returns:
163
+ List of metric names (strings) - limited to first 50 metrics
164
+ """
165
+ try:
166
+ debug_print(f"get_infrastructure_catalog_metrics called with plugin={plugin}, filter={filter}")
167
+
168
+ if not plugin:
169
+ return ["Error: plugin parameter is required"]
170
+
171
+ # Call the get_infrastructure_catalog_metrics method from the SDK
172
+ result = self.catalog_api.get_infrastructure_catalog_metrics(
173
+ plugin=plugin,
174
+ filter=filter # Pass the filter parameter to the SDK
175
+ )
176
+
177
+ # Handle different response types
178
+ if isinstance(result, list):
179
+ # If it's a list of metric names, limit to first 50
180
+ limited_metrics = result[:50]
181
+ debug_print(f"Received {len(result)} metrics for plugin {plugin}, returning first {len(limited_metrics)}")
182
+ return limited_metrics
183
+
184
+ elif hasattr(result, 'to_dict'):
185
+ # If it's an SDK object with to_dict method
186
+ result_dict = result.to_dict()
187
+
188
+ # Check if the dict contains a list of metrics
189
+ if isinstance(result_dict, list):
190
+ limited_metrics = result_dict[:50]
191
+ debug_print(f"Received {len(result_dict)} metrics for plugin {plugin}, returning first {len(limited_metrics)}")
192
+ return limited_metrics
193
+ elif isinstance(result_dict, dict):
194
+ # Try to extract metric names from dict structure
195
+ if 'metrics' in result_dict:
196
+ metrics = result_dict['metrics'][:50] if isinstance(result_dict['metrics'], list) else []
197
+ return metrics
198
+ else:
199
+ return [f"Unexpected dict structure for plugin {plugin}"]
200
+ else:
201
+ return [f"Unable to parse metrics for plugin {plugin}"]
202
+ else:
203
+ # For any other format
204
+ debug_print(f"Unexpected result type for plugin {plugin}: {type(result)}")
205
+ return [f"Unexpected response format for plugin {plugin}"]
206
+
207
+ except Exception as e:
208
+ debug_print(f"Error in get_infrastructure_catalog_metrics: {e}")
209
+ traceback.print_exc(file=sys.stderr)
210
+ return [f"Error: Failed to get metric catalog for plugin '{plugin}': {str(e)}"]
211
+
212
+
213
+ @register_as_tool
214
+ async def get_infrastructure_catalog_plugins(self, ctx=None) -> Dict[str, Any]:
215
+ """
216
+ Get plugin catalog from Instana. This tool retrieves all available plugin IDs for your monitored system, showing what types of entities Instana is monitoring in your environment.
217
+ Use this when you need to understand what technologies are being monitored, want to explore the monitoring capabilities of your Instana installation,
218
+ or need to find the correct plugin ID for other API calls. This is particularly useful for discovering what entity types are available for querying,
219
+ understanding your monitoring coverage, or preparing for more detailed data retrieval. For example, use this tool when asked about
220
+ 'what technologies are monitored', 'available plugins in Instana', 'list of monitored entity types', or when someone wants to 'see what kinds of systems Instana is tracking'.
221
+
222
+ Returns the first 50 plugins to keep the response manageable.
223
+
224
+ Args:
225
+ ctx: The MCP context (optional)
226
+
227
+ Returns:
228
+ Dictionary with plugin list and metadata
229
+ """
230
+ try:
231
+ debug_print("get_infrastructure_catalog_plugins called")
232
+
233
+ # Call the get_infrastructure_catalog_plugins method from the SDK
234
+ result = self.catalog_api.get_infrastructure_catalog_plugins()
235
+
236
+ # Handle the result based on its type
237
+ if isinstance(result, list):
238
+ # Extract just the plugin IDs from the list of dictionaries
239
+ plugin_ids = []
240
+ for item in result[:50]: # Limit to first 50 items
241
+ if isinstance(item, dict) and 'plugin' in item:
242
+ plugin_ids.append(item['plugin'])
243
+ elif hasattr(item, 'plugin'):
244
+ plugin_ids.append(item.plugin)
245
+
246
+ debug_print(f"Extracted {len(plugin_ids)} plugin IDs from response (limited to top 50)")
247
+
248
+ # Return structured response that encourages listing
249
+ return {
250
+ "message": f"Found {len(result)} total plugins. Showing first {len(plugin_ids)} plugins:",
251
+ "plugins": plugin_ids,
252
+ "total_available": len(result),
253
+ "showing": len(plugin_ids),
254
+ "note": "These are the plugin IDs for different technologies monitored by Instana"
255
+ }
256
+
257
+ elif hasattr(result, 'to_dict'):
258
+ # If it's an SDK object with to_dict method
259
+ result_dict = result.to_dict()
260
+ if isinstance(result_dict, list):
261
+ # Limit to first 50 items
262
+ limited_result = result_dict[:50]
263
+ plugin_ids = [item.get('plugin', '') for item in limited_result if isinstance(item, dict)]
264
+ return {
265
+ "message": f"Found {len(result_dict)} total plugins. Showing first {len(plugin_ids)} plugins:",
266
+ "plugins": plugin_ids,
267
+ "total_available": len(result_dict),
268
+ "showing": len(plugin_ids)
269
+ }
270
+ else:
271
+ return {"error": "Unexpected response format"}
272
+ else:
273
+ # For any other format
274
+ return {"error": "Unable to parse response"}
275
+
276
+ except Exception as e:
277
+ debug_print(f"Error in get_infrastructure_catalog_plugins: {e}")
278
+ traceback.print_exc(file=sys.stderr)
279
+ return {"error": f"Failed to get plugin catalog: {str(e)}"}
280
+
281
+
282
+
283
+ @register_as_tool
284
+ async def get_infrastructure_catalog_plugins_with_custom_metrics(self, ctx=None) -> Dict[str, Any]:
285
+ """
286
+ Get all plugins with custom metrics catalog from Instana. This tool retrieves information about which entity types (plugins) in your environment have custom metrics configured.
287
+ Use this when you need to identify which technologies have custom monitoring metrics defined, want to explore custom monitoring capabilities,
288
+ or need to find entities with extended metrics beyond the standard set. This is particularly useful for understanding your custom monitoring setup,
289
+ identifying opportunities for additional custom metrics, or troubleshooting issues with custom metric collection. For example, use this tool when asked about 'which systems have custom metrics',
290
+ 'custom monitoring configuration', 'plugins with extended metrics', or when someone wants to 'find out where custom metrics are being collected'.
291
+
292
+ Args:
293
+ ctx: The MCP context (optional)
294
+
295
+ Returns:
296
+ Dictionary containing plugins with custom metrics or error information
297
+ """
298
+ try:
299
+ debug_print("get_infrastructure_catalog_plugins_with_custom_metrics called")
300
+
301
+ # Call the get_infrastructure_catalog_plugins_with_custom_metrics method from the SDK
302
+ result = self.catalog_api.get_infrastructure_catalog_plugins_with_custom_metrics()
303
+
304
+ # Convert the result to a dictionary
305
+ if hasattr(result, 'to_dict'):
306
+ result_dict = result.to_dict()
307
+ else:
308
+ # If it's already a list or another format, convert it appropriately
309
+ if isinstance(result, list):
310
+ result_dict = [item.to_dict() if hasattr(item, 'to_dict') else item for item in result]
311
+ else:
312
+ result_dict = result
313
+
314
+ debug_print(f"Result from get_infrastructure_catalog_plugins_with_custom_metrics: {result_dict}")
315
+ return result_dict
316
+ except Exception as e:
317
+ debug_print(f"Error in get_infrastructure_catalog_plugins_with_custom_metrics: {e}")
318
+ traceback.print_exc(file=sys.stderr)
319
+ return {"error": f"Failed to get plugins with custom metrics: {str(e)}"}
320
+
321
+ @register_as_tool
322
+ async def get_infrastructure_catalog_search_fields(self, ctx=None) -> Dict[str, Any]:
323
+ """
324
+ Get search field catalog from Instana. This tool retrieves all available search keywords and fields that can be used in dynamic focus queries for infrastructure monitoring.
325
+ Use this when you need to understand what search criteria are available, want to build complex queries to filter entities, or need to find the correct search syntax for specific entity properties.
326
+ This is particularly useful for constructing advanced search queries, understanding available filtering options, or discovering how to target specific entities in your environment.
327
+ For example, use this tool when asked about 'what search fields are available', 'how to filter hosts by property', 'search syntax for Kubernetes pods', or when someone wants to 'learn how to build complex entity queries'.
328
+
329
+ This endpoint retrieves all available search keywords for dynamic focus queries.
330
+
331
+ Args:
332
+ ctx: The MCP context (optional)
333
+
334
+ Returns:
335
+ Dictionary containing search field keywords or error information
336
+ """
337
+ try:
338
+ debug_print("get_infrastructure_catalog_search_fields called")
339
+
340
+ # Call the get_infrastructure_catalog_search_fields method from the SDK
341
+ result = self.catalog_api.get_infrastructure_catalog_search_fields()
342
+
343
+ # Convert the result to a dictionary
344
+ if hasattr(result, 'to_dict'):
345
+ full_result = result.to_dict()
346
+ else:
347
+ # If it's already a list or another format, convert it appropriately
348
+ if isinstance(result, list):
349
+ full_result = [item.to_dict() if hasattr(item, 'to_dict') else item for item in result]
350
+ else:
351
+ full_result = result
352
+
353
+ debug_print(f"Full result from get_infrastructure_catalog_search_fields: {full_result}")
354
+
355
+ # Extract only the keywords from the search fields
356
+ keywords = self._extract_search_field_keywords(full_result)
357
+
358
+ return {
359
+ "keywords": keywords,
360
+ "count": len(keywords),
361
+ "summary": "List of available search keywords for dynamic focus queries"
362
+ }
363
+ except Exception as e:
364
+ debug_print(f"Error in get_infrastructure_catalog_search_fields: {e}")
365
+ traceback.print_exc(file=sys.stderr)
366
+ return {"error": f"Failed to get search field catalog: {str(e)}"}
367
+
368
+
369
+ @register_as_tool
370
+ async def get_tag_catalog(self, plugin: str, ctx=None) -> Dict[str, Any]:
371
+ """
372
+ Get available tags for a particular plugin. This tool retrieves the tag catalog filtered by plugin.
373
+
374
+ Args:
375
+ plugin: The plugin name (e.g., 'host', 'jvm', 'openTelemetry')
376
+ ctx: The MCP context (optional)
377
+
378
+ Returns:
379
+ Dictionary containing available tags for the plugin or error information
380
+ """
381
+ try:
382
+ debug_print(f"get_tag_catalog called with plugin={plugin}")
383
+
384
+ if not plugin:
385
+ return {"error": "plugin parameter is required"}
386
+
387
+ # Try calling the SDK method first
388
+ try:
389
+ # Call the get_tag_catalog method from the SDK
390
+ result = self.catalog_api.get_tag_catalog(
391
+ plugin=plugin
392
+ )
393
+
394
+ # Convert the result to a dictionary
395
+ if hasattr(result, 'to_dict'):
396
+ result_dict = result.to_dict()
397
+ else:
398
+ # If it's already a dict or another format, use it as is
399
+ result_dict = result
400
+
401
+ debug_print(f"Result from get_tag_catalog: {result_dict}")
402
+ return result_dict
403
+
404
+ except Exception as sdk_error:
405
+ debug_print(f"SDK method failed: {sdk_error}, trying with custom headers")
406
+
407
+ # Check if it's a 406 error
408
+ is_406_error = False
409
+ if hasattr(sdk_error, 'status') and sdk_error.status == 406:
410
+ is_406_error = True
411
+ elif "406" in str(sdk_error) and "Not Acceptable" in str(sdk_error):
412
+ is_406_error = True
413
+
414
+ if is_406_error:
415
+ # Try using the SDK's method with custom headers
416
+ # The SDK should have a method that allows setting custom headers
417
+ custom_headers = {
418
+ "Accept": "*/*" # More permissive Accept header
419
+ }
420
+
421
+ # Use the without_preload_content version to get the raw response
422
+ response_data = self.catalog_api.get_tag_catalog_without_preload_content(
423
+ plugin=plugin,
424
+ _headers=custom_headers # Pass custom headers to the SDK method
425
+ )
426
+
427
+ # Check if the response was successful
428
+ if response_data.status != 200:
429
+ error_message = f"Failed to get tag catalog: HTTP {response_data.status}"
430
+ debug_print(error_message)
431
+ return {"error": error_message}
432
+
433
+ # Read the response content
434
+ response_text = response_data.data.decode('utf-8')
435
+
436
+ # Parse the JSON manually
437
+ import json
438
+ try:
439
+ result_dict = json.loads(response_text)
440
+ debug_print(f"Result from SDK with custom headers: {result_dict}")
441
+ return result_dict
442
+ except json.JSONDecodeError as json_err:
443
+ error_message = f"Failed to parse JSON response: {json_err}"
444
+ debug_print(error_message)
445
+ return {"error": error_message}
446
+ else:
447
+ # Re-raise if it's not a 406 error
448
+ raise
449
+
450
+ except Exception as e:
451
+ debug_print(f"Error in get_tag_catalog: {e}")
452
+ import traceback
453
+ traceback.print_exc(file=sys.stderr)
454
+ return {"error": f"Failed to get tag catalog: {str(e)}"}
455
+
456
+
457
+ @register_as_tool
458
+ async def get_tag_catalog_all(self, ctx=None) -> Dict[str, Any]:
459
+ """
460
+ Get all available tags. This tool retrieves the complete list of all tags available in your Instana-monitored environment. It returns every tag across all plugins, services, and technologies, allowing users to explore the full tagging taxonomy.
461
+
462
+ Use when the user asks:
463
+ "What tags are available in Instana?"
464
+ "Show me all possible tags I can use for filtering or grouping"
465
+ "What tags exist across all services or technologies?"
466
+ "Give me the complete tag catalog from Instana"
467
+
468
+ Args:
469
+ ctx: The MCP context (optional)
470
+
471
+ Returns:
472
+ Dictionary containing a summarized view of available tags or error information
473
+ """
474
+ try:
475
+ debug_print("get_tag_catalog_all called")
476
+
477
+ # Try using the standard SDK method first
478
+ try:
479
+ result = self.catalog_api.get_tag_catalog_all()
480
+
481
+ # Convert the result to a dictionary
482
+ if hasattr(result, 'to_dict'):
483
+ full_result = result.to_dict()
484
+ else:
485
+ # If it's already a dict or another format, use it as is
486
+ full_result = result
487
+
488
+ debug_print(f"Full result from get_tag_catalog_all (standard method): {full_result}")
489
+
490
+ # Create a summarized version of the response
491
+ summarized_result = self._summarize_tag_catalog(full_result)
492
+ return summarized_result
493
+
494
+ except Exception as sdk_error:
495
+ debug_print(f"Standard SDK method failed: {sdk_error}, trying fallback")
496
+
497
+ # Fallback to using the without_preload_content method
498
+ response_data = self.catalog_api.get_tag_catalog_all_without_preload_content()
499
+
500
+ # Check if the response was successful
501
+ if response_data.status != 200:
502
+ error_message = f"Failed to get tag catalog: HTTP {response_data.status}"
503
+ debug_print(error_message)
504
+
505
+ if response_data.status == 401 or response_data.status == 403:
506
+ return {"error": "Authentication failed. Please check your API token and permissions."}
507
+ else:
508
+ return {"error": error_message}
509
+
510
+ # Read the response content
511
+ response_text = response_data.data.decode('utf-8')
512
+
513
+ # Parse the JSON manually
514
+ import json
515
+ try:
516
+ full_result = json.loads(response_text)
517
+ debug_print(f"Full result from get_tag_catalog_all (fallback method): {full_result}")
518
+
519
+ # Create a summarized version of the response
520
+ summarized_result = self._summarize_tag_catalog(full_result)
521
+ return summarized_result
522
+
523
+ except json.JSONDecodeError as json_err:
524
+ error_message = f"Failed to parse JSON response: {json_err}"
525
+ debug_print(error_message)
526
+ debug_print(f"Response text: {response_text}")
527
+ return {"error": error_message}
528
+
529
+ except Exception as e:
530
+ debug_print(f"Error in get_tag_catalog_all: {e}")
531
+ import traceback
532
+ traceback.print_exc(file=sys.stderr)
533
+ return {"error": f"Failed to get tag catalog: {str(e)}"}
534
+
535
+ def _summarize_tag_catalog(self, full_catalog: Dict[str, Any]) -> Dict[str, Any]:
536
+ """
537
+ Create a summarized version of the tag catalog response that includes tag labels.
538
+
539
+ Args:
540
+ full_catalog: The complete tag catalog response
541
+
542
+ Returns:
543
+ A simplified version of the tag catalog with tag labels
544
+ """
545
+ summary = {
546
+ "summary": "List of all available tag labels in Instana",
547
+ "categories": {},
548
+ "allLabels": []
549
+ }
550
+
551
+ # Extract tag tree if available
552
+ tag_tree = full_catalog.get("tagTree", [])
553
+
554
+ # Process each category in the tag tree
555
+ for category in tag_tree:
556
+ category_label = category.get("label", "Uncategorized")
557
+ category_tags = []
558
+
559
+ # Process children (actual tags)
560
+ if "children" in category and isinstance(category["children"], list):
561
+ for tag in category["children"]:
562
+ tag_label = tag.get("label")
563
+ if tag_label:
564
+ category_tags.append(tag_label)
565
+ summary["allLabels"].append(tag_label)
566
+
567
+ # Add category to summary if it has tags
568
+ if category_tags:
569
+ summary["categories"][category_label] = sorted(category_tags)
570
+
571
+ # Remove duplicates and sort the all labels list
572
+ summary["allLabels"] = sorted(list(set(summary["allLabels"])))
573
+ summary["count"] = len(summary["allLabels"])
574
+
575
+ return summary
576
+
577
+
578
+ @register_as_tool
579
+ async def get_infrastructure_catalog_search_fields(self, ctx=None) -> Dict[str, Any]:
580
+ """
581
+ Get search field catalog from Instana. This tool retrieves all available search keywords and fields that can be used in dynamic focus queries for infrastructure monitoring.
582
+ Use this when you need to understand what search criteria are available, want to build complex queries to filter entities, or need to find the correct search syntax for specific entity properties.
583
+ This is particularly useful for constructing advanced search queries, understanding available filtering options, or discovering how to target specific entities in your environment.
584
+ For example, use this tool when asked about 'what search fields are available', 'how to filter hosts by property', 'search syntax for Kubernetes pods', or when someone wants to 'learn how to build complex entity queries'.
585
+
586
+ This endpoint retrieves all available search keywords for dynamic focus queries.
587
+
588
+ Args:
589
+ ctx: The MCP context (optional)
590
+
591
+ Returns:
592
+ Dictionary containing search field keywords or error information
593
+ """
594
+ try:
595
+ debug_print("get_infrastructure_catalog_search_fields called")
596
+
597
+ # Call the get_infrastructure_catalog_search_fields method from the SDK
598
+ result = self.catalog_api.get_infrastructure_catalog_search_fields()
599
+ debug_print(f"API call successful, got {len(result)} search fields")
600
+
601
+ # Extract just 10 keywords to keep it very small
602
+ keywords = []
603
+
604
+ for field_obj in result[:10]:
605
+ try:
606
+ if hasattr(field_obj, 'to_dict'):
607
+ field_dict = field_obj.to_dict()
608
+ keyword = field_dict.get("keyword", "")
609
+ else:
610
+ keyword = getattr(field_obj, 'keyword', "")
611
+
612
+ if keyword:
613
+ keywords.append(keyword)
614
+
615
+ except Exception:
616
+ continue
617
+
618
+ # Return just the raw keywords list - nothing else
619
+ return keywords
620
+
621
+ except Exception as e:
622
+ debug_print(f"Error: {e}")
623
+ return {"error": str(e)}
624
+