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