diff --git a/lib/Admin_Handler.cpp b/lib/Admin_Handler.cpp index 98443b945..d36bb3566 100644 --- a/lib/Admin_Handler.cpp +++ b/lib/Admin_Handler.cpp @@ -2542,6 +2542,7 @@ bool admin_handler_command_load_or_save(char *query_no_space, unsigned int query (!strncasecmp("LOAD MCP PROFILES ", query_no_space, 18)))) { ProxySQL_Admin *SPA = (ProxySQL_Admin *)pa; + proxy_info("Received %s command\n", query_no_space); const auto load_target_auth_map_from_runtime = [&]() -> bool { char* error = NULL; @@ -2656,6 +2657,9 @@ bool admin_handler_command_load_or_save(char *query_no_space, unsigned int query SPA->send_error_msg_to_client(sess, (char *)"Failed to refresh MCP runtime profile map"); return false; } + // Ensure MCP server/query handler reflects the newly loaded runtime profiles. + // This recovers cases where MCP server was started before profiles were available. + SPA->load_mcp_server(); SPA->send_ok_msg_to_client(sess, NULL, 0, query_no_space); return false; } diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index 92b50b8cb..fdbdbbec5 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -3445,6 +3445,13 @@ void ProxySQL_Admin::load_mcp_server() { needs_restart = true; restart_reason += "SSL mode"; } + if (GloMCPH->query_tool_handler == NULL) { + needs_restart = true; + if (!restart_reason.empty()) { + restart_reason += " "; + } + restart_reason += "tool handler initialization"; + } if (needs_restart) { proxy_info("MCP: Configuration changed (%s), restarting server...\n", restart_reason.c_str()); diff --git a/test/tap/groups/ai/pre-proxysql.bash b/test/tap/groups/ai/pre-proxysql.bash index 3fc1501c5..bab19fd57 100755 --- a/test/tap/groups/ai/pre-proxysql.bash +++ b/test/tap/groups/ai/pre-proxysql.bash @@ -43,15 +43,46 @@ exec_admin() { mysql ${SSLOPT} -h"${ADMIN_HOST}" -P"${ADMIN_PORT}" -u"${ADMIN_USER}" -p"${ADMIN_PASS}" -e "$1" 2>&1 | sed '/^mysql: .*Warning/d' } +compose() { + if docker compose version >/dev/null 2>&1; then + docker compose -f "${SCRIPT_DIR}/docker-compose.yml" "$@" + elif command -v docker-compose >/dev/null 2>&1; then + docker-compose -f "${SCRIPT_DIR}/docker-compose.yml" "$@" + else + echo "[ERROR] docker compose is not available" >&2 + exit 1 + fi +} + +create_mysql_monitor_user() { + echo "[INFO] AI pre-hook: creating MySQL monitor user monitor/monitor on backend ${TAP_MYSQLHOST}:${TAP_MYSQLPORT}" + mysql -h"${TAP_MYSQLHOST}" -P"${TAP_MYSQLPORT}" -u"${TAP_MYSQLUSERNAME}" -p"${TAP_MYSQLPASSWORD}" -e "\ +CREATE USER IF NOT EXISTS 'monitor'@'%' IDENTIFIED BY 'monitor'; \ +GRANT USAGE, PROCESS, REPLICATION CLIENT ON *.* TO 'monitor'@'%'; \ +FLUSH PRIVILEGES;" +} + +create_pgsql_monitor_user() { + echo "[INFO] AI pre-hook: creating PostgreSQL monitor user monitor/monitor on backend ${AI_PGSQL_HOST}:${AI_PGSQL_PORT}" + local sql="DO \$\$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname='monitor') THEN CREATE ROLE monitor LOGIN PASSWORD 'monitor'; END IF; END \$\$; GRANT pg_monitor TO monitor; GRANT CONNECT ON DATABASE postgres TO monitor; GRANT CONNECT ON DATABASE ${AI_PGSQL_DB} TO monitor;" + if command -v psql >/dev/null 2>&1; then + PGPASSWORD="${AI_PGSQL_PASSWORD}" psql -h "${AI_PGSQL_HOST}" -p "${AI_PGSQL_PORT}" -U "${AI_PGSQL_USER}" -d "${AI_PGSQL_DB}" -v ON_ERROR_STOP=1 -c "${sql}" + else + compose exec -T pgsql psql -U "${AI_PGSQL_USER}" -d "${AI_PGSQL_DB}" -v ON_ERROR_STOP=1 -c "${sql}" + fi +} + echo "[INFO] AI pre-hook: starting group-local containers" "${SCRIPT_DIR}/docker-compose-init.bash" +create_mysql_monitor_user +create_pgsql_monitor_user echo "[INFO] AI pre-hook: configuring ProxySQL MCP and backend routing" # Configure MCP runtime variables. exec_admin "SET mcp-port='${TAP_MCPPORT}';" exec_admin "SET mcp-use_ssl='true';" -exec_admin "SET mcp-enabled='true';" +exec_admin "SET mcp-enabled='false';" exec_admin "LOAD MCP VARIABLES TO RUNTIME; SAVE MCP VARIABLES TO DISK;" # Keep predictable hostgroups for both direct tests and MCP target routing. @@ -80,6 +111,8 @@ exec_admin "INSERT INTO mcp_target_profiles (target_id, protocol, hostgroup_id, exec_admin "INSERT INTO mcp_target_profiles (target_id, protocol, hostgroup_id, auth_profile_id, description, max_rows, timeout_ms, allow_explain, allow_discovery, active, comment) VALUES ('${MCP_PGSQL_TARGET_ID}', 'pgsql', ${MCP_PGSQL_HOSTGROUP_ID}, '${MCP_PGSQL_AUTH_PROFILE_ID}', 'AI local PostgreSQL target', 200, 5000, 1, 1, 1, 'ai local');" exec_admin "LOAD MCP PROFILES TO RUNTIME; SAVE MCP PROFILES TO DISK;" +exec_admin "SET mcp-enabled='true';" +exec_admin "LOAD MCP VARIABLES TO RUNTIME; SAVE MCP VARIABLES TO DISK;" sleep 2 echo "[INFO] AI pre-hook completed" diff --git a/test/tap/tests/mcp_rules_testing/configure_mcp.sh b/test/tap/tests/mcp_rules_testing/configure_mcp.sh index f2ec817aa..c6730148f 100755 --- a/test/tap/tests/mcp_rules_testing/configure_mcp.sh +++ b/test/tap/tests/mcp_rules_testing/configure_mcp.sh @@ -195,14 +195,21 @@ configure_mcp_profiles() { # Load MCP variables/profiles/server tables to runtime load_to_runtime() { - log_step "Loading MCP variables to RUNTIME..." - if exec_admin_silent "LOAD MCP VARIABLES TO RUNTIME;" >/dev/null 2>&1; then - log_info "MCP variables loaded to RUNTIME" + log_step "Loading MySQL servers to RUNTIME..." + if exec_admin_silent "LOAD MYSQL SERVERS TO RUNTIME;" >/dev/null 2>&1; then + log_info "MySQL servers loaded to RUNTIME" else - log_error "Failed to load MCP variables to RUNTIME" + log_error "Failed to load MySQL servers to RUNTIME" return 1 fi + # Optional in MySQL-only setups, but required if pgsql MCP targets are configured. + if exec_admin_silent "LOAD PGSQL SERVERS TO RUNTIME;" >/dev/null 2>&1; then + log_info "PgSQL servers loaded to RUNTIME" + else + log_warn "LOAD PGSQL SERVERS TO RUNTIME failed (continuing)" + fi + log_step "Loading MCP profiles to RUNTIME..." if exec_admin_silent "LOAD MCP PROFILES TO RUNTIME;" >/dev/null 2>&1; then log_info "MCP profiles loaded to RUNTIME" @@ -211,11 +218,12 @@ load_to_runtime() { return 1 fi - log_step "Loading MySQL servers to RUNTIME..." - if exec_admin_silent "LOAD MYSQL SERVERS TO RUNTIME;" >/dev/null 2>&1; then - log_info "MySQL servers loaded to RUNTIME" + # Load MCP variables last so server startup sees runtime servers+profiles. + log_step "Loading MCP variables to RUNTIME..." + if exec_admin_silent "LOAD MCP VARIABLES TO RUNTIME;" >/dev/null 2>&1; then + log_info "MCP variables loaded to RUNTIME" else - log_error "Failed to load MySQL servers to RUNTIME" + log_error "Failed to load MCP variables to RUNTIME" return 1 fi } diff --git a/test/tap/tests/mcp_rules_testing/test_phase10_eval_explain.sh b/test/tap/tests/mcp_rules_testing/test_phase10_eval_explain.sh old mode 100644 new mode 100755 diff --git a/test/tap/tests/mcp_rules_testing/test_phase11_pgsql_target.sh b/test/tap/tests/mcp_rules_testing/test_phase11_pgsql_target.sh old mode 100644 new mode 100755