Add comprehensive MCP testing suite in scripts/mcp/

Created a complete testing suite for the MCP module with MySQL
connection pool and exploration tools.

Files added:
- README.md: Comprehensive testing documentation
- setup_test_db.sh: Docker-based test MySQL database setup
  - Start/stop/status/connect commands
  - Creates sample schema (customers, orders, products, order_items)
  - Includes views and stored procedures for testing
- configure_mcp.sh: ProxySQL MCP module configuration
  - Configures MySQL connection parameters
  - Enables/disables MCP server
  - Shows current configuration status
- test_mcp_tools.sh: Main MCP tools test suite
  - Tests all 15 MCP tools (list_schemas, list_tables, etc.)
  - Includes catalog tests (upsert, get, search, delete)
  - Reports pass/fail statistics
- stress_test.sh: Concurrent connection stress testing
  - Configurable number of concurrent requests
  - Response time measurement
  - Success rate calculation
- test_catalog.sh: Catalog/LLM memory specific tests
  - 12 catalog operation tests
  - FTS search testing
  - CRUD verification

All scripts are executable and include:
- Command-line argument parsing
- Colored output for readability
- Error handling and validation
- Usage/help documentation
- Environment variable support
pull/5310/head
Rene Cannao 4 months ago
parent 06aa6d6ef7
commit e9a6dd0b3e

@ -0,0 +1,155 @@
# MCP Module Testing Suite
This directory contains scripts to test the ProxySQL MCP (Model Context Protocol) module with MySQL connection pool and exploration tools.
## Prerequisites
- ProxySQL must be installed and built with MCP support
- MySQL server (either running or Docker capability)
- `mysql` client installed
- `curl` installed for HTTP testing
- `jq` installed for JSON parsing (optional but recommended)
## Quick Start
```bash
# 1. Start a test MySQL server (Docker)
./setup_test_db.sh start
# 2. Configure ProxySQL MCP module
./configure_mcp.sh
# 3. Run all MCP tool tests
./test_mcp_tools.sh
# 4. Run stress test (optional)
./stress_test.sh
# 5. Stop test MySQL server (Docker)
./setup_test_db.sh stop
```
## Scripts
| Script | Purpose |
|--------|---------|
| `setup_test_db.sh` | Create/start a test MySQL database with sample data |
| `configure_mcp.sh` | Configure ProxySQL MCP module variables |
| `test_mcp_tools.sh` | Test all MCP tools via HTTPS/JSON-RPC |
| `stress_test.sh` | Concurrent connection stress test |
| `test_catalog.sh` | Test catalog (LLM memory) functionality |
## Manual Testing
### Test via curl
```bash
# Test list_schemas
curl -k https://127.0.0.1:6071/query -X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {"name": "list_schemas", "arguments": {}},
"id": 1
}'
# Test list_tables
curl -k https://127.0.0.1:6071/query -X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {"name": "list_tables", "arguments": {"schema": "testdb"}},
"id": 1
}'
```
### Test via mysql admin
```sql
-- Connect to ProxySQL admin
mysql -h 127.0.0.1 -P 6032 -u admin -padmin
-- Check MCP configuration
SHOW VARIABLES LIKE 'mcp-%';
-- Check connection pool status
SELECT * FROM stats_mcp_connections;
```
## Expected Results
### Successful Connection Pool Initialization
ProxySQL log should show:
```
MySQL_Tool_Handler: Connected to 127.0.0.1:3307
MySQL_Tool_Handler: Connection pool initialized with 1 connection(s)
MySQL Tool Handler initialized for schema 'testdb'
```
### Successful Tool Response
```json
{
"jsonrpc": "2.0",
"result": [
{"name": "testdb", "table_count": 2},
{"name": "mysql", "table_count": 0}
],
"id": 1
}
```
## Troubleshooting
### MCP server not starting
Check ProxySQL logs:
```bash
tail -f proxysql.log | grep -i mcp
```
### Connection pool failing
Verify MySQL is accessible:
```bash
mysql -h 127.0.0.1 -P 3307 -u root -ptest testdb -e "SELECT 1"
```
### Certificate errors
The tests use `-k` to skip SSL verification. For production:
```bash
export MCP_CERT=/path/to/cert.pem
export MCP_KEY=/path/to/key.pem
```
## MCP Tools Reference
| Tool | Description |
|------|-------------|
| `list_schemas` | List available databases |
| `list_tables` | List tables in a schema |
| `describe_table` | Get table schema (columns, keys, indexes) |
| `sample_rows` | Sample rows from a table |
| `sample_distinct` | Sample distinct values from a column |
| `run_sql_readonly` | Execute read-only SQL with guardrails |
| `explain_sql` | Get query execution plan |
| `catalog_upsert` | Store entry in LLM catalog |
| `catalog_get` | Retrieve entry from LLM catalog |
| `catalog_search` | Search LLM catalog |
## Default Configuration
| Variable | Default | Description |
|----------|---------|-------------|
| `mcp-enabled` | false | Enable MCP server |
| `mcp-port` | 6071 | HTTPS port for MCP |
| `mcp-mysql_hosts` | 127.0.0.1 | MySQL server host(s) |
| `mcp-mysql_ports` | 3306 | MySQL server port(s) |
| `mcp-mysql_user` | (empty) | MySQL username |
| `mcp-mysql_password` | (empty) | MySQL password |
| `mcp-mysql_schema` | (empty) | Default schema |
| `mcp-catalog_path` | /var/lib/proxysql/mcp_catalog.db | Catalog database path |

@ -0,0 +1,301 @@
#!/bin/bash
#
# configure_mcp.sh - Configure ProxySQL MCP module
#
# Usage:
# ./configure_mcp.sh [options]
#
# Options:
# -h, --host HOST MySQL host (default: 127.0.0.1)
# -P, --port PORT MySQL port (default: 3307)
# -u, --user USER MySQL user (default: root)
# -p, --password PASS MySQL password (default: test123)
# -d, --database DB MySQL database (default: testdb)
# --mcp-port PORT MCP server port (default: 6071)
# --enable Enable MCP server
# --disable Disable MCP server
# --status Show current MCP configuration
#
set -e
# Default configuration
MYSQL_HOST="127.0.0.1"
MYSQL_PORT="3307"
MYSQL_USER="root"
MYSQL_PASSWORD="test123"
MYSQL_DATABASE="testdb"
MCP_PORT="6071"
MCP_ENABLED="false"
# ProxySQL admin configuration
PROXYSQL_ADMIN_HOST="${PROXYSQL_ADMIN_HOST:-127.0.0.1}"
PROXYSQL_ADMIN_PORT="${PROXYSQL_ADMIN_PORT:-6032}"
PROXYSQL_ADMIN_USER="${PROXYSQL_ADMIN_USER:-admin}"
PROXYSQL_ADMIN_PASSWORD="${PROXYSQL_ADMIN_PASSWORD:-admin}"
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_step() {
echo -e "${BLUE}[STEP]${NC} $1"
}
# Execute MySQL command via ProxySQL admin
exec_admin() {
mysql -h "${PROXYSQL_ADMIN_HOST}" -P "${PROXYSQL_ADMIN_PORT}" \
-u "${PROXYSQL_ADMIN_USER}" -p"${PROXYSQL_ADMIN_PASSWORD}" \
-e "$1" 2>/dev/null
}
# Check if ProxySQL admin is accessible
check_proxysql_admin() {
log_step "Checking ProxySQL admin connection..."
if exec_admin "SELECT 1" >/dev/null 2>&1; then
log_info "Connected to ProxySQL admin at ${PROXYSQL_ADMIN_HOST}:${PROXYSQL_ADMIN_PORT}"
return 0
else
log_error "Cannot connect to ProxySQL admin at ${PROXYSQL_ADMIN_HOST}:${PROXYSQL_ADMIN_PORT}"
log_error "Please ensure ProxySQL is running"
return 1
fi
}
# Check if MySQL is accessible
check_mysql_connection() {
log_step "Checking MySQL connection..."
if mysql -h "${MYSQL_HOST}" -P "${MYSQL_PORT}" \
-u "${MYSQL_USER}" -p"${MYSQL_PASSWORD}" \
-e "SELECT 1" >/dev/null 2>&1; then
log_info "Connected to MySQL at ${MYSQL_HOST}:${MYSQL_PORT}"
return 0
else
log_error "Cannot connect to MySQL at ${MYSQL_HOST}:${MYSQL_PORT}"
log_error "Please ensure MySQL is running and credentials are correct"
return 1
fi
}
# Configure MCP variables
configure_mcp() {
local enable="$1"
log_step "Configuring MCP variables..."
# Set MySQL connection configuration
cat <<EOF | exec_admin
SET mcp-mysql_hosts='${MYSQL_HOST}';
SET mcp-mysql_ports='${MYSQL_PORT}';
SET mcp-mysql_user='${MYSQL_USER}';
SET mcp-mysql_password='${MYSQL_PASSWORD}';
SET mcp-mysql_schema='${MYSQL_DATABASE}';
SET mcp-catalog_path='/var/lib/proxysql/mcp_catalog.db';
SET mcp-port='${MCP_PORT}';
SET mcp-enabled='${enable}';
EOF
log_info "MCP variables configured:"
echo " mcp-mysql_hosts = ${MYSQL_HOST}"
echo " mcp-mysql_ports = ${MYSQL_PORT}"
echo " mcp-mysql_user = ${MYSQL_USER}"
echo " mcp-mysql_password = ${MYSQL_PASSWORD}"
echo " mcp-mysql_schema = ${MYSQL_DATABASE}"
echo " mcp-catalog_path = /var/lib/proxysql/mcp_catalog.db"
echo " mcp-port = ${MCP_PORT}"
echo " mcp-enabled = ${enable}"
}
# Load MCP variables to runtime
load_to_runtime() {
log_step "Loading MCP variables to RUNTIME..."
if exec_admin "LOAD MCP VARIABLES TO RUNTIME;" >/dev/null 2>&1; then
log_info "MCP variables loaded to RUNTIME"
else
log_error "Failed to load MCP variables to RUNTIME"
return 1
fi
}
# Show current MCP configuration
show_status() {
log_step "Current MCP configuration:"
echo ""
exec_admin "SHOW VARIABLES LIKE 'mcp-%';" | column -t
echo ""
}
# Test MCP server connectivity
test_mcp_server() {
log_step "Testing MCP server connectivity..."
# Wait a moment for server to start
sleep 2
# Test ping endpoint
local response
response=$(curl -k -s -X POST "https://${PROXYSQL_ADMIN_HOST}:${MCP_PORT}/config" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"ping","id":1}' 2>/dev/null || echo "")
if [ -n "$response" ]; then
log_info "MCP server is responding"
echo " Response: $response"
else
log_warn "MCP server not responding (may still be starting)"
fi
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-h|--host)
MYSQL_HOST="$2"
shift 2
;;
-P|--port)
MYSQL_PORT="$2"
shift 2
;;
-u|--user)
MYSQL_USER="$2"
shift 2
;;
-p|--password)
MYSQL_PASSWORD="$2"
shift 2
;;
-d|--database)
MYSQL_DATABASE="$2"
shift 2
;;
--mcp-port)
MCP_PORT="$2"
shift 2
;;
--enable)
MCP_ENABLED="true"
shift
;;
--disable)
MCP_ENABLED="false"
shift
;;
--status)
show_status
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
}
# Show usage
show_usage() {
cat <<EOF
Usage: $0 [options]
Options:
-h, --host HOST MySQL host (default: 127.0.0.1)
-P, --port PORT MySQL port (default: 3307)
-u, --user USER MySQL user (default: root)
-p, --password PASS MySQL password (default: test123)
-d, --database DB MySQL database (default: testdb)
--mcp-port PORT MCP server port (default: 6071)
--enable Enable MCP server
--disable Disable MCP server
--status Show current MCP configuration
Environment Variables:
PROXYSQL_ADMIN_HOST ProxySQL admin host (default: 127.0.0.1)
PROXYSQL_ADMIN_PORT ProxySQL admin port (default: 6032)
PROXYSQL_ADMIN_USER ProxySQL admin user (default: admin)
PROXYSQL_ADMIN_PASSWORD ProxySQL admin password (default: admin)
Examples:
# Configure with test MySQL on port 3307 and enable MCP
$0 --host 127.0.0.1 --port 3307 --enable
# Disable MCP server
$0 --disable
# Show current configuration
$0 --status
EOF
}
# Main
main() {
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
show_usage
exit 0
fi
parse_args "$@"
echo "======================================"
echo "ProxySQL MCP Configuration"
echo "======================================"
echo ""
# Check ProxySQL admin connection
if ! check_proxysql_admin; then
exit 1
fi
# Check MySQL connection (only when enabling)
if [ "${MCP_ENABLED}" = "true" ]; then
if ! check_mysql_connection; then
log_warn "MySQL connection check failed, but continuing with configuration..."
fi
fi
# Configure MCP
configure_mcp "${MCP_ENABLED}"
# Load to runtime
load_to_runtime
# Show status
echo ""
show_status
# Test server if enabling
if [ "${MCP_ENABLED}" = "true" ]; then
echo ""
test_mcp_server
fi
echo ""
log_info "Configuration complete!"
if [ "${MCP_ENABLED}" = "true" ]; then
echo ""
echo "MCP server is now enabled and accessible at:"
echo " https://${PROXYSQL_ADMIN_HOST}:${MCP_PORT}/config (config endpoint)"
echo " https://${PROXYSQL_ADMIN_HOST}:${MCP_PORT}/query (query endpoint)"
echo ""
echo "Run './test_mcp_tools.sh' to test MCP tools"
fi
}
main "$@"

@ -0,0 +1,401 @@
#!/bin/bash
#
# setup_test_db.sh - Create/start a test MySQL database with sample data
#
# Usage:
# ./setup_test_db.sh start # Start test MySQL container
# ./setup_test_db.sh stop # Stop and remove test MySQL container
# ./setup_test_db.sh status # Check status of test MySQL
# ./setup_test_db.sh connect # Connect to test MySQL
#
set -e
# Configuration
CONTAINER_NAME="proxysql_mcp_test_mysql"
MYSQL_PORT="3307"
MYSQL_ROOT_PASSWORD="test123"
MYSQL_DATABASE="testdb"
MYSQL_VERSION="8.4"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Check if Docker is available
check_docker() {
if ! command -v docker &> /dev/null; then
log_error "Docker is not installed or not in PATH"
log_info "Please install Docker or use an existing MySQL server"
exit 1
fi
}
# Start test MySQL container
start_mysql() {
log_info "Starting test MySQL container..."
# Check if container already exists
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_warn "Container '${CONTAINER_NAME}' already exists"
read -p "Remove and recreate? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker rm -f "${CONTAINER_NAME}" > /dev/null 2>&1 || true
else
log_info "Starting existing container..."
docker start "${CONTAINER_NAME}"
return 0
fi
fi
# Create and start container
docker run -d \
--name "${CONTAINER_NAME}" \
-p "${MYSQL_PORT}:3306" \
-e MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}" \
-e MYSQL_DATABASE="${MYSQL_DATABASE}" \
-v "${SCRIPT_DIR}/init_testdb.sql:/docker-entrypoint-initdb.d/01-init.sql:ro" \
mysql:${MYSQL_VERSION} \
--default-authentication-plugin=mysql_native_password
log_info "Waiting for MySQL to be ready..."
for i in {1..30}; do
if docker exec "${CONTAINER_NAME}" mysqladmin ping -h localhost --silent 2>/dev/null; then
log_info "MySQL is ready!"
break
fi
sleep 1
done
# Run initialization script if not via volume
if [ ! -f "${SCRIPT_DIR}/init_testdb.sql" ]; then
log_info "Creating test schema and data..."
sleep 5 # Give MySQL extra time to fully start
docker exec -i "${CONTAINER_NAME}" mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}" <<'EOSQL'
CREATE DATABASE IF NOT EXISTS testdb;
USE testdb;
CREATE TABLE IF NOT EXISTS customers (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (email)
);
CREATE TABLE IF NOT EXISTS orders (
id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_date DATE,
total DECIMAL(10,2),
status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id),
INDEX idx_customer (customer_id),
INDEX idx_status (status)
);
CREATE TABLE IF NOT EXISTS products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200),
category VARCHAR(50),
price DECIMAL(10,2),
stock INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_category (category)
);
CREATE TABLE IF NOT EXISTS order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT DEFAULT 1,
price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Insert sample customers
INSERT INTO customers (name, email) VALUES
('Alice Johnson', 'alice@example.com'),
('Bob Smith', 'bob@example.com'),
('Charlie Brown', 'charlie@example.com'),
('Diana Prince', 'diana@example.com'),
('Eve Davis', 'eve@example.com');
-- Insert sample products
INSERT INTO products (name, category, price, stock) VALUES
('Laptop', 'Electronics', 999.99, 50),
('Mouse', 'Electronics', 29.99, 200),
('Keyboard', 'Electronics', 79.99, 150),
('Desk Chair', 'Furniture', 199.99, 75),
('Coffee Mug', 'Kitchen', 12.99, 500);
-- Insert sample orders
INSERT INTO orders (customer_id, order_date, total, status) VALUES
(1, '2024-01-15', 1029.98, 'completed'),
(2, '2024-01-16', 79.99, 'shipped'),
(1, '2024-01-17', 212.98, 'pending'),
(3, '2024-01-18', 199.99, 'completed'),
(4, '2024-01-19', 1099.98, 'shipped');
-- Insert sample order items
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES
(1, 1, 1, 999.99),
(1, 2, 1, 29.99),
(2, 3, 1, 79.99),
(3, 1, 1, 999.99),
(3, 3, 1, 79.99),
(3, 5, 3, 38.97),
(4, 4, 1, 199.99),
(5, 1, 1, 999.99),
(5, 4, 1, 199.99);
-- Create a view
CREATE OR REPLACE VIEW customer_orders AS
SELECT
c.id AS customer_id,
c.name AS customer_name,
COUNT(o.id) AS order_count,
SUM(o.total) AS total_spent
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name;
-- Create a stored procedure
DELIMITER //
CREATE PROCEDURE get_customer_stats(IN customer_id INT)
BEGIN
SELECT
c.name,
COUNT(o.id) AS order_count,
COALESCE(SUM(o.total), 0) AS total_spent
FROM customers c
LEFT JOIN orders o ON c.id = o.customer_id
WHERE c.id = customer_id;
END //
DELIMITER ;
EOSQL
fi
log_info "Test MySQL database is ready!"
log_info " Host: 127.0.0.1"
log_info " Port: ${MYSQL_PORT}"
log_info " User: root"
log_info " Password: ${MYSQL_ROOT_PASSWORD}"
log_info " Database: ${MYSQL_DATABASE}"
}
# Stop and remove test MySQL container
stop_mysql() {
log_info "Stopping test MySQL container..."
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
docker stop "${CONTAINER_NAME}"
log_info "Container stopped"
else
log_warn "Container '${CONTAINER_NAME}' is not running"
fi
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
read -p "Remove container '${CONTAINER_NAME}'? (y/N): " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
docker rm "${CONTAINER_NAME}"
log_info "Container removed"
fi
fi
}
# Check status of test MySQL
status_mysql() {
log_info "Checking test MySQL status..."
if docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo -e "${GREEN}${NC} Container '${CONTAINER_NAME}' is ${GREEN}running${NC}"
# Show connection details
echo ""
echo "Connection Details:"
echo " Host: 127.0.0.1"
echo " Port: ${MYSQL_PORT}"
echo " User: root"
echo " Password: ${MYSQL_ROOT_PASSWORD}"
echo " Database: ${MYSQL_DATABASE}"
# Test connection
if docker exec "${CONTAINER_NAME}" mysqladmin ping -h localhost --silent 2>/dev/null; then
echo -e " Status: ${GREEN}Accepting connections${NC}"
else
echo -e " Status: ${RED}Not responding${NC}"
fi
# Show database info
echo ""
echo "Database Info:"
docker exec "${CONTAINER_NAME}" mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" -e "
SELECT
table_name AS 'Table',
table_rows AS 'Rows',
ROUND((data_length + index_length) / 1024, 2) AS 'Size (KB)'
FROM information_schema.tables
WHERE table_schema = '${MYSQL_DATABASE}'
ORDER BY table_name;
" 2>/dev/null | column -t
elif docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
echo -e "${YELLOW}${NC} Container '${CONTAINER_NAME}' exists but is ${YELLOW}stopped${NC}"
echo "Start with: $0 start"
else
echo -e "${RED}${NC} Container '${CONTAINER_NAME}' does not exist"
echo "Create with: $0 start"
fi
}
# Connect to test MySQL
connect_mysql() {
log_info "Connecting to test MySQL..."
if ! docker ps --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
log_error "Container '${CONTAINER_NAME}' is not running"
exit 1
fi
docker exec -it "${CONTAINER_NAME}" mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" "${MYSQL_DATABASE}"
}
# Create initialization SQL file
create_init_sql() {
cat > "${SCRIPT_DIR}/init_testdb.sql" <<'EOSQL'
-- Test Database Schema for MCP Testing
CREATE DATABASE IF NOT EXISTS testdb;
USE testdb;
CREATE TABLE IF NOT EXISTS customers (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100),
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_email (email)
);
CREATE TABLE IF NOT EXISTS orders (
id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
order_date DATE,
total DECIMAL(10,2),
status VARCHAR(20),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (customer_id) REFERENCES customers(id),
INDEX idx_customer (customer_id),
INDEX idx_status (status)
);
CREATE TABLE IF NOT EXISTS products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(200),
category VARCHAR(50),
price DECIMAL(10,2),
stock INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_category (category)
);
CREATE TABLE IF NOT EXISTS order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
product_id INT NOT NULL,
quantity INT DEFAULT 1,
price DECIMAL(10,2),
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (product_id) REFERENCES products(id)
);
-- Insert sample customers
INSERT INTO customers (name, email) VALUES
('Alice Johnson', 'alice@example.com'),
('Bob Smith', 'bob@example.com'),
('Charlie Brown', 'charlie@example.com'),
('Diana Prince', 'diana@example.com'),
('Eve Davis', 'eve@example.com');
-- Insert sample products
INSERT INTO products (name, category, price, stock) VALUES
('Laptop', 'Electronics', 999.99, 50),
('Mouse', 'Electronics', 29.99, 200),
('Keyboard', 'Electronics', 79.99, 150),
('Desk Chair', 'Furniture', 199.99, 75),
('Coffee Mug', 'Kitchen', 12.99, 500);
-- Insert sample orders
INSERT INTO orders (customer_id, order_date, total, status) VALUES
(1, '2024-01-15', 1029.98, 'completed'),
(2, '2024-01-16', 79.99, 'shipped'),
(1, '2024-01-17', 212.98, 'pending'),
(3, '2024-01-18', 199.99, 'completed'),
(4, '2024-01-19', 1099.98, 'shipped');
-- Insert sample order items
INSERT INTO order_items (order_id, product_id, quantity, price) VALUES
(1, 1, 1, 999.99),
(1, 2, 1, 29.99),
(2, 3, 1, 79.99),
(3, 1, 1, 999.99),
(3, 3, 1, 79.99),
(3, 5, 3, 38.97),
(4, 4, 1, 199.99),
(5, 1, 1, 999.99),
(5, 4, 1, 199.99);
EOSQL
log_info "Created ${SCRIPT_DIR}/init_testdb.sql"
}
# Main script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
case "${1:-start}" in
start)
check_docker
start_mysql
;;
stop)
check_docker
stop_mysql
;;
status)
check_docker
status_mysql
;;
connect)
check_docker
connect_mysql
;;
create-sql)
create_init_sql
;;
*)
echo "Usage: $0 {start|stop|status|connect|create-sql}"
echo ""
echo "Commands:"
echo " start - Start test MySQL container"
echo " stop - Stop test MySQL container"
echo " status - Check status of test MySQL"
echo " connect - Connect to test MySQL shell"
echo " create-sql - Create init_testdb.sql file"
exit 1
;;
esac

@ -0,0 +1,286 @@
#!/bin/bash
#
# stress_test.sh - Concurrent connection stress test for MCP tools
#
# Usage:
# ./stress_test.sh [options]
#
# Options:
# -n, --num-requests N Number of concurrent requests (default: 10)
# -t, --tool NAME Tool to test (default: sample_rows)
# -d, --delay SEC Delay between requests in ms (default: 0)
# -v, --verbose Show individual responses
# -h, --help Show help
#
set -e
# Configuration
MCP_HOST="${MCP_HOST:-127.0.0.1}"
MCP_PORT="${MCP_PORT:-6071}"
MCP_URL="https://${MCP_HOST}:${MCP_PORT}/query"
# Test options
NUM_REQUESTS="${NUM_REQUESTS:-10}"
TOOL_NAME="${TOOL_NAME:-sample_rows}"
DELAY_MS="${DELAY_MS:-0}"
VERBOSE=false
# Statistics
TOTAL_REQUESTS=0
SUCCESSFUL_REQUESTS=0
FAILED_REQUESTS=0
TOTAL_TIME=0
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
# Execute MCP request
mcp_request() {
local id="$1"
local payload
payload=$(cat <<EOF
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "${TOOL_NAME}",
"arguments": {"schema": "testdb", "table": "customers", "limit": 2}
},
"id": ${id}
}
EOF
)
local start_time
start_time=$(date +%s%N)
local response
response=$(curl -k -s -w "\n%{http_code}" -X POST "${MCP_URL}" \
-H "Content-Type: application/json" \
-d "${payload}" 2>/dev/null)
local end_time
end_time=$(date +%s%N)
local duration
duration=$(( (end_time - start_time) / 1000000 )) # Convert to milliseconds
local body
body=$(echo "$response" | head -n -1)
local code
code=$(echo "$response" | tail -n 1)
echo "${body}|${duration}|${code}"
}
# Run concurrent requests
run_stress_test() {
log_info "Running stress test with ${NUM_REQUESTS} concurrent requests..."
log_info "Tool: ${TOOL_NAME}"
log_info "Target: ${MCP_URL}"
echo ""
# Create temp directory for results
local tmpdir
tmpdir=$(mktemp -d)
trap "rm -rf ${tmpdir}" EXIT
local pids=()
# Launch requests in background
for i in $(seq 1 "${NUM_REQUESTS}"); do
(
if [ -n "${DELAY_MS}" ] && [ "${DELAY_MS}" -gt 0 ]; then
sleep $(( (RANDOM % ${DELAY_MS}) / 1000 )).$(( (RANDOM % 1000) ))
fi
local result
result=$(mcp_request "${i}")
local body
local duration
local code
body=$(echo "${result}" | cut -d'|' -f1)
duration=$(echo "${result}" | cut -d'|' -f2)
code=$(echo "${result}" | cut -d'|' -f3)
echo "${body}" > "${tmpdir}/response_${i}.json"
echo "${duration}" > "${tmpdir}/duration_${i}.txt"
echo "${code}" > "${tmpdir}/code_${i}.txt"
) &
pids+=($!)
done
# Wait for all requests to complete
local start_time
start_time=$(date +%s)
for pid in "${pids[@]}"; do
wait ${pid} || true
done
local end_time
end_time=$(date +%s)
local total_wall_time
total_wall_time=$((end_time - start_time))
# Collect results
for i in $(seq 1 "${NUM_REQUESTS}"); do
TOTAL_REQUESTS=$((TOTAL_REQUESTS + 1))
local code
code=$(cat "${tmpdir}/code_${i}.txt" 2>/dev/null || echo "000")
if [ "${code}" = "200" ]; then
SUCCESSFUL_REQUESTS=$((SUCCESSFUL_REQUESTS + 1))
else
FAILED_REQUESTS=$((FAILED_REQUESTS + 1))
fi
local duration
duration=$(cat "${tmpdir}/duration_${i}.txt" 2>/dev/null || echo "0")
TOTAL_TIME=$((TOTAL_TIME + duration))
if [ "${VERBOSE}" = "true" ]; then
local body
body=$(cat "${tmpdir}/response_${i}.json" 2>/dev/null || echo "{}")
echo "Request ${i}: [${code}] ${duration}ms"
if [ "${code}" != "200" ]; then
echo " Response: ${body}"
fi
fi
done
# Calculate statistics
local avg_time
if [ ${TOTAL_REQUESTS} -gt 0 ]; then
avg_time=$((TOTAL_TIME / TOTAL_REQUESTS))
else
avg_time=0
fi
local requests_per_second
if [ ${total_wall_time} -gt 0 ]; then
requests_per_second=$(awk "BEGIN {printf \"%.2f\", ${NUM_REQUESTS} / ${total_wall_time}}")
else
requests_per_second="N/A"
fi
# Print summary
echo ""
echo "======================================"
echo "Stress Test Results"
echo "======================================"
echo "Concurrent requests: ${NUM_REQUESTS}"
echo "Total wall time: ${total_wall_time}s"
echo ""
echo "Total requests: ${TOTAL_REQUESTS}"
echo -e "Successful: ${GREEN}${SUCCESSFUL_REQUESTS}${NC}"
echo -e "Failed: ${RED}${FAILED_REQUESTS}${NC}"
echo ""
echo "Average response time: ${avg_time}ms"
echo "Requests/second: ${requests_per_second}"
echo ""
# Calculate success rate
if [ ${TOTAL_REQUESTS} -gt 0 ]; then
local success_rate
success_rate=$(awk "BEGIN {printf \"%.1f\", (${SUCCESSFUL_REQUESTS} * 100) / ${TOTAL_REQUESTS}}")
echo "Success rate: ${success_rate}%"
echo ""
if [ ${FAILED_REQUESTS} -eq 0 ]; then
log_info "All requests succeeded!"
return 0
else
log_error "Some requests failed!"
return 1
fi
else
log_error "No requests were completed!"
return 1
fi
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-n|--num-requests)
NUM_REQUESTS="$2"
shift 2
;;
-t|--tool)
TOOL_NAME="$2"
shift 2
;;
-d|--delay)
DELAY_MS="$2"
shift 2
;;
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
cat <<EOF
Usage: $0 [options]
Run concurrent stress test against MCP tools.
Options:
-n, --num-requests N Number of concurrent requests (default: 10)
-t, --tool NAME Tool to test (default: sample_rows)
-d, --delay SEC Delay between requests in ms (default: 0)
-v, --verbose Show individual responses
-h, --help Show this help
Environment Variables:
MCP_HOST MCP server host (default: 127.0.0.1)
MCP_PORT MCP server port (default: 6071)
Examples:
# Run 10 concurrent requests
$0 -n 10
# Run 50 concurrent requests with 100ms delay
$0 -n 50 -d 100
# Test different tool with verbose output
$0 -t list_tables -v
EOF
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
}
# Main
parse_args "$@"
run_stress_test

@ -0,0 +1,438 @@
#!/bin/bash
#
# test_catalog.sh - Test catalog (LLM memory) functionality
#
# Usage:
# ./test_catalog.sh [options]
#
# Options:
# -v, --verbose Show verbose output
# -h, --help Show help
#
set -e
# Configuration
MCP_HOST="${MCP_HOST:-127.0.0.1}"
MCP_PORT="${MCP_PORT:-6071}"
MCP_URL="https://${MCP_HOST}:${MCP_PORT}/query"
# Test options
VERBOSE=false
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() {
echo -e "${GREEN}[INFO]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_test() {
echo -e "${BLUE}[TEST]${NC} $1"
}
# Execute MCP request
mcp_request() {
local payload="$1"
local response
response=$(curl -k -s -X POST "${MCP_URL}" \
-H "Content-Type: application/json" \
-d "${payload}" 2>/dev/null)
echo "${response}"
}
# Test catalog operations
test_catalog() {
local test_id="$1"
local operation="$2"
local payload="$3"
local expected="$4"
log_test "${test_id}: ${operation}"
local response
response=$(mcp_request "${payload}")
if [ "${VERBOSE}" = "true" ]; then
echo "Payload: ${payload}"
echo "Response: ${response}"
fi
if echo "${response}" | grep -q "${expected}"; then
log_info "${test_id}"
return 0
else
log_error "${test_id}"
if [ "${VERBOSE}" = "true" ]; then
echo "Expected to find: ${expected}"
fi
return 1
fi
}
# Main test flow
run_catalog_tests() {
echo "======================================"
echo "Catalog (LLM Memory) Test Suite"
echo "======================================"
echo ""
echo "Testing catalog operations for LLM memory persistence"
echo ""
local passed=0
local failed=0
# Test 1: Upsert a table schema entry
local payload1
payload1='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_upsert",
"arguments": {
"kind": "table",
"key": "testdb.customers",
"document": "{\"table\": \"customers\", \"columns\": [{\"name\": \"id\", \"type\": \"INT\"}, {\"name\": \"name\", \"type\": \"VARCHAR\"}], \"row_count\": 5}",
"tags": "schema,testdb",
"links": "testdb.orders:customer_id"
}
},
"id": 1
}'
if test_catalog "CAT001" "Upsert table schema" "${payload1}" '"success"[[:space:]]*:[[:space:]]*true'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 2: Upsert a domain knowledge entry
local payload2
payload2='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_upsert",
"arguments": {
"kind": "domain",
"key": "customer_management",
"document": "{\"description\": \"Customer management domain\", \"entities\": [\"customers\", \"orders\", \"products\"], \"relationships\": [\"customer has many orders\", \"order belongs to customer\"]}",
"tags": "domain,business",
"links": ""
}
},
"id": 2
}'
if test_catalog "CAT002" "Upsert domain knowledge" "${payload2}" '"success"[[:space:]]*:[[:space:]]*true'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 3: Get the upserted table entry
local payload3
payload3='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_get",
"arguments": {
"kind": "table",
"key": "testdb.customers"
}
},
"id": 3
}'
if test_catalog "CAT003" "Get table entry" "${payload3}" '"columns"'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 4: Get the upserted domain entry
local payload4
payload4='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_get",
"arguments": {
"kind": "domain",
"key": "customer_management"
}
},
"id": 4
}'
if test_catalog "CAT004" "Get domain entry" "${payload4}" '"entities"'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 5: Search for table entries
local payload5
payload5='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_search",
"arguments": {
"query": "customers",
"limit": 10
}
},
"id": 5
}'
if test_catalog "CAT005" "Search catalog" "${payload5}" '"results"'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 6: List entries by kind
local payload6
payload6='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_list",
"arguments": {
"kind": "table",
"limit": 10
}
},
"id": 6
}'
if test_catalog "CAT006" "List by kind" "${payload6}" '"results"'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 7: Update existing entry
local payload7
payload7='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_upsert",
"arguments": {
"kind": "table",
"key": "testdb.customers",
"document": "{\"table\": \"customers\", \"columns\": [{\"name\": \"id\", \"type\": \"INT\"}, {\"name\": \"name\", \"type\": \"VARCHAR\"}, {\"name\": \"email\", \"type\": \"VARCHAR\"}], \"row_count\": 5, \"updated\": true}",
"tags": "schema,testdb,updated",
"links": "testdb.orders:customer_id"
}
},
"id": 7
}'
if test_catalog "CAT007" "Update existing entry" "${payload7}" '"success"[[:space:]]*:[[:space:]]*true'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 8: Verify update
local payload8
payload8='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_get",
"arguments": {
"kind": "table",
"key": "testdb.customers"
}
},
"id": 8
}'
if test_catalog "CAT008" "Verify update" "${payload8}" '"updated"[[:space:]]*:[[:space:]]*true'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 9: Test FTS search with special characters
local payload9
payload9='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_search",
"arguments": {
"query": "customer*",
"limit": 10
}
},
"id": 9
}'
if test_catalog "CAT009" "FTS search with wildcard" "${payload9}" '"results"'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 10: Delete entry
local payload10
payload10='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_delete",
"arguments": {
"kind": "table",
"key": "testdb.customers"
}
},
"id": 10
}'
if test_catalog "CAT010" "Delete entry" "${payload10}" '"success"[[:space:]]*:[[:space:]]*true'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Test 11: Verify deletion
local payload11
payload11='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_get",
"arguments": {
"kind": "table",
"key": "testdb.customers"
}
},
"id": 11
}'
# This should return an error since we deleted it
log_test "CAT011: Verify deletion (should fail)"
local response11
response11=$(mcp_request "${payload11}")
if echo "${response11}" | grep -q '"error"'; then
log_info "✓ CAT011"
passed=$((passed + 1))
else
log_error "✗ CAT011"
failed=$((failed + 1))
fi
# Test 12: Cleanup - delete domain entry
local payload12
payload12='{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_delete",
"arguments": {
"kind": "domain",
"key": "customer_management"
}
},
"id": 12
}'
if test_catalog "CAT012" "Cleanup domain entry" "${payload12}" '"success"[[:space:]]*:[[:space:]]*true'; then
passed=$((passed + 1))
else
failed=$((failed + 1))
fi
# Print summary
echo ""
echo "======================================"
echo "Test Summary"
echo "======================================"
echo "Total tests: $((passed + failed))"
echo -e "Passed: ${GREEN}${passed}${NC}"
echo -e "Failed: ${RED}${failed}${NC}"
echo ""
if [ ${failed} -gt 0 ]; then
log_error "Some tests failed!"
return 1
else
log_info "All catalog tests passed!"
return 0
fi
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=true
shift
;;
-h|--help)
cat <<EOF
Usage: $0 [options]
Test catalog (LLM memory) functionality.
Options:
-v, --verbose Show verbose output including request/response
-h, --help Show this help
Environment Variables:
MCP_HOST MCP server host (default: 127.0.0.1)
MCP_PORT MCP server port (default: 6071)
Tests:
CAT001: Upsert table schema entry
CAT002: Upsert domain knowledge entry
CAT003: Get table entry
CAT004: Get domain entry
CAT005: Search catalog
CAT006: List entries by kind
CAT007: Update existing entry
CAT008: Verify update
CAT009: FTS search with wildcard
CAT010: Delete entry
CAT011: Verify deletion
CAT012: Cleanup domain entry
Examples:
# Run all catalog tests
$0
# Run with verbose output
$0 -v
EOF
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
}
# Main
parse_args "$@"
run_catalog_tests

@ -0,0 +1,598 @@
#!/bin/bash
#
# test_mcp_tools.sh - Test all MCP tools via HTTPS/JSON-RPC
#
# Usage:
# ./test_mcp_tools.sh [options]
#
# Options:
# -v, --verbose Show verbose output
# -q, --quiet Suppress progress messages
# --tool NAME Test only specific tool
# --skip-tool NAME Skip specific tool
# -h, --help Show help
#
set -e
# Configuration
MCP_HOST="${MCP_HOST:-127.0.0.1}"
MCP_PORT="${MCP_PORT:-6071}"
MCP_CONFIG_URL="https://${MCP_HOST}:${MCP_PORT}/config"
MCP_QUERY_URL="https://${MCP_HOST}:${MCP_PORT}/query"
# Test options
VERBOSE=false
QUIET=false
TEST_TOOL=""
SKIP_TOOLS=()
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Statistics
TOTAL_TESTS=0
PASSED_TESTS=0
FAILED_TESTS=0
SKIPPED_TESTS=0
log_info() {
if [ "${QUIET}" = "false" ]; then
echo -e "${GREEN}[INFO]${NC} $1"
fi
}
log_warn() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
log_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
log_verbose() {
if [ "${VERBOSE}" = "true" ]; then
echo -e "${BLUE}[DEBUG]${NC} $1"
fi
}
log_test() {
if [ "${QUIET}" = "false" ]; then
echo -e "${BLUE}[TEST]${NC} $1"
fi
}
# Execute MCP request
mcp_request() {
local endpoint="$1"
local payload="$2"
local response
response=$(curl -k -s -w "\n%{http_code}" -X POST "${endpoint}" \
-H "Content-Type: application/json" \
-d "${payload}" 2>/dev/null)
local body=$(echo "$response" | head -n -1)
local code=$(echo "$response" | tail -n 1)
if [ "${VERBOSE}" = "true" ]; then
echo "Request: ${payload}"
echo "Response (${code}): ${body}"
fi
echo "${body}"
return 0
}
# Check if MCP server is accessible
check_mcp_server() {
log_test "Checking MCP server accessibility..."
local response
response=$(mcp_request "${MCP_CONFIG_URL}" '{"jsonrpc":"2.0","method":"ping","id":1}')
if echo "${response}" | grep -q "result"; then
log_info "MCP server is accessible"
return 0
else
log_error "MCP server is not accessible"
log_error "Response: ${response}"
return 1
fi
}
# Assert that JSON contains expected value
assert_json_contains() {
local response="$1"
local field="$2"
local expected="$3"
if echo "${response}" | grep -q "\"${field}\"[[:space:]]*:[[:space:]]*${expected}"; then
return 0
fi
# Try with jq if available
if command -v jq &> /dev/null; then
local actual
actual=$(echo "${response}" | jq -r "${field}" 2>/dev/null)
if [ "${actual}" = "${expected}" ]; then
return 0
fi
fi
return 1
}
# Assert that JSON array contains expected value
assert_json_array_contains() {
local response="$1"
local field="$2"
local expected="$3"
if echo "${response}" | grep -q "${expected}"; then
return 0
fi
return 1
}
# Test a tool
test_tool() {
local tool_name="$1"
local arguments="$2"
local expected_field="$3"
local expected_value="$4"
TOTAL_TESTS=$((TOTAL_TESTS + 1))
log_test "Testing tool: ${tool_name}"
local payload
payload=$(cat <<EOF
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "${tool_name}",
"arguments": ${arguments}
},
"id": ${TOTAL_TESTS}
}
EOF
)
local response
response=$(mcp_request "${MCP_QUERY_URL}" "${payload}")
# Check for error response
if echo "${response}" | grep -q '"error"'; then
log_error "Tool ${tool_name} returned error"
if [ "${VERBOSE}" = "true" ]; then
echo "Response: ${response}"
fi
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
# Check expected value if provided
if [ -n "${expected_field}" ] && [ -n "${expected_value}" ]; then
if assert_json_contains "${response}" "${expected_field}" "${expected_value}"; then
log_info "${tool_name}"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
else
log_error "${tool_name} - assertion failed"
if [ "${VERBOSE}" = "true" ]; then
echo "Expected: ${expected_field} = ${expected_value}"
echo "Response: ${response}"
fi
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
else
log_info "${tool_name}"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
fi
}
# Test list_schemas
test_list_schemas() {
test_tool "list_schemas" "{}"
}
# Test list_tables
test_list_tables() {
test_tool "list_tables" '{"schema": "testdb"}'
}
# Test describe_table
test_describe_table() {
test_tool "describe_table" '{"schema": "testdb", "table": "customers"}'
}
# Test get_constraints
test_get_constraints() {
test_tool "get_constraints" '{"schema": "testdb"}'
}
# Test describe_view
test_describe_view() {
test_tool "describe_view" '{"schema": "testdb", "view": "customer_orders"}'
}
# Test table_profile
test_table_profile() {
test_tool "table_profile" '{"schema": "testdb", "table": "customers", "mode": "quick"}'
}
# Test column_profile
test_column_profile() {
test_tool "column_profile" '{"schema": "testdb", "table": "customers", "column": "name"}'
}
# Test sample_rows
test_sample_rows() {
test_tool "sample_rows" '{"schema": "testdb", "table": "customers", "limit": 3}'
}
# Test sample_distinct
test_sample_distinct() {
test_tool "sample_distinct" '{"schema": "testdb", "table": "customers", "column": "name", "limit": 5}'
}
# Test run_sql_readonly
test_run_sql_readonly() {
test_tool "run_sql_readonly" '{"sql": "SELECT * FROM customers LIMIT 2"}'
}
# Test explain_sql
test_explain_sql() {
test_tool "explain_sql" '{"sql": "SELECT * FROM customers WHERE id = 1"}'
}
# Test catalog_upsert
test_catalog_upsert() {
local payload=$(cat <<EOF
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_upsert",
"arguments": {
"kind": "test",
"key": "test_key",
"document": "{\"test\": \"value\"}",
"tags": "test,mcp"
}
},
"id": 999
}
EOF
)
TOTAL_TESTS=$((TOTAL_TESTS + 1))
log_test "Testing tool: catalog_upsert"
local response
response=$(mcp_request "${MCP_QUERY_URL}" "${payload}")
if echo "${response}" | grep -q '"success"[[:space:]]*:[[:space:]]*true'; then
log_info "✓ catalog_upsert"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
else
log_error "✗ catalog_upsert"
if [ "${VERBOSE}" = "true" ]; then
echo "Response: ${response}"
fi
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
}
# Test catalog_get
test_catalog_get() {
local payload=$(cat <<EOF
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_get",
"arguments": {
"kind": "test",
"key": "test_key"
}
},
"id": 999
}
EOF
)
TOTAL_TESTS=$((TOTAL_TESTS + 1))
log_test "Testing tool: catalog_get"
local response
response=$(mcp_request "${MCP_QUERY_URL}" "${payload}")
if echo "${response}" | grep -q '"success"[[:space:]]*:[[:space:]]*true'; then
log_info "✓ catalog_get"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
else
log_error "✗ catalog_get"
if [ "${VERBOSE}" = "true" ]; then
echo "Response: ${response}"
fi
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
}
# Test catalog_search
test_catalog_search() {
local payload=$(cat <<EOF
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_search",
"arguments": {
"query": "test",
"limit": 10
}
},
"id": 999
}
EOF
)
TOTAL_TESTS=$((TOTAL_TESTS + 1))
log_test "Testing tool: catalog_search"
local response
response=$(mcp_request "${MCP_QUERY_URL}" "${payload}")
if echo "${response}" | grep -q '"success"'; then
log_info "✓ catalog_search"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
else
log_error "✗ catalog_search"
if [ "${VERBOSE}" = "true" ]; then
echo "Response: ${response}"
fi
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
}
# Test catalog_delete
test_catalog_delete() {
local payload=$(cat <<EOF
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "catalog_delete",
"arguments": {
"kind": "test",
"key": "test_key"
}
},
"id": 999
}
EOF
)
TOTAL_TESTS=$((TOTAL_TESTS + 1))
log_test "Testing tool: catalog_delete"
local response
response=$(mcp_request "${MCP_QUERY_URL}" "${payload}")
if echo "${response}" | grep -q '"success"[[:space:]]*:[[:space:]]*true'; then
log_info "✓ catalog_delete"
PASSED_TESTS=$((PASSED_TESTS + 1))
return 0
else
log_error "✗ catalog_delete"
if [ "${VERBOSE}" = "true" ]; then
echo "Response: ${response}"
fi
FAILED_TESTS=$((FAILED_TESTS + 1))
return 1
fi
}
# Parse command line arguments
parse_args() {
while [[ $# -gt 0 ]]; do
case $1 in
-v|--verbose)
VERBOSE=true
shift
;;
-q|--quiet)
QUIET=true
shift
;;
--tool)
TEST_TOOL="$2"
shift 2
;;
--skip-tool)
SKIP_TOOLS+=("$2")
shift 2
;;
-h|--help)
cat <<EOF
Usage: $0 [options]
Test MCP tools via HTTPS/JSON-RPC.
Options:
-v, --verbose Show verbose output including request/response
-q, --quiet Suppress progress messages
--tool NAME Test only specific tool
--skip-tool NAME Skip specific tool
-h, --help Show this help
Environment Variables:
MCP_HOST MCP server host (default: 127.0.0.1)
MCP_PORT MCP server port (default: 6071)
Available Tools:
- list_schemas
- list_tables
- describe_table
- get_constraints
- describe_view
- table_profile
- column_profile
- sample_rows
- sample_distinct
- run_sql_readonly
- explain_sql
- catalog_upsert
- catalog_get
- catalog_search
- catalog_delete
Examples:
# Test all tools
$0
# Test only list_schemas
$0 --tool list_schemas
# Test with verbose output
$0 -v
# Skip catalog tests
$0 --skip-tool catalog_upsert --skip-tool catalog_get
EOF
exit 0
;;
*)
echo "Unknown option: $1"
echo "Use --help for usage information"
exit 1
;;
esac
done
}
# Check if tool should be skipped
should_skip_tool() {
local tool="$1"
for skip in "${SKIP_TOOLS[@]}"; do
if [ "${tool}" = "${skip}" ]; then
return 0
fi
done
return 1
}
# Run all tests
run_all_tests() {
echo "======================================"
echo "MCP Tools Test Suite"
echo "======================================"
echo ""
echo "MCP Server: ${MCP_CONFIG_URL}"
echo ""
# Check MCP server
if ! check_mcp_server; then
log_error "MCP server is not accessible. Please run:"
echo " ./configure_mcp.sh --enable"
exit 1
fi
echo ""
# Determine which tests to run
local tests_to_run=()
if [ -n "${TEST_TOOL}" ]; then
# Run only specific tool
tests_to_run=("${TEST_TOOL}")
else
# Run all tools
tests_to_run=(
"list_schemas"
"list_tables"
"describe_table"
"get_constraints"
"describe_view"
"table_profile"
"column_profile"
"sample_rows"
"sample_distinct"
"run_sql_readonly"
"explain_sql"
"catalog_upsert"
"catalog_get"
"catalog_search"
"catalog_delete"
)
fi
# Run tests
for tool in "${tests_to_run[@]}"; do
if should_skip_tool "${tool}"; then
SKIPPED_TESTS=$((SKIPPED_TESTS + 1))
log_info "- ${tool} (skipped)"
continue
fi
case "${tool}" in
list_schemas) test_list_schemas ;;
list_tables) test_list_tables ;;
describe_table) test_describe_table ;;
get_constraints) test_get_constraints ;;
describe_view) test_describe_view ;;
table_profile) test_table_profile ;;
column_profile) test_column_profile ;;
sample_rows) test_sample_rows ;;
sample_distinct) test_sample_distinct ;;
run_sql_readonly) test_run_sql_readonly ;;
explain_sql) test_explain_sql ;;
catalog_upsert) test_catalog_upsert ;;
catalog_get) test_catalog_get ;;
catalog_search) test_catalog_search ;;
catalog_delete) test_catalog_delete ;;
*)
log_warn "Unknown tool: ${tool}"
;;
esac
done
# Print summary
echo ""
echo "======================================"
echo "Test Summary"
echo "======================================"
echo "Total tests: ${TOTAL_TESTS}"
echo -e "Passed: ${GREEN}${PASSED_TESTS}${NC}"
echo -e "Failed: ${RED}${FAILED_TESTS}${NC}"
echo "Skipped: ${SKIPPED_TESTS}"
echo ""
if [ ${FAILED_TESTS} -gt 0 ]; then
log_error "Some tests failed!"
exit 1
else
log_info "All tests passed!"
exit 0
fi
}
# Main
parse_args "$@"
run_all_tests
Loading…
Cancel
Save