Skip to content

Commit 7bb5de2

Browse files
FAaaS MCP server
1 parent 78c1742 commit 7bb5de2

File tree

8 files changed

+1808
-0
lines changed

8 files changed

+1808
-0
lines changed
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.

src/oci-faaas-mcp-server/README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# OCI Fusion Applications (FAaaS) MCP Server
2+
3+
## Overview
4+
5+
This server provides tools for interacting with Oracle Cloud Infrastructure (OCI) Fusion Applications (FAaaS) via the OCI Python SDK `oci.fusion_apps.FusionApplicationsClient`.
6+
7+
## Running the server
8+
9+
```sh
10+
uv run oracle.oci-faaas-mcp-server
11+
```
12+
13+
## Tools
14+
15+
| Tool Name | Description |
16+
| --- | --- |
17+
| list_fusion_environment_families | List Fusion Environment Families in a compartment |
18+
| list_fusion_environments | List Fusion Environments in a compartment (optionally by family) |
19+
| get_fusion_environment | Get details of a Fusion Environment by OCID |
20+
| get_fusion_environment_status | Get status of a Fusion Environment by OCID |
21+
22+
Notes:
23+
- All list tools handle pagination.
24+
- Responses are converted to plain dictionaries using best-effort conversion of OCI SDK models.
25+
26+
⚠️ 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.
27+
28+
## Third-Party APIs
29+
30+
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.
31+
32+
## Disclaimer
33+
34+
Users are responsible for their local environment and credential safety. Different language model selections may yield different results and performance.
35+
36+
## License
37+
38+
Copyright (c) 2025 Oracle and/or its affiliates.
39+
40+
Released under the Universal Permissive License v1.0 as shown at
41+
https://oss.oracle.com/licenses/upl/
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-faaas-mcp-server"
8+
__version__ = "1.0.0"
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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 logging import Logger
9+
from typing import Annotated
10+
11+
import oci
12+
from fastmcp import FastMCP
13+
14+
from . import __project__, __version__
15+
16+
logger = Logger(__name__, level="INFO")
17+
18+
mcp = FastMCP(name=__project__)
19+
20+
21+
def get_faaas_client():
22+
"""Initialize and return an OCI Fusion Applications client using security token auth."""
23+
logger.info("entering get_faaas_client")
24+
25+
config = oci.config.from_file(
26+
profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)
27+
)
28+
29+
user_agent_name = __project__.split("oracle.", 1)[1].split("-server", 1)[0]
30+
config["additional_user_agent"] = f"{user_agent_name}/{__version__}"
31+
32+
private_key = oci.signer.load_private_key_from_file(config["key_file"])
33+
token_file = config["security_token_file"]
34+
token = None
35+
with open(token_file, "r") as f:
36+
token = f.read()
37+
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
38+
39+
return oci.fusion_apps.FusionApplicationsClient(config, signer=signer)
40+
41+
42+
def _to_dict(obj):
43+
"""Best-effort conversion of OCI SDK model objects to plain dicts."""
44+
try:
45+
return oci.util.to_dict(obj)
46+
except Exception:
47+
try:
48+
return obj.__dict__
49+
except Exception:
50+
return obj
51+
52+
53+
def _append_items_from_response_data(accum: list[dict], data_obj):
54+
"""
55+
Normalize list responses across SDK collections/lists:
56+
- If response.data has 'items', iterate it
57+
- If it's already a list, iterate directly
58+
- Else treat it as a single object
59+
"""
60+
try:
61+
items = getattr(data_obj, "items", None)
62+
if items is not None:
63+
for it in items:
64+
accum.append(_to_dict(it))
65+
return
66+
67+
if isinstance(data_obj, list):
68+
for it in data_obj:
69+
accum.append(_to_dict(it))
70+
return
71+
72+
# Fallback: single object
73+
accum.append(_to_dict(data_obj))
74+
except Exception as e:
75+
logger.error(f"Error converting response data to dicts: {e}")
76+
raise
77+
78+
79+
@mcp.tool(
80+
description="Returns a list of Fusion Environment Families in the specified compartment."
81+
)
82+
def list_fusion_environment_families(
83+
compartment_id: Annotated[
84+
str, "The ID of the compartment in which to list resources."
85+
],
86+
display_name: Annotated[str, "Filter to match entire display name."] = None,
87+
lifecycle_state: Annotated[
88+
str,
89+
"Filter by lifecycle state. Allowed: CREATING, UPDATING, ACTIVE, DELETING, DELETED, FAILED",
90+
] = None,
91+
) -> list[dict]:
92+
client = get_faaas_client()
93+
94+
families: list[dict] = []
95+
next_page: str | None = None
96+
has_next_page = True
97+
98+
while has_next_page:
99+
response: oci.response.Response = client.list_fusion_environment_families(
100+
compartment_id=compartment_id,
101+
display_name=display_name,
102+
lifecycle_state=lifecycle_state,
103+
page=next_page,
104+
)
105+
_append_items_from_response_data(families, response.data)
106+
has_next_page = getattr(response, "has_next_page", False)
107+
next_page = getattr(response, "next_page", None)
108+
109+
logger.info(f"Found {len(families)} Fusion Environment Families")
110+
return families
111+
112+
113+
@mcp.tool(
114+
description=(
115+
"Returns a list of Fusion Environments in the specified compartment "
116+
"(optionally filtered by family)."
117+
)
118+
)
119+
def list_fusion_environments(
120+
compartment_id: Annotated[
121+
str, "The ID of the compartment in which to list resources."
122+
],
123+
fusion_environment_family_id: Annotated[
124+
str, "Optional Fusion Environment Family OCID"
125+
] = None,
126+
display_name: Annotated[str, "Filter to match entire display name."] = None,
127+
lifecycle_state: Annotated[
128+
str,
129+
"Filter by lifecycle state. Allowed: CREATING, UPDATING, ACTIVE, INACTIVE, DELETING, DELETED, FAILED",
130+
] = None,
131+
) -> list[dict]:
132+
client = get_faaas_client()
133+
134+
environments: list[dict] = []
135+
next_page: str | None = None
136+
has_next_page = True
137+
138+
while has_next_page:
139+
response: oci.response.Response = client.list_fusion_environments(
140+
compartment_id=compartment_id,
141+
fusion_environment_family_id=fusion_environment_family_id,
142+
display_name=display_name,
143+
lifecycle_state=lifecycle_state,
144+
page=next_page,
145+
)
146+
_append_items_from_response_data(environments, response.data)
147+
has_next_page = getattr(response, "has_next_page", False)
148+
next_page = getattr(response, "next_page", None)
149+
150+
logger.info(f"Found {len(environments)} Fusion Environments")
151+
return environments
152+
153+
154+
@mcp.tool(description="Gets a Fusion Environment by OCID.")
155+
def get_fusion_environment(
156+
fusion_environment_id: Annotated[str, "Unique FusionEnvironment identifier (OCID)"],
157+
) -> dict:
158+
client = get_faaas_client()
159+
response: oci.response.Response = client.get_fusion_environment(
160+
fusion_environment_id
161+
)
162+
return _to_dict(response.data)
163+
164+
165+
@mcp.tool(description="Gets the status of a Fusion Environment by OCID.")
166+
def get_fusion_environment_status(
167+
fusion_environment_id: Annotated[str, "Unique FusionEnvironment identifier (OCID)"],
168+
) -> dict:
169+
client = get_faaas_client()
170+
response: oci.response.Response = client.get_fusion_environment_status(
171+
fusion_environment_id
172+
)
173+
return _to_dict(response.data)
174+
175+
176+
def main():
177+
mcp.run()
178+
179+
180+
if __name__ == "__main__":
181+
main()

0 commit comments

Comments
 (0)