Skip to content

Commit a958b55

Browse files
committed
Add tasks creation, update and deletion features
1 parent 1503675 commit a958b55

File tree

6 files changed

+415
-7
lines changed

6 files changed

+415
-7
lines changed

cli/ca_mcp/client.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,25 @@ def make_patch_request(endpoint, payload):
8484
)
8585

8686

87+
def make_delete_request(endpoint):
88+
"""
89+
Make a DELETE request to the API
90+
91+
Args:
92+
endpoint: API endpoint (e.g., "/task-templates/{id}/")
93+
94+
Returns:
95+
Response object
96+
"""
97+
url = f"{API_URL}{endpoint}"
98+
return requests.delete(
99+
url,
100+
headers=get_headers(),
101+
verify=VERIFY_CERTIFICATE,
102+
timeout=HTTP_TIMEOUT,
103+
)
104+
105+
87106
def handle_response(res, error_message="Error"):
88107
"""
89108
Handle API response and check for errors

cli/ca_mcp/resolvers.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,3 +321,33 @@ def resolve_library_id(library_urn_or_id: str) -> str:
321321
)
322322

323323
return str(libraries[0]["id"])
324+
325+
326+
def resolve_task_template_id(task_name_or_id: str) -> str:
327+
"""Helper function to resolve task template name to UUID
328+
If already a UUID, returns it. If a name, looks it up via API.
329+
"""
330+
# Check if it's already a UUID
331+
if "-" in task_name_or_id and len(task_name_or_id) == 36:
332+
return task_name_or_id
333+
334+
# Otherwise, look up by name
335+
res = make_get_request("/task-templates/", params={"name": task_name_or_id})
336+
337+
if res.status_code != 200:
338+
raise ValueError(
339+
f"Task template '{task_name_or_id}' API error {res.status_code}"
340+
)
341+
342+
data = res.json()
343+
tasks = get_paginated_results(data)
344+
345+
if not tasks:
346+
raise ValueError(f"Task template '{task_name_or_id}' not found")
347+
348+
if len(tasks) > 1:
349+
raise ValueError(
350+
f"Ambiguous task template name '{task_name_or_id}', found {len(tasks)}"
351+
)
352+
353+
return tasks[0]["id"]

cli/ca_mcp/server.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
get_quantitative_risk_studies,
2626
get_quantitative_risk_scenarios,
2727
get_quantitative_risk_hypotheses,
28+
get_task_templates,
29+
get_task_template_details,
2830
)
2931

3032
from .tools.analysis_tools import (
@@ -52,6 +54,7 @@
5254
create_quantitative_risk_scenario,
5355
create_quantitative_risk_hypothesis,
5456
refresh_quantitative_risk_study_simulations,
57+
create_task_template,
5558
)
5659

5760
from .tools.update_tools import (
@@ -62,6 +65,8 @@
6265
update_quantitative_risk_study,
6366
update_quantitative_risk_scenario,
6467
update_quantitative_risk_hypothesis,
68+
update_task_template,
69+
delete_task_template,
6570
)
6671

6772
# Register all tools with MCP decorators
@@ -83,6 +88,8 @@
8388
mcp.tool()(get_quantitative_risk_studies)
8489
mcp.tool()(get_quantitative_risk_scenarios)
8590
mcp.tool()(get_quantitative_risk_hypotheses)
91+
mcp.tool()(get_task_templates)
92+
mcp.tool()(get_task_template_details)
8693

8794
mcp.tool()(get_all_audits_with_metrics)
8895
mcp.tool()(get_audit_gap_analysis)
@@ -104,6 +111,7 @@
104111
mcp.tool()(create_quantitative_risk_scenario)
105112
mcp.tool()(create_quantitative_risk_hypothesis)
106113
mcp.tool()(refresh_quantitative_risk_study_simulations)
114+
mcp.tool()(create_task_template)
107115

108116
mcp.tool()(update_asset)
109117
mcp.tool()(update_risk_scenario)
@@ -112,6 +120,8 @@
112120
mcp.tool()(update_quantitative_risk_study)
113121
mcp.tool()(update_quantitative_risk_scenario)
114122
mcp.tool()(update_quantitative_risk_hypothesis)
123+
mcp.tool()(update_task_template)
124+
mcp.tool()(delete_task_template)
115125

116126

117127
def run_server():

cli/ca_mcp/tools/read_tools.py

Lines changed: 110 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,17 +108,24 @@ async def get_applied_controls(folder: str = None):
108108
if filters:
109109
result += f" ({', '.join(f'{k}={v}' for k, v in filters.items())})"
110110
result += "\n\n"
111-
result += "|Ref|Name|Status|ETA|Domain|\n"
112-
result += "|---|---|---|---|---|\n"
111+
result += "|UUID|Ref|Name|Status|ETA|Domain|Category|CSF Function|Effort|Impact|Priority|Cost|\n"
112+
result += "|---|---|---|---|---|---|---|---|---|---|---|---|\n"
113113

114114
for item in controls:
115+
uuid = item.get("id")
115116
ref_id = item.get("ref_id") or "N/A"
116117
name = item.get("name", "N/A")
117118
status = item.get("status", "N/A")
118119
eta = item.get("eta") or "N/A"
119120
domain = (item.get("folder") or {}).get("str", "N/A")
121+
category = item.get("category", "N/A")
122+
csf_function = item.get("csf_function", "N/A")
123+
effort = item.get("effort", "N/A")
124+
impact = item.get("control_impact", "N/A")
125+
priority = item.get("priority", "N/A")
126+
cost = item.get("cost", 0)
120127

121-
result += f"|{ref_id}|{name}|{status}|{eta}|{domain}|\n"
128+
result += f"|{uuid}|{ref_id}|{name}|{status}|{eta}|{domain}|{category}|{csf_function}|{effort}|{impact}|{priority}|{cost}|\n"
122129

123130
return success_response(
124131
result,
@@ -896,20 +903,21 @@ async def get_requirement_assessments(
896903
return "No requirement assessments found"
897904

898905
result = f"Found {len(req_assessments)} requirement assessments\n\n"
899-
result += "|ID|Ref|Requirement|Assessment|Status|Result|\n"
900-
result += "|---|---|---|---|---|---|\n"
906+
result += "|ID|Ref|Description|Requirement|Assessment|Status|Result|\n"
907+
result += "|---|---|---|---|---|---|---|\n"
901908

902909
for req in req_assessments:
903910
req_id = req.get("id", "N/A")
904911
req_ref_id = req.get("ref_id", "N/A")
905912
requirement = req.get("name", "N/A")[:30] # Truncate
913+
description = req.get("description", "N/A")
906914
comp_assessment = (req.get("compliance_assessment") or {}).get(
907915
"name", "N/A"
908916
)[:20]
909917
status = req.get("status", "N/A")
910918
result_val = req.get("result", "N/A")
911919

912-
result += f"|{req_id}|{req_ref_id}|{requirement}|{comp_assessment}|{status}|{result_val}|\n"
920+
result += f"|{req_id}|{req_ref_id}|{description}|{requirement}|{comp_assessment}|{status}|{result_val}|\n"
913921

914922
return result
915923
except Exception as e:
@@ -1070,3 +1078,99 @@ async def get_quantitative_risk_hypotheses(scenario_id_or_name: str = None):
10701078
return result
10711079
except Exception as e:
10721080
return f"Error in get_quantitative_risk_hypotheses: {str(e)}"
1081+
1082+
1083+
async def get_task_templates(limit: int = None, offset: int = None, ordering: str = None, search: str = None):
1084+
"""List task templates with IDs, names, and details
1085+
1086+
Args:
1087+
limit: Number of results to return per page
1088+
offset: The initial index from which to return the results
1089+
ordering: Which field to use when ordering the results
1090+
search: A search term
1091+
"""
1092+
try:
1093+
params = {}
1094+
1095+
if limit is not None:
1096+
params["limit"] = limit
1097+
if offset is not None:
1098+
params["offset"] = offset
1099+
if ordering:
1100+
params["ordering"] = ordering
1101+
if search:
1102+
params["search"] = search
1103+
1104+
res = make_get_request("/task-templates/", params=params)
1105+
1106+
if res.status_code != 200:
1107+
return f"Error: HTTP {res.status_code} - {res.text}"
1108+
1109+
data = res.json()
1110+
tasks = get_paginated_results(data)
1111+
1112+
if not tasks:
1113+
return "No task tasks found"
1114+
1115+
result = f"Found {len(tasks)} task templates\n\n"
1116+
result += "|ID|Name|Description|Ref ID|Status|Recurrent|Enabled|Task Date|\n"
1117+
result += "|---|---|---|---|---|---|---|---|\n"
1118+
1119+
for task in tasks:
1120+
task_id = task.get("id", "N/A")
1121+
name = task.get("name", "N/A")
1122+
description = (task.get("description", "N/A") or "N/A")[:40] # Truncate
1123+
ref_id = task.get("ref_id", "N/A")
1124+
status = task.get("status", "N/A")
1125+
is_recurrent = "Yes" if task.get("is_recurrent") else "No"
1126+
enabled = "Yes" if task.get("enabled") else "No"
1127+
task_date = task.get("task_date", "N/A")
1128+
1129+
result += f"|{task_id}|{name}|{description}|{ref_id}|{status}|{is_recurrent}|{enabled}|{task_date}|\n"
1130+
1131+
return result
1132+
except Exception as e:
1133+
return f"Error in get_task_templates: {str(e)}"
1134+
1135+
1136+
async def get_task_template_details(task_id: str):
1137+
"""Get detailed information for a specific task template
1138+
1139+
Args:
1140+
task_id: Task template ID
1141+
"""
1142+
try:
1143+
res = make_get_request(f"/task-templates/{task_id}/")
1144+
1145+
if res.status_code != 200:
1146+
return f"Error: HTTP {res.status_code} - {res.text}"
1147+
1148+
task = res.json()
1149+
1150+
# Create result
1151+
result = f"|ID|Name|Description|Ref ID|Status|Task Date|Recurrent|Enabled|Published|Link|Folder|Path|Observation|Evidences|Created|Updated|Assets|Applied Controls|Compliance Assessment|Risk Assessment|Assign To|\n"
1152+
result += f"|{task.get('id', 'N/A')}|{task.get('name', 'N/A')}"
1153+
result += f"|{task.get('description', 'N/A')}"
1154+
result += f"|{task.get('ref_id', 'N/A')}"
1155+
result += f"|{task.get('status', 'N/A')}"
1156+
result += f"|{task.get('task_date', 'N/A')}"
1157+
result += f"|{'Yes' if task.get('is_recurrent') else 'No'}"
1158+
result += f"|{'Yes' if task.get('enabled') else 'No'}"
1159+
result += f"|{'Yes' if task.get('is_published') else 'No'}"
1160+
result += f"|{task.get('link', 'N/A')}"
1161+
result += f"|{task.get('folder', 'N/A')}"
1162+
result += f"|{task.get('path', 'N/A')}"
1163+
result += f"|{task.get('observation', 'N/A')}"
1164+
result += f"|{task.get('evidences', 'N/A')}"
1165+
result += f"|{task.get('created_at', 'N/A')}"
1166+
result += f"|{task.get('updated_at', 'N/A')}"
1167+
result += f"|{task.get('assets', [])}"
1168+
result += f"|{task.get('applied_controls', [])}"
1169+
result += f"|{task.get('compliance_assessments', [])}"
1170+
result += f"|{task.get('risk_assessments', [])}"
1171+
result += f"|{task.get('assigned_to', [])}"
1172+
result += "|\n"
1173+
1174+
return result
1175+
except Exception as e:
1176+
return f"Error in get_task_template_details: {str(e)}"

0 commit comments

Comments
 (0)