Skip to content

Commit fc2ae92

Browse files
FAaaS MCP server
1 parent 78c1742 commit fc2ae92

File tree

8 files changed

+1811
-0
lines changed

8 files changed

+1811
-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: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
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+
# Load config from default OCI config file (~/.oci/config) or as per env
25+
config = oci.config.from_file(
26+
profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)
27+
)
28+
29+
# Append project/version user-agent like other servers
30+
user_agent_name = __project__.split("oracle.", 1)[1].split("-server", 1)[0]
31+
config["additional_user_agent"] = f"{user_agent_name}/{__version__}"
32+
33+
# Use security token signer (matches pattern used in other OCI MCP servers)
34+
private_key = oci.signer.load_private_key_from_file(config["key_file"])
35+
token_file = config["security_token_file"]
36+
token = None
37+
with open(token_file, "r") as f:
38+
token = f.read()
39+
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
40+
41+
# Return Fusion Applications client
42+
return oci.fusion_apps.FusionApplicationsClient(config, signer=signer)
43+
44+
45+
def _to_dict(obj):
46+
"""Best-effort conversion of OCI SDK model objects to plain dicts."""
47+
try:
48+
return oci.util.to_dict(obj)
49+
except Exception:
50+
try:
51+
return obj.__dict__
52+
except Exception:
53+
return obj
54+
55+
56+
def _append_items_from_response_data(accum: list[dict], data_obj):
57+
"""
58+
Normalize list responses across SDK collections/lists:
59+
- If response.data has 'items', iterate it
60+
- If it's already a list, iterate directly
61+
- Else treat it as a single object
62+
"""
63+
try:
64+
items = getattr(data_obj, "items", None)
65+
if items is not None:
66+
for it in items:
67+
accum.append(_to_dict(it))
68+
return
69+
70+
if isinstance(data_obj, list):
71+
for it in data_obj:
72+
accum.append(_to_dict(it))
73+
return
74+
75+
# Fallback: single object
76+
accum.append(_to_dict(data_obj))
77+
except Exception as e:
78+
logger.error(f"Error converting response data to dicts: {e}")
79+
raise
80+
81+
82+
@mcp.tool(
83+
description="Returns a list of Fusion Environment Families in the specified compartment."
84+
)
85+
def list_fusion_environment_families(
86+
compartment_id: Annotated[
87+
str, "The ID of the compartment in which to list resources."
88+
],
89+
display_name: Annotated[str, "Filter to match entire display name."] = None,
90+
lifecycle_state: Annotated[
91+
str,
92+
"Filter by lifecycle state. Allowed: CREATING, UPDATING, ACTIVE, DELETING, DELETED, FAILED",
93+
] = None,
94+
) -> list[dict]:
95+
client = get_faaas_client()
96+
97+
families: list[dict] = []
98+
next_page: str | None = None
99+
has_next_page = True
100+
101+
while has_next_page:
102+
response: oci.response.Response = client.list_fusion_environment_families(
103+
compartment_id=compartment_id,
104+
display_name=display_name,
105+
lifecycle_state=lifecycle_state,
106+
page=next_page,
107+
)
108+
_append_items_from_response_data(families, response.data)
109+
has_next_page = getattr(response, "has_next_page", False)
110+
next_page = getattr(response, "next_page", None)
111+
112+
logger.info(f"Found {len(families)} Fusion Environment Families")
113+
return families
114+
115+
116+
@mcp.tool(
117+
description=(
118+
"Returns a list of Fusion Environments in the specified compartment "
119+
"(optionally filtered by family)."
120+
)
121+
)
122+
def list_fusion_environments(
123+
compartment_id: Annotated[
124+
str, "The ID of the compartment in which to list resources."
125+
],
126+
fusion_environment_family_id: Annotated[
127+
str, "Optional Fusion Environment Family OCID"
128+
] = None,
129+
display_name: Annotated[str, "Filter to match entire display name."] = None,
130+
lifecycle_state: Annotated[
131+
str,
132+
"Filter by lifecycle state. Allowed: CREATING, UPDATING, ACTIVE, INACTIVE, DELETING, DELETED, FAILED",
133+
] = None,
134+
) -> list[dict]:
135+
client = get_faaas_client()
136+
137+
environments: list[dict] = []
138+
next_page: str | None = None
139+
has_next_page = True
140+
141+
while has_next_page:
142+
response: oci.response.Response = client.list_fusion_environments(
143+
compartment_id=compartment_id,
144+
fusion_environment_family_id=fusion_environment_family_id,
145+
display_name=display_name,
146+
lifecycle_state=lifecycle_state,
147+
page=next_page,
148+
)
149+
_append_items_from_response_data(environments, response.data)
150+
has_next_page = getattr(response, "has_next_page", False)
151+
next_page = getattr(response, "next_page", None)
152+
153+
logger.info(f"Found {len(environments)} Fusion Environments")
154+
return environments
155+
156+
157+
@mcp.tool(description="Gets a Fusion Environment by OCID.")
158+
def get_fusion_environment(
159+
fusion_environment_id: Annotated[str, "Unique FusionEnvironment identifier (OCID)"],
160+
) -> dict:
161+
client = get_faaas_client()
162+
response: oci.response.Response = client.get_fusion_environment(
163+
fusion_environment_id
164+
)
165+
return _to_dict(response.data)
166+
167+
168+
@mcp.tool(description="Gets the status of a Fusion Environment by OCID.")
169+
def get_fusion_environment_status(
170+
fusion_environment_id: Annotated[str, "Unique FusionEnvironment identifier (OCID)"],
171+
) -> dict:
172+
client = get_faaas_client()
173+
response: oci.response.Response = client.get_fusion_environment_status(
174+
fusion_environment_id
175+
)
176+
return _to_dict(response.data)
177+
178+
179+
def main():
180+
mcp.run()
181+
182+
183+
if __name__ == "__main__":
184+
main()

0 commit comments

Comments
 (0)