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
src/{client/infrastructure_resources_mcp_tools.py → infrastructure/infrastructure_resources.py}
RENAMED
|
@@ -4,170 +4,150 @@ Infrastructure Resources MCP Tools Module
|
|
|
4
4
|
This module provides infrastructure resources-specific MCP tools for Instana monitoring.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import logging
|
|
7
8
|
import sys
|
|
8
|
-
import traceback
|
|
9
|
-
from typing import Dict, Any, Optional, List, Union
|
|
10
9
|
from datetime import datetime
|
|
10
|
+
from typing import Any, Dict, List, Optional, Union
|
|
11
11
|
|
|
12
12
|
# Import the necessary classes from the SDK
|
|
13
13
|
try:
|
|
14
|
-
from instana_client.api.infrastructure_resources_api import
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
from instana_client.api.infrastructure_resources_api import (
|
|
15
|
+
InfrastructureResourcesApi,
|
|
16
|
+
)
|
|
17
17
|
# Check if GetSnapshotsQuery exists, otherwise we'll handle it differently
|
|
18
18
|
try:
|
|
19
|
-
from instana_client.models.get_snapshots_query import GetSnapshotsQuery
|
|
20
19
|
has_get_snapshots_query = True
|
|
21
20
|
except ImportError:
|
|
22
21
|
has_get_snapshots_query = False
|
|
23
|
-
except ImportError
|
|
24
|
-
|
|
22
|
+
except ImportError:
|
|
23
|
+
import logging
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
logger.error("Failed to import infrastructure resources API", exc_info=True)
|
|
25
26
|
raise
|
|
26
27
|
|
|
27
|
-
from .
|
|
28
|
+
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
28
29
|
|
|
29
|
-
#
|
|
30
|
-
|
|
31
|
-
"""Print debug information to stderr instead of stdout"""
|
|
32
|
-
print(*args, file=sys.stderr, **kwargs)
|
|
30
|
+
# Configure logger for this module
|
|
31
|
+
logger = logging.getLogger(__name__)
|
|
33
32
|
|
|
34
33
|
class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
35
34
|
"""Tools for infrastructure resources in Instana MCP."""
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
def __init__(self, read_token: str, base_url: str):
|
|
38
37
|
"""Initialize the Infrastructure Resources MCP tools client."""
|
|
39
38
|
super().__init__(read_token=read_token, base_url=base_url)
|
|
40
|
-
|
|
41
|
-
try:
|
|
42
|
-
|
|
43
|
-
# Configure the API client with the correct base URL and authentication
|
|
44
|
-
configuration = Configuration()
|
|
45
|
-
configuration.host = base_url
|
|
46
|
-
configuration.api_key['ApiKeyAuth'] = read_token
|
|
47
|
-
configuration.api_key_prefix['ApiKeyAuth'] = 'apiToken'
|
|
48
|
-
|
|
49
|
-
# Create an API client with this configuration
|
|
50
|
-
api_client = ApiClient(configuration=configuration)
|
|
51
|
-
|
|
52
|
-
# Initialize the Instana SDK's InfrastructureResourcesApi with our configured client
|
|
53
|
-
self.infra_api = InfrastructureResourcesApi(api_client=api_client)
|
|
54
|
-
except Exception as e:
|
|
55
|
-
debug_print(f"Error initializing InfrastructureResourcesApi: {e}")
|
|
56
|
-
traceback.print_exc(file=sys.stderr)
|
|
57
|
-
raise
|
|
58
|
-
|
|
39
|
+
|
|
59
40
|
@register_as_tool
|
|
60
|
-
|
|
41
|
+
@with_header_auth(InfrastructureResourcesApi)
|
|
42
|
+
async def get_monitoring_state(self, ctx=None, api_client=None) -> Dict[str, Any]:
|
|
61
43
|
"""
|
|
62
44
|
Get the current monitoring state of the Instana system. This tool retrieves details about the number of monitored hosts and serverless entities in your environment.
|
|
63
|
-
Use this when you need an overview of your monitoring coverage, want to check how many hosts are being monitored, or need to verify the scale of your Instana deployment.
|
|
45
|
+
Use this when you need an overview of your monitoring coverage, want to check how many hosts are being monitored, or need to verify the scale of your Instana deployment.
|
|
64
46
|
Use this tool when asked about 'monitoring status', 'how many hosts are monitored', 'monitoring coverage', or when someone wants to 'check the monitoring state'.
|
|
65
|
-
|
|
47
|
+
|
|
66
48
|
Args:
|
|
67
49
|
ctx: The MCP context (optional)
|
|
68
|
-
|
|
50
|
+
|
|
69
51
|
Returns:
|
|
70
52
|
Dictionary containing monitoring state information or error information
|
|
71
53
|
"""
|
|
72
54
|
try:
|
|
73
|
-
|
|
74
|
-
|
|
55
|
+
logger.info("get_monitoring_state called")
|
|
56
|
+
|
|
75
57
|
# Call the get_monitoring_state method from the SDK
|
|
76
|
-
result =
|
|
77
|
-
|
|
78
|
-
|
|
58
|
+
result = api_client.get_monitoring_state()
|
|
59
|
+
|
|
60
|
+
logger.debug(f"Result from get_monitoring_state: {result}")
|
|
79
61
|
return result
|
|
80
62
|
except Exception as e:
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
return {"error": f"Failed to get monitoring state: {str(e)}"}
|
|
85
|
-
|
|
63
|
+
logger.error(f"Error in get_monitoring_state: {e}", exc_info=True)
|
|
64
|
+
return {"error": f"Failed to get monitoring state: {e!s}"}
|
|
65
|
+
|
|
86
66
|
@register_as_tool
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
67
|
+
@with_header_auth(InfrastructureResourcesApi)
|
|
68
|
+
async def get_plugin_payload(self,
|
|
69
|
+
snapshot_id: str,
|
|
70
|
+
payload_key: str,
|
|
71
|
+
to_time: Optional[int] = None,
|
|
72
|
+
window_size: Optional[int] = None,
|
|
73
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
93
74
|
"""
|
|
94
|
-
Get detailed payload data for a specific snapshot in Instana. This tool retrieves raw monitoring data for a particular entity snapshot using its ID and a specific payload key.
|
|
95
|
-
Use this when you need to access detailed, low-level monitoring information that isn't available through other APIs.
|
|
96
|
-
This is particularly useful for deep troubleshooting, accessing specific metrics or configuration details, or when you need the complete raw data for a monitored
|
|
75
|
+
Get detailed payload data for a specific snapshot in Instana. This tool retrieves raw monitoring data for a particular entity snapshot using its ID and a specific payload key.
|
|
76
|
+
Use this when you need to access detailed, low-level monitoring information that isn't available through other APIs.
|
|
77
|
+
This is particularly useful for deep troubleshooting, accessing specific metrics or configuration details, or when you need the complete raw data for a monitored
|
|
97
78
|
entity. For example, use this tool when asked about 'detailed snapshot data', 'raw monitoring information', 'plugin payload details', or when someone wants to 'get the complete data for a specific entity'.
|
|
98
|
-
|
|
79
|
+
|
|
99
80
|
Args:
|
|
100
81
|
snapshot_id: The ID of the snapshot
|
|
101
82
|
payload_key: The key of the payload to retrieve
|
|
102
83
|
to_time: End timestamp in milliseconds (optional)
|
|
103
84
|
window_size: Window size in milliseconds (optional)
|
|
104
85
|
ctx: The MCP context (optional)
|
|
105
|
-
|
|
86
|
+
|
|
106
87
|
Returns:
|
|
107
88
|
Dictionary containing the payload data or error information
|
|
108
89
|
"""
|
|
109
90
|
try:
|
|
110
|
-
|
|
111
|
-
|
|
91
|
+
logger.debug(f"get_plugin_payload called with snapshot_id={snapshot_id}, payload_key={payload_key}")
|
|
92
|
+
|
|
112
93
|
# Call the get_plugin_payload method from the SDK
|
|
113
|
-
result =
|
|
94
|
+
result = api_client.get_plugin_payload(
|
|
114
95
|
snapshot_id=snapshot_id,
|
|
115
96
|
payload_key=payload_key,
|
|
116
97
|
to=to_time,
|
|
117
98
|
window_size=window_size
|
|
118
99
|
)
|
|
119
|
-
|
|
120
|
-
|
|
100
|
+
|
|
101
|
+
logger.debug(f"Result from get_plugin_payload: {result}")
|
|
121
102
|
return result
|
|
122
103
|
except Exception as e:
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
return {"error": f"Failed to get plugin payload: {str(e)}"}
|
|
127
|
-
|
|
104
|
+
logger.error(f"Error in get_plugin_payload: {e}", exc_info=True)
|
|
105
|
+
return {"error": f"Failed to get plugin payload: {e!s}"}
|
|
106
|
+
|
|
128
107
|
@register_as_tool
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
108
|
+
@with_header_auth(InfrastructureResourcesApi)
|
|
109
|
+
async def get_snapshot(self,
|
|
110
|
+
snapshot_id: str,
|
|
111
|
+
to_time: Optional[int] = None,
|
|
112
|
+
window_size: Optional[int] = None,
|
|
113
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
134
114
|
"""
|
|
135
115
|
Get detailed information about a specific snapshot in Instana using its ID.
|
|
136
|
-
|
|
116
|
+
|
|
137
117
|
This tool retrieves comprehensive data for a single, specific entity snapshot that you already have the ID for.
|
|
138
|
-
Use this when you need to examine one particular entity in depth, such as investigating a specific host, container,
|
|
118
|
+
Use this when you need to examine one particular entity in depth, such as investigating a specific host, container,
|
|
139
119
|
or process that you've already identified. This is NOT for searching or discovering entities - use get_snapshots for that purpose.
|
|
140
|
-
|
|
120
|
+
|
|
141
121
|
For example, use this tool when:
|
|
142
122
|
- You already have a specific snapshot ID and need its details
|
|
143
123
|
- You want to examine one particular entity's configuration and metrics
|
|
144
124
|
- You need to troubleshoot a specific component that's already been identified
|
|
145
125
|
- Someone asks for "details about this specific entity" or "information about snapshot 12345"
|
|
146
|
-
|
|
126
|
+
|
|
147
127
|
Args:
|
|
148
128
|
snapshot_id: The ID of the snapshot to retrieve
|
|
149
129
|
to_time: End timestamp in milliseconds (optional)
|
|
150
130
|
window_size: Window size in milliseconds (optional)
|
|
151
131
|
ctx: The MCP context (optional)
|
|
152
|
-
|
|
132
|
+
|
|
153
133
|
Returns:
|
|
154
134
|
Dictionary containing snapshot details or error information
|
|
155
135
|
"""
|
|
156
136
|
try:
|
|
157
|
-
|
|
158
|
-
|
|
137
|
+
logger.debug(f"get_snapshot called with snapshot_id={snapshot_id}")
|
|
138
|
+
|
|
159
139
|
if not snapshot_id:
|
|
160
140
|
return {"error": "snapshot_id parameter is required"}
|
|
161
|
-
|
|
141
|
+
|
|
162
142
|
# Try using the standard SDK method
|
|
163
143
|
try:
|
|
164
144
|
# Call the get_snapshot method from the SDK
|
|
165
|
-
result =
|
|
145
|
+
result = api_client.get_snapshot(
|
|
166
146
|
id=snapshot_id,
|
|
167
147
|
to=to_time,
|
|
168
148
|
window_size=window_size
|
|
169
149
|
)
|
|
170
|
-
|
|
150
|
+
|
|
171
151
|
# Convert the result to a dictionary
|
|
172
152
|
if hasattr(result, 'to_dict'):
|
|
173
153
|
result_dict = result.to_dict()
|
|
@@ -176,13 +156,13 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
176
156
|
else:
|
|
177
157
|
# For any other type, convert to string representation
|
|
178
158
|
result_dict = {"data": str(result), "snapshot_id": snapshot_id}
|
|
179
|
-
|
|
180
|
-
|
|
159
|
+
|
|
160
|
+
logger.debug(f"Result from get_snapshot: {result_dict}")
|
|
181
161
|
return result_dict
|
|
182
|
-
|
|
162
|
+
|
|
183
163
|
except Exception as sdk_error:
|
|
184
|
-
|
|
185
|
-
|
|
164
|
+
logger.warning(f"SDK method failed: {sdk_error}, trying fallback")
|
|
165
|
+
|
|
186
166
|
# Check if it's a "not found" error
|
|
187
167
|
error_str = str(sdk_error).lower()
|
|
188
168
|
if "not exist" in error_str or "not found" in error_str or "not available" in error_str:
|
|
@@ -190,66 +170,65 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
190
170
|
"error": f"Snapshot with ID '{snapshot_id}' does not exist or is not available.",
|
|
191
171
|
"details": str(sdk_error)
|
|
192
172
|
}
|
|
193
|
-
|
|
173
|
+
|
|
194
174
|
# Check if it's a validation error
|
|
195
175
|
if "validation error" in error_str:
|
|
196
176
|
# Try using the without_preload_content version to get the raw response
|
|
197
177
|
try:
|
|
198
|
-
response_data =
|
|
178
|
+
response_data = api_client.get_snapshot_without_preload_content(
|
|
199
179
|
id=snapshot_id,
|
|
200
180
|
to=to_time,
|
|
201
181
|
window_size=window_size
|
|
202
182
|
)
|
|
203
|
-
|
|
183
|
+
|
|
204
184
|
# Check if the response was successful
|
|
205
185
|
if response_data.status != 200:
|
|
206
186
|
error_message = f"Failed to get snapshot: HTTP {response_data.status}"
|
|
207
|
-
|
|
187
|
+
logger.error(error_message)
|
|
208
188
|
return {"error": error_message}
|
|
209
|
-
|
|
189
|
+
|
|
210
190
|
# Read the response content
|
|
211
191
|
response_text = response_data.data.decode('utf-8')
|
|
212
|
-
|
|
192
|
+
|
|
213
193
|
# Try to parse as JSON
|
|
214
194
|
import json
|
|
215
195
|
try:
|
|
216
196
|
result_dict = json.loads(response_text)
|
|
217
|
-
|
|
197
|
+
logger.info(f"Result from fallback method: {result_dict}")
|
|
218
198
|
return result_dict
|
|
219
199
|
except json.JSONDecodeError:
|
|
220
200
|
# If not valid JSON, return as string
|
|
221
|
-
|
|
201
|
+
logger.error(f"Result from fallback method (string): {response_text}")
|
|
222
202
|
return {"message": response_text, "snapshot_id": snapshot_id}
|
|
223
|
-
|
|
203
|
+
|
|
224
204
|
except Exception as fallback_error:
|
|
225
|
-
|
|
205
|
+
logger.error(f"Fallback method failed: {fallback_error}")
|
|
226
206
|
# Continue to the general error handling
|
|
227
|
-
|
|
207
|
+
|
|
228
208
|
# Re-raise if we couldn't handle it specifically
|
|
229
209
|
raise
|
|
230
|
-
|
|
210
|
+
|
|
231
211
|
except Exception as e:
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
traceback.print_exc(file=sys.stderr)
|
|
235
|
-
return {"error": f"Failed to get snapshot: {str(e)}"}
|
|
212
|
+
logger.error(f"Error in get_snapshot: {e}", exc_info=True)
|
|
213
|
+
return {"error": f"Failed to get snapshot: {e!s}"}
|
|
236
214
|
|
|
237
215
|
@register_as_tool
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
216
|
+
@with_header_auth(InfrastructureResourcesApi)
|
|
217
|
+
async def get_snapshots(self,
|
|
218
|
+
query: Optional[str] = None,
|
|
219
|
+
from_time: Optional[int] = None,
|
|
220
|
+
to_time: Optional[int] = None,
|
|
221
|
+
size: Optional[int] = 100,
|
|
222
|
+
plugin: Optional[str] = None,
|
|
223
|
+
offline: Optional[bool] = False,
|
|
224
|
+
detailed: Optional[bool] = False,
|
|
225
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
247
226
|
"""
|
|
248
227
|
Search and discover snapshots based on search criteria.
|
|
249
228
|
|
|
250
229
|
This tool is for finding and retrieving MULTIPLE entities that match your search parameters.
|
|
251
230
|
Use this when you need to discover entities, search across your infrastructure, or find components
|
|
252
|
-
matching certain criteria. This is NOT for retrieving details about a specific entity you already know -
|
|
231
|
+
matching certain criteria. This is NOT for retrieving details about a specific entity you already know -
|
|
253
232
|
use get_snapshot (singular) for that purpose.
|
|
254
233
|
|
|
255
234
|
For example, use this tool when:
|
|
@@ -267,22 +246,22 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
267
246
|
offline: Whether to include offline snapshots (optional, default False)
|
|
268
247
|
detailed: If True, returns full raw data. If False (default), returns summarized data
|
|
269
248
|
ctx: The MCP context (optional)
|
|
270
|
-
|
|
249
|
+
|
|
271
250
|
Returns:
|
|
272
251
|
Dictionary containing matching snapshots (summarized by default) or error information
|
|
273
252
|
"""
|
|
274
253
|
try:
|
|
275
|
-
|
|
276
|
-
|
|
254
|
+
logger.debug(f"get_snapshots called with query={query}, from_time={from_time}, to_time={to_time}, size={size}, detailed={detailed}")
|
|
255
|
+
|
|
277
256
|
# Set default time range if not provided
|
|
278
257
|
if not to_time:
|
|
279
258
|
to_time = int(datetime.now().timestamp() * 1000)
|
|
280
|
-
|
|
259
|
+
|
|
281
260
|
if not from_time:
|
|
282
261
|
from_time = to_time - (60 * 60 * 1000) # Default to 1 hour
|
|
283
|
-
|
|
262
|
+
|
|
284
263
|
# Call the get_snapshots method from the SDK
|
|
285
|
-
result =
|
|
264
|
+
result = api_client.get_snapshots(
|
|
286
265
|
query=query,
|
|
287
266
|
to=to_time,
|
|
288
267
|
window_size=to_time - from_time if from_time else None,
|
|
@@ -290,9 +269,9 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
290
269
|
plugin=plugin,
|
|
291
270
|
offline=offline
|
|
292
271
|
)
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
272
|
+
|
|
273
|
+
logger.debug(f"SDK returned result type: {type(result)}")
|
|
274
|
+
|
|
296
275
|
# Convert result to dictionary if needed
|
|
297
276
|
if hasattr(result, 'to_dict'):
|
|
298
277
|
result_dict = result.to_dict()
|
|
@@ -300,48 +279,46 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
300
279
|
result_dict = result
|
|
301
280
|
else:
|
|
302
281
|
result_dict = {"data": str(result)}
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
282
|
+
|
|
283
|
+
logger.debug(f"Result dict keys: {list(result_dict.keys()) if isinstance(result_dict, dict) else 'Not a dict'}")
|
|
284
|
+
|
|
306
285
|
# Return based on detailed parameter
|
|
307
286
|
if detailed:
|
|
308
|
-
|
|
287
|
+
logger.debug("Returning detailed/raw response")
|
|
309
288
|
return result_dict
|
|
310
289
|
else:
|
|
311
|
-
|
|
290
|
+
logger.debug("Returning summarized response")
|
|
312
291
|
return self._summarize_get_snapshots_response(result_dict)
|
|
313
|
-
|
|
292
|
+
|
|
314
293
|
except Exception as e:
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
traceback.print_exc(file=sys.stderr)
|
|
318
|
-
return {"error": f"Failed to get snapshots: {str(e)}"}
|
|
294
|
+
logger.error(f"Error in get_snapshots: {e}", exc_info=True)
|
|
295
|
+
return {"error": f"Failed to get snapshots: {e!s}"}
|
|
319
296
|
|
|
320
297
|
def _summarize_get_snapshots_response(self, response_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
321
298
|
"""
|
|
322
299
|
Create a summarized version of the get_snapshots response.
|
|
323
300
|
"""
|
|
324
301
|
try:
|
|
325
|
-
|
|
302
|
+
logger.debug("Creating summarized get_snapshots response...")
|
|
326
303
|
items = response_data.get('items', [])
|
|
327
|
-
|
|
304
|
+
|
|
328
305
|
if len(items) == 0:
|
|
329
306
|
return {
|
|
330
307
|
"message": "No snapshots found matching your criteria.",
|
|
331
308
|
"total_found": 0,
|
|
332
309
|
"snapshots": []
|
|
333
310
|
}
|
|
334
|
-
|
|
311
|
+
|
|
335
312
|
# Create a readable summary
|
|
336
313
|
summary_lines = [f"Found {len(items)} snapshot(s) matching your criteria:\n"]
|
|
337
|
-
|
|
314
|
+
|
|
338
315
|
snapshots_list = []
|
|
339
316
|
for i, item in enumerate(items, 1):
|
|
340
317
|
snapshot_id = item.get('snapshotId', 'Unknown')
|
|
341
318
|
label = item.get('label', 'No label')
|
|
342
319
|
host = item.get('host', 'Unknown host')
|
|
343
320
|
plugin = item.get('plugin', 'Unknown plugin')
|
|
344
|
-
|
|
321
|
+
|
|
345
322
|
# Parse host information
|
|
346
323
|
host_info = "Unknown"
|
|
347
324
|
if 'arn:aws:ecs' in host:
|
|
@@ -350,11 +327,11 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
350
327
|
cluster_info = parts[5].split('/') if len(parts) > 5 else []
|
|
351
328
|
region = parts[3] if len(parts) > 3 else "unknown"
|
|
352
329
|
cluster = cluster_info[1] if len(cluster_info) > 1 else "unknown"
|
|
353
|
-
|
|
330
|
+
cluster_info[2] if len(cluster_info) > 2 else "unknown"
|
|
354
331
|
host_info = f"AWS ECS Task in {region} (cluster: {cluster})"
|
|
355
332
|
else:
|
|
356
333
|
host_info = host
|
|
357
|
-
|
|
334
|
+
|
|
358
335
|
# Create readable entry
|
|
359
336
|
snapshot_entry = {
|
|
360
337
|
"number": i,
|
|
@@ -364,27 +341,25 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
364
341
|
"host_info": host_info,
|
|
365
342
|
"full_host": host
|
|
366
343
|
}
|
|
367
|
-
|
|
344
|
+
|
|
368
345
|
snapshots_list.append(snapshot_entry)
|
|
369
|
-
|
|
346
|
+
|
|
370
347
|
# Add to summary lines
|
|
371
348
|
summary_lines.append(f"{i}. Snapshot ID: {snapshot_id}")
|
|
372
349
|
summary_lines.append(f" Label: {label}")
|
|
373
350
|
summary_lines.append(f" Plugin: {plugin}")
|
|
374
351
|
summary_lines.append(f" Host: {host_info}")
|
|
375
352
|
summary_lines.append("") # Empty line for spacing
|
|
376
|
-
|
|
353
|
+
|
|
377
354
|
return {
|
|
378
355
|
"summary": "\n".join(summary_lines),
|
|
379
356
|
"total_found": len(items),
|
|
380
357
|
"snapshots": snapshots_list,
|
|
381
358
|
"message": f"Successfully found {len(items)} snapshot(s). See details above."
|
|
382
359
|
}
|
|
383
|
-
|
|
360
|
+
|
|
384
361
|
except Exception as e:
|
|
385
|
-
|
|
386
|
-
import traceback
|
|
387
|
-
traceback.print_exc(file=sys.stderr)
|
|
362
|
+
logger.error(f"Error summarizing get_snapshots response: {e}", exc_info=True)
|
|
388
363
|
return {
|
|
389
364
|
"error": "Failed to summarize response",
|
|
390
365
|
"details": str(e)
|
|
@@ -393,28 +368,29 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
393
368
|
|
|
394
369
|
|
|
395
370
|
@register_as_tool
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
371
|
+
@with_header_auth(InfrastructureResourcesApi)
|
|
372
|
+
async def post_snapshots(self,
|
|
373
|
+
snapshot_ids: Union[List[str], str],
|
|
374
|
+
to_time: Optional[int] = None,
|
|
375
|
+
window_size: Optional[int] = None,
|
|
376
|
+
detailed: Optional[bool] = False,
|
|
377
|
+
ctx=None, api_client=None) -> Dict[str, Any]:
|
|
402
378
|
"""
|
|
403
379
|
Get details for multiple snapshots by their IDs using SDK.
|
|
404
|
-
|
|
380
|
+
|
|
405
381
|
Args:
|
|
406
382
|
snapshot_ids: List of snapshot IDs to retrieve, or a comma-separated string of IDs
|
|
407
383
|
to_time: End timestamp in milliseconds (optional)
|
|
408
384
|
window_size: Window size in milliseconds (optional)
|
|
409
385
|
detailed: If True, returns full raw data. If False (default), returns summarized data
|
|
410
386
|
ctx: The MCP context (optional)
|
|
411
|
-
|
|
387
|
+
|
|
412
388
|
Returns:
|
|
413
389
|
Dictionary containing snapshot details (summarized by default) or error information
|
|
414
390
|
"""
|
|
415
391
|
try:
|
|
416
|
-
|
|
417
|
-
|
|
392
|
+
logger.debug(f"post_snapshots called with snapshot_ids={snapshot_ids}, detailed={detailed}")
|
|
393
|
+
|
|
418
394
|
# Handle string input conversion
|
|
419
395
|
if isinstance(snapshot_ids, str):
|
|
420
396
|
if snapshot_ids.startswith('[') and snapshot_ids.endswith(']'):
|
|
@@ -422,21 +398,21 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
422
398
|
snapshot_ids = ast.literal_eval(snapshot_ids)
|
|
423
399
|
else:
|
|
424
400
|
snapshot_ids = [id.strip() for id in snapshot_ids.split(',')]
|
|
425
|
-
|
|
401
|
+
|
|
426
402
|
if not snapshot_ids:
|
|
427
403
|
return {"error": "snapshot_ids parameter is required"}
|
|
428
|
-
|
|
404
|
+
|
|
429
405
|
# Use working timeframe
|
|
430
406
|
if not to_time:
|
|
431
407
|
to_time = 1745389956000
|
|
432
408
|
if not window_size:
|
|
433
409
|
window_size = 3600000
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
410
|
+
|
|
411
|
+
logger.debug(f"Using to_time={to_time}, window_size={window_size}")
|
|
412
|
+
|
|
437
413
|
if has_get_snapshots_query:
|
|
438
414
|
from instana_client.models.get_snapshots_query import GetSnapshotsQuery
|
|
439
|
-
|
|
415
|
+
|
|
440
416
|
query_obj = GetSnapshotsQuery(
|
|
441
417
|
snapshot_ids=snapshot_ids,
|
|
442
418
|
time_frame={
|
|
@@ -444,30 +420,30 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
444
420
|
"windowSize": window_size
|
|
445
421
|
}
|
|
446
422
|
)
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
423
|
+
|
|
424
|
+
logger.debug("Making SDK request with without_preload_content...")
|
|
425
|
+
|
|
450
426
|
# Use the working SDK method that bypasses model validation
|
|
451
|
-
response =
|
|
427
|
+
response = api_client.post_snapshots_without_preload_content(
|
|
452
428
|
get_snapshots_query=query_obj
|
|
453
429
|
)
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
430
|
+
|
|
431
|
+
logger.debug(f"SDK response status: {response.status}")
|
|
432
|
+
|
|
457
433
|
if response.status == 200:
|
|
458
434
|
# Parse the JSON response manually
|
|
459
435
|
import json
|
|
460
436
|
response_text = response.data.decode('utf-8')
|
|
461
437
|
result_dict = json.loads(response_text)
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
438
|
+
|
|
439
|
+
logger.debug(f"Successfully parsed response with {len(result_dict.get('items', []))} items")
|
|
440
|
+
|
|
465
441
|
# Return based on detailed parameter
|
|
466
442
|
if detailed:
|
|
467
|
-
|
|
443
|
+
logger.debug("Returning detailed/raw response")
|
|
468
444
|
return result_dict
|
|
469
445
|
else:
|
|
470
|
-
|
|
446
|
+
logger.debug("Returning summarized response")
|
|
471
447
|
return self._summarize_snapshots_response(result_dict)
|
|
472
448
|
else:
|
|
473
449
|
return {
|
|
@@ -476,29 +452,27 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
476
452
|
}
|
|
477
453
|
else:
|
|
478
454
|
return {"error": "GetSnapshotsQuery model not available"}
|
|
479
|
-
|
|
455
|
+
|
|
480
456
|
except Exception as e:
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
traceback.print_exc(file=sys.stderr)
|
|
484
|
-
return {"error": f"Failed to post snapshots: {str(e)}"}
|
|
457
|
+
logger.error(f"Error in post_snapshots: {e}", exc_info=True)
|
|
458
|
+
return {"error": f"Failed to post snapshots: {e!s}"}
|
|
485
459
|
|
|
486
460
|
def _summarize_snapshots_response(self, response_data: Dict[str, Any]) -> Dict[str, Any]:
|
|
487
461
|
"""
|
|
488
462
|
Create a summarized version of the snapshots response.
|
|
489
463
|
"""
|
|
490
464
|
try:
|
|
491
|
-
|
|
465
|
+
logger.debug("Creating summarized response...")
|
|
492
466
|
items = response_data.get('items', [])
|
|
493
|
-
|
|
467
|
+
|
|
494
468
|
summary = {
|
|
495
469
|
"total_snapshots": len(items),
|
|
496
470
|
"snapshots": []
|
|
497
471
|
}
|
|
498
|
-
|
|
472
|
+
|
|
499
473
|
for item in items:
|
|
500
|
-
|
|
501
|
-
|
|
474
|
+
logger.debug(f"Processing snapshot: {item.get('snapshotId')} - {item.get('plugin')}")
|
|
475
|
+
|
|
502
476
|
snapshot_summary = {
|
|
503
477
|
"snapshotId": item.get('snapshotId'),
|
|
504
478
|
"plugin": item.get('plugin'),
|
|
@@ -510,12 +484,12 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
510
484
|
},
|
|
511
485
|
"tags": item.get('tags', [])
|
|
512
486
|
}
|
|
513
|
-
|
|
487
|
+
|
|
514
488
|
# Extract key information from data based on plugin type
|
|
515
489
|
data = item.get('data', {})
|
|
516
|
-
|
|
490
|
+
|
|
517
491
|
if item.get('plugin') == 'jvmRuntimePlatform':
|
|
518
|
-
|
|
492
|
+
logger.debug("Processing JVM snapshot...")
|
|
519
493
|
snapshot_summary["key_info"] = {
|
|
520
494
|
"process_name": data.get('name'),
|
|
521
495
|
"pid": data.get('pid'),
|
|
@@ -529,7 +503,7 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
529
503
|
"jvm_collectors": data.get('jvm.collectors', [])
|
|
530
504
|
}
|
|
531
505
|
elif item.get('plugin') == 'nodeJsRuntimePlatform':
|
|
532
|
-
|
|
506
|
+
logger.debug("Processing Node.js snapshot...")
|
|
533
507
|
versions = data.get('versions', {})
|
|
534
508
|
snapshot_summary["key_info"] = {
|
|
535
509
|
"app_name": data.get('name'),
|
|
@@ -548,21 +522,19 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
548
522
|
}
|
|
549
523
|
else:
|
|
550
524
|
# Generic summary for other plugin types
|
|
551
|
-
|
|
525
|
+
logger.debug(f"Processing generic snapshot for plugin: {item.get('plugin')}")
|
|
552
526
|
snapshot_summary["key_info"] = {
|
|
553
527
|
"data_keys": list(data.keys())[:10], # First 10 keys
|
|
554
528
|
"total_data_fields": len(data.keys())
|
|
555
529
|
}
|
|
556
|
-
|
|
530
|
+
|
|
557
531
|
summary["snapshots"].append(snapshot_summary)
|
|
558
|
-
|
|
559
|
-
|
|
532
|
+
|
|
533
|
+
logger.debug(f"Created summary with {len(summary['snapshots'])} snapshots")
|
|
560
534
|
return summary
|
|
561
|
-
|
|
535
|
+
|
|
562
536
|
except Exception as e:
|
|
563
|
-
|
|
564
|
-
import traceback
|
|
565
|
-
traceback.print_exc(file=sys.stderr)
|
|
537
|
+
logger.error(f"Error summarizing response: {e}", exc_info=True)
|
|
566
538
|
return {
|
|
567
539
|
"error": "Failed to summarize response",
|
|
568
540
|
"details": str(e),
|
|
@@ -573,57 +545,58 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
573
545
|
|
|
574
546
|
|
|
575
547
|
@register_as_tool
|
|
576
|
-
|
|
548
|
+
@with_header_auth(InfrastructureResourcesApi)
|
|
549
|
+
async def software_versions(self, ctx=None, api_client=None) -> Dict[str, Any]:
|
|
577
550
|
"""
|
|
578
551
|
Get information about installed software versions across the monitored infrastructure.
|
|
579
552
|
Retrieve information about the software that are sensed by the agent remotely, natively, or both. This includes runtime and package manager information.
|
|
580
553
|
Args:
|
|
581
554
|
ctx: The MCP context (optional)
|
|
582
|
-
|
|
555
|
+
|
|
583
556
|
Returns:
|
|
584
557
|
Dictionary containing software version information or error information
|
|
585
558
|
"""
|
|
586
559
|
try:
|
|
587
|
-
|
|
588
|
-
|
|
560
|
+
logger.info("Calling software_versions API...")
|
|
561
|
+
|
|
589
562
|
# Call the software_versions method from the SDK with no parameters
|
|
590
|
-
result =
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
563
|
+
result = api_client.software_versions()
|
|
564
|
+
|
|
565
|
+
logger.info(f"API call successful. Result type: {type(result)}")
|
|
566
|
+
|
|
594
567
|
# Handle different response formats
|
|
595
568
|
if hasattr(result, 'to_dict'):
|
|
596
|
-
|
|
569
|
+
logger.info("Converting result using to_dict() method")
|
|
597
570
|
result_dict = result.to_dict()
|
|
598
571
|
elif isinstance(result, dict):
|
|
599
|
-
|
|
572
|
+
logger.info("Result is already a dictionary")
|
|
600
573
|
result_dict = result
|
|
601
574
|
elif isinstance(result, list):
|
|
602
|
-
|
|
575
|
+
logger.info("Result is a list")
|
|
603
576
|
result_dict = {"items": result}
|
|
604
577
|
else:
|
|
605
|
-
|
|
578
|
+
logger.info(f"Unexpected result format: {type(result)}")
|
|
606
579
|
# Try to convert to a dictionary or string representation
|
|
607
580
|
try:
|
|
608
581
|
result_dict = {"data": str(result)}
|
|
609
582
|
except Exception as str_error:
|
|
610
583
|
return {"error": f"Unexpected result format: {type(result)}", "details": str(str_error)}
|
|
611
|
-
|
|
584
|
+
|
|
612
585
|
# Print a sample of the result for debugging
|
|
613
586
|
if isinstance(result_dict, dict):
|
|
614
587
|
keys = list(result_dict.keys())
|
|
615
|
-
|
|
616
|
-
|
|
588
|
+
logger.info(f"Result keys: {keys}")
|
|
589
|
+
|
|
617
590
|
# If the result is very large, return a summary
|
|
618
591
|
if 'items' in result_dict and isinstance(result_dict['items'], list):
|
|
619
592
|
items_count = len(result_dict['items'])
|
|
620
|
-
|
|
621
|
-
|
|
593
|
+
logger.info(f"Found {items_count} items in the response")
|
|
594
|
+
|
|
622
595
|
# Limit the number of items to return
|
|
623
596
|
if items_count > 10:
|
|
624
597
|
result_dict['summary'] = f"Showing 10 of {items_count} items"
|
|
625
598
|
result_dict['items'] = result_dict['items'][:10]
|
|
626
|
-
|
|
599
|
+
|
|
627
600
|
# If tagTree exists, extract tag names
|
|
628
601
|
if 'tagTree' in result_dict and isinstance(result_dict['tagTree'], list):
|
|
629
602
|
tag_names = []
|
|
@@ -637,17 +610,13 @@ class InfrastructureResourcesMCPTools(BaseInstanaClient):
|
|
|
637
610
|
'tagName': tag['tagName'],
|
|
638
611
|
'description': tag.get('description', '')
|
|
639
612
|
})
|
|
640
|
-
|
|
613
|
+
|
|
641
614
|
# Replace the large tagTree with the extracted tag names
|
|
642
615
|
result_dict['tagNames'] = tag_names
|
|
643
616
|
del result_dict['tagTree']
|
|
644
|
-
|
|
617
|
+
|
|
645
618
|
return result_dict
|
|
646
619
|
except Exception as e:
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
traceback.print_exc(file=sys.stderr)
|
|
650
|
-
return {"error": f"Failed to get software versions: {str(e)}"}
|
|
651
|
-
|
|
652
|
-
|
|
620
|
+
logger.error(f"Error in software_versions: {e}", exc_info=True)
|
|
621
|
+
return {"error": f"Failed to get software versions: {e!s}"}
|
|
653
622
|
|