Amirkia RAFIEI OSKOOEI commited on
Commit
6b3ee97
·
2 Parent(s): 0fe55ab 676f4cd

Merge pull request #6 from pia-team/selcan_test

Browse files
.idea/query_mcp_server.iml CHANGED
@@ -1,8 +1,10 @@
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <module type="PYTHON_MODULE" version="4">
3
  <component name="NewModuleRootManager">
4
- <content url="file://$MODULE_DIR$" />
5
- <orderEntry type="inheritedJdk" />
 
 
6
  <orderEntry type="sourceFolder" forTests="false" />
7
  </component>
8
  </module>
 
1
  <?xml version="1.0" encoding="UTF-8"?>
2
  <module type="PYTHON_MODULE" version="4">
3
  <component name="NewModuleRootManager">
4
+ <content url="file://$MODULE_DIR$">
5
+ <excludeFolder url="file://$MODULE_DIR$/.idea/dataSources" />
6
+ </content>
7
+ <orderEntry type="jdk" jdkName="mcptry" jdkType="Python SDK" />
8
  <orderEntry type="sourceFolder" forTests="false" />
9
  </component>
10
  </module>
README.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # PostgreSQL NLP Query Bot
2
+
3
+ A PostgreSQL MCP server and client system that accepts natural language queries from the user, translates them into SQL using an LLM agent (powered by LangChain), executes the query on the database, and returns the results in a human-readable format.
4
+
5
+ ## Features
6
+
7
+ * Natural Language to SQL Translation
8
+ * Live Interaction with PostgreSQL via MCP Protocol
9
+ * Tool Integration with Database Metadata
10
+ * In-Session Conversational Memory
11
+ * Supports multiple LLM clients (Langchain and Smolagents)
12
+ * Gradio web interface for easy interaction
13
+
14
+
15
+ ## Tech Stack
16
+
17
+ | Layer | Technology / Library | Purpose |
18
+ |-------------|------------------------------------|--------------------------------------------------------------|
19
+ | LLM Framework | [LangChain](https://www.langchain.com/) | Manages agent behavior, memory, and tool interaction |
20
+ | LLM Provider | Google Gemini (via LangChain) | Converts natural language into SQL queries |
21
+ | Agent Runtime | LangGraph’s `create_react_agent` | Runs the ReAct-style LLM agent |
22
+ | Communication | Modular Command Protocol (MCP) | Handles communication between client and PostgreSQL backend|
23
+ | Tooling | `langchain_mcp_adapters` | Loads and manages tools for the agent |
24
+ | Database | PostgreSQL | Serves queried data |
25
+ | UI (optional) | [Gradio](https://www.gradio.app/) | Provides a lightweight web interface for querying |
26
+ | Memory | `FileChatMessageHistory` | Stores short-term in-session memory for conversation context |
27
+ | Language | Python 3.10+ | Core programming language used for development |
28
+
29
+
30
+ ## Installation
31
+
32
+
33
+ 1. **Clone the repository**
34
+
35
+ ```bash
36
+ git clone https://github.com/yourusername/query_mcp_server.git
37
+ cd query_mcp_server
38
+
39
+ 2. **Create and activate a conda environment**
40
+
41
+ ```bash
42
+ conda create -n mcp-agent python=3.10
43
+ conda activate mcp-agent
44
+
45
+ 3. **Install dependencies**
46
+ ```bash
47
+ pip install -r requirements.txt
48
+
49
+
50
+ Make sure the server path is correctly set in get_server_params() in your code.
51
+
52
+ ## Configuration
53
+
54
+
55
+ Before running the application, make sure to set your configuration values in a `.env` file.
56
+
57
+ You can start by copying the example file provided:
58
+
59
+ ```bash
60
+ cp .env.sample .env
61
+
62
+ ### .env variables
63
+ * API_KEY: Your Google Gemini API key
64
+ * MCP_SERVER_PATH: Path to the MCP server script (e.g. ./postgres_mcp_server.py)
65
+
66
+ ## Running the Project
67
+
68
+
69
+ Once you've installed the dependencies and configured the `.env` file, you're ready to run the app.
70
+
71
+ ### Run the Agent
72
+
73
+ ```bash
74
+ python gradio_app.py
75
+ ```
76
+
77
+
78
+ ## Project Structure
79
+
80
+ ```
81
+ .
82
+ ├── gradio_app.py # Gradio UI to interact with any client (LangChain or SmolAgent)
83
+ ├── langchain_mcp_client.py # Uses LangChain ReAct agent + ConversationBufferMemory (in-memory, also saves to memory.json)
84
+ ├── postgre_mcp_client.py # Uses ReAct agent + custom memory class (in-session only), **WIP**: needs prompt fix
85
+ ├── postgre_smolagent_client.py # Uses SmolAgent (lightweight), no memory support yet, **WIP**: output parsing + prompt building
86
+ ├── postgre_mcp_server.py # MCP server that handles actual PostgreSQL communication
87
+ ├── conversation_memory.py # Built-in memory class used by some clients
88
+ ├── chat_history.json # Keeps session memory. Needs to be deleted after each session
89
+ ├── table_summary.txt # Database table descriptions used to enrich tools
90
+ ├── utils.py # Helper functions: intent classification, output parsing, etc.
91
+ ├── .env.sample # Template for your environment config
92
+ ├── requirements.txt # Python dependencies
93
+ └── README.md
94
+ ```
95
+
96
+ ### Work In Progress (WIP) Notes
97
+ * postgre_mcp_client.py:
98
+ - needs improvement in build_prompt() function
99
+
100
+ * postgre_smolagent_client.py:
101
+
102
+ - Doesn’t support memory yet
103
+
104
+ - Uses a different output format — not yet compatible with parse_mcp_output()
105
+
106
+ - Also needs build_prompt() logic finalized
107
+ * gradio_app.py:
108
+ - Doesn't support conversation chat ui
gradio_app.py CHANGED
@@ -3,7 +3,7 @@ from pathlib import Path
3
  import gradio as gr
4
  import asyncio
5
  from postgre_mcp_client import pg_mcp_exec
6
- from postgre_smolagent_clinet import pg_mcp_smolagent_exec
7
  from langchain_mcp_client import lc_mcp_exec
8
 
9
  def load_db_configs():
 
3
  import gradio as gr
4
  import asyncio
5
  from postgre_mcp_client import pg_mcp_exec
6
+ from postgre_smolagent_client import pg_mcp_smolagent_exec
7
  from langchain_mcp_client import lc_mcp_exec
8
 
9
  def load_db_configs():
langchain_mcp_client.py CHANGED
@@ -25,14 +25,14 @@ async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
25
  """
26
  try:
27
  history_file = os.path.join(os.path.dirname(__file__), "chat_history.json")
28
-
29
  # Initialize chat history file if it doesn't exist or is empty
30
  if not os.path.exists(history_file) or os.path.getsize(history_file) == 0:
31
  with open(history_file, 'w') as f:
32
  json.dump({"messages": []}, f)
33
 
34
  message_history = FileChatMessageHistory(file_path=history_file)
35
-
36
  try:
37
  # Load existing messages or handle bootstrap scenario
38
  existing_messages = message_history.messages
@@ -71,16 +71,16 @@ async def lc_mcp_exec(request: str, history) -> tuple[Any, Any]:
71
 
72
  # Create agent and prepare system message
73
  agent = create_react_agent(llm, tools)
74
-
75
  # Create base messages list with system message
76
  base_message = HumanMessage(content="""You are a PostgreSQL database expert assistant.
77
  Use the conversation history for context when available.""")
78
  messages = [base_message]
79
-
80
  # Add history if exists
81
  if formatted_history:
82
  messages.extend(formatted_history)
83
-
84
  # Add current request
85
  messages.append(HumanMessage(content=request))
86
 
@@ -134,7 +134,7 @@ async def load_and_enrich_tools(session: ClientSession):
134
  async def build_prompt(session, intent, request, tools, summary, chat_history):
135
  superset_prompt = await session.read_resource("resource://last_prompt")
136
  conversation_prompt = await session.read_resource("resource://base_prompt")
137
-
138
  if intent == "superset_request":
139
  template = superset_prompt.contents[0].text
140
  return template.format(
@@ -143,7 +143,7 @@ async def build_prompt(session, intent, request, tools, summary, chat_history):
143
  else:
144
  template = conversation_prompt.contents[0].text
145
  tools_str = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
146
-
147
  # Handle history formatting with proper message access
148
  history_str = ""
149
  if chat_history:
 
25
  """
26
  try:
27
  history_file = os.path.join(os.path.dirname(__file__), "chat_history.json")
28
+
29
  # Initialize chat history file if it doesn't exist or is empty
30
  if not os.path.exists(history_file) or os.path.getsize(history_file) == 0:
31
  with open(history_file, 'w') as f:
32
  json.dump({"messages": []}, f)
33
 
34
  message_history = FileChatMessageHistory(file_path=history_file)
35
+
36
  try:
37
  # Load existing messages or handle bootstrap scenario
38
  existing_messages = message_history.messages
 
71
 
72
  # Create agent and prepare system message
73
  agent = create_react_agent(llm, tools)
74
+
75
  # Create base messages list with system message
76
  base_message = HumanMessage(content="""You are a PostgreSQL database expert assistant.
77
  Use the conversation history for context when available.""")
78
  messages = [base_message]
79
+
80
  # Add history if exists
81
  if formatted_history:
82
  messages.extend(formatted_history)
83
+
84
  # Add current request
85
  messages.append(HumanMessage(content=request))
86
 
 
134
  async def build_prompt(session, intent, request, tools, summary, chat_history):
135
  superset_prompt = await session.read_resource("resource://last_prompt")
136
  conversation_prompt = await session.read_resource("resource://base_prompt")
137
+
138
  if intent == "superset_request":
139
  template = superset_prompt.contents[0].text
140
  return template.format(
 
143
  else:
144
  template = conversation_prompt.contents[0].text
145
  tools_str = "\n".join([f"- {tool.name}: {tool.description}" for tool in tools])
146
+
147
  # Handle history formatting with proper message access
148
  history_str = ""
149
  if chat_history:
main.py DELETED
@@ -1,38 +0,0 @@
1
- import yaml
2
- from pathlib import Path
3
- from typing import List
4
- import asyncio
5
- from postgre_mcp_client import pg_mcp_exec
6
- import logging
7
-
8
- from postgre_smolagent_clinet import pg_mcp_smolagent_exec
9
-
10
-
11
- #logger = logging.getLogger(__name__)
12
- # TODO add config
13
- def load_db_configs():
14
- """Load database configurations from databases.yaml"""
15
- configs_path = Path("configs.yaml")
16
-
17
- if not configs_path.exists():
18
- raise FileNotFoundError("configs.yaml not found")
19
-
20
- with open(configs_path) as f:
21
- configs = yaml.safe_load(f)
22
-
23
- return configs["db_configs"]
24
-
25
-
26
- async def main():
27
- #configs = load_db_configs()
28
-
29
- request = "send to superset"
30
- # request = "Show me the table of join posts and users tables."
31
-
32
- # await pg_mcp_exec(request)
33
- # await pg_mcp_smolagent_exec(request)
34
-
35
-
36
- if __name__ == "__main__":
37
-
38
- asyncio.run(main())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
postgre_smolagent_clinet.py → postgre_smolagent_client.py RENAMED
File without changes
prompt_temp.txt DELETED
@@ -1,58 +0,0 @@
1
- =========================
2
- # Your Role
3
- =========================
4
-
5
- You are an expert in generating SQL queries and interacting with a PostgreSQL database using FastMCP tools. These tools allow you to:
6
-
7
- - List available tables
8
- - Retrieve schema details
9
- - Execute SQL queries
10
-
11
- Each tool may also provide summaries of table contents to help you understand the data structure. You have access to **short-term memory**, which stores relevant information from earlier steps or previous queries. If the memory is not empty, you **must** use it when processing the current request. Avoid repeating the same tool with identical input unless the result is **not already present in memory**.
12
-
13
- =========================
14
- # Your Objective
15
- =========================
16
-
17
- When a user submits a request, you must:
18
-
19
- 1. **Analyze the request** to determine the required data or action.
20
- 2. **Use FastMCP tools** to gather any necessary information (e.g., list tables or retrieve schema).
21
- 3. **Generate a valid SQL SELECT query**, if needed, and clearly show the full query.
22
- 4. **Execute the SQL query** and return the results.
23
- 5. **Chain tools logically**, such as: List Tables → Get Schema → Write and Run Query.
24
- 6. **Explain your reasoning and each step taken** to ensure clarity and transparency.
25
-
26
- =========================
27
- # Critical Rules
28
- =========================
29
-
30
- - Only use **SELECT** queries. Never use destructive operations (e.g., DELETE, DROP, UPDATE).
31
- - Always display any SQL query you generate along with the result of its execution.
32
- - Validate SQL syntax before execution to ensure correctness and safety.
33
- - Never make assumptions about the database structure — always use tools to confirm table and column names.
34
- - Be cautious, deliberate, and transparent in your actions.
35
-
36
- =========================
37
- # Short-Term Memory
38
- =========================
39
-
40
- You have access to the following memory from this conversation. Use it if applicable for the current request.
41
-
42
- ### Conversation Context:
43
- - **Previous user requests**:
44
- {user_requests}
45
- - **Tools used so far**:
46
- {past_tools}
47
- - **Last SQL queries (if any)**:
48
- {last_queries}
49
- - **Last result preview**:
50
- {last_results}
51
-
52
- =========================
53
- # New User Request
54
- =========================
55
-
56
- Please fulfill the following request based on the above context:
57
-
58
- {new_request}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
streamlit_app.py DELETED
@@ -1,38 +0,0 @@
1
- import yaml
2
- from pathlib import Path
3
- import streamlit as st
4
- from postgre_mcp_client import pg_mcp_exec
5
- import asyncio
6
- import nest_asyncio
7
- nest_asyncio.apply()
8
-
9
- def load_db_configs():
10
- """Load database configurations from configs.yaml"""
11
- configs_path = Path("configs.yaml")
12
-
13
- if not configs_path.exists():
14
- st.error("configs.yaml not found")
15
- return None
16
-
17
- with open(configs_path) as f:
18
- configs = yaml.safe_load(f)
19
-
20
- return configs.get("db_configs", {})
21
-
22
- def run_agent(message):
23
- response = asyncio.run(pg_mcp_exec(message))
24
- # Return in message format
25
- return {"role": "assistant", "content": response}
26
-
27
- # Streamlit UI
28
- st.title("PostgreSQL Query Agent")
29
- st.write("Ask your database in natural language and get results using the smolagent executor.")
30
-
31
- user_input = st.text_input("Natural Language Request", placeholder="e.g., Show me the table of join posts and users tables.")
32
-
33
- if st.button("Run Query"):
34
- if user_input.strip():
35
- result = run_agent(user_input)
36
- st.text_area("SQL Query / Result", value=str(result), height=300)
37
- else:
38
- st.warning("Please enter a natural language request.")