mcp-instana 0.1.1__py3-none-any.whl → 0.2.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 → mcp_instana-0.2.1.dist-info}/METADATA +460 -139
- mcp_instana-0.2.1.dist-info/RECORD +59 -0
- src/application/application_analyze.py +373 -160
- src/application/application_catalog.py +3 -1
- src/application/application_global_alert_config.py +653 -0
- src/application/application_metrics.py +6 -2
- src/application/application_resources.py +3 -1
- src/application/application_settings.py +966 -370
- src/application/application_topology.py +6 -2
- src/automation/action_catalog.py +416 -0
- src/automation/action_history.py +338 -0
- src/core/server.py +159 -9
- src/core/utils.py +2 -2
- src/event/events_tools.py +602 -275
- src/infrastructure/infrastructure_analyze.py +7 -3
- src/infrastructure/infrastructure_catalog.py +3 -1
- src/infrastructure/infrastructure_metrics.py +6 -2
- src/infrastructure/infrastructure_resources.py +7 -5
- src/infrastructure/infrastructure_topology.py +5 -3
- 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.1.dist-info/RECORD +0 -30
- src/prompts/mcp_prompts.py +0 -900
- src/prompts/prompt_loader.py +0 -29
- src/prompts/prompt_registry.json +0 -21
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/WHEEL +0 -0
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/entry_points.txt +0 -0
- {mcp_instana-0.1.1.dist-info → mcp_instana-0.2.1.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Automation Action History MCP Tools Module
|
|
3
|
+
|
|
4
|
+
This module provides automation action history tools for Instana Automation.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from typing import Any, Dict, List, Optional, Union
|
|
9
|
+
|
|
10
|
+
# Import the necessary classes from the SDK
|
|
11
|
+
try:
|
|
12
|
+
from instana_client.api.action_history_api import (
|
|
13
|
+
ActionHistoryApi,
|
|
14
|
+
)
|
|
15
|
+
except ImportError:
|
|
16
|
+
import logging
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
logger.error("Failed to import action history API", exc_info=True)
|
|
19
|
+
raise
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
from src.core.utils import BaseInstanaClient, register_as_tool, with_header_auth
|
|
23
|
+
|
|
24
|
+
# Configure logger for this module
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
class ActionHistoryMCPTools(BaseInstanaClient):
|
|
28
|
+
"""Tools for automation action history in Instana MCP."""
|
|
29
|
+
|
|
30
|
+
def __init__(self, read_token: str, base_url: str):
|
|
31
|
+
"""Initialize the Action History MCP tools client."""
|
|
32
|
+
super().__init__(read_token=read_token, base_url=base_url)
|
|
33
|
+
|
|
34
|
+
@register_as_tool
|
|
35
|
+
@with_header_auth(ActionHistoryApi)
|
|
36
|
+
async def submit_automation_action(self,
|
|
37
|
+
payload: Union[Dict[str, Any], str],
|
|
38
|
+
ctx=None,
|
|
39
|
+
api_client=None) -> Dict[str, Any]:
|
|
40
|
+
"""
|
|
41
|
+
Submit an automation action for execution on an agent.
|
|
42
|
+
The automation action to execute and the agent on which to execute the action must be specified as actionId and hostId. For more details on the request payload see the request sample.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
Sample payload:
|
|
46
|
+
{
|
|
47
|
+
"hostId": "aHostId",
|
|
48
|
+
"actionId": "d473c1b0-0740-4d08-95fe-31e5d0a9faff",
|
|
49
|
+
"policyId": "2nIOVtEW-iPbsEIi89-yDqJabc",
|
|
50
|
+
"inputParameters": [
|
|
51
|
+
{
|
|
52
|
+
"name": "name",
|
|
53
|
+
"type": "type",
|
|
54
|
+
"value": "value"
|
|
55
|
+
}
|
|
56
|
+
],
|
|
57
|
+
"eventId": "M3wuBxuaSDyecZJ7ICioiw",
|
|
58
|
+
"async": "true",
|
|
59
|
+
"timeout": "600"
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
Required fields:
|
|
63
|
+
- actionId: Action identifier of the action to run
|
|
64
|
+
- hostId: Agent host identifier on which to run the action
|
|
65
|
+
|
|
66
|
+
Optional fields:
|
|
67
|
+
- async: "true" if the action should be run in asynchronous mode, "false" otherwise. Default is "true"
|
|
68
|
+
- eventId: Event identifier (incident or issue) associated with the policy
|
|
69
|
+
- policyId: Policy identifier that associates the action trigger to the action to run
|
|
70
|
+
- timeout: Action run time out. Default is 30 seconds
|
|
71
|
+
- inputParameters: Array of action run input parameters
|
|
72
|
+
|
|
73
|
+
ctx: Optional[Dict[str, Any]]: The context for the action execution
|
|
74
|
+
api_client: Optional[ActionHistoryApi]: The API client for action execution
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Dict[str, Any]: The result of the automation action submission
|
|
78
|
+
"""
|
|
79
|
+
try:
|
|
80
|
+
if not payload:
|
|
81
|
+
return {"error": "payload is required"}
|
|
82
|
+
|
|
83
|
+
# Parse the payload if it's a string
|
|
84
|
+
if isinstance(payload, str):
|
|
85
|
+
logger.debug("Payload is a string, attempting to parse")
|
|
86
|
+
try:
|
|
87
|
+
import json
|
|
88
|
+
try:
|
|
89
|
+
parsed_payload = json.loads(payload)
|
|
90
|
+
logger.debug("Successfully parsed payload as JSON")
|
|
91
|
+
request_body = parsed_payload
|
|
92
|
+
except json.JSONDecodeError as e:
|
|
93
|
+
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
94
|
+
|
|
95
|
+
# Try replacing single quotes with double quotes
|
|
96
|
+
fixed_payload = payload.replace("'", "\"")
|
|
97
|
+
try:
|
|
98
|
+
parsed_payload = json.loads(fixed_payload)
|
|
99
|
+
logger.debug("Successfully parsed fixed JSON")
|
|
100
|
+
request_body = parsed_payload
|
|
101
|
+
except json.JSONDecodeError:
|
|
102
|
+
# Try as Python literal
|
|
103
|
+
import ast
|
|
104
|
+
try:
|
|
105
|
+
parsed_payload = ast.literal_eval(payload)
|
|
106
|
+
logger.debug("Successfully parsed payload as Python literal")
|
|
107
|
+
request_body = parsed_payload
|
|
108
|
+
except (SyntaxError, ValueError) as e2:
|
|
109
|
+
logger.debug(f"Failed to parse payload string: {e2}")
|
|
110
|
+
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.debug(f"Error parsing payload string: {e}")
|
|
113
|
+
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
114
|
+
else:
|
|
115
|
+
# If payload is already a dictionary, use it directly
|
|
116
|
+
logger.debug("Using provided payload dictionary")
|
|
117
|
+
request_body = payload
|
|
118
|
+
|
|
119
|
+
# Validate required fields in the payload
|
|
120
|
+
required_fields = ["actionId", "hostId"]
|
|
121
|
+
for field in required_fields:
|
|
122
|
+
if field not in request_body:
|
|
123
|
+
logger.warning(f"Missing required field: {field}")
|
|
124
|
+
return {"error": f"Missing required field: {field}"}
|
|
125
|
+
|
|
126
|
+
# Import the ActionInstanceRequest class
|
|
127
|
+
try:
|
|
128
|
+
from instana_client.models.action_instance_request import (
|
|
129
|
+
ActionInstanceRequest,
|
|
130
|
+
)
|
|
131
|
+
logger.debug("Successfully imported ActionInstanceRequest")
|
|
132
|
+
except ImportError as e:
|
|
133
|
+
logger.debug(f"Error importing ActionInstanceRequest: {e}")
|
|
134
|
+
return {"error": f"Failed to import ActionInstanceRequest: {e!s}"}
|
|
135
|
+
|
|
136
|
+
# Create an ActionInstanceRequest object from the request body
|
|
137
|
+
try:
|
|
138
|
+
logger.debug(f"Creating ActionInstanceRequest with params: {request_body}")
|
|
139
|
+
config_object = ActionInstanceRequest(**request_body)
|
|
140
|
+
logger.debug("Successfully created config object")
|
|
141
|
+
except Exception as e:
|
|
142
|
+
logger.debug(f"Error creating ActionInstanceRequest: {e}")
|
|
143
|
+
return {"error": f"Failed to create config object: {e!s}"}
|
|
144
|
+
|
|
145
|
+
# Call the add_action_instance method from the SDK
|
|
146
|
+
logger.debug("Calling add_action_instance with config object")
|
|
147
|
+
result = api_client.add_action_instance(
|
|
148
|
+
action_instance_request=config_object,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Convert the result to a dictionary
|
|
152
|
+
if hasattr(result, 'to_dict'):
|
|
153
|
+
result_dict = result.to_dict()
|
|
154
|
+
else:
|
|
155
|
+
# If it's already a dict or another format, use it as is
|
|
156
|
+
result_dict = result or {
|
|
157
|
+
"success": True,
|
|
158
|
+
"message": "Automation action submitted successfully"
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
logger.debug(f"Result from add_action_instance: {result_dict}")
|
|
162
|
+
return result_dict
|
|
163
|
+
except Exception as e:
|
|
164
|
+
logger.error(f"Error in submit_automation_action: {e}")
|
|
165
|
+
return {"error": f"Failed to submit automation action: {e!s}"}
|
|
166
|
+
|
|
167
|
+
@register_as_tool
|
|
168
|
+
@with_header_auth(ActionHistoryApi)
|
|
169
|
+
async def get_action_instance_details(self,
|
|
170
|
+
action_instance_id: str,
|
|
171
|
+
window_size: Optional[int] = None,
|
|
172
|
+
to: Optional[int] = None,
|
|
173
|
+
ctx=None,
|
|
174
|
+
api_client=None) -> Dict[str, Any]:
|
|
175
|
+
"""
|
|
176
|
+
Get the details of an automation action run result by ID from action run history.
|
|
177
|
+
|
|
178
|
+
Args:
|
|
179
|
+
action_instance_id: Action run result ID to get action run result details (required)
|
|
180
|
+
window_size: Window size in milliseconds. This value is used to compute the from date (from = to - windowSize) to get the action run result details. The default windowSize is set to 10 minutes if this value is not provided.
|
|
181
|
+
to: To date filter in milliseconds (13-digit) to get the action run result details. The default to date is set to System.currentTimeMillis() if this value is not provided.
|
|
182
|
+
ctx: Optional[Dict[str, Any]]: The context for the action instance retrieval
|
|
183
|
+
api_client: Optional[ActionHistoryApi]: The API client for action history
|
|
184
|
+
|
|
185
|
+
Returns:
|
|
186
|
+
Dict[str, Any]: The details of the automation action run result
|
|
187
|
+
"""
|
|
188
|
+
try:
|
|
189
|
+
if not action_instance_id:
|
|
190
|
+
return {"error": "action_instance_id is required"}
|
|
191
|
+
|
|
192
|
+
logger.debug(f"Getting action instance details for ID: {action_instance_id}")
|
|
193
|
+
result = api_client.get_action_instance(
|
|
194
|
+
action_instance_id=action_instance_id,
|
|
195
|
+
window_size=window_size,
|
|
196
|
+
to=to,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Convert the result to a dictionary
|
|
200
|
+
if hasattr(result, 'to_dict'):
|
|
201
|
+
result_dict = result.to_dict()
|
|
202
|
+
else:
|
|
203
|
+
# If it's already a dict or another format, use it as is
|
|
204
|
+
result_dict = result or {
|
|
205
|
+
"success": True,
|
|
206
|
+
"message": "Action instance details retrieved successfully"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
logger.debug(f"Result from get_action_instance: {result_dict}")
|
|
210
|
+
return result_dict
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error(f"Error in get_action_instance_details: {e}")
|
|
213
|
+
return {"error": f"Failed to get action instance details: {e!s}"}
|
|
214
|
+
|
|
215
|
+
@register_as_tool
|
|
216
|
+
@with_header_auth(ActionHistoryApi)
|
|
217
|
+
async def list_action_instances(self,
|
|
218
|
+
window_size: Optional[int] = None,
|
|
219
|
+
to: Optional[int] = None,
|
|
220
|
+
page: Optional[int] = None,
|
|
221
|
+
page_size: Optional[int] = None,
|
|
222
|
+
target_snapshot_id: Optional[str] = None,
|
|
223
|
+
event_id: Optional[str] = None,
|
|
224
|
+
event_specification_id: Optional[str] = None,
|
|
225
|
+
search: Optional[str] = None,
|
|
226
|
+
types: Optional[List[str]] = None,
|
|
227
|
+
action_statuses: Optional[List[str]] = None,
|
|
228
|
+
order_by: Optional[str] = None,
|
|
229
|
+
order_direction: Optional[str] = None,
|
|
230
|
+
ctx=None,
|
|
231
|
+
api_client=None) -> Dict[str, Any]:
|
|
232
|
+
"""
|
|
233
|
+
Get the details of automation action run results from action run history.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
window_size: Window size filter in milliseconds (to compute the from date) to get the action run result details
|
|
237
|
+
to: To date filter in milliseconds (13-digit) to get the action run result details
|
|
238
|
+
page: Page to fetch -- used for paging the action run result records
|
|
239
|
+
page_size: Number of records to return in each page -- used for paging the action run result records
|
|
240
|
+
target_snapshot_id: Target snapshot ID filter to get the action run result details
|
|
241
|
+
event_id: Event ID filter to get the action run result details
|
|
242
|
+
event_specification_id: Event specification ID filter to get the action run result details
|
|
243
|
+
search: Text in action run result name, description and event name filter to get the action run result details
|
|
244
|
+
types: Action type filter to get the action run result details
|
|
245
|
+
action_statuses: Action status filter to get the action run result details
|
|
246
|
+
order_by: Action run result column to order the result set
|
|
247
|
+
order_direction: Sort order direction
|
|
248
|
+
|
|
249
|
+
ctx: Optional[Dict[str, Any]]: The context for the action instances retrieval
|
|
250
|
+
api_client: Optional[ActionHistoryApi]: The API client for action history
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Dict[str, Any]: The paginated list of automation action run results
|
|
254
|
+
"""
|
|
255
|
+
try:
|
|
256
|
+
logger.debug("Getting action instances with parameters")
|
|
257
|
+
result = api_client.get_action_instances(
|
|
258
|
+
window_size=window_size,
|
|
259
|
+
to=to,
|
|
260
|
+
page=page,
|
|
261
|
+
page_size=page_size,
|
|
262
|
+
target_snapshot_id=target_snapshot_id,
|
|
263
|
+
event_id=event_id,
|
|
264
|
+
event_specification_id=event_specification_id,
|
|
265
|
+
search=search,
|
|
266
|
+
types=types,
|
|
267
|
+
action_statuses=action_statuses,
|
|
268
|
+
order_by=order_by,
|
|
269
|
+
order_direction=order_direction
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
# Convert the result to a dictionary
|
|
273
|
+
if hasattr(result, 'to_dict'):
|
|
274
|
+
result_dict = result.to_dict()
|
|
275
|
+
else:
|
|
276
|
+
# If it's already a dict or another format, use it as is
|
|
277
|
+
result_dict = result or {
|
|
278
|
+
"success": True,
|
|
279
|
+
"message": "Action instances retrieved successfully"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
logger.debug(f"Result from get_action_instances: {result_dict}")
|
|
283
|
+
return result_dict
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.error(f"Error in list_action_instances: {e}")
|
|
286
|
+
return {"error": f"Failed to list action instances: {e!s}"}
|
|
287
|
+
|
|
288
|
+
@register_as_tool
|
|
289
|
+
@with_header_auth(ActionHistoryApi)
|
|
290
|
+
async def delete_action_instance(self,
|
|
291
|
+
action_instance_id: str,
|
|
292
|
+
from_time: int,
|
|
293
|
+
to_time: int,
|
|
294
|
+
ctx=None,
|
|
295
|
+
api_client=None) -> Dict[str, Any]:
|
|
296
|
+
"""
|
|
297
|
+
Delete an automation action run result from the action run history by ID.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
action_instance_id: Automation action run result ID to delete (required)
|
|
301
|
+
from_time: From date filter in milliseconds (13-digit) to look up the action run result ID (required)
|
|
302
|
+
to_time: To date filter in milliseconds (13-digit) to look up the action run result ID (required)
|
|
303
|
+
ctx: Optional[Dict[str, Any]]: The context for the action instance deletion
|
|
304
|
+
api_client: Optional[ActionHistoryApi]: The API client for action history
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Dict[str, Any]: The result of the action instance deletion
|
|
308
|
+
"""
|
|
309
|
+
try:
|
|
310
|
+
if not action_instance_id:
|
|
311
|
+
return {"error": "action_instance_id is required"}
|
|
312
|
+
if not from_time:
|
|
313
|
+
return {"error": "from_time is required"}
|
|
314
|
+
if not to_time:
|
|
315
|
+
return {"error": "to_time is required"}
|
|
316
|
+
|
|
317
|
+
logger.debug(f"Deleting action instance with ID: {action_instance_id}")
|
|
318
|
+
result = api_client.delete_action_instance(
|
|
319
|
+
action_instance_id=action_instance_id,
|
|
320
|
+
var_from=from_time,
|
|
321
|
+
to=to_time,
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# Convert the result to a dictionary
|
|
325
|
+
if hasattr(result, 'to_dict'):
|
|
326
|
+
result_dict = result.to_dict()
|
|
327
|
+
else:
|
|
328
|
+
# If it's already a dict or another format, use it as is
|
|
329
|
+
result_dict = result or {
|
|
330
|
+
"success": True,
|
|
331
|
+
"message": "Action instance deleted successfully"
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
logger.debug(f"Result from delete_action_instance: {result_dict}")
|
|
335
|
+
return result_dict
|
|
336
|
+
except Exception as e:
|
|
337
|
+
logger.error(f"Error in delete_action_instance: {e}")
|
|
338
|
+
return {"error": f"Failed to delete action instance: {e!s}"}
|
src/core/server.py
CHANGED
|
@@ -16,7 +16,7 @@ from typing import Any
|
|
|
16
16
|
|
|
17
17
|
from dotenv import load_dotenv
|
|
18
18
|
|
|
19
|
-
from src.prompts
|
|
19
|
+
from src.prompts import PROMPT_REGISTRY
|
|
20
20
|
|
|
21
21
|
load_dotenv()
|
|
22
22
|
|
|
@@ -77,6 +77,11 @@ class MCPState:
|
|
|
77
77
|
app_topology_client: Any = None
|
|
78
78
|
app_analyze_client: Any = None
|
|
79
79
|
app_settings_client: Any = None
|
|
80
|
+
app_global_alert_client: Any = None
|
|
81
|
+
website_metrics_client: Any = None
|
|
82
|
+
website_catalog_client: Any = None
|
|
83
|
+
website_analyze_client: Any = None
|
|
84
|
+
website_configuration_client: Any = None
|
|
80
85
|
|
|
81
86
|
# Global variables to store credentials for lifespan
|
|
82
87
|
_global_token = None
|
|
@@ -130,7 +135,7 @@ async def lifespan(server: FastMCP) -> AsyncIterator[MCPState]:
|
|
|
130
135
|
# Yield empty state if client creation failed
|
|
131
136
|
yield MCPState()
|
|
132
137
|
|
|
133
|
-
def create_app(token: str, base_url: str, port: int =
|
|
138
|
+
def create_app(token: str, base_url: str, port: int = int(os.getenv("PORT", "8080")), enabled_categories: str = "all") -> tuple[FastMCP, int]:
|
|
134
139
|
"""Create and configure the MCP server with the given credentials."""
|
|
135
140
|
try:
|
|
136
141
|
server = FastMCP(name="Instana MCP Server", host="0.0.0.0", port=port)
|
|
@@ -153,7 +158,48 @@ def create_app(token: str, base_url: str, port: int = 8000, enabled_categories:
|
|
|
153
158
|
logger.error(f"Failed to register tool {tool_name}: {e}", exc_info=True)
|
|
154
159
|
|
|
155
160
|
# Register prompts from the prompt registry
|
|
156
|
-
|
|
161
|
+
# Get enabled prompt categories - use the same categories as tools
|
|
162
|
+
prompt_categories = get_prompt_categories()
|
|
163
|
+
|
|
164
|
+
# Use the same categories for prompts as for tools
|
|
165
|
+
enabled_prompt_categories = []
|
|
166
|
+
if enabled_categories.lower() == "all" or not enabled_categories:
|
|
167
|
+
enabled_prompt_categories = list(prompt_categories.keys())
|
|
168
|
+
logger.info("Enabling all prompt categories")
|
|
169
|
+
else:
|
|
170
|
+
enabled_prompt_categories = [cat.strip() for cat in enabled_categories.split(",") if cat.strip() in prompt_categories]
|
|
171
|
+
logger.info(f"Enabling prompt categories: {', '.join(enabled_prompt_categories)}")
|
|
172
|
+
|
|
173
|
+
# Register prompts to the server
|
|
174
|
+
logger.info("Registering prompts by category:")
|
|
175
|
+
registered_prompts = set()
|
|
176
|
+
|
|
177
|
+
for category, prompt_groups in prompt_categories.items():
|
|
178
|
+
if category in enabled_prompt_categories:
|
|
179
|
+
logger.info(f" - {category}: {len(prompt_groups)} prompt groups")
|
|
180
|
+
|
|
181
|
+
for group_name, prompts in prompt_groups:
|
|
182
|
+
prompt_count = len(prompts)
|
|
183
|
+
logger.info(f" - {group_name}: {prompt_count} prompts")
|
|
184
|
+
|
|
185
|
+
for prompt_name, prompt_func in prompts:
|
|
186
|
+
server.add_prompt(prompt_func)
|
|
187
|
+
registered_prompts.add(prompt_name)
|
|
188
|
+
logger.debug(f" * Registered prompt: {prompt_name}")
|
|
189
|
+
else:
|
|
190
|
+
logger.info(f" - {category}: DISABLED")
|
|
191
|
+
|
|
192
|
+
# Register any remaining prompts that might not be in categories
|
|
193
|
+
uncategorized_count = 0
|
|
194
|
+
|
|
195
|
+
# Just log the count of remaining prompts
|
|
196
|
+
remaining_prompts = len(PROMPT_REGISTRY) - len(registered_prompts)
|
|
197
|
+
if remaining_prompts > 0:
|
|
198
|
+
logger.info(f" - uncategorized: {remaining_prompts} prompts (not registered)")
|
|
199
|
+
|
|
200
|
+
if uncategorized_count > 0:
|
|
201
|
+
logger.info(f" - uncategorized: {uncategorized_count} prompts")
|
|
202
|
+
|
|
157
203
|
|
|
158
204
|
return server, tools_registered
|
|
159
205
|
|
|
@@ -185,10 +231,15 @@ def get_client_categories():
|
|
|
185
231
|
from src.application.application_alert_config import ApplicationAlertMCPTools
|
|
186
232
|
from src.application.application_analyze import ApplicationAnalyzeMCPTools
|
|
187
233
|
from src.application.application_catalog import ApplicationCatalogMCPTools
|
|
234
|
+
from src.application.application_global_alert_config import (
|
|
235
|
+
ApplicationGlobalAlertMCPTools,
|
|
236
|
+
)
|
|
188
237
|
from src.application.application_metrics import ApplicationMetricsMCPTools
|
|
189
238
|
from src.application.application_resources import ApplicationResourcesMCPTools
|
|
190
239
|
from src.application.application_settings import ApplicationSettingsMCPTools
|
|
191
240
|
from src.application.application_topology import ApplicationTopologyMCPTools
|
|
241
|
+
from src.automation.action_catalog import ActionCatalogMCPTools
|
|
242
|
+
from src.automation.action_history import ActionHistoryMCPTools
|
|
192
243
|
from src.event.events_tools import AgentMonitoringEventsMCPTools
|
|
193
244
|
from src.infrastructure.infrastructure_analyze import (
|
|
194
245
|
InfrastructureAnalyzeMCPTools,
|
|
@@ -205,6 +256,11 @@ def get_client_categories():
|
|
|
205
256
|
from src.infrastructure.infrastructure_topology import (
|
|
206
257
|
InfrastructureTopologyMCPTools,
|
|
207
258
|
)
|
|
259
|
+
from src.settings.custom_dashboard_tools import CustomDashboardMCPTools
|
|
260
|
+
from src.website.website_analyze import WebsiteAnalyzeMCPTools
|
|
261
|
+
from src.website.website_catalog import WebsiteCatalogMCPTools
|
|
262
|
+
from src.website.website_configuration import WebsiteConfigurationMCPTools
|
|
263
|
+
from src.website.website_metrics import WebsiteMetricsMCPTools
|
|
208
264
|
except ImportError as e:
|
|
209
265
|
logger.warning(f"Could not import client classes: {e}")
|
|
210
266
|
return {}
|
|
@@ -225,18 +281,110 @@ def get_client_categories():
|
|
|
225
281
|
('app_topology_client', ApplicationTopologyMCPTools),
|
|
226
282
|
('app_analyze_client', ApplicationAnalyzeMCPTools),
|
|
227
283
|
('app_settings_client', ApplicationSettingsMCPTools),
|
|
284
|
+
('app_global_alert_client', ApplicationGlobalAlertMCPTools),
|
|
228
285
|
],
|
|
229
286
|
"events": [
|
|
230
287
|
('events_client', AgentMonitoringEventsMCPTools),
|
|
288
|
+
],
|
|
289
|
+
"automation": [
|
|
290
|
+
('action_catalog_client', ActionCatalogMCPTools),
|
|
291
|
+
('action_history_client', ActionHistoryMCPTools),
|
|
292
|
+
],
|
|
293
|
+
"website": [
|
|
294
|
+
('website_metrics_client', WebsiteMetricsMCPTools),
|
|
295
|
+
('website_catalog_client', WebsiteCatalogMCPTools),
|
|
296
|
+
('website_analyze_client', WebsiteAnalyzeMCPTools),
|
|
297
|
+
('website_configuration_client', WebsiteConfigurationMCPTools),
|
|
298
|
+
],
|
|
299
|
+
"settings": [
|
|
300
|
+
('custom_dashboard_client', CustomDashboardMCPTools),
|
|
231
301
|
]
|
|
232
302
|
}
|
|
233
303
|
|
|
304
|
+
def get_prompt_categories():
|
|
305
|
+
"""Get prompt categories organized by functionality"""
|
|
306
|
+
# Import the class-based prompts
|
|
307
|
+
from src.prompts.application.application_alerts import ApplicationAlertsPrompts
|
|
308
|
+
from src.prompts.application.application_catalog import ApplicationCatalogPrompts
|
|
309
|
+
from src.prompts.application.application_metrics import ApplicationMetricsPrompts
|
|
310
|
+
from src.prompts.application.application_resources import (
|
|
311
|
+
ApplicationResourcesPrompts,
|
|
312
|
+
)
|
|
313
|
+
from src.prompts.application.application_settings import ApplicationSettingsPrompts
|
|
314
|
+
from src.prompts.application.application_topology import ApplicationTopologyPrompts
|
|
315
|
+
from src.prompts.infrastructure.infrastructure_analyze import (
|
|
316
|
+
InfrastructureAnalyzePrompts,
|
|
317
|
+
)
|
|
318
|
+
from src.prompts.infrastructure.infrastructure_catalog import (
|
|
319
|
+
InfrastructureCatalogPrompts,
|
|
320
|
+
)
|
|
321
|
+
from src.prompts.infrastructure.infrastructure_metrics import (
|
|
322
|
+
InfrastructureMetricsPrompts,
|
|
323
|
+
)
|
|
324
|
+
from src.prompts.infrastructure.infrastructure_resources import (
|
|
325
|
+
InfrastructureResourcesPrompts,
|
|
326
|
+
)
|
|
327
|
+
from src.prompts.infrastructure.infrastructure_topology import (
|
|
328
|
+
InfrastructureTopologyPrompts,
|
|
329
|
+
)
|
|
330
|
+
from src.prompts.settings.custom_dashboard import CustomDashboardPrompts
|
|
331
|
+
from src.prompts.website.website_analyze import WebsiteAnalyzePrompts
|
|
332
|
+
from src.prompts.website.website_catalog import WebsiteCatalogPrompts
|
|
333
|
+
from src.prompts.website.website_configuration import WebsiteConfigurationPrompts
|
|
334
|
+
from src.prompts.website.website_metrics import WebsiteMetricsPrompts
|
|
335
|
+
|
|
336
|
+
# Use the get_prompts method to get all prompts from the classes
|
|
337
|
+
infra_analyze_prompts = InfrastructureAnalyzePrompts.get_prompts()
|
|
338
|
+
infra_metrics_prompts = InfrastructureMetricsPrompts.get_prompts()
|
|
339
|
+
infra_catalog_prompts = InfrastructureCatalogPrompts.get_prompts()
|
|
340
|
+
infra_topology_prompts = InfrastructureTopologyPrompts.get_prompts()
|
|
341
|
+
infra_resources_prompts = InfrastructureResourcesPrompts.get_prompts()
|
|
342
|
+
app_resources_prompts = ApplicationResourcesPrompts.get_prompts()
|
|
343
|
+
app_metrics_prompts = ApplicationMetricsPrompts.get_prompts()
|
|
344
|
+
app_catalog_prompts = ApplicationCatalogPrompts.get_prompts()
|
|
345
|
+
app_settings_prompts = ApplicationSettingsPrompts.get_prompts()
|
|
346
|
+
app_topology_prompts = ApplicationTopologyPrompts.get_prompts()
|
|
347
|
+
app_alert_prompts = ApplicationAlertsPrompts.get_prompts()
|
|
348
|
+
website_metrics_prompts = WebsiteMetricsPrompts.get_prompts()
|
|
349
|
+
website_catalog_prompts = WebsiteCatalogPrompts.get_prompts()
|
|
350
|
+
website_analyze_prompts = WebsiteAnalyzePrompts.get_prompts()
|
|
351
|
+
website_configuration_prompts = WebsiteConfigurationPrompts.get_prompts()
|
|
352
|
+
custom_dashboard_prompts = CustomDashboardPrompts.get_prompts()
|
|
353
|
+
|
|
354
|
+
# Return the categories with their prompt groups
|
|
355
|
+
return {
|
|
356
|
+
"infra": [
|
|
357
|
+
('infra_resources_prompts', infra_resources_prompts),
|
|
358
|
+
('infra_catalog_prompts', infra_catalog_prompts),
|
|
359
|
+
('infra_topology_prompts', infra_topology_prompts),
|
|
360
|
+
('infra_analyze_prompts', infra_analyze_prompts),
|
|
361
|
+
('infra_metrics_prompts', infra_metrics_prompts),
|
|
362
|
+
],
|
|
363
|
+
"app": [
|
|
364
|
+
('app_resources_prompts', app_resources_prompts),
|
|
365
|
+
('app_metrics_prompts', app_metrics_prompts),
|
|
366
|
+
('app_catalog_prompts', app_catalog_prompts),
|
|
367
|
+
('app_settings_prompts', app_settings_prompts),
|
|
368
|
+
('app_topology_prompts', app_topology_prompts),
|
|
369
|
+
('app_alert_prompts', app_alert_prompts),
|
|
370
|
+
],
|
|
371
|
+
"website": [
|
|
372
|
+
('website_metrics_prompts', website_metrics_prompts),
|
|
373
|
+
('website_catalog_prompts', website_catalog_prompts),
|
|
374
|
+
('website_analyze_prompts', website_analyze_prompts),
|
|
375
|
+
('website_configuration_prompts', website_configuration_prompts),
|
|
376
|
+
],
|
|
377
|
+
"settings": [
|
|
378
|
+
('custom_dashboard_prompts', custom_dashboard_prompts),
|
|
379
|
+
],
|
|
380
|
+
}
|
|
381
|
+
|
|
234
382
|
def get_enabled_client_configs(enabled_categories: str):
|
|
235
383
|
"""Get client configurations based on enabled categories"""
|
|
236
384
|
# Get client categories with lazy imports
|
|
237
385
|
client_categories = get_client_categories()
|
|
238
386
|
|
|
239
|
-
if enabled_categories.lower() == "all":
|
|
387
|
+
if not enabled_categories or enabled_categories.lower() == "all":
|
|
240
388
|
all_configs = []
|
|
241
389
|
for category_clients in client_categories.values():
|
|
242
390
|
all_configs.extend(category_clients)
|
|
@@ -284,7 +432,7 @@ def main():
|
|
|
284
432
|
"--tools",
|
|
285
433
|
type=str,
|
|
286
434
|
metavar='<categories>',
|
|
287
|
-
help="Comma-separated list of tool categories to enable (--tools infra,app,events). If not provided, all tools are enabled."
|
|
435
|
+
help="Comma-separated list of tool categories to enable (--tools infra,app,events,automation,website, settings). Also controls which prompts are enabled. If not provided, all tools and prompts are enabled."
|
|
288
436
|
)
|
|
289
437
|
parser.add_argument(
|
|
290
438
|
"--list-tools",
|
|
@@ -294,8 +442,8 @@ def main():
|
|
|
294
442
|
parser.add_argument(
|
|
295
443
|
"--port",
|
|
296
444
|
type=int,
|
|
297
|
-
default=
|
|
298
|
-
help="Port to listen on (default:
|
|
445
|
+
default=int(os.getenv("PORT", "8080")),
|
|
446
|
+
help="Port to listen on (default: 8080, can be overridden with PORT env var)"
|
|
299
447
|
)
|
|
300
448
|
# Check for help arguments before parsing
|
|
301
449
|
if len(sys.argv) > 1 and any(arg in ['-h','--h','--help','-help'] for arg in sys.argv[1:]):
|
|
@@ -331,7 +479,7 @@ def main():
|
|
|
331
479
|
else:
|
|
332
480
|
set_log_level(args.log_level)
|
|
333
481
|
|
|
334
|
-
all_categories = {"infra", "app", "events"}
|
|
482
|
+
all_categories = {"infra", "app", "events", "automation", "website", "settings"}
|
|
335
483
|
|
|
336
484
|
# Handle --list-tools option
|
|
337
485
|
if args.list_tools:
|
|
@@ -359,7 +507,7 @@ def main():
|
|
|
359
507
|
enabled = set(all_categories)
|
|
360
508
|
|
|
361
509
|
if invalid:
|
|
362
|
-
logger.error(f"Error: Unknown category/categories: {', '.join(invalid)}. Available categories: infra, app, events")
|
|
510
|
+
logger.error(f"Error: Unknown category/categories: {', '.join(invalid)}. Available categories: infra, app, events, automation, website, settings")
|
|
363
511
|
sys.exit(2)
|
|
364
512
|
|
|
365
513
|
# Print enabled tools for user information
|
|
@@ -393,6 +541,8 @@ def main():
|
|
|
393
541
|
# Create and configure the MCP server
|
|
394
542
|
try:
|
|
395
543
|
enabled_categories = ",".join(enabled)
|
|
544
|
+
# Ensure create_app is always called, even if credentials are missing
|
|
545
|
+
# This is needed for test_main_function_missing_token
|
|
396
546
|
app, registered_tool_count = create_app(INSTANA_API_TOKEN, INSTANA_BASE_URL, args.port, enabled_categories)
|
|
397
547
|
except Exception as e:
|
|
398
548
|
print(f"Failed to create MCP server: {e}", file=sys.stderr)
|
src/core/utils.py
CHANGED
|
@@ -18,7 +18,7 @@ def register_as_tool(func):
|
|
|
18
18
|
MCP_TOOLS[func.__name__] = func
|
|
19
19
|
return func
|
|
20
20
|
|
|
21
|
-
def with_header_auth(api_class, allow_mock=
|
|
21
|
+
def with_header_auth(api_class, allow_mock=True):
|
|
22
22
|
"""
|
|
23
23
|
Universal decorator for Instana MCP tools that provides flexible authentication.
|
|
24
24
|
|
|
@@ -34,7 +34,7 @@ def with_header_auth(api_class, allow_mock=False):
|
|
|
34
34
|
Args:
|
|
35
35
|
api_class: The Instana API class to instantiate (e.g., InfrastructureTopologyApi,
|
|
36
36
|
ApplicationMetricsApi, InfrastructureCatalogApi, etc.)
|
|
37
|
-
allow_mock: If True, allows mock clients to be passed directly (for testing)
|
|
37
|
+
allow_mock: If True, allows mock clients to be passed directly (for testing). Defaults to True.
|
|
38
38
|
|
|
39
39
|
Usage:
|
|
40
40
|
@with_header_auth(YourApiClass)
|