pyxecm 3.6.0__tar.gz → 3.6.2__tar.gz
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.
- {pyxecm-3.6.0 → pyxecm-3.6.2}/PKG-INFO +1 -1
- {pyxecm-3.6.0 → pyxecm-3.6.2}/pyproject.toml +1 -1
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otca.py +60 -12
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otcs.py +145 -87
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/payload.py +143 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/.gitignore +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/README.md +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/avts.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/coreshare.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/helper/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/helper/assoc.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/helper/data.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/helper/logadapter.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/helper/web.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/helper/xml.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otac.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otawp.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otds.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otiv.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otkd.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otmm.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/otpd.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm/py.typed +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/__main__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/app.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/auth/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/auth/functions.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/auth/models.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/auth/router.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/common/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/common/functions.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/common/metrics.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/common/models.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/common/router.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/settings.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/terminal/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/terminal/router.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_maintenance/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_maintenance/functions.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_maintenance/models.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_maintenance/router.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_otcs/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_otcs/functions.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_otcs/router.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_payload/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_payload/functions.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_payload/models.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_api/v1_payload/router.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/__main__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/browser_automation.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/customizer.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/exceptions.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/guidewire.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/k8s.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/log.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/m365.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/payload_list.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/salesforce.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/sap.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/servicenow.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/settings.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/successfactors.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_customizer/translate.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_maintenance_page/__init__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_maintenance_page/__main__.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_maintenance_page/app.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_maintenance_page/settings.py +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_maintenance_page/static/favicon.avif +0 -0
- {pyxecm-3.6.0 → pyxecm-3.6.2}/src/pyxecm_maintenance_page/templates/maintenance.html +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyxecm
|
|
3
|
-
Version: 3.6.
|
|
3
|
+
Version: 3.6.2
|
|
4
4
|
Summary: A Python library to interact with Opentext Content Management Rest API
|
|
5
5
|
Project-URL: Homepage, https://github.com/opentext/pyxecm
|
|
6
6
|
Author-email: Kai Gatzweiler <kgatzweiler@opentext.com>, "Dr. Marc Diefenbruch" <mdiefenb@opentext.com>
|
|
@@ -122,18 +122,22 @@ class OTCA:
|
|
|
122
122
|
# Stats endpoint:
|
|
123
123
|
otca_config["usageStatsUrl"] = base_url + "/v1/usage-stats"
|
|
124
124
|
|
|
125
|
+
# Tools Url
|
|
126
|
+
tools_url = base_url + "/v1/tools"
|
|
127
|
+
otca_config["toolsUrl"] = tools_url
|
|
128
|
+
|
|
125
129
|
# MCP Client endpoints:
|
|
126
|
-
|
|
127
|
-
otca_config["
|
|
128
|
-
otca_config["
|
|
129
|
-
otca_config["
|
|
130
|
+
mcp_client_url = base_url + "/mcp-client"
|
|
131
|
+
otca_config["mcpClientServersUrl"] = mcp_client_url + "/servers"
|
|
132
|
+
otca_config["mcpClientToolsUrl"] = mcp_client_url + "/tools"
|
|
133
|
+
otca_config["mcpClientToolsRefreshUrl"] = otca_config["mcpClientToolsUrl"] + "/refresh"
|
|
134
|
+
otca_config["mcpClientToolsSyncUrl"] = otca_config["mcpClientToolsUrl"] + "/sync"
|
|
135
|
+
otca_config["mcpClientHealthUrl"] = mcp_client_url + "/health"
|
|
130
136
|
|
|
131
137
|
# MCP Server endpoints:
|
|
132
|
-
|
|
133
|
-
otca_config["
|
|
134
|
-
otca_config["
|
|
135
|
-
otca_config["mcpServerDeleteToolsUrl"] = base_url + "/mcp-server/tools"
|
|
136
|
-
otca_config["mcpServerHealthUrl"] = base_url + "/mcp-server/health"
|
|
138
|
+
mcp_server_url = base_url + "/mcp-server"
|
|
139
|
+
otca_config["mcpServerToolsUrl"] = mcp_server_url + "/tools"
|
|
140
|
+
otca_config["mcpServerHealthUrl"] = mcp_server_url + "/health"
|
|
137
141
|
|
|
138
142
|
otca_config["contentSystem"] = self.get_content_system()
|
|
139
143
|
otca_config["clientId"] = client_id
|
|
@@ -1268,6 +1272,27 @@ class OTCA:
|
|
|
1268
1272
|
|
|
1269
1273
|
# end method definition
|
|
1270
1274
|
|
|
1275
|
+
def get_tools(self) -> list | None:
|
|
1276
|
+
"""List all tools available from remote MCP servers.
|
|
1277
|
+
|
|
1278
|
+
Returns:
|
|
1279
|
+
list | None:
|
|
1280
|
+
List of available MCP tools or None in case of an error.
|
|
1281
|
+
|
|
1282
|
+
"""
|
|
1283
|
+
|
|
1284
|
+
request_url = self.config()["toolsUrl"]
|
|
1285
|
+
request_header = self.request_header(service_type="user")
|
|
1286
|
+
|
|
1287
|
+
return self.do_request(
|
|
1288
|
+
url=request_url,
|
|
1289
|
+
method="GET",
|
|
1290
|
+
headers=request_header,
|
|
1291
|
+
failure_message="Canbnot get list of tools",
|
|
1292
|
+
)
|
|
1293
|
+
|
|
1294
|
+
# end method definition
|
|
1295
|
+
|
|
1271
1296
|
# --- MCP Client Methods ---
|
|
1272
1297
|
|
|
1273
1298
|
def create_mcp_server(self, server_config: dict) -> dict | None:
|
|
@@ -1410,6 +1435,29 @@ class OTCA:
|
|
|
1410
1435
|
list | None:
|
|
1411
1436
|
List of available MCP tools or None in case of an error.
|
|
1412
1437
|
|
|
1438
|
+
Example:
|
|
1439
|
+
[
|
|
1440
|
+
{
|
|
1441
|
+
'id': 2,
|
|
1442
|
+
'key': 'ALLOWED_MCP_TOOLS',
|
|
1443
|
+
'value': [
|
|
1444
|
+
{
|
|
1445
|
+
'name': 'current_time',
|
|
1446
|
+
'description': 'Use this to get the current date and time, return it as a string.'
|
|
1447
|
+
},
|
|
1448
|
+
{
|
|
1449
|
+
'name': 'otcm_recman_agent',
|
|
1450
|
+
'description': "Invoke Records Management agent and its related tools."
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
'name': 'otcm_workspace_agent',
|
|
1454
|
+
'description': "Invoke the workspace operations agent for OpenText Content Management.'
|
|
1455
|
+
}
|
|
1456
|
+
],
|
|
1457
|
+
'subscription_id': None
|
|
1458
|
+
}
|
|
1459
|
+
]
|
|
1460
|
+
|
|
1413
1461
|
"""
|
|
1414
1462
|
|
|
1415
1463
|
request_url = self.config()["mcpClientToolsUrl"]
|
|
@@ -1476,7 +1524,7 @@ class OTCA:
|
|
|
1476
1524
|
|
|
1477
1525
|
"""
|
|
1478
1526
|
|
|
1479
|
-
request_url = self.config()["
|
|
1527
|
+
request_url = self.config()["mcpServerToolsUrl"]
|
|
1480
1528
|
request_header = self.request_header(service_type="user")
|
|
1481
1529
|
|
|
1482
1530
|
return self.do_request(
|
|
@@ -1501,7 +1549,7 @@ class OTCA:
|
|
|
1501
1549
|
|
|
1502
1550
|
"""
|
|
1503
1551
|
|
|
1504
|
-
request_url = self.config()["
|
|
1552
|
+
request_url = self.config()["mcpServerToolsUrl"]
|
|
1505
1553
|
request_header = self.request_header(service_type="user")
|
|
1506
1554
|
|
|
1507
1555
|
return self.do_request(
|
|
@@ -1548,7 +1596,7 @@ class OTCA:
|
|
|
1548
1596
|
|
|
1549
1597
|
"""
|
|
1550
1598
|
|
|
1551
|
-
request_url = self.config()["
|
|
1599
|
+
request_url = self.config()["mcpServerToolsUrl"]
|
|
1552
1600
|
request_header = self.request_header(service_type="user")
|
|
1553
1601
|
|
|
1554
1602
|
return self.do_request(
|
|
@@ -27519,7 +27519,13 @@ class OTCS:
|
|
|
27519
27519
|
workspace_id,
|
|
27520
27520
|
)
|
|
27521
27521
|
return None
|
|
27522
|
-
template_names = [
|
|
27522
|
+
template_names = [
|
|
27523
|
+
item.text for item in root.findall("startup/processing/templates/template") if item is not None
|
|
27524
|
+
]
|
|
27525
|
+
# below for scenarios where they only configure one template for testing
|
|
27526
|
+
if not template_names:
|
|
27527
|
+
# A single template appears only in startup/processing/template instead
|
|
27528
|
+
template_names = [item.text for item in root.findall("startup/processing/template") if item is not None]
|
|
27523
27529
|
|
|
27524
27530
|
return template_names
|
|
27525
27531
|
|
|
@@ -28189,55 +28195,25 @@ class OTCS:
|
|
|
28189
28195
|
if not forms or not isinstance(forms[0], dict):
|
|
28190
28196
|
continue
|
|
28191
28197
|
|
|
28198
|
+
form_data = forms[0].get("data", {})
|
|
28199
|
+
if not isinstance(form_data, dict):
|
|
28200
|
+
form_data = {}
|
|
28201
|
+
|
|
28192
28202
|
# we take the priority form the form data if
|
|
28193
28203
|
# the baseline follow-up does not have a priority set.
|
|
28194
|
-
form_data = forms[0].get("data", {})
|
|
28195
28204
|
if "priority" not in followup and "priority" in form_data:
|
|
28196
28205
|
followup["priority"] = form_data.get("priority")
|
|
28197
28206
|
|
|
28207
|
+
# The escalation, activation and schedule details are not part of the reminder
|
|
28208
|
+
# itself - they only live in the view form. We just hang the raw sections the
|
|
28209
|
+
# caller asked for into the followup dictionary. _extract_reminder_payload()
|
|
28210
|
+
# unpacks them into the flat reminder fields further down:
|
|
28198
28211
|
if escalation:
|
|
28199
|
-
escalation_alert = form_data.get("escalation_alert", {})
|
|
28200
|
-
send_in = escalation_alert.get("send_in", {}) if isinstance(escalation_alert, dict) else {}
|
|
28201
|
-
send_to = escalation_alert.get("send_to", []) if isinstance(escalation_alert, dict) else []
|
|
28202
|
-
|
|
28203
|
-
if isinstance(escalation_alert, dict) and "escalation_enabled" in escalation_alert:
|
|
28204
|
-
followup["escalation_enabled"] = escalation_alert.get("escalation_enabled")
|
|
28205
|
-
if isinstance(send_in, dict) and "escalation_period" in send_in:
|
|
28206
|
-
followup["escalation_period"] = send_in.get("escalation_period")
|
|
28207
|
-
if isinstance(send_in, dict) and "escalation_when" in send_in:
|
|
28208
|
-
followup["escalation_when"] = send_in.get("escalation_when")
|
|
28209
|
-
if isinstance(send_to, list) and send_to:
|
|
28210
|
-
# send_to is a plain list of member IDs; expand each one to the same
|
|
28211
|
-
# {id, name, type, type_name, [node_id]} shape as the assignees list:
|
|
28212
|
-
escalation_recipients = []
|
|
28213
|
-
for recipient_id in send_to:
|
|
28214
|
-
recipient_entry = {"id": recipient_id}
|
|
28215
|
-
recipient_member = self.get_member(
|
|
28216
|
-
member_id=recipient_id, fields="properties{name,type,type_name,node_id}"
|
|
28217
|
-
)
|
|
28218
|
-
if recipient_member:
|
|
28219
|
-
for recipient_field in ("name", "type", "type_name", "node_id"):
|
|
28220
|
-
recipient_value = self.get_result_value(
|
|
28221
|
-
response=recipient_member, key=recipient_field, show_error=False
|
|
28222
|
-
)
|
|
28223
|
-
if recipient_value is not None:
|
|
28224
|
-
recipient_entry[recipient_field] = recipient_value
|
|
28225
|
-
escalation_recipients.append(recipient_entry)
|
|
28226
|
-
followup["escalation_recipient_list"] = escalation_recipients
|
|
28227
|
-
# end if escalation:
|
|
28228
|
-
|
|
28212
|
+
followup["escalation_alert"] = form_data.get("escalation_alert", {})
|
|
28229
28213
|
if activation:
|
|
28230
|
-
|
|
28231
|
-
|
|
28232
|
-
|
|
28233
|
-
followup["activation_period"] = send_in.get("activation_period")
|
|
28234
|
-
if isinstance(send_in, dict) and "activation_unit" in send_in:
|
|
28235
|
-
followup["activation_unit"] = send_in.get("activation_unit")
|
|
28236
|
-
|
|
28237
|
-
if schedule:
|
|
28238
|
-
schedule_data = form_data.get("schedule", {}) if isinstance(form_data, dict) else {}
|
|
28239
|
-
if schedule_data:
|
|
28240
|
-
followup["schedule"] = schedule_data
|
|
28214
|
+
followup["activation_alert"] = form_data.get("activation_alert", {})
|
|
28215
|
+
if schedule and form_data.get("schedule"):
|
|
28216
|
+
followup["schedule"] = form_data["schedule"]
|
|
28241
28217
|
# end for loop to enrich reminders with view form information
|
|
28242
28218
|
|
|
28243
28219
|
# Normalize the numeric values of the REST API response to their readable form.
|
|
@@ -28466,58 +28442,24 @@ class OTCS:
|
|
|
28466
28442
|
return self._extract_reminder_payload(reminder=response)
|
|
28467
28443
|
|
|
28468
28444
|
form_data = forms[0].get("data", {})
|
|
28445
|
+
if not isinstance(form_data, dict):
|
|
28446
|
+
form_data = {}
|
|
28469
28447
|
|
|
28470
28448
|
# we take the priority from the form data if
|
|
28471
28449
|
# the baseline follow-up does not have a priority set.
|
|
28472
28450
|
if "priority" not in followup and "priority" in form_data:
|
|
28473
28451
|
followup["priority"] = form_data.get("priority")
|
|
28474
28452
|
|
|
28453
|
+
# The escalation, activation and schedule details are not part of the reminder itself -
|
|
28454
|
+
# they only live in the view form. We just hang the raw sections the caller asked for
|
|
28455
|
+
# into the followup dictionary. _extract_reminder_payload() below unpacks them into the
|
|
28456
|
+
# flat reminder fields and normalizes their values:
|
|
28475
28457
|
if escalation:
|
|
28476
|
-
escalation_alert = form_data.get("escalation_alert", {})
|
|
28477
|
-
send_in = escalation_alert.get("send_in", {}) if isinstance(escalation_alert, dict) else {}
|
|
28478
|
-
send_to = escalation_alert.get("send_to", {}) if isinstance(escalation_alert, dict) else []
|
|
28479
|
-
|
|
28480
|
-
# The fields 'escalation_enabled', 'escalation_period', and 'escalation_when' may only be aviable
|
|
28481
|
-
# if the reminder is in 'open' state. If it is already in 'active' or 'completed' state, these
|
|
28482
|
-
# fields may not be available.
|
|
28483
|
-
if isinstance(escalation_alert, dict) and "escalation_enabled" in escalation_alert:
|
|
28484
|
-
followup["escalation_enabled"] = escalation_alert.get("escalation_enabled")
|
|
28485
|
-
if isinstance(send_in, dict) and "escalation_period" in send_in:
|
|
28486
|
-
followup["escalation_period"] = send_in.get("escalation_period")
|
|
28487
|
-
if isinstance(send_in, dict) and "escalation_when" in send_in:
|
|
28488
|
-
followup["escalation_when"] = send_in.get("escalation_when")
|
|
28489
|
-
|
|
28490
|
-
if isinstance(send_to, list) and send_to:
|
|
28491
|
-
# send_to is a plain list of member IDs; expand each one to the same
|
|
28492
|
-
# {id, name, type, type_name, [node_id]} shape as the assignees list:
|
|
28493
|
-
escalation_recipients = []
|
|
28494
|
-
for escalation_recipient_id in send_to:
|
|
28495
|
-
escalation_recipient_entry = {"id": escalation_recipient_id}
|
|
28496
|
-
escalation_recipient_member = self.get_member(
|
|
28497
|
-
member_id=escalation_recipient_id, fields="properties{name_formatted,type,type_name,node_id}"
|
|
28498
|
-
)
|
|
28499
|
-
if escalation_recipient_member:
|
|
28500
|
-
for escalation_recipient_field in ("name_formatted", "type", "type_name", "node_id"):
|
|
28501
|
-
escalation_recipient_value = self.get_result_value(
|
|
28502
|
-
response=escalation_recipient_member, key=escalation_recipient_field, show_error=False
|
|
28503
|
-
)
|
|
28504
|
-
if escalation_recipient_value is not None:
|
|
28505
|
-
if escalation_recipient_field == "name_formatted":
|
|
28506
|
-
escalation_recipient_field = "name"
|
|
28507
|
-
escalation_recipient_entry[escalation_recipient_field] = escalation_recipient_value
|
|
28508
|
-
escalation_recipients.append(escalation_recipient_entry)
|
|
28509
|
-
followup["escalation_recipient_list"] = escalation_recipients
|
|
28458
|
+
followup["escalation_alert"] = form_data.get("escalation_alert", {})
|
|
28510
28459
|
if activation:
|
|
28511
|
-
|
|
28512
|
-
|
|
28513
|
-
|
|
28514
|
-
followup["activation_period"] = send_in.get("activation_period")
|
|
28515
|
-
if isinstance(send_in, dict) and "activation_unit" in send_in:
|
|
28516
|
-
followup["activation_unit"] = send_in.get("activation_unit")
|
|
28517
|
-
if schedule:
|
|
28518
|
-
schedule_data = form_data.get("schedule", {}) if isinstance(form_data, dict) else {}
|
|
28519
|
-
if schedule_data:
|
|
28520
|
-
followup["schedule"] = schedule_data
|
|
28460
|
+
followup["activation_alert"] = form_data.get("activation_alert", {})
|
|
28461
|
+
if schedule and form_data.get("schedule"):
|
|
28462
|
+
followup["schedule"] = form_data["schedule"]
|
|
28521
28463
|
|
|
28522
28464
|
# Normalize the numeric values of the REST API response to their readable form.
|
|
28523
28465
|
# This happens last, so that the values added from the view form above are covered as well:
|
|
@@ -29093,6 +29035,16 @@ class OTCS:
|
|
|
29093
29035
|
escalation_when -1 -> "after"
|
|
29094
29036
|
activation_unit 1 -> "day"
|
|
29095
29037
|
|
|
29038
|
+
In addition, this method unpacks the raw sections of the reminder view form that
|
|
29039
|
+
``get_reminder()`` and ``get_reminders()`` hang into the reminder: "escalation_alert"
|
|
29040
|
+
becomes ``escalation_enabled``, ``escalation_period``, ``escalation_when``,
|
|
29041
|
+
``escalation_unit`` and ``escalation_recipient_list`` (each recipient expanded via
|
|
29042
|
+
``get_member()``), and "activation_alert" becomes ``activation_period``,
|
|
29043
|
+
``activation_unit`` and ``activation_when``. The raw sections are removed afterwards.
|
|
29044
|
+
Depending on the state of the reminder, OTCS delivers these settings either as
|
|
29045
|
+
discrete fields (state "open") or as readable text in "activationAlert1" /
|
|
29046
|
+
"activationAlert2" (states "active" and "in progress") - both forms are handled.
|
|
29047
|
+
|
|
29096
29048
|
NOTE: Values that are only delivered by the reminder view form (priority, escalation,
|
|
29097
29049
|
activation and the relative due date) are only present if ``get_reminder()`` or
|
|
29098
29050
|
``get_reminders()`` were called with ``escalation=True``, ``activation=True`` or
|
|
@@ -29100,6 +29052,9 @@ class OTCS:
|
|
|
29100
29052
|
encoding is kept as it is and reported with a warning - this method never
|
|
29101
29053
|
guesses and never removes information.
|
|
29102
29054
|
|
|
29055
|
+
NOTE: Expanding the escalation recipients issues one ``get_member()`` request per
|
|
29056
|
+
recipient.
|
|
29057
|
+
|
|
29103
29058
|
Args:
|
|
29104
29059
|
reminder (dict | None):
|
|
29105
29060
|
A reminder as returned by ``get_reminder()``, a single entry as yielded by
|
|
@@ -29148,10 +29103,99 @@ class OTCS:
|
|
|
29148
29103
|
return
|
|
29149
29104
|
data[field] = names[value]
|
|
29150
29105
|
|
|
29106
|
+
# get_reminder() and get_reminders() hang the raw "escalation_alert" and
|
|
29107
|
+
# "activation_alert" sections of the reminder view form into the followup dictionary -
|
|
29108
|
+
# but only if the caller asked for them. They are unpacked into the flat reminder
|
|
29109
|
+
# fields here and dropped afterwards, so that callers only ever see the flat fields.
|
|
29110
|
+
# The numeric values they carry are normalized by the map_value() calls below:
|
|
29111
|
+
escalation_alert = followup.pop("escalation_alert", None)
|
|
29112
|
+
if isinstance(escalation_alert, dict):
|
|
29113
|
+
send_in = escalation_alert.get("send_in", {})
|
|
29114
|
+
if not isinstance(send_in, dict):
|
|
29115
|
+
send_in = {}
|
|
29116
|
+
send_to = escalation_alert.get("send_to", [])
|
|
29117
|
+
|
|
29118
|
+
# The fields 'escalation_enabled', 'escalation_period', and 'escalation_when' may only
|
|
29119
|
+
# be available if the reminder is in 'open' state. If it is already in 'active' or
|
|
29120
|
+
# 'in progress' state, OTCS reports them as readable text in 'activationAlert1' and
|
|
29121
|
+
# 'activationAlert2' instead:
|
|
29122
|
+
if "escalation_enabled" in escalation_alert:
|
|
29123
|
+
followup["escalation_enabled"] = escalation_alert.get("escalation_enabled")
|
|
29124
|
+
else:
|
|
29125
|
+
followup["escalation_enabled"] = "activationAlert1" in send_in
|
|
29126
|
+
|
|
29127
|
+
if "escalation_period" in send_in:
|
|
29128
|
+
followup["escalation_period"] = send_in.get("escalation_period")
|
|
29129
|
+
elif "activationAlert1" in send_in:
|
|
29130
|
+
followup["escalation_period"] = int(send_in.get("activationAlert1", "0").split(" ", 1)[0])
|
|
29131
|
+
|
|
29132
|
+
if "escalation_when" in send_in:
|
|
29133
|
+
followup["escalation_when"] = send_in.get("escalation_when")
|
|
29134
|
+
elif "activationAlert2" in send_in:
|
|
29135
|
+
followup["escalation_when"] = self.REMINDER_ESCALATION_WHEN_MAP.get(
|
|
29136
|
+
"after" if "after" in send_in.get("activationAlert2", "") else "before",
|
|
29137
|
+
)
|
|
29138
|
+
|
|
29139
|
+
for unit in self.REMINDER_UNIT_MAP:
|
|
29140
|
+
if unit in send_in.get("activationAlert1", ""):
|
|
29141
|
+
followup["escalation_unit"] = self.REMINDER_UNIT_MAP[unit]
|
|
29142
|
+
break
|
|
29143
|
+
|
|
29144
|
+
if isinstance(send_to, list) and send_to:
|
|
29145
|
+
# send_to is a plain list of member IDs; expand each one to the same
|
|
29146
|
+
# {id, name, type, type_name, [node_id]} shape as the assignees list:
|
|
29147
|
+
escalation_recipients = []
|
|
29148
|
+
for recipient_id in send_to:
|
|
29149
|
+
recipient_entry = {"id": recipient_id}
|
|
29150
|
+
recipient_member = self.get_member(
|
|
29151
|
+
member_id=recipient_id,
|
|
29152
|
+
fields="properties{name_formatted,type,type_name,node_id}",
|
|
29153
|
+
)
|
|
29154
|
+
if recipient_member:
|
|
29155
|
+
for recipient_field in ("name_formatted", "type", "type_name", "node_id"):
|
|
29156
|
+
recipient_value = self.get_result_value(
|
|
29157
|
+
response=recipient_member, key=recipient_field, show_error=False
|
|
29158
|
+
)
|
|
29159
|
+
if recipient_value is not None:
|
|
29160
|
+
# the members REST API calls the name "name_formatted":
|
|
29161
|
+
key = "name" if recipient_field == "name_formatted" else recipient_field
|
|
29162
|
+
recipient_entry[key] = recipient_value
|
|
29163
|
+
escalation_recipients.append(recipient_entry)
|
|
29164
|
+
followup["escalation_recipient_list"] = escalation_recipients
|
|
29165
|
+
# end if isinstance(send_to, list) and send_to
|
|
29166
|
+
# end if isinstance(escalation_alert, dict)
|
|
29167
|
+
|
|
29168
|
+
activation_alert = followup.pop("activation_alert", None)
|
|
29169
|
+
if isinstance(activation_alert, dict):
|
|
29170
|
+
send_in = activation_alert.get("send_in", {})
|
|
29171
|
+
if not isinstance(send_in, dict):
|
|
29172
|
+
send_in = {}
|
|
29173
|
+
|
|
29174
|
+
if "activation_period" in send_in:
|
|
29175
|
+
followup["activation_period"] = send_in.get("activation_period")
|
|
29176
|
+
elif "activationAlert1" in send_in:
|
|
29177
|
+
followup["activation_period"] = int(send_in.get("activationAlert1", "0").split(" ", 1)[0])
|
|
29178
|
+
|
|
29179
|
+
if "activation_unit" in send_in:
|
|
29180
|
+
followup["activation_unit"] = send_in.get("activation_unit")
|
|
29181
|
+
else:
|
|
29182
|
+
for unit in self.REMINDER_UNIT_MAP:
|
|
29183
|
+
if unit in send_in.get("activationAlert1", ""):
|
|
29184
|
+
followup["activation_unit"] = self.REMINDER_UNIT_MAP[unit]
|
|
29185
|
+
break
|
|
29186
|
+
|
|
29187
|
+
if "activationAlert2" in send_in:
|
|
29188
|
+
followup["activation_when"] = self.REMINDER_ESCALATION_WHEN_MAP.get(
|
|
29189
|
+
"after" if "after" in send_in.get("activationAlert2", "") else "before",
|
|
29190
|
+
)
|
|
29191
|
+
# end if isinstance(activation_alert, dict)
|
|
29192
|
+
|
|
29151
29193
|
map_value(followup, "status", self.REMINDER_STATUS_NAMES)
|
|
29152
29194
|
map_value(followup, "priority", self.REMINDER_PRIORITY_NAMES)
|
|
29153
29195
|
map_value(followup, "rule", self.REMINDER_RULE_NAMES)
|
|
29154
29196
|
map_value(followup, "escalation_when", self.REMINDER_ESCALATION_WHEN_NAMES)
|
|
29197
|
+
map_value(followup, "escalation_unit", self.REMINDER_UNIT_NAMES)
|
|
29198
|
+
map_value(followup, "activation_when", self.REMINDER_ESCALATION_WHEN_NAMES)
|
|
29155
29199
|
map_value(followup, "activation_unit", self.REMINDER_UNIT_NAMES)
|
|
29156
29200
|
|
|
29157
29201
|
# The unit of a relative due date lives in the schedule delivered by the view form:
|
|
@@ -29160,6 +29204,20 @@ class OTCS:
|
|
|
29160
29204
|
due_in = schedule.get("due_in")
|
|
29161
29205
|
if isinstance(due_in, dict):
|
|
29162
29206
|
map_value(due_in, "due_in_unit", self.REMINDER_UNIT_NAMES)
|
|
29207
|
+
seq_repeat = schedule.get("seq_repeat")
|
|
29208
|
+
if seq_repeat:
|
|
29209
|
+
if "Every day" in seq_repeat:
|
|
29210
|
+
schedule["repeat_on"] = OTCS.REMINDER_SEQUENCE_MAP.get("daily")
|
|
29211
|
+
if "Every 2 weeks" in seq_repeat:
|
|
29212
|
+
schedule["repeat_on"] = OTCS.REMINDER_SEQUENCE_MAP.get("bi-weekly")
|
|
29213
|
+
elif "Every 2 months" in seq_repeat:
|
|
29214
|
+
schedule["repeat_on"] = OTCS.REMINDER_SEQUENCE_MAP.get("every 2 months")
|
|
29215
|
+
elif "every 3 months" in seq_repeat:
|
|
29216
|
+
schedule["repeat_on"] = OTCS.REMINDER_SEQUENCE_MAP.get("quarterly")
|
|
29217
|
+
elif "every 6 months" in seq_repeat:
|
|
29218
|
+
schedule["repeat_on"] = OTCS.REMINDER_SEQUENCE_MAP.get("semi-annually")
|
|
29219
|
+
elif "Every year" in seq_repeat:
|
|
29220
|
+
schedule["repeat_on"] = OTCS.REMINDER_SEQUENCE_MAP.get("yearly")
|
|
29163
29221
|
|
|
29164
29222
|
# OTCS reports this flag as 0 / 1 while the reminder methods take a boolean:
|
|
29165
29223
|
if isinstance(followup.get("activation_by_day"), int):
|
|
@@ -608,6 +608,7 @@ class Payload:
|
|
|
608
608
|
self._embeddings = self.get_payload_section("embeddings")
|
|
609
609
|
self._nifi_flows = self.get_payload_section("nifi")
|
|
610
610
|
self._aviator_mcp_servers = self.get_payload_section("aviatorMcpServers")
|
|
611
|
+
self._aviator_mcp_tools = self.get_payload_section("aviatorMcpTools")
|
|
611
612
|
|
|
612
613
|
return self._payload
|
|
613
614
|
|
|
@@ -2951,6 +2952,14 @@ class Payload:
|
|
|
2951
2952
|
self.logger.warning(
|
|
2952
2953
|
"aviatorMcpServers in payload but Content Aviator (OTCA) is not configured! MCP servers will not be processed.",
|
|
2953
2954
|
)
|
|
2955
|
+
case "aviatorMcpTools":
|
|
2956
|
+
if self._otca and isinstance(self._otca, OTCA):
|
|
2957
|
+
self._log_header_callback("Process Aviator MCP Tools")
|
|
2958
|
+
self.process_mcp_tools()
|
|
2959
|
+
else:
|
|
2960
|
+
self.logger.warning(
|
|
2961
|
+
"aviatorMcpTools in payload but Content Aviator (OTCA) is not configured! MCP tools will not be processed.",
|
|
2962
|
+
)
|
|
2954
2963
|
case _:
|
|
2955
2964
|
self.logger.error(
|
|
2956
2965
|
"Illegal payload section name -> '%s' in payloadSections!",
|
|
@@ -32224,3 +32233,137 @@ class Payload:
|
|
|
32224
32233
|
return success
|
|
32225
32234
|
|
|
32226
32235
|
# end method definition
|
|
32236
|
+
|
|
32237
|
+
@tracer.start_as_current_span(attributes=OTEL_TRACING_ATTRIBUTES, name="process_mcp_tools")
|
|
32238
|
+
def process_mcp_tools(self, section_name: str = "aviatorMcpTools") -> bool:
|
|
32239
|
+
"""Register Content Aviator agents as MCP tools.
|
|
32240
|
+
|
|
32241
|
+
Payload item keys:
|
|
32242
|
+
- enabled (bool, optional, default=True)
|
|
32243
|
+
- name (str, required)
|
|
32244
|
+
|
|
32245
|
+
Args:
|
|
32246
|
+
section_name (str, optional):
|
|
32247
|
+
The name of the payload section. It can be overridden
|
|
32248
|
+
for cases where multiple sections of same type
|
|
32249
|
+
are used (e.g. the "Post" sections).
|
|
32250
|
+
This name is also used for the "success" status
|
|
32251
|
+
files written to the Admin Personal Workspace.
|
|
32252
|
+
|
|
32253
|
+
Returns:
|
|
32254
|
+
bool:
|
|
32255
|
+
True, if payload has been processed without errors, False otherwise.
|
|
32256
|
+
|
|
32257
|
+
"""
|
|
32258
|
+
|
|
32259
|
+
if not self._aviator_mcp_tools:
|
|
32260
|
+
self.logger.info(
|
|
32261
|
+
"Payload section -> '%s' is empty. Skipping...",
|
|
32262
|
+
section_name,
|
|
32263
|
+
)
|
|
32264
|
+
return True
|
|
32265
|
+
|
|
32266
|
+
# If this payload section has been processed successfully before we
|
|
32267
|
+
# can return True and skip processing it once more:
|
|
32268
|
+
if self.check_status_file(payload_section_name=section_name):
|
|
32269
|
+
return True
|
|
32270
|
+
|
|
32271
|
+
success: bool = True
|
|
32272
|
+
|
|
32273
|
+
# list_mcp_tools() returns a list of settings entries (e.g. key -> 'ALLOWED_MCP_TOOLS')
|
|
32274
|
+
# whose "value" is the actual list of tool dictionaries with "name" and "description":
|
|
32275
|
+
existing_mcp_tools = self._otca.list_registered_mcp_tools()
|
|
32276
|
+
if existing_mcp_tools is None:
|
|
32277
|
+
self.logger.warning(
|
|
32278
|
+
"Failed to retrieve the list of available MCP tools from Content Aviator. Cannot check if tools are already registered."
|
|
32279
|
+
)
|
|
32280
|
+
|
|
32281
|
+
existing_mcp_tool_names = [
|
|
32282
|
+
tool.get("name")
|
|
32283
|
+
for entry in existing_mcp_tools or []
|
|
32284
|
+
for tool in entry.get("value") or []
|
|
32285
|
+
if tool.get("name")
|
|
32286
|
+
]
|
|
32287
|
+
if existing_mcp_tool_names:
|
|
32288
|
+
self.logger.info(
|
|
32289
|
+
"Already registered MCP tools in Content Aviator: %s",
|
|
32290
|
+
", ".join(existing_mcp_tool_names),
|
|
32291
|
+
)
|
|
32292
|
+
|
|
32293
|
+
for mcp_tool in self._aviator_mcp_tools:
|
|
32294
|
+
tool_name = mcp_tool.get("name")
|
|
32295
|
+
if not tool_name:
|
|
32296
|
+
self.logger.error("Content Aviator MCP tool needs a name in payload! Skipping...")
|
|
32297
|
+
success = False
|
|
32298
|
+
continue
|
|
32299
|
+
|
|
32300
|
+
# Check if element has been disabled in payload (enabled = false).
|
|
32301
|
+
# In this case we skip the element:
|
|
32302
|
+
if not mcp_tool.get("enabled", True):
|
|
32303
|
+
self.logger.info(
|
|
32304
|
+
"Payload for Content Aviator MCP tool -> '%s' is disabled. Skipping...",
|
|
32305
|
+
tool_name,
|
|
32306
|
+
)
|
|
32307
|
+
continue
|
|
32308
|
+
|
|
32309
|
+
if tool_name in existing_mcp_tool_names:
|
|
32310
|
+
self.logger.info(
|
|
32311
|
+
"Content Aviator MCP tool -> '%s' is already registered in Content Aviator. No need to register it again. Skipping...",
|
|
32312
|
+
tool_name,
|
|
32313
|
+
)
|
|
32314
|
+
continue
|
|
32315
|
+
|
|
32316
|
+
response = self._otca.register_mcp_tools(tools=[tool_name])
|
|
32317
|
+
if response:
|
|
32318
|
+
tool_id = response.get("id", "Unknown ID")
|
|
32319
|
+
# The response value is the complete list of tools registered for the
|
|
32320
|
+
# tenant - we need to pick the entry of the tool we just registered:
|
|
32321
|
+
value = response.get("value") or []
|
|
32322
|
+
tool_entry = next((entry for entry in value if entry.get("name") == tool_name), None)
|
|
32323
|
+
if tool_entry is None:
|
|
32324
|
+
self.logger.warning(
|
|
32325
|
+
"Content Aviator MCP tool -> '%s' is not included in the registration response!",
|
|
32326
|
+
tool_name,
|
|
32327
|
+
)
|
|
32328
|
+
tool_description = (
|
|
32329
|
+
tool_entry.get("description", "Unknown Description") if tool_entry else "Unknown Description"
|
|
32330
|
+
)
|
|
32331
|
+
self.logger.info(
|
|
32332
|
+
"Registered Content Aviator MCP tool -> '%s' (%s).",
|
|
32333
|
+
tool_name,
|
|
32334
|
+
tool_id,
|
|
32335
|
+
)
|
|
32336
|
+
mcp_tool["id"] = tool_id # record the ID of the MCP tool settings record in the payload
|
|
32337
|
+
mcp_tool["description"] = tool_description # record the created tool description in the payload
|
|
32338
|
+
mcp_tool["registered"] = True
|
|
32339
|
+
else:
|
|
32340
|
+
self.logger.error(
|
|
32341
|
+
"Failed to register Content Aviator MCP tool -> '%s'!",
|
|
32342
|
+
tool_name,
|
|
32343
|
+
)
|
|
32344
|
+
mcp_tool["registered"] = False
|
|
32345
|
+
success = False
|
|
32346
|
+
# end for mcp_tool in self._aviator_mcp_tools
|
|
32347
|
+
|
|
32348
|
+
registered_tools = [tool.get("name") for tool in self._aviator_mcp_tools if tool.get("registered") is True]
|
|
32349
|
+
if registered_tools:
|
|
32350
|
+
self.logger.info(
|
|
32351
|
+
"Successfully registered the following additional MCP tools: %s",
|
|
32352
|
+
registered_tools,
|
|
32353
|
+
)
|
|
32354
|
+
failed_tools = [tool.get("name") for tool in self._aviator_mcp_tools if tool.get("registered") is False]
|
|
32355
|
+
if failed_tools:
|
|
32356
|
+
self.logger.info(
|
|
32357
|
+
"Failed to register the following MCP tools: %s",
|
|
32358
|
+
failed_tools,
|
|
32359
|
+
)
|
|
32360
|
+
|
|
32361
|
+
self.write_status_file(
|
|
32362
|
+
success=success,
|
|
32363
|
+
payload_section_name=section_name,
|
|
32364
|
+
payload_section=self._aviator_mcp_tools,
|
|
32365
|
+
)
|
|
32366
|
+
|
|
32367
|
+
return success
|
|
32368
|
+
|
|
32369
|
+
# end method definition
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|