Skip to content

Commit efbe0dd

Browse files
committed
add resource search tool (oracle#15)
* add resource search tool --------- Signed-off-by: Richard Gebhardt <[email protected]>
1 parent 6374a88 commit efbe0dd

File tree

2 files changed

+155
-1
lines changed

2 files changed

+155
-1
lines changed

mcphost.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
{
22
"mcpServers": {
3+
"filesystem": {
4+
"type": "builtin",
5+
"name": "fs",
6+
"options": {
7+
"allowed_directories": ["/tmp", "/private/tmp"]
8+
},
9+
"allowedTools": ["read_file", "write_file", "list_directory"]
10+
},
311
"oci_compute": {
412
"type": "local",
513
"command": [
@@ -18,6 +26,14 @@
1826
"excludedTools": [
1927
"terminate_instance"
2028
]
29+
},
30+
"oci_resource_search": {
31+
"type": "local",
32+
"command": [
33+
"uv",
34+
"run",
35+
"oci_resource_search.py"
36+
]
2137
}
2238
}
23-
}
39+
}

oci_resource_search.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
import os
2+
from logging import Logger
3+
from typing import Annotated
4+
5+
import oci
6+
from oci.resource_search.models import (
7+
StructuredSearchDetails,
8+
FreeTextSearchDetails,
9+
)
10+
from fastmcp import FastMCP
11+
12+
logger = Logger("oci_resource_search_mcp", level="INFO")
13+
14+
mcp = FastMCP("oci_resource_search")
15+
16+
17+
def get_search_client():
18+
logger.info("entering get_search_client")
19+
config = oci.config.from_file(
20+
profile_name=os.getenv("OCI_CONFIG_PROFILE", oci.config.DEFAULT_PROFILE)
21+
)
22+
23+
private_key = oci.signer.load_private_key_from_file(config["key_file"])
24+
token_file = config["security_token_file"]
25+
token = None
26+
with open(token_file, "r") as f:
27+
token = f.read()
28+
signer = oci.auth.signers.SecurityTokenSigner(token, private_key)
29+
return oci.resource_search.ResourceSearchClient(config, signer=signer)
30+
31+
32+
@mcp.tool
33+
def list_all_resources(compartment_id: str):
34+
"""Returns all resources"""
35+
search_client = get_search_client()
36+
structured_search = StructuredSearchDetails(
37+
type="Structured",
38+
query=f"query all resources where compartmentId = '{compartment_id}'",
39+
)
40+
response = search_client.search_resources(structured_search).data
41+
return [
42+
{
43+
"resource_id": resource.identifier,
44+
"compartment_id": compartment_id,
45+
"display_name": resource.display_name,
46+
"resource_type": resource.resource_type,
47+
"lifecycle_state": resource.lifecycle_state,
48+
"freeform_tags": resource.freeform_tags,
49+
"defined_tags": resource.defined_tags,
50+
}
51+
for resource in response.items
52+
]
53+
54+
55+
@mcp.tool
56+
def search_resources(
57+
compartment_id: str,
58+
display_name: Annotated[str, "Full display name or display name substring"],
59+
):
60+
"""Searches for resources by display name"""
61+
search_client = get_search_client()
62+
structured_search = FreeTextSearchDetails(
63+
type="Structured",
64+
query=f"query all resources where compartmentId = '{compartment_id}' and displayName =~ '{display_name}'",
65+
)
66+
response = search_client.search_resources(structured_search).data
67+
return [
68+
{
69+
"resource_id": resource.identifier,
70+
"compartment_id": compartment_id,
71+
"display_name": resource.display_name,
72+
"resource_type": resource.resource_type,
73+
"lifecycle_state": resource.lifecycle_state,
74+
"freeform_tags": resource.freeform_tags,
75+
"defined_tags": resource.defined_tags,
76+
}
77+
for resource in response.items
78+
]
79+
80+
81+
@mcp.tool
82+
def search_resources_free_form(
83+
compartment_id: str,
84+
text: Annotated[str, "Free-form search string"],
85+
):
86+
"""Searches for the presence of the search string in all resource fields"""
87+
search_client = get_search_client()
88+
freetext_search = StructuredSearchDetails(
89+
type="FreeText",
90+
text=text,
91+
)
92+
response = search_client.search_resources(freetext_search).data
93+
return [
94+
{
95+
"resource_id": resource.identifier,
96+
"compartment_id": compartment_id,
97+
"display_name": resource.display_name,
98+
"resource_type": resource.resource_type,
99+
"lifecycle_state": resource.lifecycle_state,
100+
"freeform_tags": resource.freeform_tags,
101+
"defined_tags": resource.defined_tags,
102+
}
103+
for resource in response.items
104+
]
105+
106+
107+
def search_resources_by_type(compartment_id: str, resource_type: str):
108+
"""Search for resources by resource type"""
109+
search_client = get_search_client()
110+
structured_search = StructuredSearchDetails(
111+
type="Structured",
112+
query=f"query all {resource_type} resources where compartmentId = '{compartment_id}'",
113+
)
114+
response = search_client.search_resources(structured_search).data
115+
return [
116+
{
117+
"resource_id": resource.identifier,
118+
"compartment_id": compartment_id,
119+
"display_name": resource.display_name,
120+
"resource_type": resource.resource_type,
121+
"lifecycle_state": resource.lifecycle_state,
122+
"freeform_tags": resource.freeform_tags,
123+
"defined_tags": resource.defined_tags,
124+
}
125+
for resource in response.items
126+
]
127+
128+
129+
@mcp.tool
130+
def list_resource_types():
131+
"""Returns a list of all supported OCI resource types"""
132+
search_client = get_search_client()
133+
resource_types = search_client.list_resource_types()
134+
return map(lambda x: x.name, resource_types.data)
135+
136+
137+
if __name__ == "__main__":
138+
mcp.run()

0 commit comments

Comments
 (0)