first
Browse files- .gitignore +3 -0
- EuijP.png +0 -0
- db_work.py +105 -0
- gradio_mcp.py +109 -0
- list_columns_in_table.sql +25 -0
- list_database_infos.sql +16 -0
- list_schema.sql +25 -0
- list_tables_in_schema.sql +23 -0
- requirements.txt +13 -0
.gitignore
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
__pycache__
|
| 3 |
+
.gradio
|
EuijP.png
ADDED
|
db_work.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from typing import Dict, List, Any, Optional
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
import psycopg2
|
| 5 |
+
from psycopg2.extras import RealDictCursor
|
| 6 |
+
import pandas as pd
|
| 7 |
+
from mcp.server.fastmcp import FastMCP
|
| 8 |
+
from pathlib import Path
|
| 9 |
+
|
| 10 |
+
# Load environment variables
|
| 11 |
+
load_dotenv()
|
| 12 |
+
|
| 13 |
+
LIST_SCHEMA=os.getenv('LIST_SCHEMA')
|
| 14 |
+
LIST_DATABASE_INFOS=os.getenv('LIST_DATABASE_INFOS')
|
| 15 |
+
TABLE_IN_SCHEMA=os.getenv('TABLE_IN_SCHEMA')
|
| 16 |
+
COLUMN_IN_TABLE=os.getenv('COLUMN_IN_TABLE')
|
| 17 |
+
|
| 18 |
+
class DatabaseInterface:
|
| 19 |
+
def __init__(self):
|
| 20 |
+
# Initialize FastMCP server
|
| 21 |
+
self.mcp = FastMCP("ecommerce-mcp-server")
|
| 22 |
+
|
| 23 |
+
self.db_config = {
|
| 24 |
+
'host': os.getenv('DB_HOST'),
|
| 25 |
+
'port': os.getenv('DB_PORT'),
|
| 26 |
+
'database': os.getenv('DB_NAME'),
|
| 27 |
+
'user': os.getenv('DB_USER'),
|
| 28 |
+
'password': os.getenv('DB_PASSWORD')
|
| 29 |
+
}
|
| 30 |
+
print('=============>',self.db_config)
|
| 31 |
+
|
| 32 |
+
def get_db_connection(self):
|
| 33 |
+
"""Create database connection"""
|
| 34 |
+
return psycopg2.connect(**self.db_config)
|
| 35 |
+
|
| 36 |
+
def list_schemas(self):
|
| 37 |
+
print("=======>", LIST_SCHEMA)
|
| 38 |
+
sql_path = Path(LIST_SCHEMA)
|
| 39 |
+
with sql_path.open("r", encoding="utf-8") as f:
|
| 40 |
+
query = f.read()
|
| 41 |
+
|
| 42 |
+
conn = self.get_db_connection()
|
| 43 |
+
try:
|
| 44 |
+
with conn.cursor() as cur:
|
| 45 |
+
cur.execute(query)
|
| 46 |
+
result = cur.fetchone()[0] # JSON object
|
| 47 |
+
return result
|
| 48 |
+
finally:
|
| 49 |
+
conn.close()
|
| 50 |
+
|
| 51 |
+
def list_database_info(self):
|
| 52 |
+
sql_path = Path(LIST_DATABASE_INFOS)
|
| 53 |
+
with sql_path.open("r", encoding="utf-8") as f:
|
| 54 |
+
query = f.read()
|
| 55 |
+
|
| 56 |
+
conn = self.get_db_connection()
|
| 57 |
+
try:
|
| 58 |
+
with conn.cursor() as cur:
|
| 59 |
+
cur.execute(query)
|
| 60 |
+
result = cur.fetchone()[0] # JSON object
|
| 61 |
+
return result
|
| 62 |
+
finally:
|
| 63 |
+
conn.close()
|
| 64 |
+
|
| 65 |
+
def list_tables_in_schema(self, schema_name: str):
|
| 66 |
+
sql_path = Path(TABLE_IN_SCHEMA)
|
| 67 |
+
with sql_path.open("r", encoding="utf-8") as f:
|
| 68 |
+
query = f.read()
|
| 69 |
+
|
| 70 |
+
conn = self.get_db_connection()
|
| 71 |
+
try:
|
| 72 |
+
with conn.cursor() as cur:
|
| 73 |
+
cur.execute(query, {'schema_name': schema_name})
|
| 74 |
+
result = cur.fetchone()[0] # JSON object
|
| 75 |
+
return result
|
| 76 |
+
finally:
|
| 77 |
+
conn.close()
|
| 78 |
+
|
| 79 |
+
def list_columns_in_table(self, schema_name: str, table_name: str):
|
| 80 |
+
sql_path = Path(COLUMN_IN_TABLE)
|
| 81 |
+
with sql_path.open("r", encoding="utf-8") as f:
|
| 82 |
+
query = f.read()
|
| 83 |
+
|
| 84 |
+
conn = self.get_db_connection()
|
| 85 |
+
try:
|
| 86 |
+
with conn.cursor() as cur:
|
| 87 |
+
cur.execute(query, {
|
| 88 |
+
'schema_name': schema_name,
|
| 89 |
+
'table_name': table_name
|
| 90 |
+
})
|
| 91 |
+
result = cur.fetchone()[0] # JSON object
|
| 92 |
+
return result
|
| 93 |
+
finally:
|
| 94 |
+
conn.close()
|
| 95 |
+
|
| 96 |
+
def read_only_query(self, query):
|
| 97 |
+
try:
|
| 98 |
+
conn = self.get_db_connection()
|
| 99 |
+
with conn.cursor() as cur:
|
| 100 |
+
cur.execute("SET TRANSACTION READ ONLY")
|
| 101 |
+
cur.execute(query)
|
| 102 |
+
result = cur.fetchall() # JSON object
|
| 103 |
+
return result
|
| 104 |
+
finally:
|
| 105 |
+
conn.close()
|
gradio_mcp.py
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
from db_work import DatabaseInterface
|
| 3 |
+
import os
|
| 4 |
+
from PIL import Image
|
| 5 |
+
|
| 6 |
+
db_interface = DatabaseInterface()
|
| 7 |
+
# Define the functions
|
| 8 |
+
def get_schemas():
|
| 9 |
+
'''
|
| 10 |
+
this function allow you to acknowledge the database schema in order for
|
| 11 |
+
you to know which schema to query to get the relevant informations
|
| 12 |
+
'''
|
| 13 |
+
return db_interface.list_schemas()
|
| 14 |
+
|
| 15 |
+
def get_db_infos():
|
| 16 |
+
'''
|
| 17 |
+
this function allow you to acknowledge the relevant database information for you to better understand what is it about
|
| 18 |
+
'''
|
| 19 |
+
return db_interface.list_database_info()
|
| 20 |
+
|
| 21 |
+
def get_list_of_tables_in_schema(schema):
|
| 22 |
+
"""
|
| 23 |
+
this function allows you to get the list of tables (associated with their description if exist)
|
| 24 |
+
of all the tables that exist in a schema
|
| 25 |
+
"""
|
| 26 |
+
return db_interface.list_tables_in_schema(schema)
|
| 27 |
+
|
| 28 |
+
def get_list_of_column_in_table(schema, table):
|
| 29 |
+
"""
|
| 30 |
+
this function allows you to get the list of columns of a specific table of a specific schema.
|
| 31 |
+
each column is associated with its datatype and its description if exist
|
| 32 |
+
"""
|
| 33 |
+
return db_interface.list_columns_in_table(schema, table)
|
| 34 |
+
|
| 35 |
+
def run_read_only_query(query:str):
|
| 36 |
+
"""
|
| 37 |
+
based on what you know about the database properties, you can use this function to run read-only query
|
| 38 |
+
in order to make analysis
|
| 39 |
+
the output is of shape:
|
| 40 |
+
List(Tuple()) where each entry if the list is a row and each entry of the tuple is a column value
|
| 41 |
+
"""
|
| 42 |
+
return db_interface.read_only_query(query)
|
| 43 |
+
|
| 44 |
+
def create_sample_image():
|
| 45 |
+
img_path = "./EuijP.png"
|
| 46 |
+
if not os.path.exists(img_path):
|
| 47 |
+
img = Image.new("RGB", (300, 150), color="lightgreen")
|
| 48 |
+
img.save(img_path)
|
| 49 |
+
return img_path
|
| 50 |
+
|
| 51 |
+
def serve_image_from_path():
|
| 52 |
+
"""
|
| 53 |
+
get a scatter plot of 2 variables.
|
| 54 |
+
input type: [[list_x], [list_y]]
|
| 55 |
+
it is up to you to determine if the variable need to be standardize or not
|
| 56 |
+
"""
|
| 57 |
+
return create_sample_image()
|
| 58 |
+
|
| 59 |
+
# Create the Gradio Blocks interface
|
| 60 |
+
with gr.Blocks() as interface:
|
| 61 |
+
with gr.Row():
|
| 62 |
+
with gr.Column(scale=1):
|
| 63 |
+
# Get info on the schema
|
| 64 |
+
discover_input = gr.Textbox(label="Get info on schemas of the database")
|
| 65 |
+
discover_btn = gr.Button("run get infos on the schemas of the database")
|
| 66 |
+
|
| 67 |
+
# Get info on the database
|
| 68 |
+
database_info = gr.Textbox(label="Get info on the database")
|
| 69 |
+
database_info_btn = gr.Button("Run Get info on the database")
|
| 70 |
+
|
| 71 |
+
# Get table in schema
|
| 72 |
+
table_in_schema_input = gr.Textbox(label="What schema you want table name for")
|
| 73 |
+
table_in_schema_btn = gr.Button("Run Get list of table in schema")
|
| 74 |
+
|
| 75 |
+
# Get Columns in Table
|
| 76 |
+
gr.Markdown("### Get Columns in Table\nRetrieve the columns of a table in a schema.")
|
| 77 |
+
schema_input = gr.Textbox(label="Schema Name")
|
| 78 |
+
table_input = gr.Textbox(label="Table Name")
|
| 79 |
+
column_btn = gr.Button("Get Columns")
|
| 80 |
+
|
| 81 |
+
gr.Markdown("### Enter a read-only query")
|
| 82 |
+
query_input = gr.Textbox(label="read-only query")
|
| 83 |
+
query_btn = gr.Button("Get Columns")
|
| 84 |
+
|
| 85 |
+
gr.Markdown("### generate a scatter-plot")
|
| 86 |
+
input_text = gr.Textbox(label="Prompt")
|
| 87 |
+
generate_button = gr.Button("Generate")
|
| 88 |
+
|
| 89 |
+
with gr.Column(scale=2):
|
| 90 |
+
schema_info = gr.Textbox(label="Discover DB Output")
|
| 91 |
+
db_info = gr.Textbox(label="Query DB Output")
|
| 92 |
+
table_in_schema = gr.Textbox(label="what table are in the selected schema")
|
| 93 |
+
column_output = gr.Textbox(label="Table Columns Output")
|
| 94 |
+
query_output = gr.Textbox(label="your query output")
|
| 95 |
+
output_image = gr.Image(label="Generated Image", type="filepath")
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
# Bind functions to buttons
|
| 100 |
+
discover_btn.click(get_schemas, outputs=schema_info)
|
| 101 |
+
database_info_btn.click(get_db_infos, outputs=db_info)
|
| 102 |
+
table_in_schema_btn.click(get_list_of_tables_in_schema, inputs=table_in_schema_input, outputs=table_in_schema)
|
| 103 |
+
column_btn.click(get_list_of_column_in_table, inputs=[schema_input, table_input], outputs=column_output)
|
| 104 |
+
query_btn.click(run_read_only_query, inputs=query_input, outputs=query_output)
|
| 105 |
+
generate_button.click(fn=serve_image_from_path, outputs=output_image)
|
| 106 |
+
|
| 107 |
+
# Launch the app
|
| 108 |
+
interface.launch(mcp_server=True, share=True)
|
| 109 |
+
|
list_columns_in_table.sql
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- server/resources/sql/list_columns_in_table.sql
|
| 2 |
+
-- Returns column metadata for a specific table as a JSON object
|
| 3 |
+
-- Uses parameters: :schema_name, :table_name
|
| 4 |
+
|
| 5 |
+
WITH columns AS (
|
| 6 |
+
SELECT
|
| 7 |
+
cols.column_name,
|
| 8 |
+
cols.data_type,
|
| 9 |
+
col_description(('"' || cols.table_schema || '"."' || cols.table_name || '"')::regclass, cols.ordinal_position) AS description
|
| 10 |
+
FROM information_schema.columns cols
|
| 11 |
+
WHERE cols.table_schema = %(schema_name)s
|
| 12 |
+
AND cols.table_name = %(table_name)s
|
| 13 |
+
ORDER BY cols.ordinal_position
|
| 14 |
+
)
|
| 15 |
+
SELECT jsonb_build_object(
|
| 16 |
+
'columns',
|
| 17 |
+
jsonb_agg(
|
| 18 |
+
jsonb_build_object(
|
| 19 |
+
'name', column_name,
|
| 20 |
+
'type', data_type,
|
| 21 |
+
'description', description
|
| 22 |
+
)
|
| 23 |
+
)
|
| 24 |
+
) AS column_list
|
| 25 |
+
FROM columns;
|
list_database_infos.sql
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- server/resources/sql/list_database_info.sql
|
| 2 |
+
-- Returns basic information about the current database in JSON format
|
| 3 |
+
|
| 4 |
+
SELECT jsonb_build_object(
|
| 5 |
+
'database',
|
| 6 |
+
jsonb_build_object(
|
| 7 |
+
'name', current_database(),
|
| 8 |
+
'description', (
|
| 9 |
+
SELECT description
|
| 10 |
+
FROM pg_shdescription
|
| 11 |
+
JOIN pg_database ON pg_database.oid = pg_shdescription.objoid
|
| 12 |
+
WHERE pg_database.datname = current_database()
|
| 13 |
+
LIMIT 1
|
| 14 |
+
)
|
| 15 |
+
)
|
| 16 |
+
) AS database_info;
|
list_schema.sql
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- server/resources/sql/list_schemas.sql
|
| 2 |
+
-- List all non-system schemas in the database
|
| 3 |
+
-- Returns a JSON array of schema objects
|
| 4 |
+
|
| 5 |
+
WITH schemas AS (
|
| 6 |
+
SELECT
|
| 7 |
+
schema_name,
|
| 8 |
+
obj_description(pg_namespace.oid) as description
|
| 9 |
+
FROM information_schema.schemata
|
| 10 |
+
JOIN pg_namespace ON pg_namespace.nspname = schema_name
|
| 11 |
+
WHERE
|
| 12 |
+
schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
|
| 13 |
+
AND schema_name NOT LIKE 'pg_%'
|
| 14 |
+
ORDER BY schema_name
|
| 15 |
+
)
|
| 16 |
+
SELECT jsonb_build_object(
|
| 17 |
+
'schemas',
|
| 18 |
+
jsonb_agg(
|
| 19 |
+
jsonb_build_object(
|
| 20 |
+
'name', schema_name,
|
| 21 |
+
'description', description
|
| 22 |
+
)
|
| 23 |
+
)
|
| 24 |
+
) AS schema_list
|
| 25 |
+
FROM schemas;
|
list_tables_in_schema.sql
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
-- server/resources/sql/list_tables_in_schema.sql
|
| 2 |
+
-- Returns all user-defined tables in a given schema with their descriptions as JSON
|
| 3 |
+
-- Uses a parameter :schema_name
|
| 4 |
+
|
| 5 |
+
WITH tables AS (
|
| 6 |
+
SELECT
|
| 7 |
+
table_name,
|
| 8 |
+
obj_description(('"' || table_schema || '"."' || table_name || '"')::regclass) AS description
|
| 9 |
+
FROM information_schema.tables
|
| 10 |
+
WHERE table_schema = %(schema_name)s
|
| 11 |
+
AND table_type = 'BASE TABLE'
|
| 12 |
+
ORDER BY table_name
|
| 13 |
+
)
|
| 14 |
+
SELECT jsonb_build_object(
|
| 15 |
+
'tables',
|
| 16 |
+
jsonb_agg(
|
| 17 |
+
jsonb_build_object(
|
| 18 |
+
'name', table_name,
|
| 19 |
+
'description', description
|
| 20 |
+
)
|
| 21 |
+
)
|
| 22 |
+
) AS table_list
|
| 23 |
+
FROM tables;
|
requirements.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
mcp>=1.0.0
|
| 2 |
+
gradio>=4.0.0
|
| 3 |
+
gradio[mcp]
|
| 4 |
+
psycopg2-binary>=2.9.0
|
| 5 |
+
pandas>=2.0.0
|
| 6 |
+
sqlalchemy>=2.0.0
|
| 7 |
+
python-dotenv>=1.0.0
|
| 8 |
+
numpy>=1.24.0
|
| 9 |
+
scikit-learn>=1.3.0
|
| 10 |
+
plotly>=5.0.0 # For interactive visualizations
|
| 11 |
+
seaborn>=0.12.0 # For statistical plots
|
| 12 |
+
asyncio-throttle>=1.0.0 # For rate limiting
|
| 13 |
+
cachetools>=5.0.0 # For caching optimization
|