mcp-instana 0.3.1__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/METADATA +186 -311
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/RECORD +30 -22
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/WHEEL +1 -1
- src/application/application_alert_config.py +393 -136
- src/application/application_analyze.py +597 -594
- src/application/application_call_group.py +528 -0
- src/application/application_catalog.py +0 -8
- src/application/application_global_alert_config.py +275 -57
- src/application/application_metrics.py +377 -237
- src/application/application_resources.py +414 -325
- src/application/application_settings.py +608 -1530
- src/application/application_topology.py +62 -62
- src/core/custom_dashboard_smart_router_tool.py +135 -0
- src/core/server.py +95 -119
- src/core/smart_router_tool.py +574 -0
- src/core/utils.py +17 -8
- src/custom_dashboard/custom_dashboard_tools.py +422 -0
- src/event/events_tools.py +57 -9
- src/infrastructure/elicitation_handler.py +338 -0
- src/infrastructure/entity_registry.py +329 -0
- src/infrastructure/infrastructure_analyze_new.py +600 -0
- src/infrastructure/{infrastructure_analyze.py → infrastructure_analyze_old.py} +1 -16
- src/infrastructure/infrastructure_catalog.py +37 -32
- src/infrastructure/infrastructure_metrics.py +93 -16
- src/infrastructure/infrastructure_resources.py +6 -24
- src/infrastructure/infrastructure_topology.py +29 -23
- src/observability.py +29 -0
- src/prompts/application/application_settings.py +58 -0
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/entry_points.txt +0 -0
- {mcp_instana-0.3.1.dist-info → mcp_instana-0.7.0.dist-info}/licenses/LICENSE.md +0 -0
|
@@ -34,6 +34,8 @@ try:
|
|
|
34
34
|
NewApplicationConfig, #type: ignore
|
|
35
35
|
NewManualServiceConfig, #type: ignore
|
|
36
36
|
ServiceConfig, #type: ignore
|
|
37
|
+
TagFilter, #type: ignore
|
|
38
|
+
TagFilterExpression, #type: ignore
|
|
37
39
|
)
|
|
38
40
|
except ImportError as e:
|
|
39
41
|
print(f"Error importing Instana SDK: {e}", file=sys.stderr)
|
|
@@ -71,14 +73,101 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
71
73
|
traceback.print_exc(file=sys.stderr)
|
|
72
74
|
raise
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
76
|
+
# CRUD Operations Dispatcher - called by smart_router_tool.py
|
|
77
|
+
async def execute_settings_operation(
|
|
78
|
+
self,
|
|
79
|
+
operation: str,
|
|
80
|
+
resource_subtype: str,
|
|
81
|
+
id: Optional[str] = None,
|
|
82
|
+
payload: Optional[Union[Dict[str, Any], str]] = None,
|
|
83
|
+
request_body: Optional[List[str]] = None,
|
|
84
|
+
ctx=None
|
|
85
|
+
) -> Dict[str, Any]:
|
|
86
|
+
"""
|
|
87
|
+
Execute Application Settings CRUD operations.
|
|
88
|
+
Called by the smart router tool.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
operation: Operation to perform (get_all, get, create, update, delete, order, replace_all)
|
|
92
|
+
resource_subtype: Type of settings resource (application, endpoint, service, manual_service)
|
|
93
|
+
id: Resource ID (for get, update, delete operations)
|
|
94
|
+
payload: Configuration payload (for create, update operations)
|
|
95
|
+
request_body: List of IDs (for order, replace_all operations)
|
|
96
|
+
ctx: MCP context
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Operation result dictionary
|
|
100
|
+
"""
|
|
101
|
+
try:
|
|
102
|
+
# Route based on resource_subtype and operation
|
|
103
|
+
if resource_subtype == "application":
|
|
104
|
+
if operation == "get_all":
|
|
105
|
+
return await self._get_all_applications_configs(ctx)
|
|
106
|
+
elif operation == "get":
|
|
107
|
+
return await self._get_application_config(id, ctx)
|
|
108
|
+
elif operation == "create":
|
|
109
|
+
return await self._add_application_config(payload, ctx)
|
|
110
|
+
elif operation == "update":
|
|
111
|
+
return await self._update_application_config(id, payload, ctx)
|
|
112
|
+
elif operation == "delete":
|
|
113
|
+
return await self._delete_application_config(id, ctx)
|
|
114
|
+
|
|
115
|
+
elif resource_subtype == "endpoint":
|
|
116
|
+
if operation == "get_all":
|
|
117
|
+
return await self._get_all_endpoint_configs(ctx)
|
|
118
|
+
elif operation == "get":
|
|
119
|
+
return await self._get_endpoint_config(id, ctx)
|
|
120
|
+
elif operation == "create":
|
|
121
|
+
return await self._create_endpoint_config(payload, ctx)
|
|
122
|
+
elif operation == "update":
|
|
123
|
+
return await self._update_endpoint_config(id, payload, ctx)
|
|
124
|
+
elif operation == "delete":
|
|
125
|
+
return await self._delete_endpoint_config(id, ctx)
|
|
126
|
+
|
|
127
|
+
elif resource_subtype == "service":
|
|
128
|
+
if operation == "get_all":
|
|
129
|
+
return await self._get_all_service_configs(ctx)
|
|
130
|
+
elif operation == "get":
|
|
131
|
+
return await self._get_service_config(id, ctx)
|
|
132
|
+
elif operation == "create":
|
|
133
|
+
return await self._add_service_config(payload, ctx)
|
|
134
|
+
elif operation == "update":
|
|
135
|
+
return await self._update_service_config(id, payload, ctx)
|
|
136
|
+
elif operation == "delete":
|
|
137
|
+
return await self._delete_service_config(id, ctx)
|
|
138
|
+
elif operation == "order":
|
|
139
|
+
return await self._order_service_config(request_body, ctx)
|
|
140
|
+
elif operation == "replace_all":
|
|
141
|
+
return await self._replace_all_service_configs(payload, ctx)
|
|
142
|
+
|
|
143
|
+
elif resource_subtype == "manual_service":
|
|
144
|
+
if operation == "get_all":
|
|
145
|
+
return await self._get_all_manual_service_configs(ctx)
|
|
146
|
+
elif operation == "create":
|
|
147
|
+
return await self._add_manual_service_config(payload, ctx)
|
|
148
|
+
elif operation == "update":
|
|
149
|
+
return await self._update_manual_service_config(id, payload, ctx)
|
|
150
|
+
elif operation == "delete":
|
|
151
|
+
return await self._delete_manual_service_config(id, ctx)
|
|
152
|
+
elif operation == "replace_all":
|
|
153
|
+
return await self._replace_all_manual_service_config(payload, ctx)
|
|
154
|
+
|
|
155
|
+
return {"error": f"Operation '{operation}' not supported for resource_subtype '{resource_subtype}'"}
|
|
156
|
+
|
|
157
|
+
except Exception as e:
|
|
158
|
+
logger.error(f"Error executing {operation} on {resource_subtype}: {e}", exc_info=True)
|
|
159
|
+
return {"error": f"Error executing {operation} on {resource_subtype}: {e!s}"}
|
|
160
|
+
|
|
161
|
+
# Individual operation functions
|
|
162
|
+
|
|
163
|
+
# @register_as_tool(
|
|
164
|
+
# title="Get All Applications Configs",
|
|
165
|
+
# annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
166
|
+
# )
|
|
78
167
|
@with_header_auth(ApplicationSettingsApi)
|
|
79
|
-
async def
|
|
80
|
-
|
|
81
|
-
|
|
168
|
+
async def _get_all_applications_configs(self,
|
|
169
|
+
ctx=None,
|
|
170
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
82
171
|
"""
|
|
83
172
|
All Application Perspectives Configuration
|
|
84
173
|
Get a list of all Application Perspectives with their configuration settings.
|
|
@@ -115,1693 +204,682 @@ class ApplicationSettingsMCPTools(BaseInstanaClient):
|
|
|
115
204
|
traceback.print_exc(file=sys.stderr)
|
|
116
205
|
return [{"error": f"Failed to get all applications: {e!s}"}]
|
|
117
206
|
|
|
118
|
-
|
|
119
|
-
title="Add Application Config",
|
|
120
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
121
|
-
)
|
|
122
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
123
|
-
async def add_application_config(self,
|
|
124
|
-
payload: Union[Dict[str, Any], str],
|
|
125
|
-
ctx=None,
|
|
126
|
-
api_client=None) -> Dict[str, Any]:
|
|
207
|
+
def _validate_and_prepare_application_payload(self, payload: Union[Dict[str, Any], str]) -> Dict[str, Any]:
|
|
127
208
|
"""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
Sample Payload: {
|
|
131
|
-
"accessRules": [
|
|
132
|
-
{
|
|
133
|
-
"accessType": "READ_WRITE",
|
|
134
|
-
"relationType": "GLOBAL",
|
|
135
|
-
"relatedId": null
|
|
136
|
-
}
|
|
137
|
-
],
|
|
138
|
-
"boundaryScope": "INBOUND",
|
|
139
|
-
"label": "Discount Build 6987",
|
|
140
|
-
"scope": "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING",
|
|
141
|
-
"tagFilterExpression": {
|
|
142
|
-
"type": "EXPRESSION",
|
|
143
|
-
"logicalOperator": "AND",
|
|
144
|
-
"elements": [
|
|
145
|
-
{
|
|
146
|
-
"type": "TAG_FILTER",
|
|
147
|
-
"name": "kubernetes.label",
|
|
148
|
-
"stringValue": "stage=canary",
|
|
149
|
-
"numberValue": null,
|
|
150
|
-
"booleanValue": null,
|
|
151
|
-
"key": "stage",
|
|
152
|
-
"value": "canary",
|
|
153
|
-
"operator": "EQUALS",
|
|
154
|
-
"entity": "DESTINATION"
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
"type": "TAG_FILTER",
|
|
158
|
-
"name": "kubernetes.label",
|
|
159
|
-
"stringValue": "build=6987",
|
|
160
|
-
"numberValue": null,
|
|
161
|
-
"booleanValue": null,
|
|
162
|
-
"key": "build",
|
|
163
|
-
"value": "6987",
|
|
164
|
-
"operator": "EQUALS",
|
|
165
|
-
"entity": "DESTINATION"
|
|
166
|
-
},
|
|
167
|
-
{
|
|
168
|
-
"type": "TAG_FILTER",
|
|
169
|
-
"name": "kubernetes.label",
|
|
170
|
-
"stringValue": "app=discount",
|
|
171
|
-
"numberValue": null,
|
|
172
|
-
"booleanValue": null,
|
|
173
|
-
"key": "app",
|
|
174
|
-
"value": "discount",
|
|
175
|
-
"operator": "EQUALS",
|
|
176
|
-
"entity": "DESTINATION"
|
|
177
|
-
}
|
|
178
|
-
]
|
|
179
|
-
}
|
|
180
|
-
}
|
|
209
|
+
Validate and prepare application configuration payload with proper defaults.
|
|
210
|
+
|
|
181
211
|
Returns:
|
|
182
|
-
|
|
212
|
+
Dict with either 'payload' (validated) or 'error' and 'missing_fields'
|
|
183
213
|
"""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
return {"error": "payload is required"}
|
|
188
|
-
|
|
189
|
-
# Parse the payload if it's a string
|
|
190
|
-
if isinstance(payload, str):
|
|
191
|
-
logger.debug("Payload is a string, attempting to parse")
|
|
192
|
-
try:
|
|
193
|
-
import json
|
|
194
|
-
try:
|
|
195
|
-
parsed_payload = json.loads(payload)
|
|
196
|
-
logger.debug("Successfully parsed payload as JSON")
|
|
197
|
-
request_body = parsed_payload
|
|
198
|
-
except json.JSONDecodeError as e:
|
|
199
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
200
|
-
|
|
201
|
-
# Try replacing single quotes with double quotes
|
|
202
|
-
fixed_payload = payload.replace("'", "\"")
|
|
203
|
-
try:
|
|
204
|
-
parsed_payload = json.loads(fixed_payload)
|
|
205
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
206
|
-
request_body = parsed_payload
|
|
207
|
-
except json.JSONDecodeError:
|
|
208
|
-
# Try as Python literal
|
|
209
|
-
import ast
|
|
210
|
-
try:
|
|
211
|
-
parsed_payload = ast.literal_eval(payload)
|
|
212
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
213
|
-
request_body = parsed_payload
|
|
214
|
-
except (SyntaxError, ValueError) as e2:
|
|
215
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
216
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
217
|
-
except Exception as e:
|
|
218
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
219
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
220
|
-
else:
|
|
221
|
-
# If payload is already a dictionary, use it directly
|
|
222
|
-
logger.debug("Using provided payload dictionary")
|
|
223
|
-
request_body = payload
|
|
224
|
-
|
|
225
|
-
# Import the NewApplicationConfig class
|
|
226
|
-
try:
|
|
227
|
-
from instana_client.models.new_application_config import (
|
|
228
|
-
NewApplicationConfig,
|
|
229
|
-
)
|
|
230
|
-
logger.debug("Successfully imported NewApplicationConfig")
|
|
231
|
-
except ImportError as e:
|
|
232
|
-
logger.debug(f"Error importing NewApplicationConfig: {e}")
|
|
233
|
-
return {"error": f"Failed to import NewApplicationConfig: {e!s}"}
|
|
234
|
-
|
|
235
|
-
# Create an NewApplicationConfig object from the request body
|
|
214
|
+
# Parse the payload if it's a string
|
|
215
|
+
if isinstance(payload, str):
|
|
216
|
+
import json
|
|
236
217
|
try:
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
218
|
+
request_body = json.loads(payload)
|
|
219
|
+
except json.JSONDecodeError:
|
|
220
|
+
import ast
|
|
221
|
+
try:
|
|
222
|
+
request_body = ast.literal_eval(payload)
|
|
223
|
+
except (SyntaxError, ValueError) as e:
|
|
224
|
+
return {"error": f"Invalid payload format: {e}"}
|
|
225
|
+
else:
|
|
226
|
+
request_body = payload.copy() if payload else {}
|
|
227
|
+
|
|
228
|
+
# Define required fields
|
|
229
|
+
required_fields = ['label']
|
|
230
|
+
missing_fields = []
|
|
231
|
+
|
|
232
|
+
# Check for required fields
|
|
233
|
+
for field in required_fields:
|
|
234
|
+
if field not in request_body or not request_body[field]:
|
|
235
|
+
missing_fields.append(field)
|
|
236
|
+
|
|
237
|
+
if missing_fields:
|
|
238
|
+
return {
|
|
239
|
+
"error": "Missing required fields for application configuration",
|
|
240
|
+
"missing_fields": missing_fields,
|
|
241
|
+
"required_fields": {
|
|
242
|
+
"label": "Application perspective name (string, required)",
|
|
243
|
+
},
|
|
244
|
+
"optional_fields": {
|
|
245
|
+
"tagFilterExpression": "Tag filter to match services (dict, optional - defaults to empty EXPRESSION)",
|
|
246
|
+
"scope": "Monitoring scope (string, optional - defaults to 'INCLUDE_ALL_DOWNSTREAM')",
|
|
247
|
+
"boundaryScope": "Boundary scope (string, optional - defaults to 'ALL')",
|
|
248
|
+
"accessRules": "Access control rules (list, optional - defaults to READ_WRITE GLOBAL access)"
|
|
249
|
+
},
|
|
250
|
+
"scope_options": ["INCLUDE_ALL_DOWNSTREAM", "INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING", "INCLUDE_NO_DOWNSTREAM"],
|
|
251
|
+
"boundary_scope_options": ["ALL", "INBOUND", "DEFAULT"],
|
|
252
|
+
"access_rules_options": ["READ_WRITE_GLOBAL", "READ_ONLY_GLOBAL", "CUSTOM"],
|
|
253
|
+
"elicitation_prompt": "Please provide the following configuration options:\n1. Scope (INCLUDE_ALL_DOWNSTREAM/INCLUDE_IMMEDIATE_DOWNSTREAM_DATABASE_AND_MESSAGING/INCLUDE_NO_DOWNSTREAM)\n2. Boundary Scope (ALL/INBOUND/DEFAULT)\n3. Access Rules (READ_WRITE_GLOBAL/READ_ONLY_GLOBAL/CUSTOM)\n4. Tag Filter Expression (optional)",
|
|
254
|
+
"example_minimal": {
|
|
255
|
+
"label": "My Application"
|
|
256
|
+
},
|
|
257
|
+
"example_with_options": {
|
|
258
|
+
"label": "My Application",
|
|
259
|
+
"scope": "INCLUDE_ALL_DOWNSTREAM",
|
|
260
|
+
"boundaryScope": "ALL",
|
|
261
|
+
"accessRules": [{"accessType": "READ_WRITE", "relationType": "GLOBAL"}],
|
|
262
|
+
"tagFilterExpression": {
|
|
263
|
+
"type": "TAG_FILTER",
|
|
264
|
+
"name": "service.name",
|
|
265
|
+
"operator": "CONTAINS",
|
|
266
|
+
"entity": "DESTINATION",
|
|
267
|
+
"value": "my-service"
|
|
268
|
+
}
|
|
258
269
|
}
|
|
270
|
+
}
|
|
259
271
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
return {"error": f"Failed to add new application config: {e!s}"}
|
|
265
|
-
|
|
266
|
-
@register_as_tool(
|
|
267
|
-
title="Delete Application Config",
|
|
268
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
269
|
-
)
|
|
270
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
271
|
-
async def delete_application_config(self,
|
|
272
|
-
id: str,
|
|
273
|
-
ctx=None,
|
|
274
|
-
api_client=None) -> Dict[str, Any]:
|
|
275
|
-
"""
|
|
276
|
-
Delete an Application Perspective configuration.
|
|
277
|
-
This tool allows you to delete an existing Application Perspective by its ID.
|
|
278
|
-
|
|
279
|
-
Args:
|
|
280
|
-
application_id: The ID of the application perspective to delete
|
|
281
|
-
ctx: The MCP context (optional)
|
|
282
|
-
|
|
283
|
-
Returns:
|
|
284
|
-
Dictionary containing the result of the deletion or error information
|
|
285
|
-
"""
|
|
286
|
-
try:
|
|
287
|
-
if not id:
|
|
288
|
-
return {"error": "Application perspective ID is required for deletion"}
|
|
289
|
-
|
|
272
|
+
# Apply defaults for optional fields only if not provided
|
|
273
|
+
if 'scope' not in request_body:
|
|
274
|
+
request_body['scope'] = 'INCLUDE_ALL_DOWNSTREAM'
|
|
275
|
+
debug_print("Applied default scope: INCLUDE_ALL_DOWNSTREAM")
|
|
290
276
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
277
|
+
if 'boundaryScope' not in request_body:
|
|
278
|
+
request_body['boundaryScope'] = 'ALL'
|
|
279
|
+
debug_print("Applied default boundaryScope: ALL")
|
|
294
280
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
281
|
+
if 'accessRules' not in request_body:
|
|
282
|
+
request_body['accessRules'] = [
|
|
283
|
+
{
|
|
284
|
+
"accessType": "READ_WRITE",
|
|
285
|
+
"relationType": "GLOBAL"
|
|
286
|
+
}
|
|
287
|
+
]
|
|
288
|
+
debug_print("Applied default accessRules: READ_WRITE GLOBAL")
|
|
289
|
+
|
|
290
|
+
# If no tagFilterExpression provided, use empty EXPRESSION
|
|
291
|
+
if 'tagFilterExpression' not in request_body:
|
|
292
|
+
request_body['tagFilterExpression'] = {
|
|
293
|
+
"type": "EXPRESSION",
|
|
294
|
+
"logicalOperator": "AND",
|
|
295
|
+
"elements": []
|
|
298
296
|
}
|
|
297
|
+
debug_print("Applied default tagFilterExpression: empty EXPRESSION")
|
|
298
|
+
|
|
299
|
+
# Convert nested tagFilterExpression to model objects if present
|
|
300
|
+
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
301
|
+
tag_expr = request_body['tagFilterExpression']
|
|
302
|
+
|
|
303
|
+
# Handle EXPRESSION type with nested elements
|
|
304
|
+
if tag_expr.get('type') == 'EXPRESSION' and 'elements' in tag_expr:
|
|
305
|
+
converted_elements = []
|
|
306
|
+
for element in tag_expr['elements']:
|
|
307
|
+
if isinstance(element, dict):
|
|
308
|
+
element_copy = element.copy()
|
|
309
|
+
element_copy.pop('value', None)
|
|
310
|
+
element_copy.pop('key', None)
|
|
311
|
+
converted_elements.append(TagFilter(**element_copy))
|
|
312
|
+
else:
|
|
313
|
+
converted_elements.append(element)
|
|
314
|
+
tag_expr['elements'] = converted_elements
|
|
315
|
+
request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
|
|
316
|
+
|
|
317
|
+
# Handle TAG_FILTER type (simple filter)
|
|
318
|
+
elif tag_expr.get('type') == 'TAG_FILTER':
|
|
319
|
+
# For TAG_FILTER, ensure both 'value' and 'stringValue' are present
|
|
320
|
+
# Don't convert to TagFilter model - keep as dict to preserve both fields
|
|
321
|
+
tag_filter_copy = tag_expr.copy()
|
|
322
|
+
tag_filter_copy.pop('key', None)
|
|
323
|
+
if 'stringValue' not in tag_filter_copy and 'value' in tag_expr:
|
|
324
|
+
tag_filter_copy['stringValue'] = tag_expr['value']
|
|
325
|
+
if 'value' not in tag_filter_copy and 'stringValue' in tag_expr:
|
|
326
|
+
tag_filter_copy['value'] = tag_expr['stringValue']
|
|
327
|
+
# Keep as dictionary - don't convert to TagFilter model
|
|
328
|
+
request_body['tagFilterExpression'] = tag_filter_copy
|
|
329
|
+
|
|
330
|
+
return {"payload": request_body}
|
|
299
331
|
|
|
300
|
-
debug_print(f"Successfully deleted application perspective with ID: {id}")
|
|
301
|
-
return result_dict
|
|
302
|
-
except Exception as e:
|
|
303
|
-
debug_print(f"Error in delete_application_config: {e}")
|
|
304
|
-
traceback.print_exc(file=sys.stderr)
|
|
305
|
-
return {"error": f"Failed to delete application configuration: {e!s}"}
|
|
306
|
-
|
|
307
|
-
@register_as_tool(
|
|
308
|
-
title="Get Application Config",
|
|
309
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
310
|
-
)
|
|
311
332
|
@with_header_auth(ApplicationSettingsApi)
|
|
312
|
-
async def
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
333
|
+
async def _add_application_config(self,
|
|
334
|
+
payload: Union[Dict[str, Any], str],
|
|
335
|
+
ctx=None,
|
|
336
|
+
api_client=None) -> Dict[str, Any]:
|
|
316
337
|
"""
|
|
317
|
-
|
|
318
|
-
This tool retrieves the configuration settings for a specific Application Perspective.
|
|
338
|
+
Add a new Application Perspective configuration.
|
|
319
339
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
ctx: The MCP context (optional)
|
|
340
|
+
Required fields:
|
|
341
|
+
- label: Application perspective name
|
|
323
342
|
|
|
324
|
-
|
|
325
|
-
|
|
343
|
+
Optional fields (with defaults):
|
|
344
|
+
- tagFilterExpression: Tag filter (defaults to empty EXPRESSION)
|
|
345
|
+
- scope: Monitoring scope (defaults to 'INCLUDE_ALL_DOWNSTREAM')
|
|
346
|
+
- boundaryScope: Boundary scope (defaults to 'ALL')
|
|
347
|
+
- accessRules: Access rules (defaults to READ_WRITE GLOBAL)
|
|
326
348
|
"""
|
|
327
349
|
try:
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
350
|
+
if not payload:
|
|
351
|
+
return {
|
|
352
|
+
"error": "payload is required",
|
|
353
|
+
"required_fields": {
|
|
354
|
+
"label": "Application perspective name (string, required)"
|
|
355
|
+
},
|
|
356
|
+
"example": {
|
|
357
|
+
"label": "My Application"
|
|
358
|
+
}
|
|
359
|
+
}
|
|
331
360
|
|
|
332
|
-
#
|
|
333
|
-
|
|
334
|
-
result_dict = result.to_dict()
|
|
335
|
-
else:
|
|
336
|
-
result_dict = result
|
|
361
|
+
# Validate and prepare payload with defaults
|
|
362
|
+
validation_result = self._validate_and_prepare_application_payload(payload)
|
|
337
363
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
except Exception as e:
|
|
341
|
-
debug_print(f"Error in get_application_config: {e}")
|
|
342
|
-
traceback.print_exc(file=sys.stderr)
|
|
343
|
-
return {"error": f"Failed to get application configuration: {e!s}"}
|
|
364
|
+
if "error" in validation_result:
|
|
365
|
+
return validation_result
|
|
344
366
|
|
|
345
|
-
|
|
346
|
-
title="Update Application Config",
|
|
347
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
348
|
-
)
|
|
349
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
350
|
-
async def update_application_config(
|
|
351
|
-
self,
|
|
352
|
-
id: str,
|
|
353
|
-
payload: Union[Dict[str, Any], str],
|
|
354
|
-
ctx=None,
|
|
355
|
-
api_client=None
|
|
356
|
-
) -> Dict[str, Any]:
|
|
357
|
-
"""
|
|
358
|
-
Update an existing Application Perspective configuration.
|
|
359
|
-
This tool allows you to update an existing Application Perspective with specified application Id.
|
|
367
|
+
request_body = validation_result["payload"]
|
|
360
368
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
Sample payload: {
|
|
364
|
-
"accessRules": [
|
|
365
|
-
{
|
|
366
|
-
"accessType": "READ",
|
|
367
|
-
"relationType": "ROLE"
|
|
368
|
-
}
|
|
369
|
-
],
|
|
370
|
-
"boundaryScope": "INBOUND",
|
|
371
|
-
"id": "CxJ55sRbQwqBIfw5DzpRmQ",
|
|
372
|
-
"label": "Discount Build 1",
|
|
373
|
-
"scope": "INCLUDE_NO_DOWNSTREAM",
|
|
374
|
-
"tagFilterExpression": {
|
|
375
|
-
"type": "EXPRESSION",
|
|
376
|
-
"logicalOperator": "AND",
|
|
377
|
-
"elements": [
|
|
378
|
-
{
|
|
379
|
-
"type": "TAG_FILTER",
|
|
380
|
-
"name": "kubernetes.label",
|
|
381
|
-
"stringValue": "stage=canary",
|
|
382
|
-
"numberValue": null,
|
|
383
|
-
"booleanValue": null,
|
|
384
|
-
"key": "stage",
|
|
385
|
-
"value": "canary",
|
|
386
|
-
"operator": "EQUALS",
|
|
387
|
-
"entity": "DESTINATION"
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
"type": "TAG_FILTER",
|
|
391
|
-
"name": "kubernetes.label",
|
|
392
|
-
"stringValue": "build=6987",
|
|
393
|
-
"numberValue": null,
|
|
394
|
-
"booleanValue": null,
|
|
395
|
-
"key": "build",
|
|
396
|
-
"value": "6987",
|
|
397
|
-
"operator": "EQUALS",
|
|
398
|
-
"entity": "DESTINATION"
|
|
399
|
-
},
|
|
400
|
-
{
|
|
401
|
-
"type": "TAG_FILTER",
|
|
402
|
-
"name": "kubernetes.label",
|
|
403
|
-
"stringValue": "app=discount",
|
|
404
|
-
"numberValue": null,
|
|
405
|
-
"booleanValue": null,
|
|
406
|
-
"key": "app",
|
|
407
|
-
"value": "discount",
|
|
408
|
-
"operator": "EQUALS",
|
|
409
|
-
"entity": "DESTINATION"
|
|
410
|
-
}
|
|
411
|
-
]
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
ctx: The MCP context (optional)
|
|
415
|
-
Returns:
|
|
416
|
-
Dictionary containing the created application perspective configuration or error information
|
|
417
|
-
"""
|
|
418
|
-
try:
|
|
369
|
+
# Debug: Log the request body before creating the config object
|
|
370
|
+
debug_print(f"DEBUG: request_body before NewApplicationConfig: {request_body}")
|
|
419
371
|
|
|
420
|
-
|
|
421
|
-
return {"error": "missing arguments"}
|
|
372
|
+
config_object = NewApplicationConfig(**request_body)
|
|
422
373
|
|
|
423
|
-
#
|
|
424
|
-
if
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
try:
|
|
429
|
-
parsed_payload = json.loads(payload)
|
|
430
|
-
logger.debug("Successfully parsed payload as JSON")
|
|
431
|
-
request_body = parsed_payload
|
|
432
|
-
except json.JSONDecodeError as e:
|
|
433
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
434
|
-
|
|
435
|
-
# Try replacing single quotes with double quotes
|
|
436
|
-
fixed_payload = payload.replace("'", "\"")
|
|
437
|
-
try:
|
|
438
|
-
parsed_payload = json.loads(fixed_payload)
|
|
439
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
440
|
-
request_body = parsed_payload
|
|
441
|
-
except json.JSONDecodeError:
|
|
442
|
-
# Try as Python literal
|
|
443
|
-
import ast
|
|
444
|
-
try:
|
|
445
|
-
parsed_payload = ast.literal_eval(payload)
|
|
446
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
447
|
-
request_body = parsed_payload
|
|
448
|
-
except (SyntaxError, ValueError) as e2:
|
|
449
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
450
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
451
|
-
except Exception as e:
|
|
452
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
453
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
454
|
-
else:
|
|
455
|
-
# If payload is already a dictionary, use it directly
|
|
456
|
-
logger.debug("Using provided payload dictionary")
|
|
457
|
-
request_body = payload
|
|
374
|
+
# Debug: Log what the config object looks like after creation
|
|
375
|
+
if hasattr(config_object, 'to_dict'):
|
|
376
|
+
debug_print(f"DEBUG: config_object.to_dict(): {config_object.to_dict()}")
|
|
377
|
+
|
|
378
|
+
result = api_client.add_application_config(new_application_config=config_object)
|
|
458
379
|
|
|
459
|
-
# Import the ApplicationConfig class
|
|
460
|
-
try:
|
|
461
|
-
from instana_client.models.application_config import (
|
|
462
|
-
ApplicationConfig,
|
|
463
|
-
)
|
|
464
|
-
logger.debug("Successfully imported ApplicationConfig")
|
|
465
|
-
except ImportError as e:
|
|
466
|
-
logger.debug(f"Error importing ApplicationConfig: {e}")
|
|
467
|
-
return {"error": f"Failed to import ApplicationConfig: {e!s}"}
|
|
468
|
-
|
|
469
|
-
# Create an ApplicationConfig object from the request body
|
|
470
|
-
try:
|
|
471
|
-
logger.debug(f"Creating ApplicationConfig with params: {request_body}")
|
|
472
|
-
config_object = ApplicationConfig(**request_body)
|
|
473
|
-
logger.debug("Successfully updated application config object")
|
|
474
|
-
except Exception as e:
|
|
475
|
-
logger.debug(f"Error updating ApplicationConfig: {e}")
|
|
476
|
-
return {"error": f"Failed to update config object: {e!s}"}
|
|
477
|
-
|
|
478
|
-
# Call the put_application_config method from the SDK
|
|
479
|
-
logger.debug("Calling put_application_config with config object")
|
|
480
|
-
result = api_client.put_application_config(
|
|
481
|
-
id=id,
|
|
482
|
-
application_config=config_object
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
# Convert the result to a dictionary
|
|
486
380
|
if hasattr(result, 'to_dict'):
|
|
487
381
|
result_dict = result.to_dict()
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
"
|
|
492
|
-
"
|
|
382
|
+
# Add helpful information about what was created
|
|
383
|
+
return {
|
|
384
|
+
**result_dict,
|
|
385
|
+
"message": f"Application perspective '{request_body.get('label')}' created successfully",
|
|
386
|
+
"applied_defaults": {
|
|
387
|
+
"scope": request_body.get('scope'),
|
|
388
|
+
"boundaryScope": request_body.get('boundaryScope'),
|
|
389
|
+
"accessRules": "READ_WRITE GLOBAL" if not payload or 'accessRules' not in (payload if isinstance(payload, dict) else {}) else "Custom"
|
|
390
|
+
}
|
|
493
391
|
}
|
|
494
|
-
|
|
495
|
-
logger.debug(f"Result from put_application_config: {result_dict}")
|
|
496
|
-
return result_dict
|
|
392
|
+
return result or {"success": True, "message": "Application config created"}
|
|
497
393
|
except Exception as e:
|
|
498
|
-
logger.error(f"Error in
|
|
499
|
-
return {"error": f"Failed to
|
|
394
|
+
logger.error(f"Error in _add_application_config: {e}", exc_info=True)
|
|
395
|
+
return {"error": f"Failed to add application config: {e!s}"}
|
|
500
396
|
|
|
501
|
-
@register_as_tool(
|
|
502
|
-
title="Get All Endpoint Configs",
|
|
503
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
504
|
-
)
|
|
505
397
|
@with_header_auth(ApplicationSettingsApi)
|
|
506
|
-
async def
|
|
507
|
-
|
|
508
|
-
|
|
398
|
+
async def _get_application_config(self,
|
|
399
|
+
id: str,
|
|
400
|
+
ctx=None,
|
|
401
|
+
api_client=None) -> Dict[str, Any]:
|
|
509
402
|
"""
|
|
510
|
-
|
|
511
|
-
|
|
403
|
+
Get an Application Perspective configuration by ID.
|
|
404
|
+
|
|
405
|
+
Note: To get by application name instead of ID, use the smart router tool
|
|
406
|
+
with application_name parameter. The router will automatically resolve
|
|
407
|
+
the name to ID and call this method.
|
|
408
|
+
|
|
512
409
|
Args:
|
|
513
|
-
|
|
410
|
+
id: Application perspective configuration ID
|
|
411
|
+
ctx: MCP context
|
|
412
|
+
api_client: API client instance
|
|
514
413
|
|
|
515
414
|
Returns:
|
|
516
|
-
|
|
415
|
+
Application configuration dictionary
|
|
517
416
|
"""
|
|
518
417
|
try:
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
# Convert the result to a dictionary
|
|
522
|
-
if hasattr(result, 'to_dict'):
|
|
523
|
-
result_dict = result.to_dict()
|
|
524
|
-
else:
|
|
525
|
-
# If it's already a dict or another format, use it as is
|
|
526
|
-
result_dict = result
|
|
527
|
-
|
|
528
|
-
debug_print(f"Result from get_endpoint_configs: {result_dict}")
|
|
529
|
-
return result_dict
|
|
418
|
+
if not id:
|
|
419
|
+
return {"error": "id is required"}
|
|
530
420
|
|
|
421
|
+
result = api_client.get_application_config(id=id)
|
|
422
|
+
if hasattr(result, 'to_dict'):
|
|
423
|
+
return result.to_dict()
|
|
424
|
+
return result
|
|
531
425
|
except Exception as e:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
return [{"error": f"Failed to get endpoint configs: {e!s}"}]
|
|
426
|
+
logger.error(f"Error in _get_application_config: {e}", exc_info=True)
|
|
427
|
+
return {"error": f"Failed to get application config: {e!s}"}
|
|
535
428
|
|
|
536
|
-
@register_as_tool(
|
|
537
|
-
title="Create Endpoint Config",
|
|
538
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
539
|
-
)
|
|
540
429
|
@with_header_auth(ApplicationSettingsApi)
|
|
541
|
-
async def
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
"""
|
|
548
|
-
Create or update endpoint configuration for a service.
|
|
549
|
-
|
|
550
|
-
Sample Payload: {
|
|
551
|
-
"serviceId": "d0cedae516f2182ede16f57f67476dd4c7dab9cd",
|
|
552
|
-
"endpointCase": "LOWER",
|
|
553
|
-
"endpointNameByFirstPathSegmentRuleEnabled": false,
|
|
554
|
-
"endpointNameByCollectedPathTemplateRuleEnabled": false,
|
|
555
|
-
"rules": null
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
Returns:
|
|
559
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
560
|
-
"""
|
|
430
|
+
async def _update_application_config(self,
|
|
431
|
+
id: str,
|
|
432
|
+
payload: Union[Dict[str, Any], str],
|
|
433
|
+
ctx=None,
|
|
434
|
+
api_client=None) -> Dict[str, Any]:
|
|
435
|
+
"""Update an existing Application Perspective configuration."""
|
|
561
436
|
try:
|
|
562
|
-
if not payload:
|
|
563
|
-
return {"error": "
|
|
437
|
+
if not id or not payload:
|
|
438
|
+
return {"error": "id and payload are required"}
|
|
564
439
|
|
|
565
440
|
# Parse the payload if it's a string
|
|
566
441
|
if isinstance(payload, str):
|
|
567
|
-
|
|
442
|
+
import json
|
|
568
443
|
try:
|
|
569
|
-
|
|
444
|
+
request_body = json.loads(payload)
|
|
445
|
+
except json.JSONDecodeError:
|
|
446
|
+
import ast
|
|
570
447
|
try:
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
except json.JSONDecodeError as e:
|
|
575
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
576
|
-
|
|
577
|
-
# Try replacing single quotes with double quotes
|
|
578
|
-
fixed_payload = payload.replace("'", "\"")
|
|
579
|
-
try:
|
|
580
|
-
parsed_payload = json.loads(fixed_payload)
|
|
581
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
582
|
-
request_body = parsed_payload
|
|
583
|
-
except json.JSONDecodeError:
|
|
584
|
-
# Try as Python literal
|
|
585
|
-
import ast
|
|
586
|
-
try:
|
|
587
|
-
parsed_payload = ast.literal_eval(payload)
|
|
588
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
589
|
-
request_body = parsed_payload
|
|
590
|
-
except (SyntaxError, ValueError) as e2:
|
|
591
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
592
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
593
|
-
except Exception as e:
|
|
594
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
595
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
448
|
+
request_body = ast.literal_eval(payload)
|
|
449
|
+
except (SyntaxError, ValueError) as e:
|
|
450
|
+
return {"error": f"Invalid payload format: {e}"}
|
|
596
451
|
else:
|
|
597
|
-
# If payload is already a dictionary, use it directly
|
|
598
|
-
logger.debug("Using provided payload dictionary")
|
|
599
452
|
request_body = payload
|
|
600
453
|
|
|
601
|
-
#
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
454
|
+
# Convert nested tagFilterExpression to model objects if present
|
|
455
|
+
if 'tagFilterExpression' in request_body and isinstance(request_body['tagFilterExpression'], dict):
|
|
456
|
+
tag_expr = request_body['tagFilterExpression']
|
|
457
|
+
|
|
458
|
+
# Handle EXPRESSION type with nested elements
|
|
459
|
+
if tag_expr.get('type') == 'EXPRESSION' and 'elements' in tag_expr:
|
|
460
|
+
converted_elements = []
|
|
461
|
+
for element in tag_expr['elements']:
|
|
462
|
+
if isinstance(element, dict):
|
|
463
|
+
element_copy = element.copy()
|
|
464
|
+
element_copy.pop('value', None)
|
|
465
|
+
element_copy.pop('key', None)
|
|
466
|
+
converted_elements.append(TagFilter(**element_copy))
|
|
467
|
+
else:
|
|
468
|
+
converted_elements.append(element)
|
|
469
|
+
tag_expr['elements'] = converted_elements
|
|
470
|
+
request_body['tagFilterExpression'] = TagFilterExpression(**tag_expr)
|
|
471
|
+
|
|
472
|
+
# Handle TAG_FILTER type (simple filter)
|
|
473
|
+
elif tag_expr.get('type') == 'TAG_FILTER':
|
|
474
|
+
# For TAG_FILTER, ensure both 'value' and 'stringValue' are present
|
|
475
|
+
# Don't convert to TagFilter model - keep as dict to preserve both fields
|
|
476
|
+
tag_filter_copy = tag_expr.copy()
|
|
477
|
+
tag_filter_copy.pop('key', None)
|
|
478
|
+
if 'stringValue' not in tag_filter_copy and 'value' in tag_expr:
|
|
479
|
+
tag_filter_copy['stringValue'] = tag_expr['value']
|
|
480
|
+
if 'value' not in tag_filter_copy and 'stringValue' in tag_expr:
|
|
481
|
+
tag_filter_copy['value'] = tag_expr['stringValue']
|
|
482
|
+
# Keep as dictionary - don't convert to TagFilter model
|
|
483
|
+
request_body['tagFilterExpression'] = tag_filter_copy
|
|
484
|
+
|
|
485
|
+
config_object = ApplicationConfig(**request_body)
|
|
486
|
+
result = api_client.put_application_config(id=id, application_config=config_object)
|
|
634
487
|
|
|
635
|
-
|
|
636
|
-
|
|
488
|
+
if hasattr(result, 'to_dict'):
|
|
489
|
+
return result.to_dict()
|
|
490
|
+
return result or {"success": True, "message": f"Application config '{id}' updated"}
|
|
637
491
|
except Exception as e:
|
|
638
|
-
logger.error(f"Error in
|
|
639
|
-
return {"error": f"Failed to
|
|
492
|
+
logger.error(f"Error in _update_application_config: {e}", exc_info=True)
|
|
493
|
+
return {"error": f"Failed to update application config: {e!s}"}
|
|
640
494
|
|
|
641
|
-
@register_as_tool(
|
|
642
|
-
title="Delete Endpoint Config",
|
|
643
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
644
|
-
)
|
|
645
495
|
@with_header_auth(ApplicationSettingsApi)
|
|
646
|
-
async def
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
) -> Dict[str, Any]:
|
|
652
|
-
"""
|
|
653
|
-
Delete an endpoint configuration of a service.
|
|
654
|
-
|
|
655
|
-
Args:
|
|
656
|
-
id: An Instana generated unique identifier for a Service.
|
|
657
|
-
ctx: The MCP context (optional)
|
|
658
|
-
|
|
659
|
-
Returns:
|
|
660
|
-
Dict[str, Any]: Response from the delete endpoint configuration API.
|
|
661
|
-
"""
|
|
496
|
+
async def _delete_application_config(self,
|
|
497
|
+
id: str,
|
|
498
|
+
ctx=None,
|
|
499
|
+
api_client=None) -> Dict[str, Any]:
|
|
500
|
+
"""Delete an Application Perspective configuration."""
|
|
662
501
|
try:
|
|
663
|
-
debug_print("Delete endpoint configs")
|
|
664
502
|
if not id:
|
|
665
|
-
return {"error": "
|
|
666
|
-
|
|
667
|
-
api_client.delete_endpoint_config(id=id)
|
|
668
|
-
|
|
669
|
-
result_dict = {
|
|
670
|
-
"success": True,
|
|
671
|
-
"message": f"Endpoint Confiuguration '{id}' has been successfully deleted"
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
debug_print(f"Successfully deleted endpoint perspective with ID: {id}")
|
|
675
|
-
return result_dict
|
|
503
|
+
return {"error": "id is required"}
|
|
676
504
|
|
|
505
|
+
api_client.delete_application_config(id=id)
|
|
506
|
+
return {"success": True, "message": f"Application config '{id}' deleted successfully"}
|
|
677
507
|
except Exception as e:
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return {"error": f"Failed to delete endpoint configs: {e!s}"}
|
|
508
|
+
logger.error(f"Error in _delete_application_config: {e}", exc_info=True)
|
|
509
|
+
return {"error": f"Failed to delete application config: {e!s}"}
|
|
681
510
|
|
|
682
|
-
|
|
683
|
-
title="Get Endpoint Config",
|
|
684
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
685
|
-
)
|
|
511
|
+
# Endpoint Config Operations
|
|
686
512
|
@with_header_auth(ApplicationSettingsApi)
|
|
687
|
-
async def
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
513
|
+
async def _get_all_endpoint_configs(self,
|
|
514
|
+
ctx=None,
|
|
515
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
516
|
+
"""Get all Endpoint Perspectives Configuration."""
|
|
517
|
+
try:
|
|
518
|
+
result = api_client.get_endpoint_configs_without_preload_content()
|
|
519
|
+
import json
|
|
520
|
+
response_text = result.data.decode('utf-8')
|
|
521
|
+
json_data = json.loads(response_text)
|
|
522
|
+
return json_data if isinstance(json_data, list) else [json_data] if json_data else []
|
|
523
|
+
except Exception as e:
|
|
524
|
+
logger.error(f"Error in _get_all_endpoint_configs: {e}", exc_info=True)
|
|
525
|
+
return [{"error": f"Failed to get endpoint configs: {e!s}"}]
|
|
701
526
|
|
|
702
|
-
|
|
527
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
528
|
+
async def _get_endpoint_config(self,
|
|
529
|
+
id: str,
|
|
530
|
+
ctx=None,
|
|
531
|
+
api_client=None) -> Dict[str, Any]:
|
|
532
|
+
"""Get an Endpoint configuration by ID."""
|
|
703
533
|
try:
|
|
704
|
-
debug_print("get endpoint config")
|
|
705
534
|
if not id:
|
|
706
|
-
return {"error": "
|
|
707
|
-
|
|
708
|
-
result = api_client.get_endpoint_config(
|
|
709
|
-
id=id
|
|
710
|
-
)
|
|
711
|
-
# Convert the result to a dictionary
|
|
535
|
+
return {"error": "id is required"}
|
|
536
|
+
result = api_client.get_endpoint_config(id=id)
|
|
712
537
|
if hasattr(result, 'to_dict'):
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
# If it's already a dict or another format, use it as is
|
|
716
|
-
result_dict = result
|
|
717
|
-
|
|
718
|
-
debug_print(f"Result from get_endpoint_configs: {result_dict}")
|
|
719
|
-
return result_dict
|
|
538
|
+
return result.to_dict()
|
|
539
|
+
return result
|
|
720
540
|
except Exception as e:
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
return {"error": f"Failed to get endpoint configs: {e!s}"}
|
|
541
|
+
logger.error(f"Error in _get_endpoint_config: {e}", exc_info=True)
|
|
542
|
+
return {"error": f"Failed to get endpoint config: {e!s}"}
|
|
724
543
|
|
|
725
|
-
@register_as_tool(
|
|
726
|
-
title="Update Endpoint Config",
|
|
727
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
728
|
-
)
|
|
729
544
|
@with_header_auth(ApplicationSettingsApi)
|
|
730
|
-
async def
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
api_client=None
|
|
736
|
-
) -> Dict[str, Any]:
|
|
737
|
-
"""
|
|
738
|
-
update endpoint configuration for a service.
|
|
739
|
-
|
|
740
|
-
Args:
|
|
741
|
-
id: An Instana generated unique identifier for a Service.
|
|
742
|
-
{
|
|
743
|
-
"serviceId": "20ba31821b079e7d845a08096124880db3eeeb40",
|
|
744
|
-
"endpointNameByCollectedPathTemplateRuleEnabled": true,
|
|
745
|
-
"endpointNameByFirstPathSegmentRuleEnabled": true,
|
|
746
|
-
"rules": [
|
|
747
|
-
{
|
|
748
|
-
"enabled": true,
|
|
749
|
-
"pathSegments": [
|
|
750
|
-
{
|
|
751
|
-
"name": "api",
|
|
752
|
-
"type": "FIXED"
|
|
753
|
-
},
|
|
754
|
-
{
|
|
755
|
-
"name": "version",
|
|
756
|
-
"type": "PARAMETER"
|
|
757
|
-
}
|
|
758
|
-
],
|
|
759
|
-
"testCases": [
|
|
760
|
-
"/api/v2/users"
|
|
761
|
-
]
|
|
762
|
-
}
|
|
763
|
-
],
|
|
764
|
-
"endpointCase": "UPPER"
|
|
765
|
-
}
|
|
766
|
-
ctx: The MCP context (optional)
|
|
767
|
-
|
|
768
|
-
Returns:
|
|
769
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
770
|
-
"""
|
|
545
|
+
async def _create_endpoint_config(self,
|
|
546
|
+
payload: Union[Dict[str, Any], str],
|
|
547
|
+
ctx=None,
|
|
548
|
+
api_client=None) -> Dict[str, Any]:
|
|
549
|
+
"""Create or update endpoint configuration for a service."""
|
|
771
550
|
try:
|
|
772
|
-
if not payload
|
|
773
|
-
return {"error": "
|
|
551
|
+
if not payload:
|
|
552
|
+
return {"error": "payload is required"}
|
|
774
553
|
|
|
775
|
-
# Parse the payload if it's a string
|
|
776
554
|
if isinstance(payload, str):
|
|
777
|
-
|
|
555
|
+
import json
|
|
778
556
|
try:
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
request_body = parsed_payload
|
|
784
|
-
except json.JSONDecodeError as e:
|
|
785
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
786
|
-
|
|
787
|
-
# Try replacing single quotes with double quotes
|
|
788
|
-
fixed_payload = payload.replace("'", "\"")
|
|
789
|
-
try:
|
|
790
|
-
parsed_payload = json.loads(fixed_payload)
|
|
791
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
792
|
-
request_body = parsed_payload
|
|
793
|
-
except json.JSONDecodeError:
|
|
794
|
-
# Try as Python literal
|
|
795
|
-
import ast
|
|
796
|
-
try:
|
|
797
|
-
parsed_payload = ast.literal_eval(payload)
|
|
798
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
799
|
-
request_body = parsed_payload
|
|
800
|
-
except (SyntaxError, ValueError) as e2:
|
|
801
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
802
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
803
|
-
except Exception as e:
|
|
804
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
805
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
557
|
+
request_body = json.loads(payload)
|
|
558
|
+
except json.JSONDecodeError:
|
|
559
|
+
import ast
|
|
560
|
+
request_body = ast.literal_eval(payload)
|
|
806
561
|
else:
|
|
807
|
-
# If payload is already a dictionary, use it directly
|
|
808
|
-
logger.debug("Using provided payload dictionary")
|
|
809
562
|
request_body = payload
|
|
810
563
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
from instana_client.models.endpoint_config import (
|
|
814
|
-
EndpointConfig,
|
|
815
|
-
)
|
|
816
|
-
logger.debug("Successfully imported EndpointConfig")
|
|
817
|
-
except ImportError as e:
|
|
818
|
-
logger.debug(f"Error importing EndpointConfig: {e}")
|
|
819
|
-
return {"error": f"Failed to import EndpointConfig: {e!s}"}
|
|
820
|
-
|
|
821
|
-
# Create an EndpointConfig object from the request body
|
|
822
|
-
try:
|
|
823
|
-
logger.debug(f"Creating EndpointConfig with params: {request_body}")
|
|
824
|
-
config_object = EndpointConfig(**request_body)
|
|
825
|
-
logger.debug("Successfully updated endpoint config object")
|
|
826
|
-
except Exception as e:
|
|
827
|
-
logger.debug(f"Error updating EndpointConfig: {e}")
|
|
828
|
-
return {"error": f"Failed to update config object: {e!s}"}
|
|
829
|
-
|
|
830
|
-
# Call the update_endpoint_config method from the SDK
|
|
831
|
-
logger.debug("Calling update_endpoint_config with config object")
|
|
832
|
-
result = api_client.update_endpoint_config(
|
|
833
|
-
id=id,
|
|
834
|
-
endpoint_config=config_object
|
|
835
|
-
)
|
|
836
|
-
|
|
837
|
-
# Convert the result to a dictionary
|
|
564
|
+
config_object = EndpointConfig(**request_body)
|
|
565
|
+
result = api_client.create_endpoint_config(endpoint_config=config_object)
|
|
838
566
|
if hasattr(result, 'to_dict'):
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
# If it's already a dict or another format, use it as is
|
|
842
|
-
result_dict = result or {
|
|
843
|
-
"success": True,
|
|
844
|
-
"message": "update existing endpoint config"
|
|
845
|
-
}
|
|
846
|
-
|
|
847
|
-
logger.debug(f"Result from update_endpoint_config: {result_dict}")
|
|
848
|
-
return result_dict
|
|
567
|
+
return result.to_dict()
|
|
568
|
+
return result or {"success": True, "message": "Endpoint config created"}
|
|
849
569
|
except Exception as e:
|
|
850
|
-
logger.error(f"Error in
|
|
851
|
-
return {"error": f"Failed to
|
|
852
|
-
|
|
570
|
+
logger.error(f"Error in _create_endpoint_config: {e}", exc_info=True)
|
|
571
|
+
return {"error": f"Failed to create endpoint config: {e!s}"}
|
|
853
572
|
|
|
854
|
-
@register_as_tool(
|
|
855
|
-
title="Get All Manual Service Configs",
|
|
856
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
857
|
-
)
|
|
858
573
|
@with_header_auth(ApplicationSettingsApi)
|
|
859
|
-
async def
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
Args:
|
|
866
|
-
ctx: The MCP context (optional)
|
|
867
|
-
|
|
868
|
-
Returns:
|
|
869
|
-
Dictionary containing endpoints data or error information
|
|
870
|
-
"""
|
|
574
|
+
async def _update_endpoint_config(self,
|
|
575
|
+
id: str,
|
|
576
|
+
payload: Union[Dict[str, Any], str],
|
|
577
|
+
ctx=None,
|
|
578
|
+
api_client=None) -> Dict[str, Any]:
|
|
579
|
+
"""Update an endpoint configuration."""
|
|
871
580
|
try:
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
# Convert the result to a dictionary
|
|
875
|
-
if hasattr(result, 'to_dict'):
|
|
876
|
-
result_dict = result.to_dict()
|
|
877
|
-
else:
|
|
878
|
-
# If it's already a dict or another format, use it as is
|
|
879
|
-
result_dict = result
|
|
581
|
+
if not id or not payload:
|
|
582
|
+
return {"error": "id and payload are required"}
|
|
880
583
|
|
|
881
|
-
debug_print(f"Result from get_all_manual_service_configs: {result_dict}")
|
|
882
|
-
return result_dict
|
|
883
|
-
|
|
884
|
-
except Exception as e:
|
|
885
|
-
debug_print(f"Error in get_all_manual_service_configs: {e}")
|
|
886
|
-
traceback.print_exc(file=sys.stderr)
|
|
887
|
-
return [{"error": f"Failed to get manual service configs: {e!s}"}]
|
|
888
|
-
|
|
889
|
-
@register_as_tool(
|
|
890
|
-
title="Add Manual Service Config",
|
|
891
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
892
|
-
)
|
|
893
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
894
|
-
async def add_manual_service_config(
|
|
895
|
-
self,
|
|
896
|
-
payload: Union[Dict[str, Any], str],
|
|
897
|
-
ctx=None,
|
|
898
|
-
api_client=None
|
|
899
|
-
) -> Dict[str, Any]:
|
|
900
|
-
"""
|
|
901
|
-
Create a manual service mapping configuration.
|
|
902
|
-
|
|
903
|
-
Requires `CanConfigureServiceMapping` permission on the API token.
|
|
904
|
-
|
|
905
|
-
Sample payload:
|
|
906
|
-
{
|
|
907
|
-
"description": "Map source service example",
|
|
908
|
-
"enabled": true,
|
|
909
|
-
"existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
|
|
910
|
-
"tagFilterExpression": {
|
|
911
|
-
"type": "TAG_FILTER",
|
|
912
|
-
"name": "service.name",
|
|
913
|
-
"stringValue": "front",
|
|
914
|
-
"numberValue": null,
|
|
915
|
-
"booleanValue": null,
|
|
916
|
-
"key": null,
|
|
917
|
-
"value": "front",
|
|
918
|
-
"operator": "EQUALS",
|
|
919
|
-
"entity": "SOURCE"
|
|
920
|
-
},
|
|
921
|
-
"unmonitoredServiceName": null
|
|
922
|
-
}
|
|
923
|
-
ctx: Optional execution context.
|
|
924
|
-
|
|
925
|
-
Returns:
|
|
926
|
-
Dict[str, Any]: API response indicating success or failure.
|
|
927
|
-
"""
|
|
928
|
-
try:
|
|
929
|
-
if not payload:
|
|
930
|
-
return {"error": "missing arguments"}
|
|
931
|
-
|
|
932
|
-
# Parse the payload if it's a string
|
|
933
584
|
if isinstance(payload, str):
|
|
934
|
-
|
|
585
|
+
import json
|
|
935
586
|
try:
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
request_body = parsed_payload
|
|
941
|
-
except json.JSONDecodeError as e:
|
|
942
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
943
|
-
|
|
944
|
-
# Try replacing single quotes with double quotes
|
|
945
|
-
fixed_payload = payload.replace("'", "\"")
|
|
946
|
-
try:
|
|
947
|
-
parsed_payload = json.loads(fixed_payload)
|
|
948
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
949
|
-
request_body = parsed_payload
|
|
950
|
-
except json.JSONDecodeError:
|
|
951
|
-
# Try as Python literal
|
|
952
|
-
import ast
|
|
953
|
-
try:
|
|
954
|
-
parsed_payload = ast.literal_eval(payload)
|
|
955
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
956
|
-
request_body = parsed_payload
|
|
957
|
-
except (SyntaxError, ValueError) as e2:
|
|
958
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
959
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
960
|
-
except Exception as e:
|
|
961
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
962
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
587
|
+
request_body = json.loads(payload)
|
|
588
|
+
except json.JSONDecodeError:
|
|
589
|
+
import ast
|
|
590
|
+
request_body = ast.literal_eval(payload)
|
|
963
591
|
else:
|
|
964
|
-
# If payload is already a dictionary, use it directly
|
|
965
|
-
logger.debug("Using provided payload dictionary")
|
|
966
592
|
request_body = payload
|
|
967
593
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
from instana_client.models.new_manual_service_config import (
|
|
971
|
-
NewManualServiceConfig,
|
|
972
|
-
)
|
|
973
|
-
logger.debug("Successfully imported NewManualServiceConfig")
|
|
974
|
-
except ImportError as e:
|
|
975
|
-
logger.debug(f"Error importing NewManualServiceConfig: {e}")
|
|
976
|
-
return {"error": f"Failed to import NewManualServiceConfig: {e!s}"}
|
|
977
|
-
|
|
978
|
-
# Create an NewManualServiceConfig object from the request body
|
|
979
|
-
try:
|
|
980
|
-
logger.debug(f"Creating NewManualServiceConfig with params: {request_body}")
|
|
981
|
-
config_object = NewManualServiceConfig(**request_body)
|
|
982
|
-
logger.debug("Successfully created manual service config object")
|
|
983
|
-
except Exception as e:
|
|
984
|
-
logger.debug(f"Error creating NewManualServiceConfig: {e}")
|
|
985
|
-
return {"error": f"Failed to create config object: {e!s}"}
|
|
986
|
-
|
|
987
|
-
# Call the add_manual_service_config method from the SDK
|
|
988
|
-
logger.debug("Calling add_manual_service_config with config object")
|
|
989
|
-
result = api_client.add_manual_service_config(
|
|
990
|
-
new_manual_service_config=config_object
|
|
991
|
-
)
|
|
992
|
-
|
|
993
|
-
# Convert the result to a dictionary
|
|
594
|
+
config_object = EndpointConfig(**request_body)
|
|
595
|
+
result = api_client.update_endpoint_config(id=id, endpoint_config=config_object)
|
|
994
596
|
if hasattr(result, 'to_dict'):
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
# If it's already a dict or another format, use it as is
|
|
998
|
-
result_dict = result or {
|
|
999
|
-
"success": True,
|
|
1000
|
-
"message": "Create new manual service config"
|
|
1001
|
-
}
|
|
1002
|
-
|
|
1003
|
-
logger.debug(f"Result from add_manual_service_config: {result_dict}")
|
|
1004
|
-
return result_dict
|
|
597
|
+
return result.to_dict()
|
|
598
|
+
return result or {"success": True, "message": f"Endpoint config '{id}' updated"}
|
|
1005
599
|
except Exception as e:
|
|
1006
|
-
logger.error(f"Error in
|
|
1007
|
-
return {"error": f"Failed to
|
|
600
|
+
logger.error(f"Error in _update_endpoint_config: {e}", exc_info=True)
|
|
601
|
+
return {"error": f"Failed to update endpoint config: {e!s}"}
|
|
1008
602
|
|
|
1009
|
-
@register_as_tool(
|
|
1010
|
-
title="Delete Manual Service Config",
|
|
1011
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1012
|
-
)
|
|
1013
603
|
@with_header_auth(ApplicationSettingsApi)
|
|
1014
|
-
async def
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
) -> Dict[str, Any]:
|
|
1020
|
-
"""
|
|
1021
|
-
Delete a manual service configuration.
|
|
1022
|
-
|
|
1023
|
-
Args:
|
|
1024
|
-
id: A unique id of the manual service configuration.
|
|
1025
|
-
ctx: The MCP context (optional)
|
|
1026
|
-
|
|
1027
|
-
Returns:
|
|
1028
|
-
Dict[str, Any]: Response from the delete manual service configuration API.
|
|
1029
|
-
"""
|
|
604
|
+
async def _delete_endpoint_config(self,
|
|
605
|
+
id: str,
|
|
606
|
+
ctx=None,
|
|
607
|
+
api_client=None) -> Dict[str, Any]:
|
|
608
|
+
"""Delete an endpoint configuration."""
|
|
1030
609
|
try:
|
|
1031
|
-
debug_print("Delete manual service configs")
|
|
1032
610
|
if not id:
|
|
1033
|
-
return {"error": "
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
result_dict = {
|
|
1038
|
-
"success": True,
|
|
1039
|
-
"message": f"Manual Service Confiuguration '{id}' has been successfully deleted"
|
|
1040
|
-
}
|
|
1041
|
-
|
|
1042
|
-
debug_print(f"Successfully deleted manual service config perspective with ID: {id}")
|
|
1043
|
-
return result_dict
|
|
1044
|
-
|
|
611
|
+
return {"error": "id is required"}
|
|
612
|
+
api_client.delete_endpoint_config(id=id)
|
|
613
|
+
return {"success": True, "message": f"Endpoint config '{id}' deleted successfully"}
|
|
1045
614
|
except Exception as e:
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
return {"error": f"Failed to delete manual service configs: {e!s}"}
|
|
615
|
+
logger.error(f"Error in _delete_endpoint_config: {e}", exc_info=True)
|
|
616
|
+
return {"error": f"Failed to delete endpoint config: {e!s}"}
|
|
1049
617
|
|
|
1050
|
-
|
|
1051
|
-
title="Update Manual Service Config",
|
|
1052
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1053
|
-
)
|
|
618
|
+
# Service Config Operations
|
|
1054
619
|
@with_header_auth(ApplicationSettingsApi)
|
|
1055
|
-
async def
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
Link Calls to an Existing Monitored Service. For example, Link database calls (jdbc:mysql://10.128.0.1:3306) to an existing service like MySQL@3306 on demo-host by referencing its service ID.
|
|
620
|
+
async def _get_all_service_configs(self,
|
|
621
|
+
ctx=None,
|
|
622
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
623
|
+
"""Get all Service configurations."""
|
|
624
|
+
try:
|
|
625
|
+
result = api_client.get_service_configs_without_preload_content()
|
|
626
|
+
import json
|
|
627
|
+
response_text = result.data.decode('utf-8')
|
|
628
|
+
json_data = json.loads(response_text)
|
|
629
|
+
return json_data if isinstance(json_data, list) else [json_data] if json_data else []
|
|
630
|
+
except Exception as e:
|
|
631
|
+
logger.error(f"Error in _get_all_service_configs: {e}", exc_info=True)
|
|
632
|
+
return [{"error": f"Failed to get service configs: {e!s}"}]
|
|
1069
633
|
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
"
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
"
|
|
1085
|
-
"
|
|
1086
|
-
"entity": "SOURCE"
|
|
1087
|
-
},
|
|
1088
|
-
"unmonitoredServiceName": null
|
|
1089
|
-
}
|
|
634
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
635
|
+
async def _get_service_config(self,
|
|
636
|
+
id: str,
|
|
637
|
+
ctx=None,
|
|
638
|
+
api_client=None) -> Dict[str, Any]:
|
|
639
|
+
"""Get a Service configuration by ID."""
|
|
640
|
+
try:
|
|
641
|
+
if not id:
|
|
642
|
+
return {"error": "id is required"}
|
|
643
|
+
result = api_client.get_service_config(id=id)
|
|
644
|
+
if hasattr(result, 'to_dict'):
|
|
645
|
+
return result.to_dict()
|
|
646
|
+
return result
|
|
647
|
+
except Exception as e:
|
|
648
|
+
logger.error(f"Error in _get_service_config: {e}", exc_info=True)
|
|
649
|
+
return {"error": f"Failed to get service config: {e!s}"}
|
|
1090
650
|
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
651
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
652
|
+
async def _add_service_config(self,
|
|
653
|
+
payload: Union[Dict[str, Any], str],
|
|
654
|
+
ctx=None,
|
|
655
|
+
api_client=None) -> Dict[str, Any]:
|
|
656
|
+
"""Add a new Service configuration."""
|
|
1094
657
|
try:
|
|
1095
|
-
if not payload
|
|
1096
|
-
return {"error": "
|
|
658
|
+
if not payload:
|
|
659
|
+
return {"error": "payload is required"}
|
|
1097
660
|
|
|
1098
|
-
# Parse the payload if it's a string
|
|
1099
661
|
if isinstance(payload, str):
|
|
1100
|
-
|
|
662
|
+
import json
|
|
1101
663
|
try:
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
request_body = parsed_payload
|
|
1107
|
-
except json.JSONDecodeError as e:
|
|
1108
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1109
|
-
|
|
1110
|
-
# Try replacing single quotes with double quotes
|
|
1111
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1112
|
-
try:
|
|
1113
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1114
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1115
|
-
request_body = parsed_payload
|
|
1116
|
-
except json.JSONDecodeError:
|
|
1117
|
-
# Try as Python literal
|
|
1118
|
-
import ast
|
|
1119
|
-
try:
|
|
1120
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1121
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1122
|
-
request_body = parsed_payload
|
|
1123
|
-
except (SyntaxError, ValueError) as e2:
|
|
1124
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1125
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
1126
|
-
except Exception as e:
|
|
1127
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1128
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
664
|
+
request_body = json.loads(payload)
|
|
665
|
+
except json.JSONDecodeError:
|
|
666
|
+
import ast
|
|
667
|
+
request_body = ast.literal_eval(payload)
|
|
1129
668
|
else:
|
|
1130
|
-
# If payload is already a dictionary, use it directly
|
|
1131
|
-
logger.debug("Using provided payload dictionary")
|
|
1132
669
|
request_body = payload
|
|
1133
670
|
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
from instana_client.models.manual_service_config import (
|
|
1137
|
-
ManualServiceConfig,
|
|
1138
|
-
)
|
|
1139
|
-
logger.debug("Successfully imported ManualServiceConfig")
|
|
1140
|
-
except ImportError as e:
|
|
1141
|
-
logger.debug(f"Error importing ManualServiceConfig: {e}")
|
|
1142
|
-
return {"error": f"Failed to import ManualServiceConfig: {e!s}"}
|
|
1143
|
-
|
|
1144
|
-
# Create an ManualServiceConfig object from the request body
|
|
1145
|
-
try:
|
|
1146
|
-
logger.debug(f"Creating ManualServiceConfig with params: {request_body}")
|
|
1147
|
-
config_object = ManualServiceConfig(**request_body)
|
|
1148
|
-
logger.debug("Successfully update manual service config object")
|
|
1149
|
-
except Exception as e:
|
|
1150
|
-
logger.debug(f"Error creating ManualServiceConfig: {e}")
|
|
1151
|
-
return {"error": f"Failed to update manual config object: {e!s}"}
|
|
1152
|
-
|
|
1153
|
-
# Call the update_manual_service_config method from the SDK
|
|
1154
|
-
logger.debug("Calling update_manual_service_config with config object")
|
|
1155
|
-
result = api_client.update_manual_service_config(
|
|
1156
|
-
id=id,
|
|
1157
|
-
manual_service_config=config_object
|
|
1158
|
-
)
|
|
1159
|
-
|
|
1160
|
-
# Convert the result to a dictionary
|
|
671
|
+
config_object = ServiceConfig(**request_body)
|
|
672
|
+
result = api_client.add_service_config(service_config=config_object)
|
|
1161
673
|
if hasattr(result, 'to_dict'):
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
# If it's already a dict or another format, use it as is
|
|
1165
|
-
result_dict = result or {
|
|
1166
|
-
"success": True,
|
|
1167
|
-
"message": "update manual service config"
|
|
1168
|
-
}
|
|
1169
|
-
|
|
1170
|
-
logger.debug(f"Result from update_manual_service_config: {result_dict}")
|
|
1171
|
-
return result_dict
|
|
674
|
+
return result.to_dict()
|
|
675
|
+
return result or {"success": True, "message": "Service config created"}
|
|
1172
676
|
except Exception as e:
|
|
1173
|
-
logger.error(f"Error in
|
|
1174
|
-
return {"error": f"Failed to
|
|
1175
|
-
|
|
677
|
+
logger.error(f"Error in _add_service_config: {e}", exc_info=True)
|
|
678
|
+
return {"error": f"Failed to add service config: {e!s}"}
|
|
1176
679
|
|
|
1177
|
-
@register_as_tool(
|
|
1178
|
-
title="Replace All Manual Service Config",
|
|
1179
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1180
|
-
)
|
|
1181
680
|
@with_header_auth(ApplicationSettingsApi)
|
|
1182
|
-
async def
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
"""
|
|
1189
|
-
This tool is used if one wants to update more than 1 manual service configurations.
|
|
1190
|
-
|
|
1191
|
-
There are two use cases on the usage of these APIs:
|
|
1192
|
-
|
|
1193
|
-
Map to an Unmonitored Service with a Custom Name. For example, Map HTTP calls to different Google domains (www.ibm.com, www.ibm.fr) into a single service named IBM using the call.http.host tag.
|
|
1194
|
-
Link Calls to an Existing Monitored Service. For example, Link database calls (jdbc:mysql://10.128.0.1:3306) to an existing service like MySQL@3306 on demo-host by referencing its service ID.
|
|
1195
|
-
|
|
1196
|
-
Args:
|
|
1197
|
-
Sample payload: [
|
|
1198
|
-
{
|
|
1199
|
-
"description": "Map source service",
|
|
1200
|
-
"enabled": true,
|
|
1201
|
-
"existingServiceId": "c467ca0fa21477fee3cde75a140b2963307388a7",
|
|
1202
|
-
"tagFilterExpression": {
|
|
1203
|
-
"type": "TAG_FILTER",
|
|
1204
|
-
"name": "service.name",
|
|
1205
|
-
"stringValue": "front",
|
|
1206
|
-
"numberValue": null,
|
|
1207
|
-
"booleanValue": null,
|
|
1208
|
-
"key": null,
|
|
1209
|
-
"value": "front",
|
|
1210
|
-
"operator": "EQUALS",
|
|
1211
|
-
"entity": "SOURCE"
|
|
1212
|
-
},
|
|
1213
|
-
"unmonitoredServiceName": null
|
|
1214
|
-
}
|
|
1215
|
-
]
|
|
1216
|
-
ctx: Optional execution context.
|
|
1217
|
-
|
|
1218
|
-
Returns:
|
|
1219
|
-
Dict[str, Any]: API response indicating success or failure.
|
|
1220
|
-
"""
|
|
681
|
+
async def _update_service_config(self,
|
|
682
|
+
id: str,
|
|
683
|
+
payload: Union[Dict[str, Any], str],
|
|
684
|
+
ctx=None,
|
|
685
|
+
api_client=None) -> Dict[str, Any]:
|
|
686
|
+
"""Update a Service configuration."""
|
|
1221
687
|
try:
|
|
1222
|
-
if not payload:
|
|
1223
|
-
return
|
|
688
|
+
if not id or not payload:
|
|
689
|
+
return {"error": "id and payload are required"}
|
|
1224
690
|
|
|
1225
|
-
# Parse the payload if it's a string
|
|
1226
691
|
if isinstance(payload, str):
|
|
1227
|
-
|
|
692
|
+
import json
|
|
1228
693
|
try:
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
request_body = parsed_payload
|
|
1234
|
-
except json.JSONDecodeError as e:
|
|
1235
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1236
|
-
|
|
1237
|
-
# Try replacing single quotes with double quotes
|
|
1238
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1239
|
-
try:
|
|
1240
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1241
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1242
|
-
request_body = parsed_payload
|
|
1243
|
-
except json.JSONDecodeError:
|
|
1244
|
-
# Try as Python literal
|
|
1245
|
-
import ast
|
|
1246
|
-
try:
|
|
1247
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1248
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1249
|
-
request_body = parsed_payload
|
|
1250
|
-
except (SyntaxError, ValueError) as e2:
|
|
1251
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1252
|
-
return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
|
|
1253
|
-
except Exception as e:
|
|
1254
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1255
|
-
return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
|
|
694
|
+
request_body = json.loads(payload)
|
|
695
|
+
except json.JSONDecodeError:
|
|
696
|
+
import ast
|
|
697
|
+
request_body = ast.literal_eval(payload)
|
|
1256
698
|
else:
|
|
1257
|
-
# If payload is already a dictionary, use it directly
|
|
1258
|
-
logger.debug("Using provided payload dictionary")
|
|
1259
699
|
request_body = payload
|
|
1260
700
|
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
from instana_client.models.new_manual_service_config import (
|
|
1264
|
-
NewManualServiceConfig,
|
|
1265
|
-
)
|
|
1266
|
-
logger.debug("Successfully imported ManualServiceConfig")
|
|
1267
|
-
except ImportError as e:
|
|
1268
|
-
logger.debug(f"Error importing ManualServiceConfig: {e}")
|
|
1269
|
-
return [{"error": f"Failed to import ManualServiceConfig: {e!s}"}]
|
|
1270
|
-
|
|
1271
|
-
# Create an ManualServiceConfig object from the request body
|
|
1272
|
-
try:
|
|
1273
|
-
logger.debug(f"Creating ManualServiceConfig with params: {request_body}")
|
|
1274
|
-
config_object = [NewManualServiceConfig(**request_body)]
|
|
1275
|
-
logger.debug("Successfully replace all manual service config object")
|
|
1276
|
-
except Exception as e:
|
|
1277
|
-
logger.debug(f"Error creating ManualServiceConfig: {e}")
|
|
1278
|
-
return [{"error": f"Failed to replace all manual config object: {e!s}"}]
|
|
1279
|
-
|
|
1280
|
-
# Call the replace_all_manual_service_config method from the SDK
|
|
1281
|
-
logger.debug("Calling replace_all_manual_service_config with config object")
|
|
1282
|
-
result = api_client.replace_all_manual_service_config(
|
|
1283
|
-
new_manual_service_config=config_object
|
|
1284
|
-
)
|
|
1285
|
-
|
|
1286
|
-
# Convert the result to a dictionary
|
|
701
|
+
config_object = ServiceConfig(**request_body)
|
|
702
|
+
result = api_client.update_service_config(id=id, service_config=config_object)
|
|
1287
703
|
if hasattr(result, 'to_dict'):
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
# If it's already a dict or another format, use it as is
|
|
1291
|
-
result_dict = result or {
|
|
1292
|
-
"success": True,
|
|
1293
|
-
"message": "Create replace all manual service config"
|
|
1294
|
-
}
|
|
1295
|
-
|
|
1296
|
-
logger.debug(f"Result from replace_all_manual_service_config: {result_dict}")
|
|
1297
|
-
return [result_dict]
|
|
704
|
+
return result.to_dict()
|
|
705
|
+
return result or {"success": True, "message": f"Service config '{id}' updated"}
|
|
1298
706
|
except Exception as e:
|
|
1299
|
-
logger.error(f"Error in
|
|
1300
|
-
return
|
|
707
|
+
logger.error(f"Error in _update_service_config: {e}", exc_info=True)
|
|
708
|
+
return {"error": f"Failed to update service config: {e!s}"}
|
|
1301
709
|
|
|
1302
|
-
@register_as_tool(
|
|
1303
|
-
title="Get All Service Configs",
|
|
1304
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
1305
|
-
)
|
|
1306
710
|
@with_header_auth(ApplicationSettingsApi)
|
|
1307
|
-
async def
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
Get a list of all Service Perspectives with their configuration settings.
|
|
1313
|
-
Args:
|
|
1314
|
-
ctx: The MCP context (optional)
|
|
1315
|
-
|
|
1316
|
-
Returns:
|
|
1317
|
-
Dictionary containing endpoints data or error information
|
|
1318
|
-
"""
|
|
711
|
+
async def _delete_service_config(self,
|
|
712
|
+
id: str,
|
|
713
|
+
ctx=None,
|
|
714
|
+
api_client=None) -> Dict[str, Any]:
|
|
715
|
+
"""Delete a Service configuration."""
|
|
1319
716
|
try:
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
result_dict = result.to_dict()
|
|
1325
|
-
else:
|
|
1326
|
-
# If it's already a dict or another format, use it as is
|
|
1327
|
-
result_dict = result
|
|
1328
|
-
|
|
1329
|
-
debug_print(f"Result from get_service_configs: {result_dict}")
|
|
1330
|
-
return result_dict
|
|
1331
|
-
|
|
717
|
+
if not id:
|
|
718
|
+
return {"error": "id is required"}
|
|
719
|
+
api_client.delete_service_config(id=id)
|
|
720
|
+
return {"success": True, "message": f"Service config '{id}' deleted successfully"}
|
|
1332
721
|
except Exception as e:
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
return [{"error": f"Failed to get application data metrics: {e}"}]
|
|
722
|
+
logger.error(f"Error in _delete_service_config: {e}", exc_info=True)
|
|
723
|
+
return {"error": f"Failed to delete service config: {e!s}"}
|
|
1336
724
|
|
|
1337
|
-
@register_as_tool(
|
|
1338
|
-
title="Add Service Config",
|
|
1339
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1340
|
-
)
|
|
1341
725
|
@with_header_auth(ApplicationSettingsApi)
|
|
1342
|
-
async def
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
"""
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
{
|
|
1356
|
-
"key": "gce.zone",
|
|
1357
|
-
"value": ".*"
|
|
1358
|
-
},
|
|
1359
|
-
{
|
|
1360
|
-
"key": "jvm.args.abc",
|
|
1361
|
-
"value": ".*"
|
|
1362
|
-
}
|
|
1363
|
-
],
|
|
1364
|
-
"name": "ABC is good"
|
|
1365
|
-
}
|
|
1366
|
-
ctx: The MCP context (optional)
|
|
726
|
+
async def _order_service_config(self,
|
|
727
|
+
request_body: List[str],
|
|
728
|
+
ctx=None,
|
|
729
|
+
api_client=None) -> Dict[str, Any]:
|
|
730
|
+
"""Order Service configurations."""
|
|
731
|
+
try:
|
|
732
|
+
if not request_body:
|
|
733
|
+
return {"error": "request_body is required"}
|
|
734
|
+
result = api_client.order_service_config(request_body=request_body)
|
|
735
|
+
return {"success": True, "message": "Service configs ordered successfully", "result": result}
|
|
736
|
+
except Exception as e:
|
|
737
|
+
logger.error(f"Error in _order_service_config: {e}", exc_info=True)
|
|
738
|
+
return {"error": f"Failed to order service configs: {e!s}"}
|
|
1367
739
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
740
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
741
|
+
async def _replace_all_service_configs(self,
|
|
742
|
+
payload: Union[Dict[str, Any], str],
|
|
743
|
+
ctx=None,
|
|
744
|
+
api_client=None) -> Dict[str, Any]:
|
|
745
|
+
"""Replace all Service configurations."""
|
|
1371
746
|
try:
|
|
1372
747
|
if not payload:
|
|
1373
|
-
return {"error": "
|
|
748
|
+
return {"error": "payload is required"}
|
|
1374
749
|
|
|
1375
|
-
# Parse the payload if it's a string
|
|
1376
750
|
if isinstance(payload, str):
|
|
1377
|
-
|
|
751
|
+
import json
|
|
1378
752
|
try:
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
request_body = parsed_payload
|
|
1384
|
-
except json.JSONDecodeError as e:
|
|
1385
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1386
|
-
|
|
1387
|
-
# Try replacing single quotes with double quotes
|
|
1388
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1389
|
-
try:
|
|
1390
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1391
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1392
|
-
request_body = parsed_payload
|
|
1393
|
-
except json.JSONDecodeError:
|
|
1394
|
-
# Try as Python literal
|
|
1395
|
-
import ast
|
|
1396
|
-
try:
|
|
1397
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1398
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1399
|
-
request_body = parsed_payload
|
|
1400
|
-
except (SyntaxError, ValueError) as e2:
|
|
1401
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1402
|
-
return {"error": f"Invalid payload format: {e2}", "payload": payload}
|
|
1403
|
-
except Exception as e:
|
|
1404
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1405
|
-
return {"error": f"Failed to parse payload: {e}", "payload": payload}
|
|
753
|
+
request_body = json.loads(payload)
|
|
754
|
+
except json.JSONDecodeError:
|
|
755
|
+
import ast
|
|
756
|
+
request_body = ast.literal_eval(payload)
|
|
1406
757
|
else:
|
|
1407
|
-
# If payload is already a dictionary, use it directly
|
|
1408
|
-
logger.debug("Using provided payload dictionary")
|
|
1409
758
|
request_body = payload
|
|
1410
759
|
|
|
1411
|
-
#
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
)
|
|
1416
|
-
logger.debug("Successfully imported ServiceConfig")
|
|
1417
|
-
except ImportError as e:
|
|
1418
|
-
logger.debug(f"Error importing ServiceConfig: {e}")
|
|
1419
|
-
return {"error": f"Failed to import ServiceConfig: {e!s}"}
|
|
1420
|
-
|
|
1421
|
-
# Create an ServiceConfig object from the request body
|
|
1422
|
-
try:
|
|
1423
|
-
logger.debug(f"Creating ServiceConfig with params: {request_body}")
|
|
1424
|
-
config_object = ServiceConfig(**request_body)
|
|
1425
|
-
logger.debug("Successfully add service config object")
|
|
1426
|
-
except Exception as e:
|
|
1427
|
-
logger.debug(f"Error creating ServiceConfig: {e}")
|
|
1428
|
-
return {"error": f"Failed to add service config object: {e!s}"}
|
|
1429
|
-
|
|
1430
|
-
# Call the ServiceConfig method from the SDK
|
|
1431
|
-
logger.debug("Calling add_service_config with config object")
|
|
1432
|
-
result = api_client.add_service_config(
|
|
1433
|
-
service_config=config_object
|
|
1434
|
-
)
|
|
1435
|
-
|
|
1436
|
-
# Convert the result to a dictionary
|
|
1437
|
-
if hasattr(result, 'to_dict'):
|
|
1438
|
-
result_dict = result.to_dict()
|
|
1439
|
-
else:
|
|
1440
|
-
# If it's already a dict or another format, use it as is
|
|
1441
|
-
result_dict = result or {
|
|
1442
|
-
"success": True,
|
|
1443
|
-
"message": "Create service config"
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1446
|
-
logger.debug(f"Result from add_service_config: {result_dict}")
|
|
1447
|
-
return result_dict
|
|
760
|
+
# Assuming request_body is a list of ServiceConfig objects
|
|
761
|
+
config_objects = [ServiceConfig(**item) if isinstance(item, dict) else item for item in request_body]
|
|
762
|
+
result = api_client.replace_all_service_configs(service_config=config_objects)
|
|
763
|
+
return {"success": True, "message": "All service configs replaced successfully", "result": result}
|
|
1448
764
|
except Exception as e:
|
|
1449
|
-
logger.error(f"Error in
|
|
1450
|
-
return {"error": f"Failed to
|
|
765
|
+
logger.error(f"Error in _replace_all_service_configs: {e}", exc_info=True)
|
|
766
|
+
return {"error": f"Failed to replace all service configs: {e!s}"}
|
|
1451
767
|
|
|
1452
|
-
|
|
1453
|
-
title="Replace All Service Configs",
|
|
1454
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1455
|
-
)
|
|
768
|
+
# Manual Service Config Operations
|
|
1456
769
|
@with_header_auth(ApplicationSettingsApi)
|
|
1457
|
-
async def
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
{
|
|
1471
|
-
"key": "docker.container.name",
|
|
1472
|
-
"value": ".*"
|
|
1473
|
-
}
|
|
1474
|
-
],
|
|
1475
|
-
"name": "Rule"
|
|
1476
|
-
}
|
|
1477
|
-
]
|
|
1478
|
-
ctx: The MCP context (optional)
|
|
770
|
+
async def _get_all_manual_service_configs(self,
|
|
771
|
+
ctx=None,
|
|
772
|
+
api_client=None) -> List[Dict[str, Any]]:
|
|
773
|
+
"""Get all Manual Service configurations."""
|
|
774
|
+
try:
|
|
775
|
+
result = api_client.get_manual_service_configs_without_preload_content()
|
|
776
|
+
import json
|
|
777
|
+
response_text = result.data.decode('utf-8')
|
|
778
|
+
json_data = json.loads(response_text)
|
|
779
|
+
return json_data if isinstance(json_data, list) else [json_data] if json_data else []
|
|
780
|
+
except Exception as e:
|
|
781
|
+
logger.error(f"Error in _get_all_manual_service_configs: {e}", exc_info=True)
|
|
782
|
+
return [{"error": f"Failed to get manual service configs: {e!s}"}]
|
|
1479
783
|
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
784
|
+
@with_header_auth(ApplicationSettingsApi)
|
|
785
|
+
async def _add_manual_service_config(self,
|
|
786
|
+
payload: Union[Dict[str, Any], str],
|
|
787
|
+
ctx=None,
|
|
788
|
+
api_client=None) -> Dict[str, Any]:
|
|
789
|
+
"""Add a new Manual Service configuration."""
|
|
1483
790
|
try:
|
|
1484
791
|
if not payload:
|
|
1485
|
-
return
|
|
792
|
+
return {"error": "payload is required"}
|
|
1486
793
|
|
|
1487
|
-
# Parse the payload if it's a string
|
|
1488
794
|
if isinstance(payload, str):
|
|
1489
|
-
|
|
795
|
+
import json
|
|
1490
796
|
try:
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
request_body = parsed_payload
|
|
1496
|
-
except json.JSONDecodeError as e:
|
|
1497
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1498
|
-
|
|
1499
|
-
# Try replacing single quotes with double quotes
|
|
1500
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1501
|
-
try:
|
|
1502
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1503
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1504
|
-
request_body = parsed_payload
|
|
1505
|
-
except json.JSONDecodeError:
|
|
1506
|
-
# Try as Python literal
|
|
1507
|
-
import ast
|
|
1508
|
-
try:
|
|
1509
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1510
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1511
|
-
request_body = parsed_payload
|
|
1512
|
-
except (SyntaxError, ValueError) as e2:
|
|
1513
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1514
|
-
return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
|
|
1515
|
-
except Exception as e:
|
|
1516
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1517
|
-
return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
|
|
797
|
+
request_body = json.loads(payload)
|
|
798
|
+
except json.JSONDecodeError:
|
|
799
|
+
import ast
|
|
800
|
+
request_body = ast.literal_eval(payload)
|
|
1518
801
|
else:
|
|
1519
|
-
# If payload is already a dictionary, use it directly
|
|
1520
|
-
logger.debug("Using provided payload dictionary")
|
|
1521
802
|
request_body = payload
|
|
1522
803
|
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
from instana_client.models.service_config import (
|
|
1526
|
-
ServiceConfig,
|
|
1527
|
-
)
|
|
1528
|
-
logger.debug("Successfully imported ServiceConfig")
|
|
1529
|
-
except ImportError as e:
|
|
1530
|
-
logger.debug(f"Error importing ServiceConfig: {e}")
|
|
1531
|
-
return [{"error": f"Failed to import ServiceConfig: {e!s}"}]
|
|
1532
|
-
|
|
1533
|
-
# Create an ServiceConfig object from the request body
|
|
1534
|
-
try:
|
|
1535
|
-
logger.debug(f"Creating ServiceConfig with params: {request_body}")
|
|
1536
|
-
config_object = [ServiceConfig(**request_body)]
|
|
1537
|
-
logger.debug("Successfully replace all manual service config object")
|
|
1538
|
-
except Exception as e:
|
|
1539
|
-
logger.debug(f"Error creating ServiceConfig: {e}")
|
|
1540
|
-
return [{"error": f"Failed to replace all manual config object: {e!s}"}]
|
|
1541
|
-
|
|
1542
|
-
# Call the replace_all method from the SDK
|
|
1543
|
-
logger.debug("Calling replace_all with config object")
|
|
1544
|
-
result = api_client.replace_all(
|
|
1545
|
-
service_config=config_object
|
|
1546
|
-
)
|
|
1547
|
-
|
|
1548
|
-
# Convert the result to a list of dictionaries
|
|
804
|
+
config_object = NewManualServiceConfig(**request_body)
|
|
805
|
+
result = api_client.add_manual_service_config(new_manual_service_config=config_object)
|
|
1549
806
|
if hasattr(result, 'to_dict'):
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
# If it's already a dict or another format, use it as is
|
|
1553
|
-
result_dict = result or {
|
|
1554
|
-
"success": True,
|
|
1555
|
-
"message": "replace all service config"
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
logger.debug(f"Result from replace_all: {result_dict}")
|
|
1559
|
-
return [result_dict]
|
|
807
|
+
return result.to_dict()
|
|
808
|
+
return result or {"success": True, "message": "Manual service config created"}
|
|
1560
809
|
except Exception as e:
|
|
1561
|
-
logger.error(f"Error in
|
|
1562
|
-
return
|
|
810
|
+
logger.error(f"Error in _add_manual_service_config: {e}", exc_info=True)
|
|
811
|
+
return {"error": f"Failed to add manual service config: {e!s}"}
|
|
1563
812
|
|
|
1564
|
-
|
|
1565
|
-
@register_as_tool(
|
|
1566
|
-
title="Order Service Config",
|
|
1567
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1568
|
-
)
|
|
1569
813
|
@with_header_auth(ApplicationSettingsApi)
|
|
1570
|
-
async def
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
This tool changes the order of service configurations based on the provided list of IDs.
|
|
1578
|
-
All service configuration IDs must be included in the request.
|
|
1579
|
-
|
|
1580
|
-
Args:
|
|
1581
|
-
request_body: List of service configuration IDs in the desired order.
|
|
1582
|
-
ctx: The MCP context (optional)
|
|
1583
|
-
|
|
1584
|
-
Returns:
|
|
1585
|
-
A dictionary with the API response or error message.
|
|
1586
|
-
"""
|
|
814
|
+
async def _update_manual_service_config(self,
|
|
815
|
+
id: str,
|
|
816
|
+
payload: Union[Dict[str, Any], str],
|
|
817
|
+
ctx=None,
|
|
818
|
+
api_client=None) -> Dict[str, Any]:
|
|
819
|
+
"""Update a Manual Service configuration."""
|
|
1587
820
|
try:
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
if not request_body:
|
|
1591
|
-
return {"error": "The list of service configuration IDs cannot be empty."}
|
|
821
|
+
if not id or not payload:
|
|
822
|
+
return {"error": "id and payload are required"}
|
|
1592
823
|
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
824
|
+
if isinstance(payload, str):
|
|
825
|
+
import json
|
|
826
|
+
try:
|
|
827
|
+
request_body = json.loads(payload)
|
|
828
|
+
except json.JSONDecodeError:
|
|
829
|
+
import ast
|
|
830
|
+
request_body = ast.literal_eval(payload)
|
|
831
|
+
else:
|
|
832
|
+
request_body = payload
|
|
1596
833
|
|
|
1597
|
-
|
|
834
|
+
config_object = ManualServiceConfig(**request_body)
|
|
835
|
+
result = api_client.update_manual_service_config(id=id, manual_service_config=config_object)
|
|
1598
836
|
if hasattr(result, 'to_dict'):
|
|
1599
837
|
return result.to_dict()
|
|
1600
|
-
return result
|
|
1601
|
-
|
|
1602
|
-
except Exception as e:
|
|
1603
|
-
debug_print(f"Error in order_service_config: {e}")
|
|
1604
|
-
traceback.print_exc(file=sys.stderr)
|
|
1605
|
-
return {"error": f"Failed to order service configs: {e!s}"}
|
|
1606
|
-
|
|
1607
|
-
@register_as_tool(
|
|
1608
|
-
title="Delete Service Config",
|
|
1609
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=True)
|
|
1610
|
-
)
|
|
1611
|
-
@with_header_auth(ApplicationSettingsApi)
|
|
1612
|
-
async def delete_service_config(self,
|
|
1613
|
-
id: str,
|
|
1614
|
-
ctx=None,
|
|
1615
|
-
api_client=None) -> Dict[str, Any]:
|
|
1616
|
-
"""
|
|
1617
|
-
Delete a Service Perspective configuration.
|
|
1618
|
-
This tool allows you to delete an existing Service Config by its ID.
|
|
1619
|
-
|
|
1620
|
-
Args:
|
|
1621
|
-
id: The ID of the application perspective to delete
|
|
1622
|
-
ctx: The MCP context (optional)
|
|
1623
|
-
|
|
1624
|
-
Returns:
|
|
1625
|
-
Dictionary containing the result of the deletion or error information
|
|
1626
|
-
"""
|
|
1627
|
-
try:
|
|
1628
|
-
if not id:
|
|
1629
|
-
return {"error": "Service perspective ID is required for deletion"}
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
debug_print(f"Deleting application perspective with ID: {id}")
|
|
1633
|
-
# Call the delete_service_config method from the SDK
|
|
1634
|
-
api_client.delete_service_config(id=id)
|
|
1635
|
-
|
|
1636
|
-
result_dict = {
|
|
1637
|
-
"success": True,
|
|
1638
|
-
"message": f"Service Confiuguration '{id}' has been successfully deleted"
|
|
1639
|
-
}
|
|
1640
|
-
|
|
1641
|
-
debug_print(f"Successfully deleted service perspective with ID: {id}")
|
|
1642
|
-
return result_dict
|
|
838
|
+
return result or {"success": True, "message": f"Manual service config '{id}' updated"}
|
|
1643
839
|
except Exception as e:
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
return {"error": f"Failed to delete service configuration: {e!s}"}
|
|
840
|
+
logger.error(f"Error in _update_manual_service_config: {e}", exc_info=True)
|
|
841
|
+
return {"error": f"Failed to update manual service config: {e!s}"}
|
|
1647
842
|
|
|
1648
|
-
@register_as_tool(
|
|
1649
|
-
title="Get Service Config",
|
|
1650
|
-
annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False)
|
|
1651
|
-
)
|
|
1652
843
|
@with_header_auth(ApplicationSettingsApi)
|
|
1653
|
-
async def
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
) -> Dict[str, Any]:
|
|
1659
|
-
"""
|
|
1660
|
-
This MCP tool is used if one wants to retrieve the particular custom service configuration.
|
|
1661
|
-
Args:
|
|
1662
|
-
id: An Instana generated unique identifier for a Service.
|
|
1663
|
-
ctx: The MCP context (optional)
|
|
1664
|
-
|
|
1665
|
-
Returns:
|
|
1666
|
-
Dict[str, Any]: Response from the create/update endpoint configuration API.
|
|
1667
|
-
|
|
1668
|
-
"""
|
|
844
|
+
async def _delete_manual_service_config(self,
|
|
845
|
+
id: str,
|
|
846
|
+
ctx=None,
|
|
847
|
+
api_client=None) -> Dict[str, Any]:
|
|
848
|
+
"""Delete a Manual Service configuration."""
|
|
1669
849
|
try:
|
|
1670
|
-
debug_print("get service config")
|
|
1671
850
|
if not id:
|
|
1672
|
-
return {"error": "
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
id=id
|
|
1676
|
-
)
|
|
1677
|
-
# Convert the result to a dictionary
|
|
1678
|
-
if hasattr(result, 'to_dict'):
|
|
1679
|
-
result_dict = result.to_dict()
|
|
1680
|
-
else:
|
|
1681
|
-
# If it's already a dict or another format, use it as is
|
|
1682
|
-
result_dict = result
|
|
1683
|
-
|
|
1684
|
-
debug_print(f"Result from get_service_config: {result_dict}")
|
|
1685
|
-
return result_dict
|
|
851
|
+
return {"error": "id is required"}
|
|
852
|
+
api_client.delete_manual_service_config(id=id)
|
|
853
|
+
return {"success": True, "message": f"Manual service config '{id}' deleted successfully"}
|
|
1686
854
|
except Exception as e:
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
return {"error": f"Failed to get service config: {e!s}"}
|
|
855
|
+
logger.error(f"Error in _delete_manual_service_config: {e}", exc_info=True)
|
|
856
|
+
return {"error": f"Failed to delete manual service config: {e!s}"}
|
|
1690
857
|
|
|
1691
|
-
@register_as_tool(
|
|
1692
|
-
title="Update Service Config",
|
|
1693
|
-
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False)
|
|
1694
|
-
)
|
|
1695
858
|
@with_header_auth(ApplicationSettingsApi)
|
|
1696
|
-
async def
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
"""
|
|
1702
|
-
This tool gives is used if one wants to update a particular custom service rule.
|
|
1703
|
-
Args:
|
|
1704
|
-
{
|
|
1705
|
-
"comment": null,
|
|
1706
|
-
"enabled": true,
|
|
1707
|
-
"id": "9uma4MhnTTSyBzwu_FKBJA",
|
|
1708
|
-
"label": "{gce.zone}-{jvm.args.abc}",
|
|
1709
|
-
"matchSpecification": [
|
|
1710
|
-
{
|
|
1711
|
-
"key": "gce.zone",
|
|
1712
|
-
"value": ".*"
|
|
1713
|
-
},
|
|
1714
|
-
{
|
|
1715
|
-
"key": "jvm.args.abc",
|
|
1716
|
-
"value": ".*"
|
|
1717
|
-
}
|
|
1718
|
-
],
|
|
1719
|
-
"name": "DEF is good"
|
|
1720
|
-
}
|
|
1721
|
-
ctx: The MCP context (optional)
|
|
1722
|
-
|
|
1723
|
-
Returns:
|
|
1724
|
-
Dictionary containing endpoints data or error information
|
|
1725
|
-
"""
|
|
859
|
+
async def _replace_all_manual_service_config(self,
|
|
860
|
+
payload: Union[Dict[str, Any], str],
|
|
861
|
+
ctx=None,
|
|
862
|
+
api_client=None) -> Dict[str, Any]:
|
|
863
|
+
"""Replace all Manual Service configurations."""
|
|
1726
864
|
try:
|
|
1727
|
-
if not payload
|
|
1728
|
-
return
|
|
865
|
+
if not payload:
|
|
866
|
+
return {"error": "payload is required"}
|
|
1729
867
|
|
|
1730
|
-
# Parse the payload if it's a string
|
|
1731
868
|
if isinstance(payload, str):
|
|
1732
|
-
|
|
869
|
+
import json
|
|
1733
870
|
try:
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
request_body = parsed_payload
|
|
1739
|
-
except json.JSONDecodeError as e:
|
|
1740
|
-
logger.debug(f"JSON parsing failed: {e}, trying with quotes replaced")
|
|
1741
|
-
|
|
1742
|
-
# Try replacing single quotes with double quotes
|
|
1743
|
-
fixed_payload = payload.replace("'", "\"")
|
|
1744
|
-
try:
|
|
1745
|
-
parsed_payload = json.loads(fixed_payload)
|
|
1746
|
-
logger.debug("Successfully parsed fixed JSON")
|
|
1747
|
-
request_body = parsed_payload
|
|
1748
|
-
except json.JSONDecodeError:
|
|
1749
|
-
# Try as Python literal
|
|
1750
|
-
import ast
|
|
1751
|
-
try:
|
|
1752
|
-
parsed_payload = ast.literal_eval(payload)
|
|
1753
|
-
logger.debug("Successfully parsed payload as Python literal")
|
|
1754
|
-
request_body = parsed_payload
|
|
1755
|
-
except (SyntaxError, ValueError) as e2:
|
|
1756
|
-
logger.debug(f"Failed to parse payload string: {e2}")
|
|
1757
|
-
return [{"error": f"Invalid payload format: {e2}", "payload": payload}]
|
|
1758
|
-
except Exception as e:
|
|
1759
|
-
logger.debug(f"Error parsing payload string: {e}")
|
|
1760
|
-
return [{"error": f"Failed to parse payload: {e}", "payload": payload}]
|
|
871
|
+
request_body = json.loads(payload)
|
|
872
|
+
except json.JSONDecodeError:
|
|
873
|
+
import ast
|
|
874
|
+
request_body = ast.literal_eval(payload)
|
|
1761
875
|
else:
|
|
1762
|
-
# If payload is already a dictionary, use it directly
|
|
1763
|
-
logger.debug("Using provided payload dictionary")
|
|
1764
876
|
request_body = payload
|
|
1765
877
|
|
|
1766
|
-
#
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
)
|
|
1771
|
-
logger.debug("Successfully imported ServiceConfig")
|
|
1772
|
-
except ImportError as e:
|
|
1773
|
-
logger.debug(f"Error importing ServiceConfig: {e}")
|
|
1774
|
-
return [{"error": f"Failed to import ServiceConfig: {e!s}"}]
|
|
1775
|
-
|
|
1776
|
-
# Create an ServiceConfig object from the request body
|
|
1777
|
-
try:
|
|
1778
|
-
logger.debug(f"Creating ServiceConfig with params: {request_body}")
|
|
1779
|
-
config_object = [ServiceConfig(**request_body)]
|
|
1780
|
-
logger.debug("Successfully update service config object")
|
|
1781
|
-
except Exception as e:
|
|
1782
|
-
logger.debug(f"Error creating ServiceConfig: {e}")
|
|
1783
|
-
return [{"error": f"Failed to replace all manual config object: {e!s}"}]
|
|
1784
|
-
|
|
1785
|
-
# Call the put_service_config method from the SDK
|
|
1786
|
-
logger.debug("Calling put_service_config with config object")
|
|
1787
|
-
result = api_client.put_service_config(
|
|
1788
|
-
id=id,
|
|
1789
|
-
service_config=config_object
|
|
1790
|
-
)
|
|
1791
|
-
|
|
1792
|
-
# Convert the result to a dictionary
|
|
1793
|
-
if hasattr(result, 'to_dict'):
|
|
1794
|
-
result_dict = result.to_dict()
|
|
1795
|
-
else:
|
|
1796
|
-
# If it's already a dict or another format, use it as is
|
|
1797
|
-
result_dict = result or {
|
|
1798
|
-
"success": True,
|
|
1799
|
-
"message": "put service config"
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
logger.debug(f"Result from put_service_config: {result_dict}")
|
|
1803
|
-
return [result_dict]
|
|
878
|
+
# Assuming request_body is a list of ManualServiceConfig objects
|
|
879
|
+
config_objects = [ManualServiceConfig(**item) if isinstance(item, dict) else item for item in request_body]
|
|
880
|
+
result = api_client.replace_all_manual_service_configs(manual_service_config=config_objects)
|
|
881
|
+
return {"success": True, "message": "All manual service configs replaced successfully", "result": result}
|
|
1804
882
|
except Exception as e:
|
|
1805
|
-
logger.error(f"Error in
|
|
1806
|
-
return
|
|
883
|
+
logger.error(f"Error in _replace_all_manual_service_config: {e}", exc_info=True)
|
|
884
|
+
return {"error": f"Failed to replace all manual service configs: {e!s}"}
|
|
1807
885
|
|