Skip to content

Commit c8ac65b

Browse files
committed
feat(cloud-guard): add problems mcp server
1 parent c36757f commit c8ac65b

File tree

13 files changed

+1732
-0
lines changed

13 files changed

+1732
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ venv.bak/
1818
# Mac files
1919
.DS_Store
2020

21+
#IDE
22+
.idea
23+
2124
# test environments
2225
.env
2326

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1+
pydantic
12
fastmcp
23
oci
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3.13
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
Copyright (c) 2025 Oracle and/or its affiliates.
2+
3+
The Universal Permissive License (UPL), Version 1.0
4+
5+
Subject to the condition set forth below, permission is hereby granted to any
6+
person obtaining a copy of this software, associated documentation and/or data
7+
(collectively the "Software"), free of charge and under any and all copyright
8+
rights in the Software, and any and all patent rights owned or freely
9+
licensable by each licensor hereunder covering either (i) the unmodified
10+
Software as contributed to or provided by such licensor, or (ii) the Larger
11+
Works (as defined below), to deal in both
12+
13+
(a) the Software, and
14+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
15+
one is included with the Software (each a "Larger Work" to which the Software
16+
is contributed by such licensors),
17+
18+
without restriction, including without limitation the rights to copy, create
19+
derivative works of, display, perform, and distribute the Software and make,
20+
use, sell, offer for sale, import, export, have made, and have sold the
21+
Software and the Larger Work(s), and to sublicense the foregoing rights on
22+
either these or other terms.
23+
24+
This license is subject to the following condition:
25+
The above copyright notice and either this complete permission notice or at
26+
a minimum a reference to the UPL must be included in all copies or
27+
substantial portions of the Software.
28+
29+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
30+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
31+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
32+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
33+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
34+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
35+
SOFTWARE.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# OCI Cloud Guard MCP Server
2+
3+
## Overview
4+
5+
This package implements certain functions of the [OCI Cloud Guard Service](https://docs.oracle.com/en-us/iaas/Content/cloud-guard/home.htm).
6+
It includes tools to help with managing cloud guard problems.
7+
8+
## Running the server
9+
10+
```sh
11+
uv run oracle.oci-cloud-guard-mcp-server
12+
```
13+
14+
## Tools
15+
16+
| Tool Name | Description |
17+
|-----------------------|-------------------------------------------|
18+
| list_problems | List the problems in a given compartment |
19+
| get_problem_details | Get the problem details with a given OCID |
20+
| update_problem_status | Updates the status of a problem |
21+
22+
⚠️ **NOTE**: All actions are performed with the permissions of the configured OCI CLI profile. We advise least-privilege IAM setup, secure credential management, safe network practices, secure logging, and warn against exposing secrets.
23+
24+
## Third-Party APIs
25+
26+
Developers choosing to distribute a binary implementation of this project are responsible for obtaining and providing all required licenses and copyright notices for the third-party code used in order to ensure compliance with their respective open source licenses.
27+
28+
## Disclaimer
29+
30+
Users are responsible for their local environment and credential safety. Different language model selections may yield different results and performance.
31+
32+
## License
33+
34+
Copyright (c) 2025 Oracle and/or its affiliates.
35+
36+
Released under the Universal Permissive License v1.0 as shown at
37+
<https://oss.oracle.com/licenses/upl/>.
38+
39+
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
Licensed under the Universal Permissive License v1.0 as shown at
4+
https://oss.oracle.com/licenses/upl.
5+
"""
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
"""
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
Licensed under the Universal Permissive License v1.0 as shown at
4+
https://oss.oracle.com/licenses/upl.
5+
"""
6+
7+
__project__ = "oracle.oci-cloud-guard-mcp-server"
8+
__version__ = "1.0.0"
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"""
2+
Copyright (c) 2025, Oracle and/or its affiliates.
3+
Licensed under the Universal Permissive License v1.0 as shown at
4+
https://oss.oracle.com/licenses/upl.
5+
"""
6+
7+
from datetime import datetime
8+
from typing import Dict, List, Literal, Optional
9+
10+
import oci
11+
from pydantic import BaseModel, Field
12+
13+
14+
def _oci_to_dict(obj):
15+
"""Best-effort conversion of OCI SDK model objects to plain dicts."""
16+
if obj is None:
17+
return None
18+
try:
19+
from oci.util import to_dict as oci_to_dict
20+
21+
return oci_to_dict(obj)
22+
except Exception:
23+
pass
24+
if isinstance(obj, dict):
25+
return obj
26+
if hasattr(obj, "__dict__"):
27+
return {k: v for k, v in obj.__dict__.items() if not k.startswith("_")}
28+
return None
29+
30+
31+
# region Cloud Guard - Problem
32+
33+
34+
class ResourceLock(BaseModel):
35+
"""
36+
Pydantic model mirroring oci.cloud_guard.models.ResourceLock
37+
(subset needed for Problem.locks).
38+
"""
39+
40+
type: Optional[Literal["FULL", "DELETE", "UNKNOWN_ENUM_VALUE"]] = Field(
41+
None, description="Type of the lock."
42+
)
43+
related_resource_id: Optional[str] = Field(
44+
None,
45+
description=(
46+
"The ID of the resource that is locking this resource. Deleting the "
47+
"related resource will remove the lock."
48+
),
49+
)
50+
message: Optional[str] = Field(
51+
None,
52+
description=(
53+
"A message added by the creator of the lock, typically indicating why "
54+
"the resource is locked."
55+
),
56+
)
57+
time_created: Optional[datetime] = Field(
58+
None, description="When the lock was created (RFC3339)."
59+
)
60+
61+
62+
class Problem(BaseModel):
63+
"""
64+
Pydantic model mirroring oci.cloud_guard.models.Problem.
65+
"""
66+
67+
id: Optional[str] = Field(
68+
None, description="Unique identifier that can't be changed after creation."
69+
)
70+
compartment_id: Optional[str] = Field(
71+
None, description="Compartment OCID where the resource is created."
72+
)
73+
detector_rule_id: Optional[str] = Field(
74+
None,
75+
description="Unique identifier of the detector rule that triggered the problem.",
76+
)
77+
region: Optional[str] = Field(None, description="DEPRECATED.")
78+
regions: Optional[List[str]] = Field(
79+
None, description="Regions where the problem is found."
80+
)
81+
risk_level: Optional[
82+
Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "MINOR", "UNKNOWN_ENUM_VALUE"]
83+
] = Field(None, description="The risk level for the problem.")
84+
risk_score: Optional[float] = Field(
85+
None, description="The risk score for the problem."
86+
)
87+
peak_risk_score_date: Optional[str] = Field(
88+
None,
89+
description=(
90+
"The date and time for the peak risk score observed for the problem (RFC3339)."
91+
),
92+
)
93+
peak_risk_score: Optional[float] = Field(
94+
None, description="Peak risk score for the problem."
95+
)
96+
auto_resolve_date: Optional[str] = Field(
97+
None,
98+
description="The date and time when the problem will be auto resolved (RFC3339).",
99+
)
100+
peak_risk_score_lookup_period_in_days: Optional[int] = Field(
101+
None,
102+
description="Number of days for which peak score is calculated for the problem.",
103+
)
104+
resource_id: Optional[str] = Field(
105+
None, description="Unique identifier of the resource affected by the problem."
106+
)
107+
resource_name: Optional[str] = Field(
108+
None, description="Display name of the affected resource."
109+
)
110+
resource_type: Optional[str] = Field(
111+
None, description="Type of the affected resource."
112+
)
113+
labels: Optional[List[str]] = Field(
114+
None, description="User-defined labels on the problem."
115+
)
116+
time_last_detected: Optional[datetime] = Field(
117+
None, description="The date and time the problem was last detected (RFC3339)."
118+
)
119+
time_first_detected: Optional[datetime] = Field(
120+
None, description="The date and time the problem was first detected (RFC3339)."
121+
)
122+
lifecycle_state: Optional[Literal["ACTIVE", "INACTIVE", "UNKNOWN_ENUM_VALUE"]] = (
123+
Field(None, description="The current lifecycle state of the problem.")
124+
)
125+
lifecycle_detail: Optional[
126+
Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"]
127+
] = Field(
128+
None, description="Additional details on the substate of the lifecycle state."
129+
)
130+
detector_id: Optional[
131+
Literal[
132+
"IAAS_ACTIVITY_DETECTOR",
133+
"IAAS_CONFIGURATION_DETECTOR",
134+
"IAAS_THREAT_DETECTOR",
135+
"IAAS_LOG_INSIGHT_DETECTOR",
136+
"IAAS_INSTANCE_SECURITY_DETECTOR",
137+
"IAAS_CONTAINER_SECURITY_DETECTOR",
138+
"UNKNOWN_ENUM_VALUE",
139+
]
140+
] = Field(
141+
None,
142+
description="Unique identifier of the detector that triggered the problem.",
143+
)
144+
target_id: Optional[str] = Field(
145+
None, description="Unique identifier of the target associated with the problem."
146+
)
147+
additional_details: Optional[Dict[str, str]] = Field(
148+
None, description="Additional details of the problem as key/value pairs."
149+
)
150+
description: Optional[str] = Field(None, description="Description of the problem.")
151+
recommendation: Optional[str] = Field(
152+
None, description="Recommendation for the problem."
153+
)
154+
comment: Optional[str] = Field(None, description="User comments on the problem.")
155+
impacted_resource_id: Optional[str] = Field(
156+
None, description="Unique identifier of the resource impacted by the problem."
157+
)
158+
impacted_resource_name: Optional[str] = Field(
159+
None, description="Display name of the impacted resource."
160+
)
161+
impacted_resource_type: Optional[str] = Field(
162+
None, description="Type of the impacted resource."
163+
)
164+
locks: Optional[List[ResourceLock]] = Field(
165+
None, description="Locks associated with this resource."
166+
)
167+
168+
169+
def map_resource_lock(rl) -> ResourceLock | None:
170+
if not rl:
171+
return None
172+
return ResourceLock(
173+
type=getattr(rl, "type", None),
174+
related_resource_id=getattr(rl, "related_resource_id", None),
175+
message=getattr(rl, "message", None),
176+
time_created=getattr(rl, "time_created", None),
177+
)
178+
179+
180+
def map_resource_locks(items) -> list[ResourceLock] | None:
181+
if not items:
182+
return None
183+
result: list[ResourceLock] = []
184+
for it in items:
185+
result.append(map_resource_lock(it))
186+
return result
187+
188+
189+
def map_problem(problem_data: oci.cloud_guard.models.Problem) -> Problem:
190+
"""
191+
Convert an oci.cloud_guard.models.Problem to oracle.oci_cloud_guard_mcp_server.models.Problem.
192+
"""
193+
return Problem(
194+
id=getattr(problem_data, "id", None),
195+
compartment_id=getattr(problem_data, "compartment_id", None),
196+
detector_rule_id=getattr(problem_data, "detector_rule_id", None),
197+
region=getattr(problem_data, "region", None),
198+
regions=getattr(problem_data, "regions", None),
199+
risk_level=getattr(problem_data, "risk_level", None),
200+
risk_score=getattr(problem_data, "risk_score", None),
201+
peak_risk_score_date=getattr(problem_data, "peak_risk_score_date", None),
202+
peak_risk_score=getattr(problem_data, "peak_risk_score", None),
203+
auto_resolve_date=getattr(problem_data, "auto_resolve_date", None),
204+
peak_risk_score_lookup_period_in_days=getattr(
205+
problem_data, "peak_risk_score_lookup_period_in_days", None
206+
),
207+
resource_id=getattr(problem_data, "resource_id", None),
208+
resource_name=getattr(problem_data, "resource_name", None),
209+
resource_type=getattr(problem_data, "resource_type", None),
210+
labels=getattr(problem_data, "labels", None),
211+
time_last_detected=getattr(problem_data, "time_last_detected", None),
212+
time_first_detected=getattr(problem_data, "time_first_detected", None),
213+
lifecycle_state=getattr(problem_data, "lifecycle_state", None),
214+
lifecycle_detail=getattr(problem_data, "lifecycle_detail", None),
215+
detector_id=getattr(problem_data, "detector_id", None),
216+
target_id=getattr(problem_data, "target_id", None),
217+
additional_details=getattr(problem_data, "additional_details", None),
218+
description=getattr(problem_data, "description", None),
219+
recommendation=getattr(problem_data, "recommendation", None),
220+
comment=getattr(problem_data, "comment", None),
221+
impacted_resource_id=getattr(problem_data, "impacted_resource_id", None),
222+
impacted_resource_name=getattr(problem_data, "impacted_resource_name", None),
223+
impacted_resource_type=getattr(problem_data, "impacted_resource_type", None),
224+
locks=map_resource_locks(getattr(problem_data, "locks", None)),
225+
)
226+
227+
228+
# region Cloud Guard - UpdateProblemStatusDetails
229+
230+
231+
class UpdateProblemStatusDetails(BaseModel):
232+
"""
233+
Pydantic model mirroring oci.cloud_guard.models.UpdateProblemStatusDetails.
234+
"""
235+
236+
status: Optional[
237+
Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"]
238+
] = Field(None, description="Action taken by user.")
239+
comment: Optional[str] = Field(None, description="User comments.")
240+
241+
242+
def map_update_problem_status_details(
243+
upd: oci.cloud_guard.models.UpdateProblemStatusDetails,
244+
) -> UpdateProblemStatusDetails | None:
245+
"""
246+
Convert an oci.cloud_guard.models.UpdateProblemStatusDetails to
247+
oracle.oci_cloud_guard_mcp_server.models.UpdateProblemStatusDetails.
248+
"""
249+
if not upd:
250+
return None
251+
return UpdateProblemStatusDetails(
252+
status=getattr(upd, "status", None),
253+
comment=getattr(upd, "comment", None),
254+
)
255+
256+
257+
# endregion

0 commit comments

Comments
 (0)