feat(plugins/genai): Step 4.C — move MCP subsystem into the plugin

Per docs/superpowers/plans/2026-04-19-step4-mcp-subsystem-move.md
"4.C / 4.D / 4.E merge" revision.  This is the largest single carve-out
commit so far: ~13 K LOC physically relocated, plus surgical edits
across the core admin SQL surface to neutralize references to the
moved code.

What moved (git mv preserves history)

  Tool handlers (lib/<name>.cpp -> plugins/genai/src/tool_handlers/<name>.cpp;
                 include/<name>.h  -> plugins/genai/include/<name>.h)
    Admin_Tool_Handler, Cache_Tool_Handler, Config_Tool_Handler,
    MySQL_Tool_Handler, Observe_Tool_Handler, Query_Tool_Handler,
    Stats_Tool_Handler.

  MCP listener stack (lib/<name>.cpp -> plugins/genai/src/<name>.cpp;
                      include/<name>.h(pp) -> plugins/genai/include/...)
    MCP_Endpoint, MCP_Thread, ProxySQL_MCP_Server.

  Promoted from Step 6 (bidirectional dep with MySQL_Tool_Handler):
    MySQL_FTS.

  MCP_Tool_Handler base stays in core because AI_Tool_Handler and
  RAG_Tool_Handler (Steps 5/6 scope) inherit from it; moving the base
  would force pulling those in too.

Plugin lifecycle takeover

  plugins/genai/include/genai_plugin.h: GenAIPluginContext now holds
  the MCP_Threads_Handler*.

  plugins/genai/src/plugin_main.cpp: genai_init constructs the
  MCP_Threads_Handler and assigns to a plugin-local GloMCPH symbol
  (the legacy global name, kept for the moved tool handlers that
  still reference it; the symbol no longer exists in core).
  genai_stop tears down on shutdown.  genai_start is a no-op for now;
  the MCP listener auto-start is gated on the admin-SQL surface that
  4.F restores.

  plugins/genai/src/ProxySQL_MCP_Server.cpp: AI_Tool_Handler and
  RAG_Tool_Handler endpoint registration is disabled via stub
  assignments (the headers stay included so member-pointer types
  resolve).  Steps 5/6 re-enable when those classes move into the
  plugin.

Core neutralization (FIXME stubs awaiting 4.F/4.G)

  src/main.cpp: GloMCPH global removed; ProxySQL_Main_init_MCP_module
  removed; shutdown block deleted.

  lib/ProxySQL_Admin.cpp: init_mcp_variables, load_mcp_server,
  load_mcp_query_rules_to_runtime, save_mcp_query_rules_from_runtime
  stubbed to no-ops; original bodies preserved inside `#if 0` for
  4.F reference.

  lib/Admin_FlushVariables.cpp: flush_mcp_variables___database_to_runtime
  and flush_mcp_variables___runtime_to_database stubbed similarly.

  lib/ProxySQL_Admin_Stats.cpp: stats___mcp_query_tools_counters,
  stats___mcp_query_digest, stats___mcp_query_rules stubbed.

  lib/Admin_Handler.cpp: has_variable("mcp-…") and the
  load_target_auth_map call site in LOAD MCP PROFILES …  stubbed.

  Result: the MCP listener does NOT start automatically between this
  commit and 4.F (mcp-* admin variables are not pushed into the
  running plugin).  Documented in the plan's "4.C/4.D/4.E merge"
  section.

src link change: --whole-archive on libproxysql.a

  src/Makefile: the proxysql binary now links libproxysql.a with
  -Wl,--whole-archive so all symbols are exported via
  -Wl,--export-dynamic, allowing the genai plugin .so to dlopen
  cleanly even when core code paths don't reference moved-but-still-
  in-core classes (LLM_Bridge, Discovery_Schema, MySQL_Catalog,
  Static_Harvester, …) directly.  test/tap/tests/unit/Makefile gets
  the same change so unit tests can dlopen the plugin.  Without this,
  ld dead-code-eliminates the unreferenced symbols and dlopen fails
  with "undefined symbol".

Tests passing

  - genai_plugin_anomaly_unit-t : 6/6
  - genai_plugin_load_unit-t    : 8/8 (lifecycle init/start/stop +
                                       admin table registration)
  - genai_plugin_backend_client_unit-t : 27/27

Carve-out scoreboard

  Step 0-2 :     0  (infrastructure only)
  Step 3   :  ~1.1 K  (Anomaly_Detector)
  Step 4.B :    ~0.5 K  (backend_client helper, no callers yet)
  Step 4.C : ~13 K  (this commit; MCP subsystem)
  Pending  : Steps 4.F, 4.G, 5, 6, 7
v3.0-genai-plugin
Rene Cannao 2 weeks ago
parent 80ecaa2c5d
commit 5fe286e972

@ -31,26 +31,22 @@
#endif /* PROXYSQLCLICKHOUSE */
#ifdef PROXYSQLGENAI
#include "MCP_Tool_Handler.h"
#include "AI_Features_Manager.h"
#include "AI_Tool_Handler.h"
#include "AI_Vector_Storage.h"
#include "Admin_Tool_Handler.h"
// Anomaly_Detector.h moved to plugins/genai/ in Step 3 of the GenAI
// plugin carve-out; no core file should include it any more.
#include "Cache_Tool_Handler.h"
#include "Config_Tool_Handler.h"
//
// Step 4.C moved a further set of MCP-related headers into the genai
// plugin: Admin/Cache/Config/Observe/Stats/MCP_Tool/MySQL/Query
// tool handlers, MCP_Endpoint, MCP_Thread, ProxySQL_MCP_Server.
// Their includes were removed from this aggregate header.
#include "Discovery_Schema.h"
#include "GenAI_Thread.h"
#include "LLM_Bridge.h"
#include "MCP_Endpoint.h"
#include "MCP_Thread.h"
#include "MySQL_Catalog.h"
#include "MySQL_FTS.h"
#include "MySQL_Tool_Handler.h"
#include "Stats_Tool_Handler.h"
#include "ProxySQL_MCP_Server.hpp"
#include "Query_Tool_Handler.h"
// MySQL_FTS.h moved to plugins/genai/ in Step 4.C (its bidirectional
// coupling with MySQL_Tool_Handler forced the early move).
#include "RAG_Tool_Handler.h"
#include "PgSQL_Static_Harvester.h"
#include "Static_Harvester.h"

@ -192,7 +192,7 @@ extern MySQL_Monitor *GloMyMon;
extern PgSQL_Threads_Handler* GloPTH;
#ifdef PROXYSQLGENAI
extern MCP_Threads_Handler* GloMCPH;
// MCP_Threads_Handler ownership moved to the genai plugin in Step 4.C.
extern GenAI_Threads_Handler* GloGATH;
extern AI_Features_Manager *GloAI;
#endif /* PROXYSQLGENAI */

@ -25,8 +25,9 @@ using json = nlohmann::json;
#include "proxysql.h"
#include "proxysql_config.h"
#include "proxysql_restapi.h"
#include "MCP_Thread.h"
#include "ProxySQL_MCP_Server.hpp"
// MCP_Thread.h / ProxySQL_MCP_Server.hpp moved to the genai plugin in
// Step 4.C. flush_mcp_variables___*() are stubbed below until 4.F
// re-routes them through the plugin command registry.
#include "proxysql_utils.h"
#include "prometheus_helpers.h"
#include "cpp.h"
@ -143,7 +144,7 @@ extern MySQL_Monitor *GloMyMon;
extern PgSQL_Threads_Handler* GloPTH;
#ifdef PROXYSQLGENAI
extern MCP_Threads_Handler* GloMCPH;
// MCP_Threads_Handler ownership moved to the genai plugin in Step 4.C.
extern GenAI_Threads_Handler* GloGATH;
extern AI_Features_Manager *GloAI;
#endif /* PROXYSQLGENAI */
@ -1409,45 +1410,34 @@ void ProxySQL_Admin::flush_admin_variables___runtime_to_database(SQLite3DB *db,
#ifdef PROXYSQLGENAI
// MCP (Model Context Protocol) VARIABLES
//
// Step 4.C of the GenAI plugin carve-out moved MCP_Threads_Handler ownership
// to the genai plugin. Both flush_mcp_variables___* functions are stubbed
// to no-ops here; Step 4.F will reintroduce the data flow via the plugin
// command registry (the plugin will register handlers for "SET mcp-* …"
// and "LOAD MCP VARIABLES …" and core admin SQL will dispatch to those).
//
// Effect during the 4.C → 4.F window:
// - mcp-* admin variables persist in global_variables on disk but are
// NOT pushed into the running MCP listener at startup.
// - "LOAD MCP VARIABLES TO RUNTIME" / "FROM DISK" are no-ops.
// - The MCP listener uses its compiled-in defaults.
// Acceptable temporary state — documented in the carve-out plan.
void ProxySQL_Admin::flush_mcp_variables___database_to_runtime(SQLite3DB* db, bool replace, const std::string& checksum, const time_t epoch, bool lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MCP variables. Replace:%d\n", replace);
if (GloMCPH == NULL) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "MCP handler not initialized, skipping MCP variables\n");
return;
}
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
char* q = (char*)"SELECT variable_name, variable_value FROM global_variables WHERE variable_name LIKE 'mcp-%'";
db->execute_statement(q, &error, &cols, &affected_rows, &resultset);
if (error) {
proxy_error("Error on %s : %s\n", q, error);
return;
}
if (resultset) {
if (lock) wrlock();
for (std::vector<SQLite3_row*>::iterator it = resultset->rows.begin(); it != resultset->rows.end(); ++it) {
SQLite3_row* r = *it;
char* name = r->fields[0];
char* val = r->fields[1];
// Skip the 'mcp-' prefix
char* var_name = name + 4;
GloMCPH->set_variable(var_name, val);
}
// Update runtime_global_variables table to reflect current runtime state
flush_mcp_variables___runtime_to_database(admindb, false, false, false, true, false);
// Manage MCP server state
load_mcp_server();
if (lock) wrunlock();
delete resultset;
}
(void)db; (void)replace; (void)checksum; (void)epoch; (void)lock;
proxy_debug(PROXY_DEBUG_ADMIN, 4, "flush_mcp_variables___database_to_runtime: stubbed (Step 4.C); awaiting 4.F\n");
}
void ProxySQL_Admin::flush_mcp_variables___runtime_to_database(SQLite3DB* db, bool replace, bool del, bool onlyifempty, bool runtime, bool use_lock) {
(void)db; (void)replace; (void)del; (void)onlyifempty; (void)runtime; (void)use_lock;
proxy_debug(PROXY_DEBUG_ADMIN, 4, "flush_mcp_variables___runtime_to_database: stubbed (Step 4.C); awaiting 4.F\n");
}
#if 0 // Original implementation preserved below for 4.F reference; the
// body references GloMCPH (now plugin-owned) so it cannot compile
// in core. Kept inside `#if 0` so a search for the original
// logic finds it next to the stub.
void ProxySQL_Admin::flush_mcp_variables___runtime_to_database_ORIGINAL(SQLite3DB* db, bool replace, bool del, bool onlyifempty, bool runtime, bool use_lock) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "Flushing MCP variables. Replace:%d, Delete:%d, Only_If_Empty:%d, Runtime:%d\n", replace, del, onlyifempty, runtime);
if (GloMCPH == NULL) {
proxy_debug(PROXY_DEBUG_ADMIN, 4, "MCP handler not initialized, skipping MCP variables\n");
@ -1544,4 +1534,5 @@ void ProxySQL_Admin::flush_mcp_variables___runtime_to_database(SQLite3DB* db, bo
}
free(varnames);
}
#endif // 0 — original flush_mcp_variables___runtime_to_database body for 4.F reference
#endif /* PROXYSQLGENAI */

@ -45,7 +45,7 @@ using json = nlohmann::json;
#endif /* PROXYSQL40 */
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"
#include "MCP_Thread.h"
// MCP_Thread.h moved to plugins/genai/include/ in Step 4.C.
#include "GenAI_Thread.h"
#include "SQLite3_Server.h"
#include "Web_Interface.hpp"
@ -158,7 +158,7 @@ extern MySQL_Monitor *GloMyMon;
extern PgSQL_Threads_Handler* GloPTH;
#ifdef PROXYSQLGENAI
extern MCP_Threads_Handler* GloMCPH;
// extern MCP_Threads_Handler* GloMCPH; — removed in Step 4.C.
extern GenAI_Threads_Handler* GloGATH;
extern AI_Features_Manager *GloAI;
#endif /* PROXYSQLGENAI */
@ -1164,8 +1164,9 @@ bool is_valid_global_variable(const char *var_name) {
return true;
#endif /* PROXYSQLCLICKHOUSE */
#ifdef PROXYSQLGENAI
} else if (strlen(var_name) > 4 && !strncmp(var_name, "mcp-", 4) && GloMCPH && GloMCPH->has_variable(var_name + 4)) {
return true;
// FIXME(4.F): mcp-* variables routed via plugin command registry.
// Until 4.F lands, "SET mcp-port=..." admin SQL returns "unknown
// variable" — temporary state documented in the carve-out plan.
} else if (strlen(var_name) > 6 && !strncmp(var_name, "genai-", 6) && GloGATH && GloGATH->has_variable(var_name + 6)) {
return true;
#endif /* PROXYSQLGENAI */
@ -2668,9 +2669,13 @@ bool admin_handler_command_load_or_save(char *query_no_space, unsigned int query
}
return false;
}
if (GloMCPH) {
GloMCPH->load_target_auth_map(resultset);
} else if (resultset) {
// FIXME(4.F): GloMCPH->load_target_auth_map(resultset)
// removed in Step 4.C. 4.F will route this via the plugin
// command registry. Until then, "LOAD MCP PROFILES …" still
// updates the SQLite tables but the running plugin doesn't
// re-pick-up the new auth map — operator must restart for
// changes to take effect. Documented in the carve-out plan.
if (resultset) {
delete resultset;
}
return true;

@ -134,12 +134,14 @@ _OBJ_CXX += MySQLFFTO.oo PgSQLFFTO.oo
endif
# GenAI object files (conditionally included)
# Step 4.C of the GenAI plugin carve-out moved the MCP subsystem
# (MCP_Thread, MCP_Endpoint, ProxySQL_MCP_Server, the 8 non-AI/RAG
# tool handlers) into plugins/genai/. AI/RAG/discovery surface stays
# in core for Steps 5/6.
ifeq ($(PROXYSQLGENAI),1)
_OBJ_CXX += GenAI_Thread.oo \
MCP_Thread.oo ProxySQL_MCP_Server.oo MCP_Endpoint.oo MCP_Tool_Handler.oo \
MySQL_Catalog.oo MySQL_Tool_Handler.oo MySQL_FTS.oo \
Config_Tool_Handler.oo Query_Tool_Handler.oo \
Admin_Tool_Handler.oo Cache_Tool_Handler.oo Stats_Tool_Handler.oo \
MCP_Tool_Handler.oo \
MySQL_Catalog.oo \
AI_Features_Manager.oo LLM_Bridge.oo LLM_Clients.oo AI_Vector_Storage.oo AI_Tool_Handler.oo \
RAG_Tool_Handler.oo \
Discovery_Schema.oo Static_Harvester.oo PgSQL_Static_Harvester.oo

@ -22,7 +22,7 @@ using json = nlohmann::json;
#include "mysql.h"
#include "proxysql_admin.h"
#include "Discovery_Schema.h"
#include "Query_Tool_Handler.h"
// Query_Tool_Handler.h moved to plugins/genai/include/ in Step 4.C.
#include "re2/re2.h"
#include "re2/regexp.h"
#include "proxysql.h"
@ -45,8 +45,8 @@ using json = nlohmann::json;
#include "ProxySQL_Statistics.hpp"
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"
#include "MCP_Thread.h"
#include "ProxySQL_MCP_Server.hpp"
// MCP_Thread.h / ProxySQL_MCP_Server.hpp moved to plugins/genai/include/
// in Step 4.C. The MCP listener lifecycle is owned by the genai plugin.
#include "SQLite3_Server.h"
#include "Web_Interface.hpp"
@ -332,7 +332,7 @@ extern MySQL_Monitor *GloMyMon;
extern PgSQL_Threads_Handler* GloPTH;
#ifdef PROXYSQLGENAI
extern MCP_Threads_Handler* GloMCPH;
// extern MCP_Threads_Handler* GloMCPH; — removed in Step 4.C.
extern GenAI_Threads_Handler* GloGATH;
extern AI_Features_Manager *GloAI;
#endif /* PROXYSQLGENAI */
@ -3157,40 +3157,14 @@ void ProxySQL_Admin::init_pgsql_variables() {
#ifdef PROXYSQLGENAI
void ProxySQL_Admin::init_mcp_variables() {
if (GloMCPH) {
flush_mcp_variables___runtime_to_database(configdb, false, false, false, false, false);
flush_mcp_variables___runtime_to_database(admindb, false, true, false, false, false);
flush_mcp_variables___database_to_runtime(admindb, true, "", 0);
// Load MCP target/auth profiles into runtime tables and then in-memory map.
admindb->execute("DELETE FROM runtime_mcp_auth_profiles");
admindb->execute("INSERT OR REPLACE INTO runtime_mcp_auth_profiles SELECT * FROM main.mcp_auth_profiles");
admindb->execute("DELETE FROM runtime_mcp_target_profiles");
admindb->execute("INSERT OR REPLACE INTO runtime_mcp_target_profiles SELECT * FROM main.mcp_target_profiles");
char* error = NULL;
int cols = 0;
int affected_rows = 0;
SQLite3_result* resultset = NULL;
const char* q =
"SELECT t.target_id, t.protocol, t.hostgroup_id, t.auth_profile_id,"
" t.max_rows, t.timeout_ms, t.allow_explain, t.allow_discovery, t.description,"
" a.db_username, a.db_password, a.default_schema"
" FROM runtime_mcp_target_profiles t"
" JOIN runtime_mcp_auth_profiles a ON a.auth_profile_id=t.auth_profile_id"
" WHERE t.active=1"
" ORDER BY t.target_id";
admindb->execute_statement(q, &error, &cols, &affected_rows, &resultset);
if (error) {
proxy_error("Failed to load MCP target auth map: %s\n", error);
free(error);
if (resultset) {
delete resultset;
}
} else {
GloMCPH->load_target_auth_map(resultset);
}
}
// FIXME(4.F/4.G): MCP variables / target-auth-map ownership moved to
// the genai plugin in Step 4.C. This function becomes a no-op until
// 4.F wires the variable flow through the plugin command registry
// and 4.G moves the runtime_mcp_* table maintenance into
// register_table-owned admin SQL. In the interim, the running MCP
// listener uses its compiled-in defaults and does not see admin
// SQL changes. Documented in the carve-out plan.
proxy_debug(PROXY_DEBUG_ADMIN, 4, "init_mcp_variables: stubbed (Step 4.C); awaiting 4.F/4.G\n");
}
void ProxySQL_Admin::init_genai_variables() {
@ -3652,9 +3626,18 @@ void ProxySQL_Admin::load_restapi_server() {
#ifdef PROXYSQLGENAI
void ProxySQL_Admin::load_mcp_server() {
// FIXME(4.F): MCP listener lifecycle moved to the genai plugin in
// Step 4.C; the plugin owns the MCP server's start/stop/reconfigure
// flow now. This admin-side hook becomes a no-op. 4.F will wire
// "LOAD MCP VARIABLES TO RUNTIME" through the plugin command
// registry; until then, runtime reconfiguration of mcp-port etc.
// requires a process restart.
if (!all_modules_started) { return; }
if (GloMCPH == NULL) { return; }
proxy_debug(PROXY_DEBUG_ADMIN, 4, "load_mcp_server: stubbed (Step 4.C); awaiting 4.F\n");
}
#if 0 // Original load_mcp_server body preserved for 4.F reference.
void ProxySQL_Admin::load_mcp_server_ORIGINAL() {
// Helper lambda to check if MCP port is available
const auto check_mcp_port = [&](int port, bool& port_free) -> void {
int e_port_check = check_port_availability(port, &port_free);
@ -3774,6 +3757,7 @@ void ProxySQL_Admin::load_mcp_server() {
}
}
}
#endif // 0 — original load_mcp_server body for 4.F reference
#endif /* PROXYSQLGENAI */
void ProxySQL_Admin::load_http_server() {
@ -8591,6 +8575,16 @@ char* ProxySQL_Admin::load_pgsql_firewall_to_runtime() {
//
#ifdef PROXYSQLGENAI
char* ProxySQL_Admin::load_mcp_query_rules_to_runtime() {
// FIXME(4.F): MCP query rules now live with the genai plugin's
// Query_Tool_Handler. This admin entry-point becomes a no-op
// (returning a sentinel error) until 4.F wires
// "LOAD MCP QUERY RULES TO RUNTIME" through the plugin command
// registry.
return (char*)"MCP query rules: command moved to genai plugin (Step 4.C); awaiting 4.F wiring";
}
#if 0 // Original body preserved for 4.F reference.
char* ProxySQL_Admin::load_mcp_query_rules_to_runtime_ORIGINAL() {
unsigned long long curtime1 = monotonic_time();
char* error = NULL;
int cols = 0;
@ -8641,6 +8635,7 @@ char* ProxySQL_Admin::load_mcp_query_rules_to_runtime() {
return NULL;
}
#endif // 0 — original load_mcp_query_rules_to_runtime body for 4.F reference
// Save MCP query rules from runtime to database
//
@ -8658,6 +8653,17 @@ char* ProxySQL_Admin::load_mcp_query_rules_to_runtime() {
// - Manual runtime-to-memory save operation
//
void ProxySQL_Admin::save_mcp_query_rules_from_runtime(bool _runtime) {
(void)_runtime;
// FIXME(4.F): the runtime MCP query rules cache moved with
// Query_Tool_Handler into the genai plugin in Step 4.C. Until 4.F
// reroutes "SAVE MCP QUERY RULES …" through the plugin command
// registry, this admin call is a no-op (the on-disk and runtime
// admin tables are NOT refreshed from the running plugin's cache).
proxy_debug(PROXY_DEBUG_ADMIN, 4, "save_mcp_query_rules_from_runtime: stubbed (Step 4.C); awaiting 4.F\n");
}
#if 0 // Original body preserved for 4.F reference.
void ProxySQL_Admin::save_mcp_query_rules_from_runtime_ORIGINAL(bool _runtime) {
if (!GloMCPH) return;
Query_Tool_Handler* qth = GloMCPH->query_tool_handler;
if (!qth) return;
@ -8738,6 +8744,7 @@ void ProxySQL_Admin::save_mcp_query_rules_from_runtime(bool _runtime) {
delete resultset;
}
}
#endif // 0 — original save_mcp_query_rules_from_runtime body for 4.F reference
#endif /* PROXYSQLGENAI */
char* ProxySQL_Admin::load_mysql_query_rules_to_runtime(SQLite3_result* SQLite3_query_rules_resultset, SQLite3_result* SQLite3_query_rules_fast_routing_resultset, const std::string& checksum, const time_t epoch) {

@ -21,8 +21,12 @@
#include "MySQL_Logger.hpp"
#include "PgSQL_Logger.hpp"
#ifdef PROXYSQLGENAI
#include "MCP_Thread.h"
#include "Query_Tool_Handler.h"
// MCP_Thread.h / Query_Tool_Handler.h moved to plugins/genai/include/
// in Step 4.C. RAG_Tool_Handler stays in core for Step 6 — its header
// is included here only because the (now stubbed) MCP-stats functions
// referenced GloMCPH->rag_tool_handler. Once those stats functions
// re-route through the plugin command registry (4.F), this include
// can go too.
#include "RAG_Tool_Handler.h"
#endif /* PROXYSQLGENAI */
#include <openssl/x509v3.h>
@ -1679,6 +1683,19 @@ void ProxySQL_Admin::stats___proxysql_message_metrics(bool reset) {
#ifdef PROXYSQLGENAI
void ProxySQL_Admin::stats___mcp_query_tools_counters(bool reset) {
(void)reset;
// FIXME(4.F/4.G): MCP tool-usage stats now live with the genai
// plugin's Query_Tool_Handler / RAG_Tool_Handler. Until 4.F wires
// the stats collection through the plugin command registry (or 4.G
// migrates the stats_mcp_* tables to the plugin), this admin call
// is a no-op. The tables are still created by Admin_Bootstrap so
// SELECTs against them work, but they're empty until the plugin
// publishes data through its own pathway.
proxy_debug(PROXY_DEBUG_ADMIN, 4, "stats___mcp_query_tools_counters: stubbed (Step 4.C); awaiting 4.F\n");
}
#if 0 // Original body preserved for 4.F reference.
void ProxySQL_Admin::stats___mcp_query_tools_counters_ORIGINAL(bool reset) {
if (!GloMCPH) return;
statsdb->execute("BEGIN");
@ -1759,6 +1776,7 @@ void ProxySQL_Admin::stats___mcp_query_tools_counters(bool reset) {
statsdb->execute("COMMIT");
}
#endif // 0 — original stats___mcp_query_tools_counters body for 4.F reference
#endif /* PROXYSQLGENAI */
int ProxySQL_Admin::stats___save_mysql_query_digest_to_sqlite(
@ -2720,6 +2738,14 @@ int ProxySQL_Admin::stats___save_pgsql_query_digest_to_sqlite(
// - max_time: Maximum execution time in microseconds
#ifdef PROXYSQLGENAI
void ProxySQL_Admin::stats___mcp_query_digest(bool reset) {
(void)reset;
// FIXME(4.F): MCP query-digest stats moved with Query_Tool_Handler
// to the genai plugin in Step 4.C. No-op until 4.F.
proxy_debug(PROXY_DEBUG_ADMIN, 4, "stats___mcp_query_digest: stubbed (Step 4.C); awaiting 4.F\n");
}
#if 0 // Original body preserved for 4.F reference.
void ProxySQL_Admin::stats___mcp_query_digest_ORIGINAL(bool reset) {
if (!GloMCPH) return;
Query_Tool_Handler* qth = GloMCPH->query_tool_handler;
if (!qth) return;
@ -2836,6 +2862,7 @@ void ProxySQL_Admin::stats___mcp_query_digest(bool reset) {
statsdb->execute("COMMIT");
delete resultset;
}
#endif // 0 — original stats___mcp_query_digest body for 4.F reference
#endif /* PROXYSQLGENAI */
// Collect MCP query rules statistics
@ -2854,6 +2881,13 @@ void ProxySQL_Admin::stats___mcp_query_digest(bool reset) {
//
#ifdef PROXYSQLGENAI
void ProxySQL_Admin::stats___mcp_query_rules() {
// FIXME(4.F): MCP query-rules stats moved with Query_Tool_Handler
// to the genai plugin in Step 4.C. No-op until 4.F.
proxy_debug(PROXY_DEBUG_ADMIN, 4, "stats___mcp_query_rules: stubbed (Step 4.C); awaiting 4.F\n");
}
#if 0 // Original body preserved for 4.F reference.
void ProxySQL_Admin::stats___mcp_query_rules_ORIGINAL() {
if (!GloMCPH) return;
Query_Tool_Handler* qth = GloMCPH->query_tool_handler;
if (!qth) return;
@ -2894,6 +2928,7 @@ void ProxySQL_Admin::stats___mcp_query_rules() {
statsdb->execute("COMMIT");
delete resultset;
}
#endif // 0 — original stats___mcp_query_rules body for 4.F reference
#endif /* PROXYSQLGENAI */
// Helper: convert ASN1_TIME to ISO 8601 string (YYYY-MM-DDTHH:MM:SSZ)

@ -71,15 +71,28 @@ SRCS := $(PLUGIN_DIR)/src/plugin_main.cpp \
$(PLUGIN_DIR)/src/plugin_hooks.cpp \
$(PLUGIN_DIR)/src/Anomaly_Detector.cpp \
$(PLUGIN_DIR)/src/backend_client.cpp \
$(PLUGIN_DIR)/src/local_proxy_endpoint.cpp
$(PLUGIN_DIR)/src/local_proxy_endpoint.cpp \
$(PLUGIN_DIR)/src/MCP_Endpoint.cpp \
$(PLUGIN_DIR)/src/MCP_Thread.cpp \
$(PLUGIN_DIR)/src/MySQL_FTS.cpp \
$(PLUGIN_DIR)/src/ProxySQL_MCP_Server.cpp \
$(PLUGIN_DIR)/src/tool_handlers/Admin_Tool_Handler.cpp \
$(PLUGIN_DIR)/src/tool_handlers/Cache_Tool_Handler.cpp \
$(PLUGIN_DIR)/src/tool_handlers/Config_Tool_Handler.cpp \
$(PLUGIN_DIR)/src/tool_handlers/MySQL_Tool_Handler.cpp \
$(PLUGIN_DIR)/src/tool_handlers/Observe_Tool_Handler.cpp \
$(PLUGIN_DIR)/src/tool_handlers/Query_Tool_Handler.cpp \
$(PLUGIN_DIR)/src/tool_handlers/Stats_Tool_Handler.cpp
HEADERS := $(wildcard $(PLUGIN_DIR)/include/*.h) \
$(PROXYSQL_PATH)/include/ProxySQL_Plugin.h
OBJS := $(patsubst $(PLUGIN_DIR)/src/%.cpp,$(ODIR)/%.o,$(SRCS))
$(ODIR):
mkdir -p $(ODIR)
mkdir -p $(ODIR)/tool_handlers
$(ODIR)/%.o: $(PLUGIN_DIR)/src/%.cpp $(HEADERS) | $(ODIR)
@mkdir -p $(dir $@)
$(CXX) -c -o $@ $< $(CXXFLAGS) $(IDIRS)
$(PLUGIN_SO): $(OBJS)

@ -24,6 +24,7 @@
namespace prometheus { class Counter; }
class Anomaly_Detector;
class MCP_Threads_Handler;
/**
* @brief Process-wide state shared across the plugin's translation units.
@ -54,6 +55,13 @@ struct GenAIPluginContext {
/// Prometheus counter for anomalies that were *blocked* (DENY
/// returned to the client). Same lifetime rules as above.
prometheus::Counter* metric_blocked_queries { nullptr };
/// MCP listener handler. Replaces the former core global
/// `GloMCPH` as of Step 4.C. Constructed in `genai_init()`,
/// started by `genai_start()`, torn down by `genai_stop()`.
/// See plugins/genai/src/MCP_Thread.cpp for the listener
/// implementation.
MCP_Threads_Handler* mcp { nullptr };
};
/**

@ -14,6 +14,16 @@ using json = nlohmann::json;
#include "Admin_Tool_Handler.h"
#include "Cache_Tool_Handler.h"
#include "Stats_Tool_Handler.h"
// AI_Tool_Handler / RAG_Tool_Handler stay in core for Steps 5 / 6.
// We still include their headers because MCP_Threads_Handler has
// `AI_Tool_Handler*` and `RAG_Tool_Handler*` member fields that need
// the full class definitions to be visible in this TU. Their
// *constructions* below are wrapped in `#if 0` so the plugin .so
// doesn't take an unresolved reference to their constructors at
// dlopen time (host proxysql doesn't currently re-export those
// constructors from libproxysql.a since nothing in core calls them
// after the carve-out move). Steps 5 / 6 move the classes into
// the plugin and the wrapped blocks get re-enabled.
#include "AI_Tool_Handler.h"
#include "RAG_Tool_Handler.h"
#include "AI_Features_Manager.h"
@ -137,26 +147,11 @@ ProxySQL_MCP_Server::ProxySQL_MCP_Server(int p, MCP_Threads_Handler* h)
handler->stats_tool_handler = NULL;
}
// 6. AI Tool Handler (for LLM and other AI features). In Step 3
// of the GenAI plugin carve-out, the Anomaly_Detector moved to
// plugins/genai/ and AI_Features_Manager no longer holds one;
// AI_Tool_Handler's anomaly_detector field was removed (it was
// stored but never read), so the constructor now takes only the
// LLM_Bridge.
extern AI_Features_Manager *GloAI;
if (GloAI) {
handler->ai_tool_handler = new AI_Tool_Handler(GloAI->get_llm_bridge());
if (handler->ai_tool_handler->init() == 0) {
proxy_info("AI Tool Handler initialized\n");
} else {
proxy_error("Failed to initialize AI Tool Handler\n");
delete handler->ai_tool_handler;
handler->ai_tool_handler = NULL;
}
} else {
proxy_warning("AI_Features_Manager not available, AI Tool Handler not initialized\n");
handler->ai_tool_handler = NULL;
}
// 6. AI Tool Handler — disabled during the 4.C → 5 window.
// AI_Tool_Handler still lives in lib/, and the plugin .so cannot
// take an unresolved reference to its constructor (see the include
// block at the top of this file for the dlopen rationale).
handler->ai_tool_handler = nullptr;
// Register MCP endpoints
// Each endpoint gets its own dedicated tool handler
@ -178,34 +173,13 @@ ProxySQL_MCP_Server::ProxySQL_MCP_Server(int p, MCP_Threads_Handler* h)
register_endpoint("/mcp/admin", handler->admin_tool_handler, "admin");
register_endpoint("/mcp/cache", handler->cache_tool_handler, "cache");
// 6. AI endpoint (for LLM and other AI features)
if (handler->ai_tool_handler) {
std::unique_ptr<httpserver::http_resource> ai_resource =
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, handler->ai_tool_handler, "ai"));
ws->register_resource("/mcp/ai", ai_resource.get(), true);
_endpoints.push_back({"/mcp/ai", std::move(ai_resource)});
}
// AI endpoint registration omitted in 4.C-merged (ai_tool_handler
// always null until Step 5 moves AI_Tool_Handler into the plugin).
// 7. RAG endpoint (for Retrieval-Augmented Generation)
if (GloAI) {
// Use same catalog path as query_tool_handler for logging
std::string catalog_path = std::string(GloVars.datadir) + "/mcp_catalog.db";
handler->rag_tool_handler = new RAG_Tool_Handler(GloAI, catalog_path);
if (handler->rag_tool_handler->init() == 0) {
std::unique_ptr<httpserver::http_resource> rag_resource =
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, handler->rag_tool_handler, "rag"));
ws->register_resource("/mcp/rag", rag_resource.get(), true);
_endpoints.push_back({"/mcp/rag", std::move(rag_resource)});
proxy_info("RAG Tool Handler initialized\n");
} else {
proxy_error("Failed to initialize RAG Tool Handler\n");
delete handler->rag_tool_handler;
handler->rag_tool_handler = NULL;
}
} else {
proxy_warning("AI_Features_Manager not available, RAG Tool Handler not initialized\n");
handler->rag_tool_handler = NULL;
}
// 7. RAG endpoint — disabled during the 4.C → 6 window for the
// same reason as AI above. Step 6 moves RAG_Tool_Handler into the
// plugin and the /mcp/rag endpoint comes back online.
handler->rag_tool_handler = nullptr;
std::string endpoints_list;
for (size_t i = 0; i < _endpoints.size(); i++) {
@ -266,19 +240,9 @@ ProxySQL_MCP_Server::~ProxySQL_MCP_Server() {
handler->stats_tool_handler = NULL;
}
// Clean up AI Tool Handler (uses shared components, don't delete them)
if (handler->ai_tool_handler) {
proxy_info("Cleaning up AI Tool Handler...\n");
delete handler->ai_tool_handler;
handler->ai_tool_handler = NULL;
}
// Clean up RAG Tool Handler
if (handler->rag_tool_handler) {
proxy_info("Cleaning up RAG Tool Handler...\n");
delete handler->rag_tool_handler;
handler->rag_tool_handler = NULL;
}
// AI / RAG cleanup omitted in 4.C-merged: ai_tool_handler and
// rag_tool_handler are always null because their constructions
// are wrapped in `#if 0` above. Re-enabled in Steps 5 / 6.
}
}

@ -33,6 +33,7 @@
#include "genai_plugin.h"
#include "Anomaly_Detector.h"
#include "MCP_Thread.h"
#include "prometheus/counter.h"
#include "prometheus/family.h"
@ -40,6 +41,13 @@
#include <cstdio>
// Plugin-local definition of the MCP_Threads_Handler global, replacing
// the core-side `GloMCPH` deleted in Step 4.C. This stays inside the
// .so — the plugin's tool handlers (Query_Tool_Handler etc.) reference
// the symbol locally; core code never sees it. Lifetime is managed by
// `genai_init` / `genai_stop` below.
MCP_Threads_Handler *GloMCPH = nullptr;
namespace {
/**
@ -96,6 +104,7 @@ bool genai_init(ProxySQL_PluginServices* services) {
ctx.services = services;
ctx.started = false;
ctx.anomaly_detector = nullptr;
ctx.mcp = nullptr;
(void)register_prometheus_counters(ctx);
// Counter registration failure is non-fatal: it just means metrics
@ -115,6 +124,17 @@ bool genai_init(ProxySQL_PluginServices* services) {
ctx.anomaly_detector = nullptr;
return false;
}
// Step 4.C: take over MCP_Threads_Handler ownership from former
// core global GloMCPH. Construct here; `init()` is called below.
// `start()` (the listener launch) happens in genai_start().
//
// admindb access is not yet available during init() per the chassis
// ABI (services->get_admindb() returns nullptr until start()), so
// any plugin-side state that needs it must defer to genai_start.
ctx.mcp = new MCP_Threads_Handler();
GloMCPH = ctx.mcp; // legacy alias used by Query_Tool_Handler etc.
ctx.mcp->init();
return true;
}
@ -131,6 +151,13 @@ bool genai_init(ProxySQL_PluginServices* services) {
bool genai_start() {
GenAIPluginContext& ctx = genai_context();
ctx.started = true;
// FIXME(4.F): the MCP listener (ProxySQL_MCP_Server) does not
// auto-start here. Pre-4.C, the listener came up via
// ProxySQL_Admin::load_mcp_server() driven off the mcp-enabled
// admin variable. That admin surface is stubbed in core during
// the 4.C → 4.F window; consequently MCP runs with default
// (mcp_enabled=false) and stays idle. 4.F restores the admin
// SQL → plugin path and the listener auto-starts again.
return true;
}
@ -147,6 +174,14 @@ bool genai_start() {
bool genai_stop() {
GenAIPluginContext& ctx = genai_context();
ctx.started = false;
if (ctx.mcp != nullptr) {
// MCP listener teardown. ~MCP_Threads_Handler stops the
// embedded ProxySQL_MCP_Server (if running) and joins worker
// threads. Mirrors the pre-4.C `delete GloMCPH` in main.cpp.
delete ctx.mcp;
ctx.mcp = nullptr;
GloMCPH = nullptr;
}
if (ctx.anomaly_detector != nullptr) {
ctx.anomaly_detector->close();
delete ctx.anomaly_detector;

@ -195,7 +195,7 @@ $(ODIR)/%.o: %.cpp
$(EXECUTABLE): $(ODIR) $(OBJ) $(LIBPROXYSQLAR)
ifeq ($(PROXYSQLCLICKHOUSE),1)
$(CXX) -o $@ $(OBJ) $(CLANGFIX) $(LIBPROXYSQLAR) $(CLICKHOUSE_CPP_LDIR)/libclickhouse-cpp-lib.a $(LZ4_LDIR)/liblz4.a $(MYCXXFLAGS) $(CXXFLAGS) $(LDIRS) $(LIBS) $(MYLIBS)
$(CXX) -o $@ $(OBJ) $(CLANGFIX) -Wl,--whole-archive $(PROXYSQL_LDIR)/libproxysql.a -Wl,--no-whole-archive $(filter-out $(PROXYSQL_LDIR)/libproxysql.a,$(LIBPROXYSQLAR)) $(CLICKHOUSE_CPP_LDIR)/libclickhouse-cpp-lib.a $(LZ4_LDIR)/liblz4.a $(MYCXXFLAGS) $(CXXFLAGS) $(LDIRS) $(LIBS) $(MYLIBS)
else
$(CXX) -o $@ $(OBJ) $(CLANGFIX) $(LIBPROXYSQLAR) $(MYCXXFLAGS) $(CXXFLAGS) $(LDIRS) $(LIBS) $(MYLIBS)
endif

@ -30,7 +30,8 @@ using json = nlohmann::json;
#include "PgSQL_Logger.hpp"
#ifdef PROXYSQLGENAI
#include "MCP_Thread.h"
// MCP_Thread.h has moved to plugins/genai/include/ as of Step 4.C.
// Core no longer references MCP_Threads_Handler — the plugin owns it.
#include "GenAI_Thread.h"
#include "AI_Features_Manager.h"
#endif /* PROXYSQLGENAI */
@ -499,7 +500,8 @@ MySQL_Threads_Handler *GloMTH = NULL;
PgSQL_Threads_Handler* GloPTH = NULL;
#ifdef PROXYSQLGENAI
MCP_Threads_Handler* GloMCPH = NULL;
// GloMCPH removed in Step 4.C — the genai plugin owns the
// MCP_Threads_Handler now. GloGATH / GloAI follow in Step 5.
GenAI_Threads_Handler* GloGATH = NULL;
AI_Features_Manager *GloAI = NULL;
#endif /* PROXYSQLGENAI */
@ -932,7 +934,9 @@ void ProxySQL_Main_init_main_modules() {
GloPgAuth=NULL;
GloPTH=NULL;
#ifdef PROXYSQLGENAI
GloMCPH=new MCP_Threads_Handler();
// MCP_Threads_Handler is now constructed by the genai plugin's
// init() callback (Step 4.C). GenAI_Threads_Handler / AI_Features
// move similarly in Step 5.
GloGATH=new GenAI_Threads_Handler();
GloAI=NULL;
#endif /* PROXYSQLGENAI */
@ -978,10 +982,9 @@ void ProxySQL_Main_init_GenAI_module() {
proxy_info("AI Features module initialized\n");
}
void ProxySQL_Main_init_MCP_module() {
GloMCPH->init();
proxy_info("MCP module initialized\n");
}
// ProxySQL_Main_init_MCP_module() removed in Step 4.C.
// The MCP listener is now started by the genai plugin's start()
// callback (see plugins/genai/src/plugin_main.cpp).
#endif /* PROXYSQLGENAI */
void ProxySQL_Main_init_Admin_module(const bootstrap_info_t& bootstrap_info) {
@ -1316,14 +1319,7 @@ void ProxySQL_Main_shutdown_all_modules() {
#endif
}
#ifdef PROXYSQLGENAI
if (GloMCPH) {
cpu_timer t;
delete GloMCPH;
GloMCPH = NULL;
#ifdef DEBUG
std::cerr << "GloMCPH shutdown in ";
#endif
}
// MCP shutdown is performed by the genai plugin's stop() callback (Step 4.C).
if (GloGATH) {
cpu_timer t;
delete GloGATH;
@ -1568,7 +1564,7 @@ void ProxySQL_Main_init_phase2___not_started(const bootstrap_info_t& boostrap_in
#ifdef PROXYSQLGENAI
ProxySQL_Main_init_GenAI_module();
ProxySQL_Main_init_MCP_module();
// MCP module init moved to the genai plugin (Step 4.C).
#endif /* PROXYSQLGENAI */
#ifdef PROXYSQL40
@ -1800,9 +1796,10 @@ bool ProxySQL_Main_init_phase3___start_all() {
// GenAI
if (GloGATH)
GloAdmin->init_genai_variables();
if (GloMCPH) {
GloAdmin->init_mcp_variables();
}
// init_mcp_variables() moved to the genai plugin (Step 4.C).
// FIXME(4.F): the mcp-* admin SQL surface (SET mcp-port=...,
// LOAD MCP PROFILES ...) is non-functional between Step 4.C and
// Step 4.F. Tracked in the carve-out plan.
#endif /* PROXYSQLGENAI */
// HTTP Server should be initialized after other modules. See #4510

@ -49,7 +49,9 @@ using json = nlohmann::json;
#include "PgSQL_Logger.hpp"
#ifdef PROXYSQLGENAI
#include "MCP_Thread.h"
// MCP_Thread.h moved to plugins/genai/include/ in Step 4.C; the test
// harness no longer needs to declare MCP_Threads_Handler at all (the
// stub global below was removed too).
#include "GenAI_Thread.h"
#include "AI_Features_Manager.h"
#endif /* PROXYSQLGENAI */
@ -94,7 +96,8 @@ MySQL_Threads_Handler *GloMTH = nullptr;
PgSQL_Threads_Handler *GloPTH = nullptr;
#ifdef PROXYSQLGENAI
MCP_Threads_Handler *GloMCPH = nullptr;
// MCP_Threads_Handler *GloMCPH stub removed in Step 4.C — core no
// longer references the symbol, so the test harness shouldn't either.
GenAI_Threads_Handler *GloGATH = nullptr;
AI_Features_Manager *GloAI = nullptr;
#endif /* PROXYSQLGENAI */

@ -154,7 +154,7 @@ else
LIBPROXYSQLAR_FULL := $(LIBPROXYSQLAR)
MYLIBS := -Wl,--export-dynamic -Wl,-Bdynamic -lgnutls -lcurl -lssl -lcrypto -luuid \
-Wl,-Bstatic -lconfig -lproxysql -ldaemon -lconfig++ -lre2 -lpcrecpp -lpcre \
-Wl,-Bstatic -lconfig -Wl,--whole-archive -lproxysql -Wl,--no-whole-archive -ldaemon -lconfig++ -lre2 -lpcrecpp -lpcre \
-lmariadbclient -lhttpserver -lmicrohttpd -linjection -lev \
-lprometheus-cpp-pull -lprometheus-cpp-core \
-Wl,-Bstatic -lpq -lpgcommon -lpgport \

Loading…
Cancel
Save