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