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.
- mcp_instana-0.1.0.dist-info/LICENSE +201 -0
- mcp_instana-0.1.0.dist-info/METADATA +649 -0
- mcp_instana-0.1.0.dist-info/RECORD +19 -0
- mcp_instana-0.1.0.dist-info/WHEEL +4 -0
- mcp_instana-0.1.0.dist-info/entry_points.txt +3 -0
- src/__init__.py +0 -0
- src/client/What is the sum of queue depth for all q +55 -0
- src/client/application_alert_config_mcp_tools.py +680 -0
- src/client/application_metrics_mcp_tools.py +377 -0
- src/client/application_resources_mcp_tools.py +391 -0
- src/client/events_mcp_tools.py +531 -0
- src/client/infrastructure_analyze_mcp_tools.py +634 -0
- src/client/infrastructure_catalog_mcp_tools.py +624 -0
- src/client/infrastructure_resources_mcp_tools.py +653 -0
- src/client/infrastructure_topology_mcp_tools.py +319 -0
- src/client/instana_client_base.py +93 -0
- src/client/log_alert_configuration_mcp_tools.py +316 -0
- src/client/show the top 5 services with the highest +28 -0
- src/mcp_server.py +343 -0
|
@@ -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
|
+
|