slahlou commited on
Commit
d2c5e3d
Β·
1 Parent(s): 6f97068
.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