Skip to content

Commit 4c25921

Browse files
Merge pull request #193 from oracle-samples/185-langchain-mcp-export
langchain mcp export
2 parents f3e52ca + 0f77509 commit 4c25921

File tree

9 files changed

+471
-1
lines changed

9 files changed

+471
-1
lines changed

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,12 @@ spring_ai/target/**
6363
spring_ai/create_user.sql
6464
spring_ai/drop.sql
6565
src/client/spring_ai/target/classes/*
66-
api_server_key
66+
api_server_key
67+
src/client/mcp/rag/optimizer_settings.json
68+
src/client/mcp/rag/pyproject.toml
69+
src/client/mcp/rag/main.py
70+
src/client/mcp/rag/.python-version
71+
src/client/mcp/rag/uv.lock
72+
src/client/mcp/rag/node_modules/
73+
src/client/mcp/rag/package-lock.json
74+
src/client/mcp/rag/package.json

src/client/mcp/rag/README.md

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
2+
# MCP Server for a tested AI Optimizer & Toolkit configuration
3+
4+
**Version:** *Developer preview*
5+
6+
## Introduction
7+
This document describe how to re-use the configuration tested in the **AI Optimizer & Toolkit** an expose it as an MCP tool to a local **Claude Desktop** and how to setup as a remote MCP server. This early draft implementation utilizes the `stdio` and `sse` to interact between the agent dashboard, represented by the **Claude Desktop**, and the tool.
8+
9+
**NOTICE**: Only `Ollama` or `OpenAI` configurations are currently supported. Full support will come.
10+
11+
## Pre-requisites.
12+
You need:
13+
- Node.js: v20.17.0+
14+
- npx/npm: v11.2.0+
15+
- uv: v0.7.10+
16+
- Claude Desktop free
17+
18+
## Setup
19+
With **[`uv`](https://docs.astral.sh/uv/getting-started/installation/)** installed, run the following commands in your current project directory `<PROJECT_DIR>/src/client/mcp/rag/`:
20+
21+
```bash
22+
uv init --python=3.11 --no-workspace
23+
uv venv --python=3.11
24+
source .venv/bin/activate
25+
uv add mcp langchain-core==0.3.52 oracledb~=3.1 langchain-community==0.3.21 langchain-huggingface==0.1.2 langchain-openai==0.3.13 langchain-ollama==0.3.2
26+
```
27+
28+
## Export config
29+
In the **AI Optimizer & Toolkit** web interface, after tested a configuration, in `Settings/Client Settings`:
30+
31+
![Client Settings](./images/export.png)
32+
33+
* select the checkbox `Include Sensitive Settings`
34+
* press button `Download Settings` to download configuration in the project directory: `src/client/mcp/rag` as `optimizer_settings.json`.
35+
* in `<PROJECT_DIR>/src/client/mcp/rag/rag_base_optimizer_config_mcp.py` change filepath with the absolute path of your `optimizer_settings.json` file.
36+
37+
38+
## Standalone client
39+
There is a client that you can run without MCP via commandline to test it:
40+
41+
```bash
42+
uv run rag_base_optimizer_config.py
43+
```
44+
45+
## Quick test via MCP "inspector"
46+
47+
* Run the inspector:
48+
49+
```bash
50+
npx @modelcontextprotocol/inspector uv run rag_base_optimizer_config_mcp.py
51+
```
52+
53+
* connect to the port `http://localhost:6274/` with your browser
54+
* setup the `Inspector Proxy Address` with `http://127.0.0.1:6277`
55+
* test the tool developed.
56+
57+
58+
## Claude Desktop setup
59+
60+
* In **Claude Desktop** application, in `Settings/Developer/Edit Config`, get the `claude_desktop_config.json` to add the references to the local MCP server for RAG in the `<PROJECT_DIR>/src/client/mcp/rag/`:
61+
```json
62+
{
63+
"mcpServers": {
64+
...
65+
,
66+
"rag":{
67+
"command":"bash",
68+
"args":[
69+
"-c",
70+
"source <PROJECT_DIR>/src/client/mcp/rag/.venv/bin/activate && uv run <PROJECT_DIR>/src/client/mcp/rag/rag_base_optimizer_config_mcp.py"
71+
]
72+
}
73+
}
74+
}
75+
```
76+
* In **Claude Desktop** application, in `Settings/General/Claude Settings/Configure`, under `Profile` tab, update fields like:
77+
- `Full Name`
78+
- `What should we call you`
79+
80+
and so on, putting in `What personal preferences should Claude consider in responses?`
81+
the following text:
82+
83+
```
84+
#INSTRUCTION:
85+
Always call the rag_tool tool when the user asks a factual or information-seeking question, even if you think you know the answer.
86+
Show the rag_tool message as-is, without modification.
87+
```
88+
This will impose the usage of `rag_tool` in any case.
89+
90+
**NOTICE**: If you prefer, in this agent dashboard or any other, you could setup a message in the conversation with the same content of `Instruction` to enforce the LLM to use the rag tool as well.
91+
92+
* Restart **Claude Desktop**.
93+
94+
* You will see two warnings on rag_tool configuration: they will disappear and will not cause any issue in activating the tool.
95+
96+
* Start a conversation. You should see a pop up that ask to allow the `rag` tool usage to answer the questions:
97+
98+
![Rag Tool](./images/rag_tool.png)
99+
100+
If the question is related to the knowledge base content stored in the vector store, you will have an answer based on that information. Otherwise, it will try to answer considering information on which has been trained the LLM o other tools configured in the same Claude Desktop.
101+
102+
103+
## Make a remote MCP server the RAG Tool
104+
105+
In `rag_base_optimizer_config_mcp.py`:
106+
107+
* Update the absolute path of your `optimizer_settings.json`. Example:
108+
109+
```python
110+
rag.set_optimizer_settings_path("/Users/cdebari/Documents/GitHub/ai-optimizer-mcp-export/src/client/mcp/rag/optimizer_settings.json")
111+
```
112+
113+
* Substitute `Local` with `Remote client` line:
114+
115+
```python
116+
#mcp = FastMCP("rag", port=8001) #Remote client
117+
mcp = FastMCP("rag") #Local
118+
```
119+
120+
* Substitute `stdio` with `sse` line of code:
121+
```python
122+
mcp.run(transport='stdio')
123+
#mcp.run(transport='sse')
124+
```
125+
126+
* Start MCP server in another shell with:
127+
```bash
128+
uv run rag_base_optimizer_config_mcp.py
129+
```
130+
131+
132+
## Quick test
133+
134+
* Run the inspector:
135+
136+
```bash
137+
npx @modelcontextprotocol/inspector
138+
```
139+
140+
* connect the browser to `http://127.0.0.1:6274`
141+
142+
* set the Transport Type to `SSE`
143+
144+
* set the `URL` to `http://localhost:8001/sse`
145+
146+
* test the tool developed.
147+
148+
149+
150+
## Claude Desktop setup for remote/local server
151+
Claude Desktop, in free version, not allows to connect remote server. You can overcome, for testing purpose only, with a proxy library called `mcp-remote`. These are the options.
152+
If you have already installed Node.js v20.17.0+, it should work:
153+
154+
* replace `rag` mcpServer, setting in `claude_desktop_config.json`:
155+
```json
156+
{
157+
"mcpServers": {
158+
"remote": {
159+
"command": "npx",
160+
"args": [
161+
"mcp-remote",
162+
"http://127.0.0.1:8001/sse"
163+
]
164+
}
165+
}
166+
}
167+
```
168+
* restart Claude Desktop.
169+
170+
**NOTICE**: If you have any problem running, check the logs if it's related to an old npx/nodejs version used with mcp-remote library. Check with:
171+
```bash
172+
nvm -list
173+
```
174+
if you have any other versions available than the default. It could happen that Claude Desktop uses the older one. Try to remove any other nvm versions available to force the use the only one avalable, at minimum v20.17.0+.
175+
176+
* restart and test as remote server
177+
178+

src/client/mcp/rag/cover.png

123 KB
Loading
129 KB
Loading
39.9 KB
Loading
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from langchain_openai import ChatOpenAI
2+
from langchain_openai import OpenAIEmbeddings
3+
from langchain_huggingface import HuggingFaceEmbeddings
4+
from langchain_ollama import OllamaEmbeddings
5+
from langchain_ollama import OllamaLLM
6+
7+
from langchain_community.vectorstores.utils import DistanceStrategy
8+
9+
from langchain_community.vectorstores import oraclevs
10+
from langchain_community.vectorstores.oraclevs import OracleVS
11+
import oracledb
12+
13+
14+
def get_llm(data):
15+
llm={}
16+
llm_config = data["ll_model_config"][data["user_settings"]["ll_model"]["model"]]
17+
api=llm_config["api"]
18+
url=llm_config["url"]
19+
api_key=llm_config["api_key"]
20+
model=data["user_settings"]["ll_model"]["model"]
21+
print(f"CHAT_MODEL: {model} {api} {url} {api_key}")
22+
if api == "ChatOllama":
23+
# Initialize the LLM
24+
llm = OllamaLLM(
25+
model=model,
26+
base_url=url
27+
)
28+
elif api == "OpenAI":
29+
30+
llm=llm = ChatOpenAI(
31+
model=model,
32+
api_key=api_key
33+
)
34+
return llm
35+
36+
def get_embeddings(data):
37+
embeddings={}
38+
model=data["user_settings"]["rag"]["model"]
39+
api=data["embed_model_config"][model]["api"]
40+
url=data["embed_model_config"][model]["url"]
41+
api_key=data["embed_model_config"][model]["api_key"]
42+
print(f"EMBEDDINGS: {model} {api} {url} {api_key}")
43+
embeddings = {}
44+
if api=="OllamaEmbeddings":
45+
embeddings=OllamaEmbeddings(
46+
model=model,
47+
base_url=url)
48+
elif api == "OpenAIEmbeddings":
49+
print("BEFORE create embbedding")
50+
embeddings = OpenAIEmbeddings(
51+
model=model,
52+
api_key=api_key
53+
)
54+
print("AFTER create emebdding")
55+
return embeddings
56+
57+
def get_vectorstore(data,embeddings):
58+
59+
config=data["database_config"][data["user_settings"]["rag"]["database"]]
60+
61+
conn23c = oracledb.connect(user=config["user"],
62+
password=config["password"], dsn=config["dsn"])
63+
64+
print("DB Connection successful!")
65+
metric=data["user_settings"]["rag"]["distance_metric"]
66+
67+
dist_strategy=DistanceStrategy.COSINE
68+
if metric=="COSINE":
69+
dist_strategy=DistanceStrategy.COSINE
70+
elif metric == "EUCLIDEAN":
71+
dist_strategy=DistanceStrategy.EUCLIDEAN
72+
73+
print("1")
74+
a=data["user_settings"]["rag"]["vector_store"]
75+
print(f"{a}")
76+
print(f"BEFORE KNOWLEDGE BASE")
77+
print(embeddings)
78+
knowledge_base = OracleVS(conn23c, embeddings, data["user_settings"]["rag"]["vector_store"], dist_strategy)
79+
return knowledge_base
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
"""
2+
Copyright (c) 2024, 2025, Oracle and/or its affiliates.
3+
Licensed under the Universal Permissive License v1.0 as shown at http://oss.oracle.com/licenses/upl.
4+
"""
5+
from typing import List
6+
from mcp.server.fastmcp import FastMCP
7+
import os
8+
from dotenv import load_dotenv
9+
#from sentence_transformers import CrossEncoder
10+
#from langchain_community.embeddings import HuggingFaceEmbeddings
11+
from langchain_core.prompts import PromptTemplate
12+
from langchain_core.runnables import RunnablePassthrough
13+
from langchain_core.output_parsers import StrOutputParser
14+
import json
15+
import logging
16+
logging.basicConfig(level=logging.DEBUG)
17+
18+
from optimizer_utils import config
19+
20+
_optimizer_settings_path= ""
21+
22+
def set_optimizer_settings_path(path: str):
23+
global _optimizer_settings_path
24+
_optimizer_settings_path = path
25+
26+
def rag_tool_base(question: str) -> str:
27+
"""
28+
Use this tool to answer any question that may benefit from up-to-date or domain-specific information.
29+
30+
Args:
31+
question: the question for which are you looking for an answer
32+
33+
Returns:
34+
JSON string with answer
35+
"""
36+
with open(_optimizer_settings_path, "r") as file:
37+
data = json.load(file)
38+
try:
39+
40+
embeddings = config.get_embeddings(data)
41+
42+
print("Embedding successful!")
43+
knowledge_base = config.get_vectorstore(data,embeddings)
44+
print("DB Connection successful!")
45+
46+
print("knowledge_base successful!")
47+
user_question = question
48+
#result_chunks=knowledge_base.similarity_search(user_question, 5)
49+
50+
for d in data["prompts_config"]:
51+
if d["name"]==data["user_settings"]["prompts"]["sys"]:
52+
53+
rag_prompt=d["prompt"]
54+
55+
template = """DOCUMENTS: {context} \n"""+rag_prompt+"""\nQuestion: {question} """
56+
#template = """Answer the question based only on the following context:{context} Question: {question} """
57+
print(template)
58+
prompt = PromptTemplate.from_template(template)
59+
print("before retriever")
60+
print(data["user_settings"]["rag"]["top_k"])
61+
retriever = knowledge_base.as_retriever(search_kwargs={"k": data["user_settings"]["rag"]["top_k"]})
62+
print("after retriever")
63+
64+
65+
# Initialize the LLM
66+
llm = config.get_llm(data)
67+
68+
chain = (
69+
{"context": retriever, "question": RunnablePassthrough()}
70+
| prompt
71+
| llm
72+
| StrOutputParser()
73+
)
74+
print("pre-chain successful!")
75+
answer = chain.invoke(user_question)
76+
77+
#print(f"Results provided for question: {question}")
78+
#print(f"{answer}")
79+
except Exception as e:
80+
print(e)
81+
print("Connection failed!")
82+
answer=""
83+
84+
return f"{answer}"

0 commit comments

Comments
 (0)