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