Skip to content

Commit 94c8726

Browse files
committed
feat(cloud-guard): add problems mcp server
1 parent bdcf6a2 commit 94c8726

File tree

10 files changed

+1511
-0
lines changed

10 files changed

+1511
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@ uv.lock
1919
# Mac files
2020
.DS_Store
2121

22+
#IDE
23+
.idea
24+
2225
# test environments
2326
.env
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: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
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+
import os
8+
from datetime import datetime, timedelta, timezone
9+
from logging import Logger
10+
from typing import Annotated, Any, Dict, List, Optional
11+
12+
import oci
13+
from fastmcp import FastMCP
14+
from oci.cloud_guard import CloudGuardClient
15+
16+
from . import __project__, __version__
17+
18+
logger = Logger(__name__, level="INFO")
19+
20+
mcp = FastMCP(name=__project__)
21+
22+
23+
def get_cloud_guard_client():
24+
config = oci.config.from_file(
25+
profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)
26+
)
27+
28+
user_agent_name = __project__.split("oracle.", 1)[1].split("-server", 1)[0]
29+
config["additional_user_agent"] = f"{user_agent_name}/{__version__}"
30+
31+
private_key = oci.signer.load_private_key_from_file(config["key_file"])
32+
token_file = config["security_token_file"]
33+
token = None
34+
with open(token_file, "r") as f:
35+
token = f.read()
36+
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
37+
return CloudGuardClient(config, signer=signer)
38+
39+
40+
@mcp.tool(
41+
name="list_problems",
42+
description="Returns a list of all Problems identified by Cloud Guard.",
43+
)
44+
def list_problems(
45+
compartment_id: Annotated[
46+
Optional[str], "The OCID of the compartment in which to list resources."
47+
] = None,
48+
risk_level: Annotated[Optional[str], "Risk level of the problem"] = None,
49+
lifecycle_state: Annotated[
50+
Optional[str],
51+
"The field lifecycle state. Only one state can be provided. Default value for state is active.",
52+
] = "ACTIVE",
53+
detector_rule_ids: Annotated[
54+
Optional[list[str]],
55+
"Comma seperated list of detector rule IDs to be passed in to match against Problems.",
56+
] = None,
57+
time_range_days: Annotated[
58+
Optional[int], "Number of days to look back for problems"
59+
] = 30,
60+
limit: Annotated[int, "The number of problems to return"] = 10,
61+
) -> List[Dict[str, Any]]:
62+
time_filter = (
63+
datetime.now(timezone.utc) - timedelta(days=time_range_days)
64+
).isoformat()
65+
66+
kwargs = {
67+
"compartment_id": compartment_id,
68+
"time_last_detected_greater_than_or_equal_to": time_filter,
69+
"limit": limit,
70+
}
71+
72+
if risk_level:
73+
kwargs["risk_level"] = risk_level
74+
if lifecycle_state:
75+
kwargs["lifecycle_state"] = lifecycle_state
76+
if detector_rule_ids:
77+
kwargs["detector_rule_id_list"] = detector_rule_ids
78+
79+
response = get_cloud_guard_client().list_problems(**kwargs).data.items
80+
return [
81+
{
82+
"id": problem.id,
83+
"status": problem.lifecycle_detail,
84+
"detector_rule_id": problem.detector_rule_id,
85+
"risk_level": problem.risk_level,
86+
"risk_score": problem.risk_score,
87+
"resource_name": problem.resource_name,
88+
"resource_type": problem.resource_type,
89+
"lifecycle_state": problem.lifecycle_state,
90+
"time_first_detected": (
91+
problem.time_first_detected.isoformat()
92+
if problem.time_first_detected
93+
else None
94+
),
95+
"time_last_detected": (
96+
problem.time_last_detected.isoformat()
97+
if problem.time_last_detected
98+
else None
99+
),
100+
"region": problem.region,
101+
"labels": problem.labels,
102+
"compartment_id": problem.compartment_id,
103+
}
104+
for problem in response
105+
]
106+
107+
108+
@mcp.tool(
109+
name="get_problem_details",
110+
description="Get the details for a Problem identified by problemId.",
111+
)
112+
def get_problem_details(
113+
problem_id: Annotated[str, "The OCID of the problem"],
114+
):
115+
response = get_cloud_guard_client().get_problem(problem_id=problem_id)
116+
problem = response.data
117+
118+
return {
119+
"id": problem.id,
120+
"detector_rule_id": problem.detector_rule_id,
121+
"detector_id": problem.detector_id,
122+
"risk_level": problem.risk_level,
123+
"risk_score": problem.risk_score,
124+
"resource_id": problem.resource_id,
125+
"resource_name": problem.resource_name,
126+
"resource_type": problem.resource_type,
127+
"lifecycle_state": problem.lifecycle_state,
128+
"lifecycle_detail": problem.lifecycle_detail,
129+
"time_first_detected": (
130+
problem.time_first_detected.isoformat()
131+
if problem.time_first_detected
132+
else None
133+
),
134+
"time_last_detected": (
135+
problem.time_last_detected.isoformat()
136+
if problem.time_last_detected
137+
else None
138+
),
139+
"description": problem.description,
140+
"recommendation": problem.recommendation,
141+
"additional_details": problem.additional_details,
142+
"comment": problem.comment,
143+
}
144+
145+
146+
@mcp.tool(
147+
name="update_problem_status",
148+
description="Changes the current status of the problem, identified by problemId, to the status "
149+
"specified in the UpdateProblemStatusDetails resource that you pass.",
150+
)
151+
def update_problem_status(
152+
problem_id: Annotated[str, "OCID of the problem"],
153+
status: Annotated[
154+
str,
155+
"Action taken by user. Allowed values are: OPEN, RESOLVED, DISMISSED, CLOSED",
156+
],
157+
comment: Annotated[Optional[str], "A comment from the user"] = None,
158+
):
159+
updated_problem_status = oci.cloud_guard.models.UpdateProblemStatusDetails(
160+
status=status, comment=comment
161+
)
162+
response = get_cloud_guard_client().update_problem_status(
163+
problem_id=problem_id,
164+
update_problem_status_details=updated_problem_status,
165+
)
166+
problem = response.data
167+
168+
return {
169+
"id": problem.id,
170+
"detector_rule_id": problem.detector_rule_id,
171+
"detector_id": problem.detector_id,
172+
"risk_level": problem.risk_level,
173+
"risk_score": problem.risk_score,
174+
"resource_id": problem.resource_id,
175+
"resource_name": problem.resource_name,
176+
"resource_type": problem.resource_type,
177+
"lifecycle_state": problem.lifecycle_state,
178+
"lifecycle_detail": problem.lifecycle_detail,
179+
"time_first_detected": (
180+
problem.time_first_detected.isoformat()
181+
if problem.time_first_detected
182+
else None
183+
),
184+
"time_last_detected": (
185+
problem.time_last_detected.isoformat()
186+
if problem.time_last_detected
187+
else None
188+
),
189+
"description": problem.description,
190+
"recommendation": problem.recommendation,
191+
"additional_details": problem.additional_details,
192+
"comment": problem.comment,
193+
}
194+
195+
196+
def main():
197+
mcp.run()
198+
199+
200+
if __name__ == "__main__":
201+
main()

0 commit comments

Comments
 (0)