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