Merge pull request #6 from pia-team/selcan_test
Browse files- .idea/query_mcp_server.iml +4 -2
- README.md +108 -0
- gradio_app.py +1 -1
- langchain_mcp_client.py +7 -7
- main.py +0 -38
- postgre_smolagent_clinet.py → postgre_smolagent_client.py +0 -0
- prompt_temp.txt +0 -58
- streamlit_app.py +0 -38
.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 |
-
|
|
|
|
|
|
|
| 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
|
| 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.")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|