Skip to content

Commit 24d305d

Browse files
authored
feat(cloud-guard): add problems mcp server (#35)
1 parent 8fd532b commit 24d305d

File tree

13 files changed

+1723
-0
lines changed

13 files changed

+1723
-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: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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+
class ResourceLock(BaseModel):
32+
"""
33+
Pydantic model mirroring oci.cloud_guard.models.ResourceLock
34+
(subset needed for Problem.locks).
35+
"""
36+
37+
type: Optional[Literal["FULL", "DELETE", "UNKNOWN_ENUM_VALUE"]] = Field(
38+
None, description="Type of the lock."
39+
)
40+
related_resource_id: Optional[str] = Field(
41+
None,
42+
description=(
43+
"The ID of the resource that is locking this resource. Deleting the "
44+
"related resource will remove the lock."
45+
),
46+
)
47+
message: Optional[str] = Field(
48+
None,
49+
description=(
50+
"A message added by the creator of the lock, typically indicating why "
51+
"the resource is locked."
52+
),
53+
)
54+
time_created: Optional[datetime] = Field(
55+
None, description="When the lock was created (RFC3339)."
56+
)
57+
58+
59+
class Problem(BaseModel):
60+
"""
61+
Pydantic model mirroring oci.cloud_guard.models.Problem.
62+
"""
63+
64+
id: Optional[str] = Field(
65+
None, description="Unique identifier that can't be changed after creation."
66+
)
67+
compartment_id: Optional[str] = Field(
68+
None, description="Compartment OCID where the resource is created."
69+
)
70+
detector_rule_id: Optional[str] = Field(
71+
None,
72+
description="Unique identifier of the detector rule that triggered the problem.",
73+
)
74+
region: Optional[str] = Field(None, description="DEPRECATED.")
75+
regions: Optional[List[str]] = Field(
76+
None, description="Regions where the problem is found."
77+
)
78+
risk_level: Optional[
79+
Literal["CRITICAL", "HIGH", "MEDIUM", "LOW", "MINOR", "UNKNOWN_ENUM_VALUE"]
80+
] = Field(None, description="The risk level for the problem.")
81+
risk_score: Optional[float] = Field(
82+
None, description="The risk score for the problem."
83+
)
84+
peak_risk_score_date: Optional[str] = Field(
85+
None,
86+
description=(
87+
"The date and time for the peak risk score observed for the problem (RFC3339)."
88+
),
89+
)
90+
peak_risk_score: Optional[float] = Field(
91+
None, description="Peak risk score for the problem."
92+
)
93+
auto_resolve_date: Optional[str] = Field(
94+
None,
95+
description="The date and time when the problem will be auto resolved (RFC3339).",
96+
)
97+
peak_risk_score_lookup_period_in_days: Optional[int] = Field(
98+
None,
99+
description="Number of days for which peak score is calculated for the problem.",
100+
)
101+
resource_id: Optional[str] = Field(
102+
None, description="Unique identifier of the resource affected by the problem."
103+
)
104+
resource_name: Optional[str] = Field(
105+
None, description="Display name of the affected resource."
106+
)
107+
resource_type: Optional[str] = Field(
108+
None, description="Type of the affected resource."
109+
)
110+
labels: Optional[List[str]] = Field(
111+
None, description="User-defined labels on the problem."
112+
)
113+
time_last_detected: Optional[datetime] = Field(
114+
None, description="The date and time the problem was last detected (RFC3339)."
115+
)
116+
time_first_detected: Optional[datetime] = Field(
117+
None, description="The date and time the problem was first detected (RFC3339)."
118+
)
119+
lifecycle_state: Optional[Literal["ACTIVE", "INACTIVE", "UNKNOWN_ENUM_VALUE"]] = (
120+
Field(None, description="The current lifecycle state of the problem.")
121+
)
122+
lifecycle_detail: Optional[
123+
Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"]
124+
] = Field(
125+
None, description="Additional details on the substate of the lifecycle state."
126+
)
127+
detector_id: Optional[
128+
Literal[
129+
"IAAS_ACTIVITY_DETECTOR",
130+
"IAAS_CONFIGURATION_DETECTOR",
131+
"IAAS_THREAT_DETECTOR",
132+
"IAAS_LOG_INSIGHT_DETECTOR",
133+
"IAAS_INSTANCE_SECURITY_DETECTOR",
134+
"IAAS_CONTAINER_SECURITY_DETECTOR",
135+
"UNKNOWN_ENUM_VALUE",
136+
]
137+
] = Field(
138+
None,
139+
description="Unique identifier of the detector that triggered the problem.",
140+
)
141+
target_id: Optional[str] = Field(
142+
None, description="Unique identifier of the target associated with the problem."
143+
)
144+
additional_details: Optional[Dict[str, str]] = Field(
145+
None, description="Additional details of the problem as key/value pairs."
146+
)
147+
description: Optional[str] = Field(None, description="Description of the problem.")
148+
recommendation: Optional[str] = Field(
149+
None, description="Recommendation for the problem."
150+
)
151+
comment: Optional[str] = Field(None, description="User comments on the problem.")
152+
impacted_resource_id: Optional[str] = Field(
153+
None, description="Unique identifier of the resource impacted by the problem."
154+
)
155+
impacted_resource_name: Optional[str] = Field(
156+
None, description="Display name of the impacted resource."
157+
)
158+
impacted_resource_type: Optional[str] = Field(
159+
None, description="Type of the impacted resource."
160+
)
161+
locks: Optional[List[ResourceLock]] = Field(
162+
None, description="Locks associated with this resource."
163+
)
164+
165+
166+
def map_resource_lock(rl) -> ResourceLock | None:
167+
if not rl:
168+
return None
169+
return ResourceLock(
170+
type=getattr(rl, "type", None),
171+
related_resource_id=getattr(rl, "related_resource_id", None),
172+
message=getattr(rl, "message", None),
173+
time_created=getattr(rl, "time_created", None),
174+
)
175+
176+
177+
def map_resource_locks(items) -> list[ResourceLock] | None:
178+
if not items:
179+
return None
180+
result: list[ResourceLock] = []
181+
for it in items:
182+
result.append(map_resource_lock(it))
183+
return result
184+
185+
186+
def map_problem(problem_data: oci.cloud_guard.models.Problem) -> Problem:
187+
"""
188+
Convert an oci.cloud_guard.models.Problem to oracle.oci_cloud_guard_mcp_server.models.Problem.
189+
"""
190+
return Problem(
191+
id=getattr(problem_data, "id", None),
192+
compartment_id=getattr(problem_data, "compartment_id", None),
193+
detector_rule_id=getattr(problem_data, "detector_rule_id", None),
194+
region=getattr(problem_data, "region", None),
195+
regions=getattr(problem_data, "regions", None),
196+
risk_level=getattr(problem_data, "risk_level", None),
197+
risk_score=getattr(problem_data, "risk_score", None),
198+
peak_risk_score_date=getattr(problem_data, "peak_risk_score_date", None),
199+
peak_risk_score=getattr(problem_data, "peak_risk_score", None),
200+
auto_resolve_date=getattr(problem_data, "auto_resolve_date", None),
201+
peak_risk_score_lookup_period_in_days=getattr(
202+
problem_data, "peak_risk_score_lookup_period_in_days", None
203+
),
204+
resource_id=getattr(problem_data, "resource_id", None),
205+
resource_name=getattr(problem_data, "resource_name", None),
206+
resource_type=getattr(problem_data, "resource_type", None),
207+
labels=getattr(problem_data, "labels", None),
208+
time_last_detected=getattr(problem_data, "time_last_detected", None),
209+
time_first_detected=getattr(problem_data, "time_first_detected", None),
210+
lifecycle_state=getattr(problem_data, "lifecycle_state", None),
211+
lifecycle_detail=getattr(problem_data, "lifecycle_detail", None),
212+
detector_id=getattr(problem_data, "detector_id", None),
213+
target_id=getattr(problem_data, "target_id", None),
214+
additional_details=getattr(problem_data, "additional_details", None),
215+
description=getattr(problem_data, "description", None),
216+
recommendation=getattr(problem_data, "recommendation", None),
217+
comment=getattr(problem_data, "comment", None),
218+
impacted_resource_id=getattr(problem_data, "impacted_resource_id", None),
219+
impacted_resource_name=getattr(problem_data, "impacted_resource_name", None),
220+
impacted_resource_type=getattr(problem_data, "impacted_resource_type", None),
221+
locks=map_resource_locks(getattr(problem_data, "locks", None)),
222+
)
223+
224+
225+
class UpdateProblemStatusDetails(BaseModel):
226+
"""
227+
Pydantic model mirroring oci.cloud_guard.models.UpdateProblemStatusDetails.
228+
"""
229+
230+
status: Optional[
231+
Literal["OPEN", "RESOLVED", "DISMISSED", "DELETED", "UNKNOWN_ENUM_VALUE"]
232+
] = Field(None, description="Action taken by user.")
233+
comment: Optional[str] = Field(None, description="User comments.")
234+
235+
236+
def map_update_problem_status_details(
237+
upd: oci.cloud_guard.models.UpdateProblemStatusDetails,
238+
) -> UpdateProblemStatusDetails | None:
239+
"""
240+
Convert an oci.cloud_guard.models.UpdateProblemStatusDetails to
241+
oracle.oci_cloud_guard_mcp_server.models.UpdateProblemStatusDetails.
242+
"""
243+
if not upd:
244+
return None
245+
return UpdateProblemStatusDetails(
246+
status=getattr(upd, "status", None),
247+
comment=getattr(upd, "comment", None),
248+
)

0 commit comments

Comments
 (0)