diff --git a/include/MCP_Thread.h b/include/MCP_Thread.h index b87d74f70..0b767d66a 100644 --- a/include/MCP_Thread.h +++ b/include/MCP_Thread.h @@ -50,6 +50,8 @@ public: char* mcp_query_endpoint_auth; ///< Authentication for /mcp/query endpoint char* mcp_admin_endpoint_auth; ///< Authentication for /mcp/admin endpoint char* mcp_cache_endpoint_auth; ///< Authentication for /mcp/cache endpoint + char* mcp_rag_endpoint_auth; ///< Authentication for /mcp/rag endpoint + int mcp_timeout_ms; ///< Request timeout in milliseconds (default: 30000) // MySQL Tool Handler configuration char* mcp_mysql_hosts; ///< Comma-separated list of MySQL hosts diff --git a/include/proxysql_admin.h b/include/proxysql_admin.h index a93abe249..71494f84f 100644 --- a/include/proxysql_admin.h +++ b/include/proxysql_admin.h @@ -566,6 +566,11 @@ class ProxySQL_Admin { * @details Modules ready when 'all_modules_started=true'. See 'all_modules_started'. */ void load_restapi_server(); + /** + * @brief Loads the MCP server config to runtime if all modules are ready, no-op otherwise. + * @details Modules ready when 'all_modules_started=true'. See 'all_modules_started'. + */ + void load_mcp_server(); bool get_read_only() { return variables.admin_read_only; } bool set_read_only(bool ro) { variables.admin_read_only=ro; return variables.admin_read_only; } bool has_variable(const char *name); diff --git a/include/proxysql_utils.h b/include/proxysql_utils.h index b3a550df6..e201cfc9a 100644 --- a/include/proxysql_utils.h +++ b/include/proxysql_utils.h @@ -356,4 +356,17 @@ static inline void set_thread_name(const char(&name)[LEN], const bool en = true) */ std::string get_client_addr(struct sockaddr* client_addr); +/** + * @brief Check if a port is available for binding + * + * Creates a temporary socket and attempts to bind to the specified port + * to verify availability. The socket is closed immediately after the test. + * Sets SO_REUSEADDR to allow rebinding to recently used ports. + * + * @param port_num Port number to check + * @param port_free Output parameter - set to true if port is available, false if in use + * @return int Error code (0 = success, -1 = setsockopt failed, -2 = invalid parameters) + */ +int check_port_availability(int port_num, bool* port_free); + #endif diff --git a/lib/AI_Features_Manager.cpp b/lib/AI_Features_Manager.cpp index d33205c20..5958e962a 100644 --- a/lib/AI_Features_Manager.cpp +++ b/lib/AI_Features_Manager.cpp @@ -69,6 +69,11 @@ int AI_Features_Manager::init_vector_db() { return -1; } + // Enable SQLite extensions for vector_db + // Once enabled, SQLite loads extensions such as vec0 and rembed automatically. + // Refer - Admin_Bootstrap.cpp:590 + (*proxy_sqlite3_enable_load_extension)(vector_db->get_db(), 1); + // Create tables for LLM cache const char* create_llm_cache = "CREATE TABLE IF NOT EXISTS llm_cache (" @@ -82,7 +87,7 @@ int AI_Features_Manager::init_vector_db() { "created_at INTEGER DEFAULT (strftime('%s' , 'now'))" ");"; - if (vector_db->execute(create_llm_cache) != 0) { + if (!vector_db->execute(create_llm_cache)) { proxy_error("AI: Failed to create llm_cache table\n"); return -1; } @@ -99,7 +104,7 @@ int AI_Features_Manager::init_vector_db() { "created_at INTEGER DEFAULT (strftime('%s' , 'now'))" ");"; - if (vector_db->execute(create_anomaly_patterns) != 0) { + if (!vector_db->execute(create_anomaly_patterns)) { proxy_error("AI: Failed to create anomaly_patterns table\n"); return -1; } @@ -116,7 +121,7 @@ int AI_Features_Manager::init_vector_db() { "timestamp INTEGER DEFAULT (strftime('%s' , 'now'))" ");"; - if (vector_db->execute(create_query_history) != 0) { + if (!vector_db->execute(create_query_history)) { proxy_error("AI: Failed to create query_history table\n"); return -1; } @@ -127,10 +132,10 @@ int AI_Features_Manager::init_vector_db() { // 1. LLM cache virtual table const char* create_llm_vec = "CREATE VIRTUAL TABLE IF NOT EXISTS llm_cache_vec USING vec0(" - "embedding float(1536)" + "embedding float[1536]" ");"; - if (vector_db->execute(create_llm_vec) != 0) { + if (!vector_db->execute(create_llm_vec)) { proxy_error("AI: Failed to create llm_cache_vec virtual table\n"); // Virtual table creation failure is not critical - log and continue proxy_debug(PROXY_DEBUG_GENAI, 3, "Continuing without llm_cache_vec"); @@ -139,10 +144,10 @@ int AI_Features_Manager::init_vector_db() { // 2. Anomaly patterns virtual table const char* create_anomaly_vec = "CREATE VIRTUAL TABLE IF NOT EXISTS anomaly_patterns_vec USING vec0(" - "embedding float(1536)" + "embedding float[1536]" ");"; - if (vector_db->execute(create_anomaly_vec) != 0) { + if (!vector_db->execute(create_anomaly_vec)) { proxy_error("AI: Failed to create anomaly_patterns_vec virtual table\n"); proxy_debug(PROXY_DEBUG_GENAI, 3, "Continuing without anomaly_patterns_vec"); } @@ -150,10 +155,10 @@ int AI_Features_Manager::init_vector_db() { // 3. Query history virtual table const char* create_history_vec = "CREATE VIRTUAL TABLE IF NOT EXISTS query_history_vec USING vec0(" - "embedding float(1536)" + "embedding float[1536]" ");"; - if (vector_db->execute(create_history_vec) != 0) { + if (!vector_db->execute(create_history_vec)) { proxy_error("AI: Failed to create query_history_vec virtual table\n"); proxy_debug(PROXY_DEBUG_GENAI, 3, "Continuing without query_history_vec"); } @@ -181,7 +186,7 @@ int AI_Features_Manager::init_vector_db() { "updated_at INTEGER NOT NULL DEFAULT (unixepoch())" ");"; - if (vector_db->execute(create_rag_sources) != 0) { + if (!vector_db->execute(create_rag_sources)) { proxy_error("AI: Failed to create rag_sources table\n"); return -1; } @@ -190,7 +195,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_sources_enabled_idx = "CREATE INDEX IF NOT EXISTS idx_rag_sources_enabled ON rag_sources(enabled);"; - if (vector_db->execute(create_rag_sources_enabled_idx) != 0) { + if (!vector_db->execute(create_rag_sources_enabled_idx)) { proxy_error("AI: Failed to create idx_rag_sources_enabled index\n"); return -1; } @@ -198,7 +203,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_sources_backend_idx = "CREATE INDEX IF NOT EXISTS idx_rag_sources_backend ON rag_sources(backend_type, backend_host, backend_port, backend_db, table_name);"; - if (vector_db->execute(create_rag_sources_backend_idx) != 0) { + if (!vector_db->execute(create_rag_sources_backend_idx)) { proxy_error("AI: Failed to create idx_rag_sources_backend index\n"); return -1; } @@ -217,7 +222,7 @@ int AI_Features_Manager::init_vector_db() { "deleted INTEGER NOT NULL DEFAULT 0" ");"; - if (vector_db->execute(create_rag_documents) != 0) { + if (!vector_db->execute(create_rag_documents)) { proxy_error("AI: Failed to create rag_documents table\n"); return -1; } @@ -226,7 +231,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_documents_source_updated_idx = "CREATE INDEX IF NOT EXISTS idx_rag_documents_source_updated ON rag_documents(source_id, updated_at);"; - if (vector_db->execute(create_rag_documents_source_updated_idx) != 0) { + if (!vector_db->execute(create_rag_documents_source_updated_idx)) { proxy_error("AI: Failed to create idx_rag_documents_source_updated index\n"); return -1; } @@ -234,7 +239,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_documents_source_deleted_idx = "CREATE INDEX IF NOT EXISTS idx_rag_documents_source_deleted ON rag_documents(source_id, deleted);"; - if (vector_db->execute(create_rag_documents_source_deleted_idx) != 0) { + if (!vector_db->execute(create_rag_documents_source_deleted_idx)) { proxy_error("AI: Failed to create idx_rag_documents_source_deleted index\n"); return -1; } @@ -253,7 +258,7 @@ int AI_Features_Manager::init_vector_db() { "deleted INTEGER NOT NULL DEFAULT 0" ");"; - if (vector_db->execute(create_rag_chunks) != 0) { + if (!vector_db->execute(create_rag_chunks)) { proxy_error("AI: Failed to create rag_chunks table\n"); return -1; } @@ -262,7 +267,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_chunks_doc_idx = "CREATE UNIQUE INDEX IF NOT EXISTS uq_rag_chunks_doc_idx ON rag_chunks(doc_id, chunk_index);"; - if (vector_db->execute(create_rag_chunks_doc_idx) != 0) { + if (!vector_db->execute(create_rag_chunks_doc_idx)) { proxy_error("AI: Failed to create uq_rag_chunks_doc_idx index\n"); return -1; } @@ -270,7 +275,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_chunks_source_doc_idx = "CREATE INDEX IF NOT EXISTS idx_rag_chunks_source_doc ON rag_chunks(source_id, doc_id);"; - if (vector_db->execute(create_rag_chunks_source_doc_idx) != 0) { + if (!vector_db->execute(create_rag_chunks_source_doc_idx)) { proxy_error("AI: Failed to create idx_rag_chunks_source_doc index\n"); return -1; } @@ -278,7 +283,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_chunks_deleted_idx = "CREATE INDEX IF NOT EXISTS idx_rag_chunks_deleted ON rag_chunks(deleted);"; - if (vector_db->execute(create_rag_chunks_deleted_idx) != 0) { + if (!vector_db->execute(create_rag_chunks_deleted_idx)) { proxy_error("AI: Failed to create idx_rag_chunks_deleted index\n"); return -1; } @@ -292,7 +297,7 @@ int AI_Features_Manager::init_vector_db() { "tokenize = 'unicode61'" ");"; - if (vector_db->execute(create_rag_fts_chunks) != 0) { + if (!vector_db->execute(create_rag_fts_chunks)) { proxy_error("AI: Failed to create rag_fts_chunks virtual table\n"); proxy_debug(PROXY_DEBUG_GENAI, 3, "Continuing without rag_fts_chunks"); } @@ -306,7 +311,7 @@ int AI_Features_Manager::init_vector_db() { std::string create_rag_vec_chunks_sql = "CREATE VIRTUAL TABLE IF NOT EXISTS rag_vec_chunks USING vec0(" - "embedding float(" + std::to_string(vector_dimension) + "), " + "embedding float[" + std::to_string(vector_dimension) + "], " "chunk_id TEXT, " "doc_id TEXT, " "source_id INTEGER, " @@ -315,7 +320,7 @@ int AI_Features_Manager::init_vector_db() { const char* create_rag_vec_chunks = create_rag_vec_chunks_sql.c_str(); - if (vector_db->execute(create_rag_vec_chunks) != 0) { + if (!vector_db->execute(create_rag_vec_chunks)) { proxy_error("AI: Failed to create rag_vec_chunks virtual table\n"); proxy_debug(PROXY_DEBUG_GENAI, 3, "Continuing without rag_vec_chunks"); } @@ -338,7 +343,7 @@ int AI_Features_Manager::init_vector_db() { "JOIN rag_documents d ON d.doc_id = c.doc_id " "WHERE c.deleted = 0 AND d.deleted = 0;"; - if (vector_db->execute(create_rag_chunk_view) != 0) { + if (!vector_db->execute(create_rag_chunk_view)) { proxy_error("AI: Failed to create rag_chunk_view view\n"); proxy_debug(PROXY_DEBUG_GENAI, 3, "Continuing without rag_chunk_view"); } @@ -353,7 +358,7 @@ int AI_Features_Manager::init_vector_db() { "last_error TEXT" ");"; - if (vector_db->execute(create_rag_sync_state) != 0) { + if (!vector_db->execute(create_rag_sync_state)) { proxy_error("AI: Failed to create rag_sync_state table\n"); return -1; } diff --git a/lib/Admin_Bootstrap.cpp b/lib/Admin_Bootstrap.cpp index 2a8b2114c..2aee1bf37 100644 --- a/lib/Admin_Bootstrap.cpp +++ b/lib/Admin_Bootstrap.cpp @@ -92,8 +92,8 @@ using json = nlohmann::json; * * @see https://github.com/asg017/sqlite-vec for sqlite-vec documentation */ -extern "C" int (*proxy_sqlite3_vec_init)(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); -extern "C" int (*proxy_sqlite3_rembed_init)(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); +extern int (*proxy_sqlite3_vec_init)(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); +extern int (*proxy_sqlite3_rembed_init)(sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi); #include "microhttpd.h" #if (defined(__i386__) || defined(__x86_64__) || defined(__ARM_ARCH_3__) || defined(__mips__)) && defined(__linux) diff --git a/lib/Admin_FlushVariables.cpp b/lib/Admin_FlushVariables.cpp index 546416860..94471e0c8 100644 --- a/lib/Admin_FlushVariables.cpp +++ b/lib/Admin_FlushVariables.cpp @@ -1092,6 +1092,10 @@ void ProxySQL_Admin::flush_genai_variables___database_to_runtime(SQLite3DB* db, } } + if (GloAI && GloGATH->variables.genai_enabled) { + GloAI->init(); + } + if (lock) wrunlock(); } if (resultset) delete resultset; @@ -1378,120 +1382,8 @@ void ProxySQL_Admin::flush_mcp_variables___database_to_runtime(SQLite3DB* db, bo pthread_mutex_unlock(&GloVars.checksum_mutex); } - // Handle server start/stop based on mcp_enabled - bool enabled = GloMCPH->variables.mcp_enabled; - proxy_info("MCP: mcp_enabled=%d after loading variables\n", enabled); - - if (enabled) { - // Start the server if not already running - if (GloMCPH->mcp_server == NULL) { - // Only check SSL certificates if SSL mode is enabled - if (GloMCPH->variables.mcp_use_ssl) { - if (!GloVars.global.ssl_key_pem_mem || !GloVars.global.ssl_cert_pem_mem) { - proxy_error("MCP: Cannot start server in SSL mode - SSL certificates not loaded. " - "Please configure ssl_key_fp and ssl_cert_fp, or set mcp_use_ssl=false.\n"); - } else { - int port = GloMCPH->variables.mcp_port; - const char* mode = GloMCPH->variables.mcp_use_ssl ? "HTTPS" : "HTTP"; - proxy_info("MCP: Starting %s server on port %d\n", mode, port); - GloMCPH->mcp_server = new ProxySQL_MCP_Server(port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: Server started successfully\n"); - } else { - proxy_error("MCP: Failed to create server instance\n"); - } - } - } else { - // HTTP mode - start without SSL certificates - int port = GloMCPH->variables.mcp_port; - proxy_info("MCP: Starting HTTP server on port %d (unencrypted)\n", port); - GloMCPH->mcp_server = new ProxySQL_MCP_Server(port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: Server started successfully\n"); - } else { - proxy_error("MCP: Failed to create server instance\n"); - } - } - } else { - proxy_info("MCP: Server already running, checking if configuration changed...\n"); - - // Check if restart is needed due to configuration changes - bool needs_restart = false; - std::string restart_reason; - - // Check if port changed - int current_port = GloMCPH->variables.mcp_port; - int server_port = GloMCPH->mcp_server->get_port(); - if (current_port != server_port) { - needs_restart = true; - restart_reason += "port (" + std::to_string(server_port) + " -> " + std::to_string(current_port) + ") "; - } - - // Check if SSL mode changed - bool current_use_ssl = GloMCPH->variables.mcp_use_ssl; - bool server_use_ssl = GloMCPH->mcp_server->is_using_ssl(); - if (current_use_ssl != server_use_ssl) { - needs_restart = true; - restart_reason += "SSL mode (" + std::string(server_use_ssl ? "HTTPS" : "HTTP") + " -> " + std::string(current_use_ssl ? "HTTPS" : "HTTP") + ") "; - } - - if (needs_restart) { - proxy_info("MCP: Configuration changed (%s), restarting server...\n", restart_reason.c_str()); - - // Stop server with old configuration - const char* old_mode = server_use_ssl ? "HTTPS" : "HTTP"; - proxy_info("MCP: Stopping %s server on port %d\n", old_mode, server_port); - delete GloMCPH->mcp_server; - GloMCPH->mcp_server = NULL; - - // Start server with new configuration - int new_port = GloMCPH->variables.mcp_port; - bool new_use_ssl = GloMCPH->variables.mcp_use_ssl; - const char* new_mode = new_use_ssl ? "HTTPS" : "HTTP"; - - // Check SSL certificates if needed - if (new_use_ssl) { - if (!GloVars.global.ssl_key_pem_mem || !GloVars.global.ssl_cert_pem_mem) { - proxy_error("MCP: Cannot start server in SSL mode - SSL certificates not loaded. " - "Please configure ssl_key_fp and ssl_cert_fp, or set mcp_use_ssl=false.\n"); - // Leave server stopped - } else { - proxy_info("MCP: Starting %s server on port %d\n", new_mode, new_port); - GloMCPH->mcp_server = new ProxySQL_MCP_Server(new_port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: Server restarted successfully\n"); - } else { - proxy_error("MCP: Failed to create server instance\n"); - } - } - } else { - // HTTP mode - no SSL certificates needed - proxy_info("MCP: Starting %s server on port %d (unencrypted)\n", new_mode, new_port); - GloMCPH->mcp_server = new ProxySQL_MCP_Server(new_port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: Server restarted successfully\n"); - } else { - proxy_error("MCP: Failed to create server instance\n"); - } - } - } else { - proxy_info("MCP: Server already running, no configuration changes detected\n"); - } - } - } else { - // Stop the server if running - if (GloMCPH->mcp_server != NULL) { - const char* mode = GloMCPH->variables.mcp_use_ssl ? "HTTPS" : "HTTP"; - proxy_info("MCP: Stopping %s server\n", mode); - delete GloMCPH->mcp_server; - GloMCPH->mcp_server = NULL; - proxy_info("MCP: Server stopped successfully\n"); - } - } + // Manage MCP server state + load_mcp_server(); if (lock) wrunlock(); delete resultset; @@ -1587,80 +1479,6 @@ void ProxySQL_Admin::flush_mcp_variables___runtime_to_database(SQLite3DB* db, bo free(qualified_name); } proxy_info("MCP: Finished processing %d variables\n", var_count); - // Handle server start/stop based on mcp_enabled when runtime=true - // This ensures the server state matches the enabled flag after loading to runtime - if (runtime) { - bool enabled = GloMCPH->variables.mcp_enabled; - proxy_info("MCP: mcp_enabled=%d, managing server state\n", enabled); - - if (enabled) { - // Start the server if not already running - if (GloMCPH->mcp_server == NULL) { - // Only check SSL certificates if SSL mode is enabled - if (GloMCPH->variables.mcp_use_ssl) { - if (!GloVars.global.ssl_key_pem_mem || !GloVars.global.ssl_cert_pem_mem) { - proxy_error("MCP: Cannot start server in SSL mode - SSL certificates not loaded. " - "Please configure ssl_key_fp and ssl_cert_fp, or set mcp_use_ssl=false.\n"); - } else { - int port = GloMCPH->variables.mcp_port; - const char* mode = GloMCPH->variables.mcp_use_ssl ? "HTTPS" : "HTTP"; - proxy_info("MCP: Starting %s server on port %d\n", mode, port); - GloMCPH->mcp_server = new ProxySQL_MCP_Server(port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: Server started successfully\n"); - } else { - proxy_error("MCP: Failed to create server instance\n"); - } - } - } else { - // HTTP mode - start without SSL certificates - int port = GloMCPH->variables.mcp_port; - proxy_info("MCP: Starting HTTP server on port %d (unencrypted)\n", port); - GloMCPH->mcp_server = new ProxySQL_MCP_Server(port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: Server started successfully\n"); - } else { - proxy_error("MCP: Failed to create server instance\n"); - } - } - } else { - // Server is already running - need to stop, delete server, and recreate everything - proxy_info("MCP: Server already running, reinitializing\n"); - - // Delete the old server - its destructor will clean up all handlers - // (mysql_tool_handler, config_tool_handler, query_tool_handler, - // admin_tool_handler, cache_tool_handler, observe_tool_handler) - proxy_info("MCP: Stopping and deleting old server\n"); - delete GloMCPH->mcp_server; - GloMCPH->mcp_server = NULL; - // All handlers are now deleted and set to NULL by the destructor - proxy_info("MCP: Old server deleted\n"); - - // Create and start new server with current configuration - // The server constructor will recreate all handlers with updated settings - proxy_info("MCP: Creating and starting new server\n"); - int port = GloMCPH->variables.mcp_port; - GloMCPH->mcp_server = new ProxySQL_MCP_Server(port, GloMCPH); - if (GloMCPH->mcp_server) { - GloMCPH->mcp_server->start(); - proxy_info("MCP: New server created and started successfully\n"); - } else { - proxy_error("MCP: Failed to create new server instance\n"); - } - } - } else { - // Stop the server if running - if (GloMCPH->mcp_server != NULL) { - const char* mode = GloMCPH->variables.mcp_use_ssl ? "HTTPS" : "HTTP"; - proxy_info("MCP: Stopping %s server\n", mode); - delete GloMCPH->mcp_server; - GloMCPH->mcp_server = NULL; - proxy_info("MCP: Server stopped successfully\n"); - } - } - } if (use_lock) { proxy_info("MCP: Releasing lock\n"); diff --git a/lib/MCP_Endpoint.cpp b/lib/MCP_Endpoint.cpp index c41c81214..5104dc3bb 100644 --- a/lib/MCP_Endpoint.cpp +++ b/lib/MCP_Endpoint.cpp @@ -40,6 +40,8 @@ bool MCP_JSONRPC_Resource::authenticate_request(const httpserver::http_request& expected_token = handler->variables.mcp_admin_endpoint_auth; } else if (endpoint_name == "cache") { expected_token = handler->variables.mcp_cache_endpoint_auth; + } else if (endpoint_name == "rag") { + expected_token = handler->variables.mcp_rag_endpoint_auth; } else { proxy_error("MCP authentication on %s: unknown endpoint\n", endpoint_name.c_str()); return false; diff --git a/lib/MCP_Thread.cpp b/lib/MCP_Thread.cpp index fdbe94938..f539dc83c 100644 --- a/lib/MCP_Thread.cpp +++ b/lib/MCP_Thread.cpp @@ -23,6 +23,7 @@ static const char* mcp_thread_variables_names[] = { "query_endpoint_auth", "admin_endpoint_auth", "cache_endpoint_auth", + "rag_endpoint_auth", "timeout_ms", // MySQL Tool Handler configuration "mysql_hosts", @@ -48,6 +49,7 @@ MCP_Threads_Handler::MCP_Threads_Handler() { variables.mcp_query_endpoint_auth = strdup(""); variables.mcp_admin_endpoint_auth = strdup(""); variables.mcp_cache_endpoint_auth = strdup(""); + variables.mcp_rag_endpoint_auth = strdup(""); variables.mcp_timeout_ms = 30000; // MySQL Tool Handler default values variables.mcp_mysql_hosts = strdup("127.0.0.1"); @@ -83,6 +85,8 @@ MCP_Threads_Handler::~MCP_Threads_Handler() { free(variables.mcp_admin_endpoint_auth); if (variables.mcp_cache_endpoint_auth) free(variables.mcp_cache_endpoint_auth); + if (variables.mcp_rag_endpoint_auth) + free(variables.mcp_rag_endpoint_auth); // Free MySQL Tool Handler variables if (variables.mcp_mysql_hosts) free(variables.mcp_mysql_hosts); @@ -198,6 +202,10 @@ int MCP_Threads_Handler::get_variable(const char* name, char* val) { sprintf(val, "%s", variables.mcp_cache_endpoint_auth ? variables.mcp_cache_endpoint_auth : ""); return 0; } + if (!strcmp(name, "rag_endpoint_auth")) { + sprintf(val, "%s", variables.mcp_rag_endpoint_auth ? variables.mcp_rag_endpoint_auth : ""); + return 0; + } if (!strcmp(name, "timeout_ms")) { sprintf(val, "%d", variables.mcp_timeout_ms); return 0; @@ -291,6 +299,12 @@ int MCP_Threads_Handler::set_variable(const char* name, const char* value) { variables.mcp_cache_endpoint_auth = strdup(value); return 0; } + if (!strcmp(name, "rag_endpoint_auth")) { + if (variables.mcp_rag_endpoint_auth) + free(variables.mcp_rag_endpoint_auth); + variables.mcp_rag_endpoint_auth = strdup(value); + return 0; + } if (!strcmp(name, "timeout_ms")) { int timeout = atoi(value); if (timeout >= 0) { diff --git a/lib/Makefile b/lib/Makefile index 6f01a5702..0ed6fed63 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -95,6 +95,9 @@ HEADERS := ../include/*.h ../include/*.hpp %.ko: %.cpp $(HEADERS) $(CXX) -fPIC -c -o $@ $< $(MYCXXFLAGS) $(CXXFLAGS) +$(ODIR)/proxy_sqlite3_symbols.oo: proxy_sqlite3_symbols.cpp $(HEADERS) + $(CXX) -fPIC -c -o $@ $< $(MYCXXFLAGS) $(CXXFLAGS) -DSQLITE_CORE -DSQLITE_VEC_STATIC + $(ODIR)/%.oo: %.cpp $(HEADERS) $(CXX) -fPIC -c -o $@ $< $(MYCXXFLAGS) $(CXXFLAGS) diff --git a/lib/ProxySQL_Admin.cpp b/lib/ProxySQL_Admin.cpp index fde106045..3f0898aba 100644 --- a/lib/ProxySQL_Admin.cpp +++ b/lib/ProxySQL_Admin.cpp @@ -45,6 +45,7 @@ using json = nlohmann::json; #include "MySQL_Logger.hpp" #include "PgSQL_Logger.hpp" #include "MCP_Thread.h" +#include "ProxySQL_MCP_Server.hpp" #include "SQLite3_Server.h" #include "Web_Interface.hpp" @@ -3251,6 +3252,123 @@ void ProxySQL_Admin::load_restapi_server() { } } +void ProxySQL_Admin::load_mcp_server() { + if (!all_modules_started) { return; } + if (GloMCPH == NULL) { return; } + + // 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); + + if (port_free == false) { + if (e_port_check == -1) { + proxy_error("Unable to start MCP Server, failed to set 'SO_REUSEADDR' to check port availability.\n"); + } else if (e_port_check == -2) { + proxy_error("Unable to start MCP Server, invalid port check parameters.\n"); + } else { + proxy_error("Unable to start MCP Server, port '%d' already in use.\n", port); + } + } + }; + + // Check if MCP server is enabled and needs management + bool enabled = GloMCPH->variables.mcp_enabled; + + if (enabled) { + if (GloMCPH->mcp_server == NULL) { + // Start the server if not running + int port = GloMCPH->variables.mcp_port; + bool use_ssl = GloMCPH->variables.mcp_use_ssl; + + // Check SSL certificates if SSL mode is enabled + if (use_ssl) { + if (!GloVars.global.ssl_key_pem_mem || !GloVars.global.ssl_cert_pem_mem) { + proxy_error("MCP: Cannot start server in SSL mode - SSL certificates not loaded. " + "Please configure ssl_key_fp and ssl_cert_fp, or set mcp_use_ssl=false.\n"); + return; + } + } + + // Check port availability + bool port_free = false; + check_mcp_port(port, port_free); + + if (port_free) { + proxy_info("MCP: Starting server on port %d\n", port); + GloMCPH->mcp_server = new ProxySQL_MCP_Server(port, GloMCPH); + if (GloMCPH->mcp_server) { + GloMCPH->mcp_server->start(); + proxy_info("MCP: Server started successfully\n"); + } else { + proxy_error("MCP: Failed to create server instance\n"); + } + } + } else { + // Server is already running, check if restart is needed + int current_port = GloMCPH->variables.mcp_port; + int server_port = GloMCPH->mcp_server->get_port(); + bool current_use_ssl = GloMCPH->variables.mcp_use_ssl; + bool server_use_ssl = GloMCPH->mcp_server->is_using_ssl(); + + bool needs_restart = false; + std::string restart_reason; + + if (current_port != server_port) { + needs_restart = true; + restart_reason += "port (" + std::to_string(server_port) + " -> " + std::to_string(current_port) + ") "; + } + + if (current_use_ssl != server_use_ssl) { + needs_restart = true; + restart_reason += "SSL mode"; + } + + if (needs_restart) { + proxy_info("MCP: Configuration changed (%s), restarting server...\n", restart_reason.c_str()); + + proxy_info("MCP: Stopping server on port %d\n", server_port); + delete GloMCPH->mcp_server; + GloMCPH->mcp_server = NULL; + proxy_info("MCP: Old server deleted\n"); + + // Check SSL certificates if SSL mode is enabled + if (current_use_ssl) { + if (!GloVars.global.ssl_key_pem_mem || !GloVars.global.ssl_cert_pem_mem) { + proxy_error("MCP: Cannot start server in SSL mode - SSL certificates not loaded. " + "Please configure ssl_key_fp and ssl_cert_fp, or set mcp_use_ssl=false.\n"); + return; + } + } + + // Check port availability before starting new server + bool port_free = false; + check_mcp_port(current_port, port_free); + + if (port_free) { + proxy_info("MCP: Starting server on port %d\n", current_port); + GloMCPH->mcp_server = new ProxySQL_MCP_Server(current_port, GloMCPH); + if (GloMCPH->mcp_server) { + GloMCPH->mcp_server->start(); + proxy_info("MCP: Server restarted successfully\n"); + } else { + proxy_error("MCP: Failed to create server instance\n"); + } + } + } else { + proxy_info("MCP: Server already running, no configuration changes detected\n"); + } + } + } else { + // Stop the server if running and disabled + if (GloMCPH->mcp_server != NULL) { + proxy_info("MCP: Stopping server\n"); + delete GloMCPH->mcp_server; + GloMCPH->mcp_server = NULL; + proxy_info("MCP: Server stopped successfully\n"); + } + } +} + void ProxySQL_Admin::load_http_server() { if (!all_modules_started) { return; } diff --git a/lib/RAG_Tool_Handler.cpp b/lib/RAG_Tool_Handler.cpp index b680c0bfb..238f3f3c9 100644 --- a/lib/RAG_Tool_Handler.cpp +++ b/lib/RAG_Tool_Handler.cpp @@ -1180,12 +1180,12 @@ json RAG_Tool_Handler::execute_tool(const std::string& tool_name, const json& ar // Build FTS query with filters std::string sql = "SELECT c.chunk_id, c.doc_id, c.source_id, " "(SELECT name FROM rag_sources WHERE source_id = c.source_id) as source_name, " - "c.title, bm25(f) as score_fts_raw, " + "c.title, bm25(rag_fts_chunks) as score_fts_raw, " "c.metadata_json, c.body " "FROM rag_fts_chunks f " "JOIN rag_chunks c ON c.chunk_id = f.chunk_id " "JOIN rag_documents d ON d.doc_id = c.doc_id " - "WHERE f MATCH '" + escape_fts_query(query) + "'"; + "WHERE rag_fts_chunks MATCH '" + escape_fts_query(query) + "'"; // Apply filters using consolidated filter building function if (!build_sql_filters(filters, sql)) { @@ -1536,12 +1536,12 @@ json RAG_Tool_Handler::execute_tool(const std::string& tool_name, const json& ar // Run FTS search with filters std::string fts_sql = "SELECT c.chunk_id, c.doc_id, c.source_id, " "(SELECT name FROM rag_sources WHERE source_id = c.source_id) as source_name, " - "c.title, bm25(f) as score_fts_raw, " + "c.title, bm25(rag_fts_chunks) as score_fts_raw, " "c.metadata_json " "FROM rag_fts_chunks f " "JOIN rag_chunks c ON c.chunk_id = f.chunk_id " "JOIN rag_documents d ON d.doc_id = c.doc_id " - "WHERE f MATCH '" + escape_fts_query(query) + "'"; + "WHERE rag_fts_chunks MATCH '" + escape_fts_query(query) + "'"; // Apply filters using consolidated filter building function if (!build_sql_filters(filters, fts_sql)) { @@ -1919,7 +1919,7 @@ json RAG_Tool_Handler::execute_tool(const std::string& tool_name, const json& ar "FROM rag_fts_chunks f " "JOIN rag_chunks c ON c.chunk_id = f.chunk_id " "JOIN rag_documents d ON d.doc_id = c.doc_id " - "WHERE f MATCH '" + escape_fts_query(query) + "'"; + "WHERE rag_fts_chunks MATCH '" + escape_fts_query(query) + "'"; // Apply filters using consolidated filter building function if (!build_sql_filters(filters, fts_sql)) { @@ -2002,7 +2002,7 @@ json RAG_Tool_Handler::execute_tool(const std::string& tool_name, const json& ar fts_sql += " AND json_extract(d.metadata_json, '$.CreationDate') <= '" + created_before + "'"; } - fts_sql += " ORDER BY bm25(f) " + fts_sql += " ORDER BY bm25(rag_fts_chunks) " "LIMIT " + std::to_string(candidates_k); SQLite3_result* fts_result = execute_query(fts_sql.c_str()); diff --git a/lib/proxy_sqlite3_symbols.cpp b/lib/proxy_sqlite3_symbols.cpp index 600c8a116..b297e9603 100644 --- a/lib/proxy_sqlite3_symbols.cpp +++ b/lib/proxy_sqlite3_symbols.cpp @@ -1,4 +1,5 @@ #include "sqlite3.h" +#include "sqlite-vec.h" #include #include "sqlite3db.h" // Forward declarations for proxy types @@ -50,9 +51,10 @@ int (*proxy_sqlite3_prepare_v2)(sqlite3*, const char*, int, sqlite3_stmt**, cons int (*proxy_sqlite3_open_v2)(const char*, sqlite3**, int, const char*) = sqlite3_open_v2; int (*proxy_sqlite3_exec)(sqlite3*, const char*, int (*)(void*,int,char**,char**), void*, char**) = sqlite3_exec; -// Optional hooks used by sqlite-vec (function pointers will be set by LoadPlugin or remain NULL) -void (*proxy_sqlite3_vec_init)(sqlite3*, char**, const sqlite3_api_routines*) = NULL; -void (*proxy_sqlite3_rembed_init)(sqlite3*, char**, const sqlite3_api_routines*) = NULL; +// Hooks for sqlite-vec and sqlite-rembed +int (*proxy_sqlite3_vec_init)(sqlite3*, char**, const sqlite3_api_routines*) = sqlite3_vec_init; +// TODO: Fix sqlite-rembed header inclusion and assign the function pointer properly +int (*proxy_sqlite3_rembed_init)(sqlite3*, char**, const sqlite3_api_routines*) = NULL; // Internal helpers used by admin stats batching; keep defaults as NULL