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,26 +4,39 @@ Infrastructure Analyze MCP Tools Module
|
|
|
4
4
|
This module provides infrastructure analysis-specific MCP tools for Instana monitoring.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import logging
|
|
7
8
|
import sys
|
|
8
|
-
import
|
|
9
|
-
from typing import Dict, Any, Optional, List, Union
|
|
10
|
-
from datetime import datetime
|
|
9
|
+
from typing import Any, Dict, Optional, Union
|
|
11
10
|
|
|
12
11
|
# Import the necessary classes from the SDK
|
|
13
12
|
try:
|
|
14
|
-
from instana_client.api.infrastructure_analyze_api import
|
|
13
|
+
from instana_client.api.infrastructure_analyze_api import (
|
|
14
|
+
InfrastructureAnalyzeApi,
|
|
15
|
+
)
|
|
15
16
|
from instana_client.api_client import ApiClient
|
|
16
17
|
from instana_client.configuration import Configuration
|
|
17
|
-
from instana_client.models.get_available_metrics_query import
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
from instana_client.models.
|
|
18
|
+
from instana_client.models.get_available_metrics_query import (
|
|
19
|
+
GetAvailableMetricsQuery,
|
|
20
|
+
)
|
|
21
|
+
from instana_client.models.get_available_plugins_query import (
|
|
22
|
+
GetAvailablePluginsQuery,
|
|
23
|
+
)
|
|
24
|
+
from instana_client.models.get_infrastructure_groups_query import (
|
|
25
|
+
GetInfrastructureGroupsQuery,
|
|
26
|
+
)
|
|
27
|
+
from instana_client.models.get_infrastructure_query import (
|
|
28
|
+
GetInfrastructureQuery,
|
|
29
|
+
)
|
|
21
30
|
except ImportError as e:
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
import logging
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
logger.error(f"Error importing Instana SDK: {e}", exc_info=True)
|
|
24
34
|
raise
|
|
25
35
|
|
|
26
|
-
from .
|
|
36
|
+
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
37
|
+
|
|
38
|
+
# Configure logger for this module
|
|
39
|
+
logger = logging.getLogger(__name__)
|
|
27
40
|
|
|
28
41
|
# Helper function for debug printing
|
|
29
42
|
def debug_print(*args, **kwargs):
|
|
@@ -32,39 +45,22 @@ def debug_print(*args, **kwargs):
|
|
|
32
45
|
|
|
33
46
|
class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
34
47
|
"""Tools for infrastructure analysis in Instana MCP."""
|
|
35
|
-
|
|
48
|
+
|
|
36
49
|
def __init__(self, read_token: str, base_url: str):
|
|
37
50
|
"""Initialize the Infrastructure Analyze MCP tools client."""
|
|
38
51
|
super().__init__(read_token=read_token, base_url=base_url)
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
|
|
42
|
-
# Configure the API client with the correct base URL and authentication
|
|
43
|
-
configuration = Configuration()
|
|
44
|
-
configuration.host = base_url
|
|
45
|
-
configuration.api_key['ApiKeyAuth'] = read_token
|
|
46
|
-
configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
|
|
47
|
-
|
|
48
|
-
# Create an API client with this configuration
|
|
49
|
-
api_client = ApiClient(configuration=configuration)
|
|
50
|
-
|
|
51
|
-
# Initialize the Instana SDK's InfrastructureAnalyzeApi with our configured client
|
|
52
|
-
self.analyze_api = InfrastructureAnalyzeApi(api_client=api_client)
|
|
53
|
-
except Exception as e:
|
|
54
|
-
debug_print(f"Error initializing InfrastructureAnalyzeApi: {e}")
|
|
55
|
-
traceback.print_exc(file=sys.stderr)
|
|
56
|
-
raise
|
|
57
|
-
|
|
52
|
+
|
|
58
53
|
@register_as_tool
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
@with_header_auth(InfrastructureAnalyzeApi)
|
|
55
|
+
async def get_available_metrics(self,
|
|
56
|
+
payload: Optional[Union[Dict[str, Any], str]] = None,
|
|
57
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
62
58
|
"""
|
|
63
59
|
Get available metrics for infrastructure monitoring.
|
|
64
|
-
|
|
60
|
+
|
|
65
61
|
This tool retrieves information about available metrics for a specific entity type.
|
|
66
62
|
You can use this to discover what metrics are available for monitoring different components in your environment.
|
|
67
|
-
|
|
63
|
+
|
|
68
64
|
Sample payload:
|
|
69
65
|
{
|
|
70
66
|
"timeFrame": {
|
|
@@ -80,68 +76,71 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
80
76
|
"query": "",
|
|
81
77
|
"type": "jvmRuntimePlatform"
|
|
82
78
|
}
|
|
83
|
-
|
|
79
|
+
|
|
84
80
|
Args:
|
|
85
81
|
payload: Complete request payload as a dictionary or a JSON string
|
|
86
82
|
ctx: The MCP context (optional)
|
|
87
|
-
|
|
83
|
+
|
|
88
84
|
Returns:
|
|
89
85
|
Dictionary containing available metrics or error information
|
|
90
86
|
"""
|
|
91
87
|
try:
|
|
92
|
-
|
|
93
|
-
|
|
88
|
+
logger.debug(f"get_available_metrics called with payload={payload}")
|
|
89
|
+
|
|
94
90
|
# If payload is a string, try to parse it as JSON
|
|
95
91
|
if isinstance(payload, str):
|
|
96
|
-
|
|
92
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
97
93
|
try:
|
|
98
94
|
import json
|
|
99
95
|
try:
|
|
100
96
|
parsed_payload = json.loads(payload)
|
|
101
|
-
|
|
97
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
102
98
|
request_body = parsed_payload
|
|
103
99
|
except json.JSONDecodeError as e:
|
|
104
|
-
|
|
105
|
-
|
|
100
|
+
logger.error(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
101
|
+
|
|
106
102
|
# Try replacing single quotes with double quotes
|
|
107
103
|
fixed_payload = payload.replace("'", "\"")
|
|
108
104
|
try:
|
|
109
105
|
parsed_payload = json.loads(fixed_payload)
|
|
110
|
-
|
|
106
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
111
107
|
request_body = parsed_payload
|
|
112
108
|
except json.JSONDecodeError:
|
|
113
109
|
# Try as Python literal
|
|
114
110
|
import ast
|
|
115
111
|
try:
|
|
116
112
|
parsed_payload = ast.literal_eval(payload)
|
|
117
|
-
|
|
113
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
118
114
|
request_body = parsed_payload
|
|
119
115
|
except (SyntaxError, ValueError) as e2:
|
|
120
|
-
|
|
116
|
+
logger.error(f"Failed to parse payload string: {e2}")
|
|
121
117
|
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
122
118
|
except Exception as e:
|
|
123
|
-
|
|
119
|
+
logger.error(f"Error parsing payload string: {e}")
|
|
124
120
|
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
125
121
|
else:
|
|
126
122
|
# If payload is already a dictionary, use it directly
|
|
127
|
-
|
|
123
|
+
logger.debug("Using provided payload dictionary")
|
|
128
124
|
request_body = payload
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
125
|
+
|
|
126
|
+
logger.debug(f"Final request body: {request_body}")
|
|
127
|
+
|
|
132
128
|
# Import the GetAvailableMetricsQuery class
|
|
133
129
|
try:
|
|
134
|
-
from instana_client.models.get_available_metrics_query import
|
|
135
|
-
|
|
130
|
+
from instana_client.models.get_available_metrics_query import (
|
|
131
|
+
GetAvailableMetricsQuery,
|
|
132
|
+
)
|
|
133
|
+
logger.debug("Successfully imported GetAvailableMetricsQuery")
|
|
136
134
|
except ImportError as e:
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
135
|
+
error_msg = f"Error importing GetAvailableMetricsQuery: {e!s}"
|
|
136
|
+
logger.error(error_msg)
|
|
137
|
+
return {"error": error_msg}
|
|
138
|
+
|
|
140
139
|
# Create a GetAvailableMetricsQuery object from the request body
|
|
141
140
|
try:
|
|
142
141
|
# Extract parameters from the request body
|
|
143
142
|
query_params = {}
|
|
144
|
-
|
|
143
|
+
|
|
145
144
|
# Handle timeFrame
|
|
146
145
|
if request_body and "timeFrame" in request_body:
|
|
147
146
|
time_frame = {}
|
|
@@ -152,54 +151,54 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
152
151
|
if "windowSize" in request_body["timeFrame"]:
|
|
153
152
|
time_frame["windowSize"] = request_body["timeFrame"]["windowSize"]
|
|
154
153
|
query_params["timeFrame"] = time_frame
|
|
155
|
-
|
|
154
|
+
|
|
156
155
|
# Handle other parameters
|
|
157
156
|
if request_body and "query" in request_body:
|
|
158
157
|
query_params["query"] = request_body["query"]
|
|
159
|
-
|
|
158
|
+
|
|
160
159
|
if request_body and "type" in request_body:
|
|
161
160
|
query_params["type"] = request_body["type"]
|
|
162
|
-
|
|
161
|
+
|
|
163
162
|
if request_body and "tagFilterExpression" in request_body:
|
|
164
163
|
query_params["tagFilterExpression"] = request_body["tagFilterExpression"]
|
|
165
|
-
|
|
166
|
-
|
|
164
|
+
|
|
165
|
+
logger.debug(f"Creating GetAvailableMetricsQuery with params: {query_params}")
|
|
167
166
|
query_object = GetAvailableMetricsQuery(**query_params)
|
|
168
|
-
|
|
167
|
+
logger.debug(f"Successfully created query object: {query_object}")
|
|
169
168
|
except Exception as e:
|
|
170
|
-
|
|
171
|
-
return {"error": f"Failed to create query object: {
|
|
172
|
-
|
|
169
|
+
logger.error(f"Error creating GetAvailableMetricsQuery: {e}")
|
|
170
|
+
return {"error": f"Failed to create query object: {e!s}"}
|
|
171
|
+
|
|
173
172
|
# Call the get_available_metrics method from the SDK with the query object
|
|
174
|
-
|
|
175
|
-
result =
|
|
176
|
-
|
|
173
|
+
logger.debug("Calling get_available_metrics with query object")
|
|
174
|
+
result = api_client.get_available_metrics(get_available_metrics_query=query_object)
|
|
175
|
+
|
|
177
176
|
# Convert the result to a dictionary
|
|
178
177
|
if hasattr(result, 'to_dict'):
|
|
179
178
|
result_dict = result.to_dict()
|
|
180
179
|
else:
|
|
181
180
|
# If it's already a dict or another format, use it as is
|
|
182
181
|
result_dict = result
|
|
183
|
-
|
|
184
|
-
|
|
182
|
+
|
|
183
|
+
logger.debug(f"Result from get_available_metrics: {result_dict}")
|
|
185
184
|
return result_dict
|
|
186
185
|
except Exception as e:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return {"error": f"Failed to get available metrics: {str(e)}"}
|
|
186
|
+
logger.error(f"Error in get_available_metrics: {e}", exc_info=True)
|
|
187
|
+
return {"error": f"Failed to get available metrics: {e!s}"}
|
|
190
188
|
|
|
191
189
|
@register_as_tool
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
@with_header_auth(InfrastructureAnalyzeApi)
|
|
191
|
+
async def get_entities(self,
|
|
192
|
+
payload: Optional[Union[Dict[str, Any], str]] = None,
|
|
193
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
195
194
|
"""
|
|
196
195
|
Get infrastructure entities for a given entity type along with requested metrics.
|
|
197
|
-
|
|
196
|
+
|
|
198
197
|
We want to know the memory used and no of blocked threads for entity named JVMruntimeplatform for last 1 hour in Instana. can you help us get the details ?
|
|
199
|
-
|
|
198
|
+
|
|
200
199
|
This tool retrieves entities of a specific type (e.g., hosts, processes, containers) along with
|
|
201
200
|
their metrics. You can filter the results using tag filters and paginate through large result sets.
|
|
202
|
-
|
|
201
|
+
|
|
203
202
|
Sample payload:
|
|
204
203
|
{
|
|
205
204
|
"tagFilterExpression": {
|
|
@@ -224,95 +223,95 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
224
223
|
{"metric": "threads.blocked", "granularity": 600000, "aggregation": "MEAN"}
|
|
225
224
|
]
|
|
226
225
|
}
|
|
227
|
-
|
|
226
|
+
|
|
228
227
|
Args:
|
|
229
228
|
payload: Complete request payload as a dictionary or a JSON string
|
|
230
229
|
ctx: The MCP context (optional)
|
|
231
|
-
|
|
230
|
+
|
|
232
231
|
Returns:
|
|
233
232
|
Dictionary containing infrastructure entities and their metrics or error information
|
|
234
233
|
"""
|
|
235
234
|
try:
|
|
236
|
-
|
|
237
|
-
|
|
235
|
+
logger.debug(f"get_entities called with payload={payload}")
|
|
236
|
+
|
|
238
237
|
# If payload is a string, try to parse it as JSON
|
|
239
238
|
if isinstance(payload, str):
|
|
240
|
-
|
|
239
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
241
240
|
try:
|
|
242
241
|
import json
|
|
243
242
|
try:
|
|
244
243
|
parsed_payload = json.loads(payload)
|
|
245
|
-
|
|
244
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
246
245
|
request_body = parsed_payload
|
|
247
246
|
except json.JSONDecodeError as e:
|
|
248
|
-
|
|
249
|
-
|
|
247
|
+
logger.warning(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
248
|
+
|
|
250
249
|
# Try replacing single quotes with double quotes
|
|
251
250
|
fixed_payload = payload.replace("'", "\"")
|
|
252
251
|
try:
|
|
253
252
|
parsed_payload = json.loads(fixed_payload)
|
|
254
|
-
|
|
253
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
255
254
|
request_body = parsed_payload
|
|
256
255
|
except json.JSONDecodeError:
|
|
257
256
|
# Try as Python literal
|
|
258
257
|
import ast
|
|
259
258
|
try:
|
|
260
259
|
parsed_payload = ast.literal_eval(payload)
|
|
261
|
-
|
|
260
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
262
261
|
request_body = parsed_payload
|
|
263
262
|
except (SyntaxError, ValueError) as e2:
|
|
264
|
-
|
|
263
|
+
logger.error(f"Failed to parse payload string: {e2}")
|
|
265
264
|
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
266
265
|
except Exception as e:
|
|
267
|
-
|
|
266
|
+
logger.error(f"Error parsing payload string: {e}")
|
|
268
267
|
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
269
268
|
else:
|
|
270
269
|
# If payload is already a dictionary, use it directly
|
|
271
|
-
|
|
270
|
+
logger.debug("Using provided payload dictionary")
|
|
272
271
|
request_body = payload
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
272
|
+
|
|
273
|
+
logger.debug(f"Final request body: {request_body}")
|
|
274
|
+
|
|
276
275
|
# Create the GetInfrastructureQuery object
|
|
277
276
|
try:
|
|
278
277
|
# Create the query object directly from the request body
|
|
279
|
-
get_infra_query = GetInfrastructureQuery(**request_body)
|
|
280
|
-
|
|
278
|
+
get_infra_query = GetInfrastructureQuery(**request_body) #type: ignore
|
|
279
|
+
logger.debug("Successfully created GetInfrastructureQuery object")
|
|
281
280
|
except Exception as model_error:
|
|
282
281
|
error_msg = f"Failed to create GetInfrastructureQuery object: {model_error}"
|
|
283
|
-
|
|
282
|
+
logger.error(error_msg)
|
|
284
283
|
return {"error": error_msg, "request_body": request_body}
|
|
285
|
-
|
|
284
|
+
|
|
286
285
|
# Call the get_entities method from the SDK
|
|
287
|
-
|
|
288
|
-
result =
|
|
286
|
+
logger.debug("Calling API method get_entities")
|
|
287
|
+
result = api_client.get_entities(
|
|
289
288
|
get_infrastructure_query=get_infra_query
|
|
290
289
|
)
|
|
291
|
-
|
|
290
|
+
|
|
292
291
|
# Convert the result to a dictionary
|
|
293
292
|
if hasattr(result, 'to_dict'):
|
|
294
293
|
result_dict = result.to_dict()
|
|
295
294
|
else:
|
|
296
295
|
# If it's already a dict or another format, use it as is
|
|
297
296
|
result_dict = result
|
|
298
|
-
|
|
299
|
-
|
|
297
|
+
|
|
298
|
+
logger.debug(f"Result from get_entities: {result_dict}")
|
|
300
299
|
return result_dict
|
|
301
300
|
except Exception as e:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
return {"error": f"Failed to get entities: {str(e)}"}
|
|
301
|
+
logger.error(f"Error in get_entities: {e}", exc_info=True)
|
|
302
|
+
return {"error": f"Failed to get entities: {e!s}"}
|
|
305
303
|
|
|
306
304
|
@register_as_tool
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
305
|
+
@with_header_auth(InfrastructureAnalyzeApi)
|
|
306
|
+
async def get_aggregated_entity_groups(self,
|
|
307
|
+
payload: Optional[Union[Dict[str, Any], str]] = None,
|
|
308
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
310
309
|
"""
|
|
311
310
|
Get grouped infrastructure entities with aggregated metrics.
|
|
312
|
-
|
|
311
|
+
|
|
313
312
|
This tool groups entities of a specific type by specified tags and aggregates metrics for these groups.
|
|
314
313
|
For example, you can group hosts by their region and get average CPU usage per region.
|
|
315
|
-
|
|
314
|
+
|
|
316
315
|
Sample payload:
|
|
317
316
|
{
|
|
318
317
|
"timeFrame": {
|
|
@@ -340,115 +339,116 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
340
339
|
"direction": "ASC"
|
|
341
340
|
}
|
|
342
341
|
}
|
|
343
|
-
|
|
342
|
+
|
|
344
343
|
Args:
|
|
345
344
|
payload: Complete request payload as a dictionary or a JSON string
|
|
346
345
|
ctx: The MCP context (optional)
|
|
347
|
-
|
|
346
|
+
|
|
348
347
|
Returns:
|
|
349
348
|
Dictionary containing grouped entities and their aggregated metrics or error information
|
|
350
349
|
"""
|
|
351
350
|
try:
|
|
352
|
-
|
|
353
|
-
|
|
351
|
+
logger.debug(f"get_aggregated_entity_groups called with payload={payload}")
|
|
352
|
+
|
|
354
353
|
# If no payload is provided, return an error
|
|
355
354
|
if not payload:
|
|
356
355
|
return {"error": "Payload is required for this operation"}
|
|
357
|
-
|
|
356
|
+
|
|
358
357
|
# If payload is a string, try to parse it as JSON
|
|
359
358
|
if isinstance(payload, str):
|
|
360
|
-
|
|
359
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
361
360
|
try:
|
|
362
361
|
import json
|
|
363
362
|
try:
|
|
364
363
|
parsed_payload = json.loads(payload)
|
|
365
|
-
|
|
364
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
366
365
|
request_body = parsed_payload
|
|
367
366
|
except json.JSONDecodeError as e:
|
|
368
|
-
|
|
369
|
-
|
|
367
|
+
logger.warning(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
368
|
+
|
|
370
369
|
# Try replacing single quotes with double quotes
|
|
371
370
|
fixed_payload = payload.replace("'", "\"")
|
|
372
371
|
try:
|
|
373
372
|
parsed_payload = json.loads(fixed_payload)
|
|
374
|
-
|
|
373
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
375
374
|
request_body = parsed_payload
|
|
376
375
|
except json.JSONDecodeError:
|
|
377
376
|
# Try as Python literal
|
|
378
377
|
import ast
|
|
379
378
|
try:
|
|
380
379
|
parsed_payload = ast.literal_eval(payload)
|
|
381
|
-
|
|
380
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
382
381
|
request_body = parsed_payload
|
|
383
382
|
except (SyntaxError, ValueError) as e2:
|
|
384
|
-
|
|
383
|
+
logger.error(f"Failed to parse payload string: {e2}")
|
|
385
384
|
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
386
385
|
except Exception as e:
|
|
387
|
-
|
|
386
|
+
logger.error(f"Error parsing payload string: {e}")
|
|
388
387
|
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
389
388
|
else:
|
|
390
389
|
# If payload is already a dictionary, use it directly
|
|
391
|
-
|
|
390
|
+
logger.debug("Using provided payload dictionary")
|
|
392
391
|
request_body = payload
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
392
|
+
|
|
393
|
+
logger.debug(f"Final request body: {request_body}")
|
|
394
|
+
|
|
396
395
|
# Create the GetInfrastructureGroupsQuery object
|
|
397
396
|
try:
|
|
398
397
|
# Import the model class
|
|
399
|
-
from instana_client.models.get_infrastructure_groups_query import
|
|
400
|
-
|
|
398
|
+
from instana_client.models.get_infrastructure_groups_query import (
|
|
399
|
+
GetInfrastructureGroupsQuery,
|
|
400
|
+
)
|
|
401
|
+
|
|
401
402
|
# Create the query object
|
|
402
403
|
get_groups_query = GetInfrastructureGroupsQuery(**request_body)
|
|
403
|
-
|
|
404
|
+
logger.debug("Successfully created GetInfrastructureGroupsQuery object")
|
|
404
405
|
except Exception as model_error:
|
|
405
406
|
error_msg = f"Failed to create GetInfrastructureGroupsQuery object: {model_error}"
|
|
406
|
-
|
|
407
|
+
logger.debug(error_msg)
|
|
407
408
|
return {"error": error_msg, "request_body": request_body}
|
|
408
|
-
|
|
409
|
+
|
|
409
410
|
# Call the get_entity_groups method from the SDK
|
|
410
|
-
|
|
411
|
+
logger.debug("Calling API method get_entity_groups")
|
|
411
412
|
try:
|
|
412
413
|
# Use the without_preload_content version to get the raw response
|
|
413
|
-
response =
|
|
414
|
+
response = api_client.get_entity_groups_without_preload_content(
|
|
414
415
|
get_infrastructure_groups_query=get_groups_query
|
|
415
416
|
)
|
|
416
|
-
|
|
417
|
+
|
|
417
418
|
# Check if the response was successful
|
|
418
419
|
if response.status != 200:
|
|
419
420
|
error_message = f"Failed to get entity groups: HTTP {response.status}"
|
|
420
|
-
|
|
421
|
+
logger.debug(error_message)
|
|
421
422
|
return {"error": error_message}
|
|
422
|
-
|
|
423
|
+
|
|
423
424
|
# Read the response content
|
|
424
425
|
response_text = response.data.decode('utf-8')
|
|
425
|
-
|
|
426
|
+
|
|
426
427
|
# Parse the response as JSON
|
|
427
428
|
import json
|
|
428
429
|
result_dict = json.loads(response_text)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
430
|
+
|
|
431
|
+
logger.debug("Successfully parsed raw response")
|
|
432
|
+
|
|
432
433
|
# Create a summarized version of the results
|
|
433
434
|
return self._summarize_entity_groups_result(result_dict, request_body)
|
|
434
435
|
except Exception as api_error:
|
|
435
436
|
error_msg = f"API call failed: {api_error}"
|
|
436
|
-
|
|
437
|
+
logger.error(error_msg)
|
|
437
438
|
return {"error": error_msg}
|
|
438
|
-
|
|
439
|
+
|
|
439
440
|
except Exception as e:
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
return {"error": f"Failed to get aggregated entity groups: {str(e)}"}
|
|
441
|
+
logger.error(f"Error in get_aggregated_entity_groups: {e}", exc_info=True)
|
|
442
|
+
return {"error": f"Failed to get aggregated entity groups: {e!s}"}
|
|
443
443
|
|
|
444
444
|
def _summarize_entity_groups_result(self, result_dict, query_body):
|
|
445
445
|
"""
|
|
446
446
|
Create a summarized version of the entity groups result.
|
|
447
|
-
|
|
447
|
+
|
|
448
448
|
Args:
|
|
449
449
|
result_dict: The full API response
|
|
450
450
|
query_body: The query body used to make the request
|
|
451
|
-
|
|
451
|
+
|
|
452
452
|
Returns:
|
|
453
453
|
A summarized version of the results
|
|
454
454
|
"""
|
|
@@ -456,15 +456,15 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
456
456
|
# Check if there's an error in the result
|
|
457
457
|
if isinstance(result_dict, dict) and "error" in result_dict:
|
|
458
458
|
return result_dict
|
|
459
|
-
|
|
459
|
+
|
|
460
460
|
# Extract the group by tag
|
|
461
461
|
group_by_tag = None
|
|
462
462
|
if "groupBy" in query_body and isinstance(query_body["groupBy"], list) and len(query_body["groupBy"]) > 0:
|
|
463
463
|
group_by_tag = query_body["groupBy"][0]
|
|
464
|
-
|
|
464
|
+
|
|
465
465
|
# Extract host names if available
|
|
466
466
|
host_names = []
|
|
467
|
-
|
|
467
|
+
|
|
468
468
|
# Process each item in the results
|
|
469
469
|
if "items" in result_dict and isinstance(result_dict["items"], list):
|
|
470
470
|
for item in result_dict["items"]:
|
|
@@ -480,39 +480,39 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
480
480
|
else:
|
|
481
481
|
# Convert other types to string
|
|
482
482
|
host_name = str(tag_value)
|
|
483
|
-
|
|
483
|
+
|
|
484
484
|
if host_name not in host_names:
|
|
485
485
|
host_names.append(host_name)
|
|
486
|
-
|
|
486
|
+
|
|
487
487
|
# Sort host names alphabetically
|
|
488
488
|
host_names.sort()
|
|
489
|
-
|
|
489
|
+
|
|
490
490
|
# Create the exact format requested
|
|
491
491
|
summary = {
|
|
492
492
|
"hosts": host_names,
|
|
493
493
|
"count": len(host_names),
|
|
494
494
|
"summary": f"Found {len(host_names)} hosts: {', '.join(host_names)}"
|
|
495
495
|
}
|
|
496
|
-
|
|
496
|
+
|
|
497
497
|
return summary
|
|
498
498
|
except Exception as e:
|
|
499
|
-
|
|
500
|
-
traceback.print_exc(file=sys.stderr)
|
|
499
|
+
logger.error(f"Error in _summarize_entity_groups_result: {e}", exc_info=True)
|
|
501
500
|
# If summarization fails, return an error message
|
|
502
501
|
return {
|
|
503
|
-
"error": f"Failed to summarize results: {
|
|
502
|
+
"error": f"Failed to summarize results: {e!s}"
|
|
504
503
|
}
|
|
505
504
|
|
|
506
505
|
@register_as_tool
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
506
|
+
@with_header_auth(InfrastructureAnalyzeApi)
|
|
507
|
+
async def get_available_plugins(self,
|
|
508
|
+
payload: Optional[Union[Dict[str, Any], str]] = None,
|
|
509
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
510
510
|
"""
|
|
511
511
|
Get available plugins for infrastructure monitoring.
|
|
512
|
-
|
|
512
|
+
|
|
513
513
|
This tool retrieves information about available plugins for infrastructure monitoring.
|
|
514
514
|
You can use this to discover what types of entities can be monitored in your environment.
|
|
515
|
-
|
|
515
|
+
|
|
516
516
|
Sample payload:
|
|
517
517
|
{
|
|
518
518
|
"timeFrame": {
|
|
@@ -527,68 +527,70 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
527
527
|
"query": "java",
|
|
528
528
|
"offline": false
|
|
529
529
|
}
|
|
530
|
-
|
|
530
|
+
|
|
531
531
|
Args:
|
|
532
532
|
payload: Complete request payload as a dictionary or a JSON string
|
|
533
533
|
ctx: The MCP context (optional)
|
|
534
|
-
|
|
534
|
+
|
|
535
535
|
Returns:
|
|
536
536
|
Dictionary containing available plugins or error information
|
|
537
537
|
"""
|
|
538
538
|
try:
|
|
539
|
-
|
|
540
|
-
|
|
539
|
+
logger.debug(f"get_available_plugins called with payload={payload}")
|
|
540
|
+
|
|
541
541
|
# If payload is a string, try to parse it as JSON
|
|
542
542
|
if isinstance(payload, str):
|
|
543
|
-
|
|
543
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
544
544
|
try:
|
|
545
545
|
import json
|
|
546
546
|
try:
|
|
547
547
|
parsed_payload = json.loads(payload)
|
|
548
|
-
|
|
548
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
549
549
|
request_body = parsed_payload
|
|
550
550
|
except json.JSONDecodeError as e:
|
|
551
|
-
|
|
552
|
-
|
|
551
|
+
logger.warning(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
552
|
+
|
|
553
553
|
# Try replacing single quotes with double quotes
|
|
554
554
|
fixed_payload = payload.replace("'", "\"")
|
|
555
555
|
try:
|
|
556
556
|
parsed_payload = json.loads(fixed_payload)
|
|
557
|
-
|
|
557
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
558
558
|
request_body = parsed_payload
|
|
559
559
|
except json.JSONDecodeError:
|
|
560
560
|
# Try as Python literal
|
|
561
561
|
import ast
|
|
562
562
|
try:
|
|
563
563
|
parsed_payload = ast.literal_eval(payload)
|
|
564
|
-
|
|
564
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
565
565
|
request_body = parsed_payload
|
|
566
566
|
except (SyntaxError, ValueError) as e2:
|
|
567
|
-
|
|
567
|
+
logger.error(f"Failed to parse payload string: {e2}")
|
|
568
568
|
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
569
569
|
except Exception as e:
|
|
570
|
-
|
|
570
|
+
logger.error(f"Error parsing payload string: {e}")
|
|
571
571
|
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
572
572
|
else:
|
|
573
573
|
# If payload is already a dictionary, use it directly
|
|
574
|
-
|
|
574
|
+
logger.debug("Using provided payload dictionary")
|
|
575
575
|
request_body = payload
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
576
|
+
|
|
577
|
+
logger.debug(f"Final request body: {request_body}")
|
|
578
|
+
|
|
579
579
|
# Import the GetAvailablePluginsQuery class
|
|
580
580
|
try:
|
|
581
|
-
from instana_client.models.get_available_plugins_query import
|
|
582
|
-
|
|
581
|
+
from instana_client.models.get_available_plugins_query import (
|
|
582
|
+
GetAvailablePluginsQuery,
|
|
583
|
+
)
|
|
584
|
+
logger.debug("Successfully imported GetAvailablePluginsQuery")
|
|
583
585
|
except ImportError as e:
|
|
584
|
-
|
|
585
|
-
return {"error": f"Failed to import GetAvailablePluginsQuery: {
|
|
586
|
-
|
|
586
|
+
logger.error(f"Error importing GetAvailablePluginsQuery: {e}")
|
|
587
|
+
return {"error": f"Failed to import GetAvailablePluginsQuery: {e!s}"}
|
|
588
|
+
|
|
587
589
|
# Create a GetAvailablePluginsQuery object from the request body
|
|
588
590
|
try:
|
|
589
591
|
# Extract parameters from the request body
|
|
590
592
|
query_params = {}
|
|
591
|
-
|
|
593
|
+
|
|
592
594
|
# Handle timeFrame
|
|
593
595
|
if request_body and "timeFrame" in request_body:
|
|
594
596
|
time_frame = {}
|
|
@@ -597,38 +599,37 @@ class InfrastructureAnalyzeMCPTools(BaseInstanaClient):
|
|
|
597
599
|
if "windowSize" in request_body["timeFrame"]:
|
|
598
600
|
time_frame["windowSize"] = request_body["timeFrame"]["windowSize"]
|
|
599
601
|
query_params["timeFrame"] = time_frame
|
|
600
|
-
|
|
602
|
+
|
|
601
603
|
# Handle other parameters
|
|
602
604
|
if request_body and "query" in request_body:
|
|
603
605
|
query_params["query"] = request_body["query"]
|
|
604
|
-
|
|
606
|
+
|
|
605
607
|
if request_body and "offline" in request_body:
|
|
606
608
|
query_params["offline"] = request_body["offline"]
|
|
607
|
-
|
|
609
|
+
|
|
608
610
|
if request_body and "tagFilterExpression" in request_body:
|
|
609
611
|
query_params["tagFilterExpression"] = request_body["tagFilterExpression"]
|
|
610
|
-
|
|
611
|
-
|
|
612
|
+
|
|
613
|
+
logger.debug(f"Creating GetAvailablePluginsQuery with params: {query_params}")
|
|
612
614
|
query_object = GetAvailablePluginsQuery(**query_params)
|
|
613
|
-
|
|
615
|
+
logger.debug(f"Successfully created query object: {query_object}")
|
|
614
616
|
except Exception as e:
|
|
615
|
-
|
|
616
|
-
return {"error": f"Failed to create query object: {
|
|
617
|
-
|
|
617
|
+
logger.error(f"Error creating GetAvailablePluginsQuery: {e}")
|
|
618
|
+
return {"error": f"Failed to create query object: {e!s}"}
|
|
619
|
+
|
|
618
620
|
# Call the get_available_plugins method from the SDK with the query object
|
|
619
|
-
|
|
620
|
-
result =
|
|
621
|
-
|
|
621
|
+
logger.debug("Calling get_available_plugins with query object")
|
|
622
|
+
result = api_client.get_available_plugins(get_available_plugins_query=query_object)
|
|
623
|
+
|
|
622
624
|
# Convert the result to a dictionary
|
|
623
625
|
if hasattr(result, 'to_dict'):
|
|
624
626
|
result_dict = result.to_dict()
|
|
625
627
|
else:
|
|
626
628
|
# If it's already a dict or another format, use it as is
|
|
627
629
|
result_dict = result
|
|
628
|
-
|
|
629
|
-
|
|
630
|
+
|
|
631
|
+
logger.debug(f"Result from get_available_plugins: {result_dict}")
|
|
630
632
|
return result_dict
|
|
631
633
|
except Exception as e:
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
return {"error": f"Failed to get available plugins: {str(e)}"}
|
|
634
|
+
logger.error(f"Error in get_available_plugins: {e}", exc_info=True)
|
|
635
|
+
return {"error": f"Failed to get available plugins: {e!s}"}
|