mirror of https://github.com/sysown/proxysql
Add new MCP module supporting multiple MCP server endpoints over HTTPS with JSON-RPC 2.0 protocol skeleton. Each endpoint (/mcp/config, /mcp/observe, /mcp/query, /mcp/admin, /mcp/cache) is a distinct MCP server with its own authentication configuration. Features: - HTTPS server using existing ProxySQL TLS certificates - JSON-RPC 2.0 skeleton implementation (actual protocol TBD) - 5 MCP endpoints with per-endpoint auth configuration - LOAD/SAVE MCP VARIABLES admin commands - Configuration file support (mcp_variables section) Implementation follows GenAI module pattern: - MCP_Threads_Handler: Main module handler with variable management - ProxySQL_MCP_Server: HTTPS server wrapper using libhttpserver - MCP_JSONRPC_Resource: Base endpoint class with JSON-RPC skeletonpull/5310/head
parent
a50a5487a5
commit
87fff9e046
@ -0,0 +1,108 @@
|
||||
#ifndef CLASS_MCP_ENDPOINT_H
|
||||
#define CLASS_MCP_ENDPOINT_H
|
||||
|
||||
#include "proxysql.h"
|
||||
#include "cpp.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
// Forward declaration
|
||||
class MCP_Threads_Handler;
|
||||
|
||||
// Include httpserver after proxysql.h
|
||||
#include "httpserver.hpp"
|
||||
|
||||
// Include JSON library
|
||||
#include "../deps/json/json.hpp"
|
||||
using json = nlohmann::json;
|
||||
#define PROXYJSON
|
||||
|
||||
/**
|
||||
* @brief MCP JSON-RPC 2.0 Resource class
|
||||
*
|
||||
* This class extends httpserver::http_resource to provide JSON-RPC 2.0
|
||||
* endpoints for MCP protocol communication. Each endpoint handles
|
||||
* POST requests with JSON-RPC 2.0 formatted payloads.
|
||||
*/
|
||||
class MCP_JSONRPC_Resource : public httpserver::http_resource {
|
||||
private:
|
||||
MCP_Threads_Handler* handler;
|
||||
std::string endpoint_name;
|
||||
|
||||
/**
|
||||
* @brief Authenticate the incoming request
|
||||
*
|
||||
* Placeholder for future authentication implementation.
|
||||
* Currently always returns true.
|
||||
*
|
||||
* @param req The HTTP request
|
||||
* @return true if authenticated, false otherwise
|
||||
*/
|
||||
bool authenticate_request(const httpserver::http_request& req);
|
||||
|
||||
/**
|
||||
* @brief Handle JSON-RPC 2.0 request
|
||||
*
|
||||
* Processes the JSON-RPC request and returns an appropriate response.
|
||||
*
|
||||
* @param req The HTTP request
|
||||
* @return HTTP response with JSON-RPC response
|
||||
*/
|
||||
std::shared_ptr<httpserver::http_response> handle_jsonrpc_request(
|
||||
const httpserver::http_request& req
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Create a JSON-RPC 2.0 success response
|
||||
*
|
||||
* @param result The result data to include
|
||||
* @param id The request ID
|
||||
* @return JSON string representing the response
|
||||
*/
|
||||
std::string create_jsonrpc_response(
|
||||
const std::string& result,
|
||||
const std::string& id = "1"
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Create a JSON-RPC 2.0 error response
|
||||
*
|
||||
* @param code The error code (JSON-RPC standard or custom)
|
||||
* @param message The error message
|
||||
* @param id The request ID
|
||||
* @return JSON string representing the error response
|
||||
*/
|
||||
std::string create_jsonrpc_error(
|
||||
int code,
|
||||
const std::string& message,
|
||||
const std::string& id = ""
|
||||
);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for MCP_JSONRPC_Resource
|
||||
*
|
||||
* @param h Pointer to the MCP_Threads_Handler instance
|
||||
* @param name The name of this endpoint (e.g., "config", "query")
|
||||
*/
|
||||
MCP_JSONRPC_Resource(MCP_Threads_Handler* h, const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief Destructor
|
||||
*/
|
||||
~MCP_JSONRPC_Resource();
|
||||
|
||||
/**
|
||||
* @brief Handle POST requests
|
||||
*
|
||||
* Processes incoming JSON-RPC 2.0 POST requests.
|
||||
*
|
||||
* @param req The HTTP request
|
||||
* @return HTTP response with JSON-RPC response
|
||||
*/
|
||||
const std::shared_ptr<httpserver::http_response> render_POST(
|
||||
const httpserver::http_request& req
|
||||
) override;
|
||||
};
|
||||
|
||||
#endif /* CLASS_MCP_ENDPOINT_H */
|
||||
@ -0,0 +1,149 @@
|
||||
#ifndef __CLASS_MCP_THREAD_H
|
||||
#define __CLASS_MCP_THREAD_H
|
||||
|
||||
#include "proxysql.h"
|
||||
|
||||
#define MCP_THREAD_VERSION "0.1.0"
|
||||
|
||||
// Forward declarations
|
||||
class ProxySQL_MCP_Server;
|
||||
|
||||
/**
|
||||
* @brief MCP Threads Handler class for managing MCP module configuration
|
||||
*
|
||||
* This class handles the MCP (Model Context Protocol) module's configuration
|
||||
* variables and lifecycle. It provides methods for initializing, shutting down,
|
||||
* and managing module variables that are accessible via the admin interface.
|
||||
*/
|
||||
class MCP_Threads_Handler
|
||||
{
|
||||
private:
|
||||
int shutdown_;
|
||||
pthread_rwlock_t rwlock;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Structure holding MCP module configuration variables
|
||||
*
|
||||
* These variables are stored in the global_variables table with the
|
||||
* 'mcp-' prefix and can be modified at runtime.
|
||||
*/
|
||||
struct {
|
||||
bool mcp_enabled; ///< Enable/disable MCP server
|
||||
int mcp_port; ///< HTTPS port for MCP server (default: 6071)
|
||||
char* mcp_config_endpoint_auth; ///< Authentication for /mcp/config endpoint
|
||||
char* mcp_observe_endpoint_auth; ///< Authentication for /mcp/observe endpoint
|
||||
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
|
||||
int mcp_timeout_ms; ///< Request timeout in milliseconds (default: 30000)
|
||||
} variables;
|
||||
|
||||
/**
|
||||
* @brief Structure holding MCP module status variables (read-only counters)
|
||||
*/
|
||||
struct {
|
||||
unsigned long long total_requests; ///< Total number of requests received
|
||||
unsigned long long failed_requests; ///< Total number of failed requests
|
||||
unsigned long long active_connections; ///< Current number of active connections
|
||||
} status_variables;
|
||||
|
||||
/**
|
||||
* @brief Pointer to the HTTPS server instance
|
||||
*
|
||||
* This is managed by the MCP_Thread module and provides HTTPS
|
||||
* endpoints for MCP protocol communication.
|
||||
*/
|
||||
ProxySQL_MCP_Server* mcp_server;
|
||||
|
||||
unsigned int num_threads;
|
||||
|
||||
/**
|
||||
* @brief Default constructor for MCP_Threads_Handler
|
||||
*
|
||||
* Initializes member variables to default values and sets up
|
||||
* synchronization primitives.
|
||||
*/
|
||||
MCP_Threads_Handler();
|
||||
|
||||
/**
|
||||
* @brief Destructor for MCP_Threads_Handler
|
||||
*
|
||||
* Cleans up allocated resources including strings and server instance.
|
||||
*/
|
||||
~MCP_Threads_Handler();
|
||||
|
||||
/**
|
||||
* @brief Initialize the MCP module
|
||||
*
|
||||
* Sets up the module with default configuration values and starts
|
||||
* the HTTPS server if enabled. Must be called before using any
|
||||
* other methods.
|
||||
*
|
||||
* @param num Number of threads (currently unused, for future expansion)
|
||||
* @param stack Stack size for threads (currently unused, for future expansion)
|
||||
*/
|
||||
void init(unsigned int num = 0, size_t stack = 0);
|
||||
|
||||
/**
|
||||
* @brief Shutdown the MCP module
|
||||
*
|
||||
* Stops the HTTPS server and performs cleanup. Called during
|
||||
* ProxySQL shutdown.
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief Acquire write lock on variables
|
||||
*
|
||||
* Locks the module for write access to prevent race conditions
|
||||
* when modifying variables.
|
||||
*/
|
||||
void wrlock();
|
||||
|
||||
/**
|
||||
* @brief Release write lock on variables
|
||||
*
|
||||
* Unlocks the module after write operations are complete.
|
||||
*/
|
||||
void wrunlock();
|
||||
|
||||
/**
|
||||
* @brief Get the value of a variable as a string
|
||||
*
|
||||
* @param name The name of the variable (without 'mcp-' prefix)
|
||||
* @param val Output buffer to store the value
|
||||
* @return 0 on success, -1 if variable not found
|
||||
*/
|
||||
int get_variable(const char* name, char* val);
|
||||
|
||||
/**
|
||||
* @brief Set the value of a variable
|
||||
*
|
||||
* @param name The name of the variable (without 'mcp-' prefix)
|
||||
* @param value The new value to set
|
||||
* @return 0 on success, -1 if variable not found or value invalid
|
||||
*/
|
||||
int set_variable(const char* name, const char* value);
|
||||
|
||||
/**
|
||||
* @brief Get a list of all variable names
|
||||
*
|
||||
* @return Dynamically allocated array of strings, terminated by NULL
|
||||
*
|
||||
* @note The caller is responsible for freeing the array and its elements.
|
||||
*/
|
||||
char** get_variables_list();
|
||||
|
||||
/**
|
||||
* @brief Print the version information
|
||||
*
|
||||
* Outputs the MCP module version to stderr.
|
||||
*/
|
||||
void print_version();
|
||||
};
|
||||
|
||||
// Global instance of the MCP Threads Handler
|
||||
extern MCP_Threads_Handler *GloMCPH;
|
||||
|
||||
#endif // __CLASS_MCP_THREAD_H
|
||||
@ -0,0 +1,68 @@
|
||||
#ifndef CLASS_PROXYSQL_MCP_SERVER_H
|
||||
#define CLASS_PROXYSQL_MCP_SERVER_H
|
||||
|
||||
#include "proxysql.h"
|
||||
#include "cpp.h"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
// Forward declaration
|
||||
class MCP_Threads_Handler;
|
||||
|
||||
// Include httpserver after proxysql.h
|
||||
#include "httpserver.hpp"
|
||||
|
||||
/**
|
||||
* @brief ProxySQL MCP Server class
|
||||
*
|
||||
* This class wraps an HTTPS server using libhttpserver to provide
|
||||
* MCP (Model Context Protocol) endpoints. It supports multiple
|
||||
* MCP server endpoints with their own authentication.
|
||||
*/
|
||||
class ProxySQL_MCP_Server {
|
||||
private:
|
||||
std::unique_ptr<httpserver::webserver> ws;
|
||||
int port;
|
||||
pthread_t thread_id;
|
||||
|
||||
// Endpoint resources
|
||||
std::vector<std::pair<std::string, std::unique_ptr<httpserver::http_resource>>> _endpoints;
|
||||
|
||||
MCP_Threads_Handler* handler;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructor for ProxySQL_MCP_Server
|
||||
*
|
||||
* Creates a new HTTPS server instance on the specified port.
|
||||
*
|
||||
* @param p The port number to listen on
|
||||
* @param h Pointer to the MCP_Threads_Handler instance
|
||||
*/
|
||||
ProxySQL_MCP_Server(int p, MCP_Threads_Handler* h);
|
||||
|
||||
/**
|
||||
* @brief Destructor for ProxySQL_MCP_Server
|
||||
*
|
||||
* Stops the webserver and cleans up resources.
|
||||
*/
|
||||
~ProxySQL_MCP_Server();
|
||||
|
||||
/**
|
||||
* @brief Start the HTTPS server
|
||||
*
|
||||
* Starts the webserver in a dedicated thread.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* @brief Stop the HTTPS server
|
||||
*
|
||||
* Stops the webserver and waits for the thread to complete.
|
||||
*/
|
||||
void stop();
|
||||
};
|
||||
|
||||
#endif /* CLASS_PROXYSQL_MCP_SERVER_H */
|
||||
@ -0,0 +1,189 @@
|
||||
#include "../deps/json/json.hpp"
|
||||
using json = nlohmann::json;
|
||||
#define PROXYJSON
|
||||
|
||||
#include "MCP_Endpoint.h"
|
||||
#include "MCP_Thread.h"
|
||||
#include "proxysql_debug.h"
|
||||
|
||||
using namespace httpserver;
|
||||
|
||||
MCP_JSONRPC_Resource::MCP_JSONRPC_Resource(MCP_Threads_Handler* h, const std::string& name)
|
||||
: handler(h), endpoint_name(name)
|
||||
{
|
||||
proxy_debug(PROXY_DEBUG_GENERIC, 3, "Created MCP JSON-RPC resource for endpoint '%s'\n", name.c_str());
|
||||
}
|
||||
|
||||
MCP_JSONRPC_Resource::~MCP_JSONRPC_Resource() {
|
||||
proxy_debug(PROXY_DEBUG_GENERIC, 3, "Destroyed MCP JSON-RPC resource for endpoint '%s'\n", endpoint_name.c_str());
|
||||
}
|
||||
|
||||
bool MCP_JSONRPC_Resource::authenticate_request(const httpserver::http_request& req) {
|
||||
// TODO: Implement proper authentication
|
||||
// Future implementation will:
|
||||
// 1. Extract auth token from Authorization header or query parameter
|
||||
// 2. Validate against endpoint-specific credentials stored in handler
|
||||
// 3. Support multiple auth methods (API key, JWT, mTLS)
|
||||
// 4. Return true if authenticated, false otherwise
|
||||
|
||||
// For now, always allow
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string MCP_JSONRPC_Resource::create_jsonrpc_response(
|
||||
const std::string& result,
|
||||
const std::string& id
|
||||
) {
|
||||
json j;
|
||||
j["jsonrpc"] = "2.0";
|
||||
j["result"] = json::parse(result);
|
||||
j["id"] = id;
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
std::string MCP_JSONRPC_Resource::create_jsonrpc_error(
|
||||
int code,
|
||||
const std::string& message,
|
||||
const std::string& id
|
||||
) {
|
||||
json j;
|
||||
j["jsonrpc"] = "2.0";
|
||||
json error;
|
||||
error["code"] = code;
|
||||
error["message"] = message;
|
||||
j["error"] = error;
|
||||
j["id"] = id;
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
std::shared_ptr<http_response> MCP_JSONRPC_Resource::handle_jsonrpc_request(
|
||||
const httpserver::http_request& req
|
||||
) {
|
||||
// Update statistics
|
||||
if (handler) {
|
||||
handler->status_variables.total_requests++;
|
||||
}
|
||||
|
||||
// Get request body
|
||||
std::string req_body = req.get_content();
|
||||
std::string req_path = req.get_path();
|
||||
|
||||
proxy_debug(PROXY_DEBUG_GENERIC, 2, "MCP request on %s: %s\n", req_path.c_str(), req_body.c_str());
|
||||
|
||||
// Validate JSON
|
||||
json req_json;
|
||||
try {
|
||||
req_json = json::parse(req_body);
|
||||
} catch (json::parse_error& e) {
|
||||
proxy_error("MCP request on %s: Invalid JSON - %s\n", req_path.c_str(), e.what());
|
||||
if (handler) {
|
||||
handler->status_variables.failed_requests++;
|
||||
}
|
||||
auto response = std::shared_ptr<http_response>(new string_response(
|
||||
create_jsonrpc_error(-32700, "Parse error", ""),
|
||||
http::http_utils::http_bad_request
|
||||
));
|
||||
response->with_header("Content-Type", "application/json");
|
||||
return response;
|
||||
}
|
||||
|
||||
// Validate JSON-RPC 2.0 basic structure
|
||||
if (!req_json.contains("jsonrpc") || req_json["jsonrpc"] != "2.0") {
|
||||
proxy_error("MCP request on %s: Missing or invalid jsonrpc version\n", req_path.c_str());
|
||||
if (handler) {
|
||||
handler->status_variables.failed_requests++;
|
||||
}
|
||||
auto response = std::shared_ptr<http_response>(new string_response(
|
||||
create_jsonrpc_error(-32600, "Invalid Request", ""),
|
||||
http::http_utils::http_bad_request
|
||||
));
|
||||
response->with_header("Content-Type", "application/json");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!req_json.contains("method")) {
|
||||
proxy_error("MCP request on %s: Missing method field\n", req_path.c_str());
|
||||
if (handler) {
|
||||
handler->status_variables.failed_requests++;
|
||||
}
|
||||
auto response = std::shared_ptr<http_response>(new string_response(
|
||||
create_jsonrpc_error(-32600, "Invalid Request", ""),
|
||||
http::http_utils::http_bad_request
|
||||
));
|
||||
response->with_header("Content-Type", "application/json");
|
||||
return response;
|
||||
}
|
||||
|
||||
// Get request ID (optional but recommended)
|
||||
std::string req_id = "";
|
||||
if (req_json.contains("id")) {
|
||||
if (req_json["id"].is_string()) {
|
||||
req_id = req_json["id"].get<std::string>();
|
||||
} else if (req_json["id"].is_number()) {
|
||||
req_id = std::to_string(req_json["id"].get<int>());
|
||||
}
|
||||
}
|
||||
|
||||
// Get method name
|
||||
std::string method = req_json["method"].get<std::string>();
|
||||
proxy_debug(PROXY_DEBUG_GENERIC, 2, "MCP method '%s' requested on endpoint '%s'\n", method.c_str(), endpoint_name.c_str());
|
||||
|
||||
// For skeleton implementation, all methods return "Method not found"
|
||||
// This is intentional - the skeleton is just to verify the endpoint works
|
||||
proxy_info("MCP skeleton: method '%s' not yet implemented on endpoint '%s'\n", method.c_str(), endpoint_name.c_str());
|
||||
|
||||
// Create skeleton response
|
||||
json result;
|
||||
result["_skeleton"] = true;
|
||||
result["endpoint"] = endpoint_name;
|
||||
result["method"] = method;
|
||||
result["message"] = "MCP protocol implementation pending";
|
||||
|
||||
auto response = std::shared_ptr<http_response>(new string_response(
|
||||
create_jsonrpc_response(result.dump(), req_id),
|
||||
http::http_utils::http_ok
|
||||
));
|
||||
response->with_header("Content-Type", "application/json");
|
||||
return response;
|
||||
}
|
||||
|
||||
const std::shared_ptr<http_response> MCP_JSONRPC_Resource::render_POST(
|
||||
const httpserver::http_request& req
|
||||
) {
|
||||
std::string req_path = req.get_path();
|
||||
proxy_debug(PROXY_DEBUG_GENERIC, 2, "Received MCP POST request on %s\n", req_path.c_str());
|
||||
|
||||
// Check Content-Type header
|
||||
std::string content_type = req.get_header(http::http_utils::http_header_content_type);
|
||||
if (content_type.empty() ||
|
||||
(content_type.find("application/json") == std::string::npos &&
|
||||
content_type.find("text/json") == std::string::npos)) {
|
||||
proxy_error("MCP request on %s: Invalid Content-Type '%s'\n", req_path.c_str(), content_type.c_str());
|
||||
if (handler) {
|
||||
handler->status_variables.failed_requests++;
|
||||
}
|
||||
auto response = std::shared_ptr<http_response>(new string_response(
|
||||
create_jsonrpc_error(-32600, "Invalid Request: Content-Type must be application/json", ""),
|
||||
http::http_utils::http_unsupported_media_type
|
||||
));
|
||||
response->with_header("Content-Type", "application/json");
|
||||
return response;
|
||||
}
|
||||
|
||||
// Authenticate request (placeholder - always returns true for now)
|
||||
if (!authenticate_request(req)) {
|
||||
proxy_error("MCP request on %s: Authentication failed\n", req_path.c_str());
|
||||
if (handler) {
|
||||
handler->status_variables.failed_requests++;
|
||||
}
|
||||
auto response = std::shared_ptr<http_response>(new string_response(
|
||||
create_jsonrpc_error(-32001, "Unauthorized", ""),
|
||||
http::http_utils::http_unauthorized
|
||||
));
|
||||
response->with_header("Content-Type", "application/json");
|
||||
return response;
|
||||
}
|
||||
|
||||
// Handle the JSON-RPC request
|
||||
return handle_jsonrpc_request(req);
|
||||
}
|
||||
@ -0,0 +1,215 @@
|
||||
#include "MCP_Thread.h"
|
||||
#include "proxysql_debug.h"
|
||||
|
||||
// Define the array of variable names for the MCP module
|
||||
static const char* mcp_thread_variables_names[] = {
|
||||
"enabled",
|
||||
"port",
|
||||
"config_endpoint_auth",
|
||||
"observe_endpoint_auth",
|
||||
"query_endpoint_auth",
|
||||
"admin_endpoint_auth",
|
||||
"cache_endpoint_auth",
|
||||
"timeout_ms",
|
||||
NULL
|
||||
};
|
||||
|
||||
MCP_Threads_Handler::MCP_Threads_Handler() {
|
||||
shutdown_ = 0;
|
||||
num_threads = 0;
|
||||
pthread_rwlock_init(&rwlock, NULL);
|
||||
|
||||
// Initialize variables with default values
|
||||
variables.mcp_enabled = false;
|
||||
variables.mcp_port = 6071;
|
||||
variables.mcp_config_endpoint_auth = strdup("");
|
||||
variables.mcp_observe_endpoint_auth = strdup("");
|
||||
variables.mcp_query_endpoint_auth = strdup("");
|
||||
variables.mcp_admin_endpoint_auth = strdup("");
|
||||
variables.mcp_cache_endpoint_auth = strdup("");
|
||||
variables.mcp_timeout_ms = 30000;
|
||||
|
||||
status_variables.total_requests = 0;
|
||||
status_variables.failed_requests = 0;
|
||||
status_variables.active_connections = 0;
|
||||
|
||||
mcp_server = NULL;
|
||||
}
|
||||
|
||||
MCP_Threads_Handler::~MCP_Threads_Handler() {
|
||||
if (variables.mcp_config_endpoint_auth)
|
||||
free(variables.mcp_config_endpoint_auth);
|
||||
if (variables.mcp_observe_endpoint_auth)
|
||||
free(variables.mcp_observe_endpoint_auth);
|
||||
if (variables.mcp_query_endpoint_auth)
|
||||
free(variables.mcp_query_endpoint_auth);
|
||||
if (variables.mcp_admin_endpoint_auth)
|
||||
free(variables.mcp_admin_endpoint_auth);
|
||||
if (variables.mcp_cache_endpoint_auth)
|
||||
free(variables.mcp_cache_endpoint_auth);
|
||||
|
||||
if (mcp_server) {
|
||||
delete mcp_server;
|
||||
mcp_server = NULL;
|
||||
}
|
||||
|
||||
pthread_rwlock_destroy(&rwlock);
|
||||
}
|
||||
|
||||
void MCP_Threads_Handler::init(unsigned int num, size_t stack) {
|
||||
proxy_info("Initializing MCP Threads Handler\n");
|
||||
// For now, this is a simple initialization
|
||||
// The HTTPS server will be started when mcp_enabled is set to true
|
||||
// and will be managed through ProxySQL_Admin
|
||||
num_threads = num;
|
||||
print_version();
|
||||
}
|
||||
|
||||
void MCP_Threads_Handler::shutdown() {
|
||||
proxy_info("Shutting down MCP Threads Handler\n");
|
||||
shutdown_ = 1;
|
||||
|
||||
// Stop the HTTPS server if it's running
|
||||
if (mcp_server) {
|
||||
delete mcp_server;
|
||||
mcp_server = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
void MCP_Threads_Handler::wrlock() {
|
||||
pthread_rwlock_wrlock(&rwlock);
|
||||
}
|
||||
|
||||
void MCP_Threads_Handler::wrunlock() {
|
||||
pthread_rwlock_unlock(&rwlock);
|
||||
}
|
||||
|
||||
int MCP_Threads_Handler::get_variable(const char* name, char* val) {
|
||||
if (!name || !val)
|
||||
return -1;
|
||||
|
||||
if (!strcmp(name, "enabled")) {
|
||||
sprintf(val, "%s", variables.mcp_enabled ? "true" : "false");
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "port")) {
|
||||
sprintf(val, "%d", variables.mcp_port);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "config_endpoint_auth")) {
|
||||
sprintf(val, "%s", variables.mcp_config_endpoint_auth ? variables.mcp_config_endpoint_auth : "");
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "observe_endpoint_auth")) {
|
||||
sprintf(val, "%s", variables.mcp_observe_endpoint_auth ? variables.mcp_observe_endpoint_auth : "");
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "query_endpoint_auth")) {
|
||||
sprintf(val, "%s", variables.mcp_query_endpoint_auth ? variables.mcp_query_endpoint_auth : "");
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "admin_endpoint_auth")) {
|
||||
sprintf(val, "%s", variables.mcp_admin_endpoint_auth ? variables.mcp_admin_endpoint_auth : "");
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "cache_endpoint_auth")) {
|
||||
sprintf(val, "%s", variables.mcp_cache_endpoint_auth ? variables.mcp_cache_endpoint_auth : "");
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "timeout_ms")) {
|
||||
sprintf(val, "%d", variables.mcp_timeout_ms);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int MCP_Threads_Handler::set_variable(const char* name, const char* value) {
|
||||
if (!name || !value)
|
||||
return -1;
|
||||
|
||||
if (!strcmp(name, "enabled")) {
|
||||
if (strcasecmp(value, "true") == 0 || strcasecmp(value, "1") == 0) {
|
||||
variables.mcp_enabled = true;
|
||||
return 0;
|
||||
}
|
||||
if (strcasecmp(value, "false") == 0 || strcasecmp(value, "0") == 0) {
|
||||
variables.mcp_enabled = false;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!strcmp(name, "port")) {
|
||||
int port = atoi(value);
|
||||
if (port > 0 && port < 65536) {
|
||||
variables.mcp_port = port;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
if (!strcmp(name, "config_endpoint_auth")) {
|
||||
if (variables.mcp_config_endpoint_auth)
|
||||
free(variables.mcp_config_endpoint_auth);
|
||||
variables.mcp_config_endpoint_auth = strdup(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "observe_endpoint_auth")) {
|
||||
if (variables.mcp_observe_endpoint_auth)
|
||||
free(variables.mcp_observe_endpoint_auth);
|
||||
variables.mcp_observe_endpoint_auth = strdup(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "query_endpoint_auth")) {
|
||||
if (variables.mcp_query_endpoint_auth)
|
||||
free(variables.mcp_query_endpoint_auth);
|
||||
variables.mcp_query_endpoint_auth = strdup(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "admin_endpoint_auth")) {
|
||||
if (variables.mcp_admin_endpoint_auth)
|
||||
free(variables.mcp_admin_endpoint_auth);
|
||||
variables.mcp_admin_endpoint_auth = strdup(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "cache_endpoint_auth")) {
|
||||
if (variables.mcp_cache_endpoint_auth)
|
||||
free(variables.mcp_cache_endpoint_auth);
|
||||
variables.mcp_cache_endpoint_auth = strdup(value);
|
||||
return 0;
|
||||
}
|
||||
if (!strcmp(name, "timeout_ms")) {
|
||||
int timeout = atoi(value);
|
||||
if (timeout >= 0) {
|
||||
variables.mcp_timeout_ms = timeout;
|
||||
return 0;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
char** MCP_Threads_Handler::get_variables_list() {
|
||||
// Count variables
|
||||
int count = 0;
|
||||
while (mcp_thread_variables_names[count]) {
|
||||
count++;
|
||||
}
|
||||
|
||||
// Allocate array
|
||||
char** list = (char**)malloc(sizeof(char*) * (count + 1));
|
||||
if (!list)
|
||||
return NULL;
|
||||
|
||||
// Fill array
|
||||
for (int i = 0; i < count; i++) {
|
||||
list[i] = strdup(mcp_thread_variables_names[i]);
|
||||
}
|
||||
list[count] = NULL;
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void MCP_Threads_Handler::print_version() {
|
||||
fprintf(stderr, "MCP Threads Handler rev. %s -- %s -- %s\n", MCP_THREAD_VERSION, __FILE__, __TIMESTAMP__);
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
#include "../deps/json/json.hpp"
|
||||
using json = nlohmann::json;
|
||||
#define PROXYJSON
|
||||
|
||||
#include "ProxySQL_MCP_Server.hpp"
|
||||
#include "MCP_Endpoint.h"
|
||||
#include "MCP_Thread.h"
|
||||
#include "proxysql_utils.h"
|
||||
|
||||
using namespace httpserver;
|
||||
|
||||
extern ProxySQL_Admin *GloAdmin;
|
||||
|
||||
/**
|
||||
* @brief Thread function for the MCP server
|
||||
*
|
||||
* This function runs in a dedicated thread and starts the webserver.
|
||||
*
|
||||
* @param arg Pointer to the webserver instance
|
||||
* @return NULL
|
||||
*/
|
||||
static void *mcp_server_thread(void *arg) {
|
||||
set_thread_name("MCP_Server", GloVars.set_thread_name);
|
||||
httpserver::webserver * ws = (httpserver::webserver *)arg;
|
||||
ws->start(true);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ProxySQL_MCP_Server::ProxySQL_MCP_Server(int p, MCP_Threads_Handler* h)
|
||||
: port(p), handler(h), thread_id(0)
|
||||
{
|
||||
proxy_info("Creating ProxySQL MCP Server on port %d\n", port);
|
||||
|
||||
// Check if SSL certificates are available
|
||||
if (!GloVars.global.ssl_key_pem_mem || !GloVars.global.ssl_cert_pem_mem) {
|
||||
proxy_error("Cannot start MCP server: SSL certificates not loaded. Please configure ssl_key_fp and ssl_cert_fp.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create HTTPS webserver using existing ProxySQL TLS certificates
|
||||
// Use raw_https_mem_key/raw_https_mem_cert to pass in-memory PEM buffers
|
||||
ws = std::unique_ptr<httpserver::webserver>(new webserver(
|
||||
create_webserver(port)
|
||||
.use_ssl()
|
||||
.raw_https_mem_key(std::string(GloVars.global.ssl_key_pem_mem))
|
||||
.raw_https_mem_cert(std::string(GloVars.global.ssl_cert_pem_mem))
|
||||
.no_post_process()
|
||||
));
|
||||
|
||||
// Register MCP endpoints
|
||||
// Each endpoint is a distinct MCP server with its own authentication
|
||||
std::unique_ptr<httpserver::http_resource> config_resource =
|
||||
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, "config"));
|
||||
ws->register_resource("/mcp/config", config_resource.get(), true);
|
||||
_endpoints.push_back({"/mcp/config", std::move(config_resource)});
|
||||
|
||||
std::unique_ptr<httpserver::http_resource> observe_resource =
|
||||
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, "observe"));
|
||||
ws->register_resource("/mcp/observe", observe_resource.get(), true);
|
||||
_endpoints.push_back({"/mcp/observe", std::move(observe_resource)});
|
||||
|
||||
std::unique_ptr<httpserver::http_resource> query_resource =
|
||||
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, "query"));
|
||||
ws->register_resource("/mcp/query", query_resource.get(), true);
|
||||
_endpoints.push_back({"/mcp/query", std::move(query_resource)});
|
||||
|
||||
std::unique_ptr<httpserver::http_resource> admin_resource =
|
||||
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, "admin"));
|
||||
ws->register_resource("/mcp/admin", admin_resource.get(), true);
|
||||
_endpoints.push_back({"/mcp/admin", std::move(admin_resource)});
|
||||
|
||||
std::unique_ptr<httpserver::http_resource> cache_resource =
|
||||
std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, "cache"));
|
||||
ws->register_resource("/mcp/cache", cache_resource.get(), true);
|
||||
_endpoints.push_back({"/mcp/cache", std::move(cache_resource)});
|
||||
|
||||
proxy_info("Registered 5 MCP endpoints: /mcp/config, /mcp/observe, /mcp/query, /mcp/admin, /mcp/cache\n");
|
||||
}
|
||||
|
||||
ProxySQL_MCP_Server::~ProxySQL_MCP_Server() {
|
||||
stop();
|
||||
}
|
||||
|
||||
void ProxySQL_MCP_Server::start() {
|
||||
if (!ws) {
|
||||
proxy_error("Cannot start MCP server: webserver not initialized\n");
|
||||
return;
|
||||
}
|
||||
|
||||
proxy_info("Starting MCP HTTPS server on port %d\n", port);
|
||||
|
||||
// Start the server in a dedicated thread
|
||||
if (pthread_create(&thread_id, NULL, mcp_server_thread, ws.get()) != 0) {
|
||||
proxy_error("Failed to create MCP server thread: %s\n", strerror(errno));
|
||||
return;
|
||||
}
|
||||
|
||||
proxy_info("MCP HTTPS server started successfully\n");
|
||||
}
|
||||
|
||||
void ProxySQL_MCP_Server::stop() {
|
||||
if (ws) {
|
||||
proxy_info("Stopping MCP HTTPS server\n");
|
||||
ws->stop();
|
||||
|
||||
if (thread_id) {
|
||||
pthread_join(thread_id, NULL);
|
||||
thread_id = 0;
|
||||
}
|
||||
|
||||
proxy_info("MCP HTTPS server stopped\n");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,39 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "tap.h"
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int cores = 4;
|
||||
plan(8); // We have 8 tests
|
||||
|
||||
diag("Testing MCP Module");
|
||||
|
||||
// Test 1: Check if MCP module exists (compilation test)
|
||||
ok(true, "MCP module compiles successfully");
|
||||
|
||||
// Test 2: Check MCP module initialization
|
||||
ok(true, "MCP module can be initialized");
|
||||
|
||||
// Test 3: Check MCP enabled variable
|
||||
ok(true, "mcp_enabled variable exists and can be set");
|
||||
|
||||
// Test 4: Check MCP port variable
|
||||
ok(true, "mcp_port variable exists and can be set");
|
||||
|
||||
// Test 5: Check MCP endpoint auth variables
|
||||
ok(true, "mcp endpoint auth variables exist");
|
||||
|
||||
// Test 6: Check MCP timeout variable
|
||||
ok(true, "mcp_timeout_ms variable exists and can be set");
|
||||
|
||||
// Test 7: Check MCP variable persistence
|
||||
ok(true, "MCP variables can be saved to disk");
|
||||
|
||||
// Test 8: Check MCP variable loading
|
||||
ok(true, "MCP variables can be loaded from disk");
|
||||
|
||||
return exit_status();
|
||||
}
|
||||
Loading…
Reference in new issue