Skip to content

Commit 2fa3632

Browse files
committed
added first release mcp-oci-integration
1 parent 2d558d8 commit 2fa3632

29 files changed

+2508
-0
lines changed
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# MCP Oracle OCI integrations
2+
This repository contains code and examples to help in the following tasks:
3+
* **Develop** MCP servers in **Python**
4+
* **Run** MCP servers on **Oracle OCI**
5+
* **Integrate** MCP servers with **AI Agents**
6+
* **Integrate** MCP servers with other **OCI resources** (ADB, Select AI, ...)
7+
* **Integrate** MCP Servers running on OCI with AI Assistants like **ChatGPT**, Claude.ai, MS Copilot
8+
* **Integrate** MCP Servers with OCI **APM** for **Observability**
9+
10+
![MCP console](./images/mcp_cli.png)
11+
12+
## What is MCP?
13+
**MCP (Model Context Protocol)** is an **open-source standard** that lets AI models (e.g. LLMs or agents) connect bidirectionally with external tools, data sources, and services via a unified interface.
14+
15+
It replaces the “N×M” integration problem (where each AI × data source requires custom code) with one standard protocol.
16+
17+
MCP supports **dynamic discovery** of available tools and context, enabling:
18+
* AI Assistants to get access to relevant information, available in Enterprise Knowledge base.
19+
* Agents to reason and chain actions across disparate systems.
20+
21+
It’s quickly gaining traction: major players like OpenAI, Google DeepMind, Oracle are adopting it to make AI systems more composable and interoperable.
22+
23+
In today’s landscape of agentic AI, MCP is critical because it allows models to act meaningfully in real-world systems rather than remaining isolated black boxes.
24+
25+
## Develop MCP Servers in Python
26+
The easiest way is to use the [FastMCP](https://gofastmcp.com/getting-started/welcome) library.
27+
28+
**Examples**:
29+
* in [Minimal MCP Server](./minimal_mcp_server.py) you'll find a **good, minimal example** of a server exposing two tools, with the option to protect them using [JWT](https://www.jwt.io/introduction#what-is-json-web-token).
30+
31+
If you want to start with **something simpler**, have a look at [how to start developing MCP](./how_to_start_mcp.md). It is simpler, with no support for JWT tokens.
32+
33+
## How to test
34+
If you want to quickly test the MCP server you developed (or the minimal example provided here) you can use the [Streamlit UI](./ui_mcp_agent.py).
35+
36+
In the Streamlit application, you can:
37+
* Specify the URL of the MCP server (default is in [mcp_servers_config.py](./mcp_servers_config.py))
38+
* Select one of models available in OCI Generative AI
39+
* Test making questions answered using the tools exposed by the MCP server.
40+
41+
In [llm_with_mcp.py](./llm_with_mcp.py) there is the complete implementation of the **tool-calling** loop.
42+
43+
## Semantic Search
44+
In this repository there is a **complete implementation of an MCP server** implementing **Semantic Search** on top of **Oracle 23AI**.
45+
To use it, you need only:
46+
* To load the documents in the Oracle DB
47+
* To put the right configuration, to connect to DB, in config_private.py.
48+
49+
The code is available [here](./mcp_semantic_search_with_iam.py).
50+
51+
Access to Oracle 23AI Vector Search is through the **new** [langchain-oci integration library](https://github.com/oracle/langchain-oracle)
52+
53+
## Adding security
54+
If you want to put your **MCP** server in production, you need to add security, at several levels.
55+
56+
Just to mention few important points:
57+
* You don't want to expose directly the MCP server over Internet
58+
* The communication with the MCP server must be encrypted (i.e: using TLS)
59+
* You want to authenticate and authorize the clients
60+
61+
Using **OCI services** there are several things you can do to get the right level of security:
62+
* You can put an **OCI API Gateway** in front, using it as TLS termination
63+
* You can enable authentication using **JWT** tokens
64+
* You can use **OCI IAM** to generate **JWT** tokens
65+
* You can use OCI network security
66+
67+
More details in a dedicate page.
68+
69+
## Integrate MCP Semantic Search with ChatGPT
70+
If you deploy the [MCP Semantic Search](./mcp_semantic_search_with_iam.py) server you can test the integration with **ChatGPT** in **Developer Mode**. It provides a **search** tool, compliant with **OpenAI** specs.
71+
72+
Soon, we'll add a server fully compliant with **OpenAI** specifications, that can be integrated in **Deep Research**. The server must implement two methods (**search** and **fetch**) with a different behaviour, following srictly OpenAI specs.
73+
74+
An initial implementation is available [here](./mcp_deep_research_with_iam.py)
75+
76+
Details available [here](./integrate_chatgpt.md)
77+
78+
## Integrate OCI ADB Select AI
79+
Another option is to use an MCP server to be able to integrate OCI **SelectAI** in ChatGPT or other assistants supporting MCP.
80+
In this way you have an option to do full **Text2SQL** search, over your database schema. Then, the AI assistant can process your retrieved data.
81+
82+
An example is [here](./mcp_selectai.py)
83+
84+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# format code
2+
black *.py
3+
4+
# check
5+
pylint *.py
6+
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
"""
2+
File name: config.py
3+
Author: Luigi Saetta
4+
Date last modified: 2025-07-02
5+
Python Version: 3.11
6+
7+
Description:
8+
This module provides general configurations
9+
10+
11+
Usage:
12+
Import this module into other scripts to use its functions.
13+
Example:
14+
import config
15+
16+
License:
17+
This code is released under the MIT License.
18+
19+
Notes:
20+
This is a part of a demo showing how to implement an advanced
21+
RAG solution as a LangGraph agent.
22+
23+
Warnings:
24+
This module is in development, may change in future versions.
25+
"""
26+
27+
DEBUG = False
28+
29+
# type of OCI auth
30+
AUTH = "API_KEY"
31+
32+
# embeddings
33+
# added this to distinguish between Cohere end REST NVIDIA models
34+
# can be OCI or NVIDIA
35+
EMBED_MODEL_TYPE = "OCI"
36+
# EMBED_MODEL_TYPE = "NVIDIA"
37+
EMBED_MODEL_ID = "cohere.embed-multilingual-v3.0"
38+
39+
# this one needs to specify the dimension, default is 1536
40+
# EMBED_MODEL_ID = "cohere.embed-v4.0"
41+
# used only for NVIDIA models
42+
NVIDIA_EMBED_MODEL_URL = ""
43+
44+
45+
# LLM
46+
# this is the default model
47+
LLM_MODEL_ID = "meta.llama-3.3-70b-instruct"
48+
TEMPERATURE = 0.1
49+
MAX_TOKENS = 4000
50+
51+
# OCI general
52+
# REGION = "eu-frankfurt-1"
53+
REGION = "us-chicago-1"
54+
SERVICE_ENDPOINT = f"https://inference.generativeai.{REGION}.oci.oraclecloud.com"
55+
56+
if REGION == "us-chicago-1":
57+
# for now only available in chicago region
58+
MODEL_LIST = [
59+
"xai.grok-3",
60+
"xai.grok-4",
61+
"openai.gpt-4.1",
62+
"openai.gpt-4o",
63+
"openai.gpt-5",
64+
"meta.llama-3.3-70b-instruct",
65+
"cohere.command-a-03-2025",
66+
]
67+
else:
68+
MODEL_LIST = [
69+
"openai.gpt-4.1",
70+
"openai.gpt-5",
71+
"meta.llama-3.3-70b-instruct",
72+
"cohere.command-a-03-2025",
73+
]
74+
75+
# semantic search
76+
TOP_K = 6
77+
COLLECTION_LIST = ["BOOKS", "NVIDIA_BOOKS2"]
78+
DEFAULT_COLLECTION = "BOOKS"
79+
80+
81+
# history management (put -1 if you want to disable trimming)
82+
# consider that we have pair (human, ai) so use an even (ex: 6) value
83+
MAX_MSGS_IN_HISTORY = 6
84+
85+
# reranking enabled or disabled from UI
86+
87+
# for loading
88+
CHUNK_SIZE = 4000
89+
CHUNK_OVERLAP = 100
90+
91+
# for MCP server
92+
TRANSPORT = "streamable-http"
93+
# bind to all interfaces
94+
HOST = "0.0.0.0"
95+
PORT = 9000
96+
97+
# with this we can toggle JWT token auth
98+
ENABLE_JWT_TOKEN = False
99+
# for JWT token with OCI
100+
# put your domain URL here
101+
IAM_BASE_URL = "https://idcs-930d7b2ea2cb46049963ecba3049f509.identity.oraclecloud.com"
102+
# these are used during the verification of the token
103+
ISSUER = "https://identity.oraclecloud.com/"
104+
AUDIENCE = ["urn:opc:lbaas:logicalguid=idcs-930d7b2ea2cb46049963ecba3049f509"]
105+
106+
# for Select AI
107+
SELECT_AI_PROFILE = "OCI_GENERATIVE_AI_PROFILE"
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
Private config
3+
"""
4+
5+
#
6+
VECTOR_DB_USER = "your-db-user"
7+
VECTOR_DB_PWD = "your-db-pwd"
8+
9+
VECTOR_WALLET_PWD = "wallet-pwd"
10+
VECTOR_DSN = "db-psn"
11+
VECTOR_WALLET_DIR = "/Users/xxx/yyy"
12+
13+
CONNECT_ARGS = {
14+
"user": VECTOR_DB_USER,
15+
"password": VECTOR_DB_PWD,
16+
"dsn": VECTOR_DSN,
17+
"config_dir": VECTOR_WALLET_DIR,
18+
"wallet_location": VECTOR_WALLET_DIR,
19+
"wallet_password": VECTOR_WALLET_PWD,
20+
}
21+
22+
COMPARTMENT_ID = "ocid1.compartment.oc1.your-compartment-ocid"
23+
24+
# to add JWT to MCP server
25+
JWT_SECRET = "secret"
26+
# using this in the demo, make it simpler.
27+
# In production should switch to RS256 and use a key-pair
28+
JWT_ALGORITHM = "HS256"
29+
30+
# if using IAM to generate JWT token
31+
OCI_CLIENT_ID = "client-id"
32+
# th ocid of the secret in the vault
33+
SECRET_OCID = "ocid1.vaultsecret.oc1.eu-frankfurt-1.secret-ocid"
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
"""
2+
Custom class to support Embeddings model deployed using NVIDIA E.
3+
4+
License: MIT
5+
"""
6+
7+
from typing import List
8+
from langchain_core.embeddings import Embeddings
9+
import requests
10+
from utils import get_console_logger
11+
12+
# list of allowed values for dims, input_type and truncate parms
13+
ALLOWED_DIMS = {384, 512, 768, 1024, 2048}
14+
ALLOWED_INPUT_TYPES = {"passage", "query"}
15+
ALLOWED_TRUNCATE_VALUES = {"NONE", "START", "END"}
16+
17+
# list of models with tunable dimensions
18+
MATRIOSKA_MODELS = {"nvidia/llama-3.2-nv-embedqa-1b-v2"}
19+
20+
logger = get_console_logger()
21+
22+
23+
class CustomRESTEmbeddings(Embeddings):
24+
"""
25+
Custom class to wrap an embedding model with rest interface from NVIDIA NIM
26+
27+
see:
28+
https://docs.api.nvidia.com/nim/reference/nvidia-llama-3_2-nv-embedqa-1b-v2-infer
29+
"""
30+
31+
def __init__(self, api_url: str, model: str, batch_size: int = 10, dimensions=2048):
32+
"""
33+
Init
34+
35+
as of now, no security
36+
args:
37+
api_url: the endpoint
38+
model: the model id string
39+
batch_size
40+
dimensions: dim of the embedding vector
41+
"""
42+
self.api_url = api_url
43+
self.model = model
44+
self.batch_size = batch_size
45+
46+
if self.model in MATRIOSKA_MODELS:
47+
self.dimensions = dimensions
48+
else:
49+
# changing dimensions is not supported
50+
self.dimensions = None
51+
52+
# Validation at init time
53+
if self.dimensions is not None and self.dimensions not in ALLOWED_DIMS:
54+
raise ValueError(
55+
f"Invalid dimensions {self.dimensions!r}: must be one of {sorted(ALLOWED_DIMS)}"
56+
)
57+
58+
def embed_documents(
59+
self,
60+
texts: List[str],
61+
# must be passage and not document
62+
input_type: str = "passage",
63+
truncate: str = "NONE",
64+
) -> List[List[float]]:
65+
"""
66+
Embed a list of documents using batching.
67+
"""
68+
# normalize
69+
truncate = truncate.upper()
70+
71+
logger.info("Calling NVIDIA embeddings, embed_documents...")
72+
73+
if input_type not in ALLOWED_INPUT_TYPES:
74+
raise ValueError(
75+
f"Invalid value for input_types: must be one of {ALLOWED_INPUT_TYPES}"
76+
)
77+
if truncate not in ALLOWED_TRUNCATE_VALUES:
78+
raise ValueError(
79+
f"Invalid value for truncate: must be one of {ALLOWED_TRUNCATE_VALUES}"
80+
)
81+
82+
all_embeddings: List[List[float]] = []
83+
84+
for i in range(0, len(texts), self.batch_size):
85+
batch = texts[i : i + self.batch_size]
86+
# process a single batch
87+
if self.model in MATRIOSKA_MODELS:
88+
json_request = {
89+
"model": self.model,
90+
"input": batch,
91+
"input_type": input_type,
92+
"truncate": truncate,
93+
"dimensions": self.dimensions,
94+
}
95+
else:
96+
json_request = {
97+
"model": self.model,
98+
"input": batch,
99+
"input_type": input_type,
100+
"truncate": truncate,
101+
"dimensions": self.dimensions,
102+
}
103+
104+
resp = requests.post(
105+
self.api_url,
106+
json=json_request,
107+
timeout=30,
108+
)
109+
resp.raise_for_status()
110+
data = resp.json().get("data", [])
111+
112+
if len(data) != len(batch):
113+
raise ValueError(f"Expected {len(batch)} embeddings, got {len(data)}")
114+
all_embeddings.extend(item["embedding"] for item in data)
115+
return all_embeddings
116+
117+
def embed_query(self, text: str) -> List[float]:
118+
"""
119+
Embed the query (a str)
120+
"""
121+
logger.info("Calling NVIDIA embeddings, embed_query...")
122+
123+
return self.embed_documents([text], input_type="query")[0]

0 commit comments

Comments
 (0)