Skip to content

MCP Server HTTP/SSL Toggle, FTS5 Search Fix & Enhanced Transport Support#16

Merged
renecannao merged 13 commits intoProxySQL:v3.1-vecfrom
rahim-kanji:v3.1_mcp-http-ssl-toggle
Jan 20, 2026
Merged

MCP Server HTTP/SSL Toggle, FTS5 Search Fix & Enhanced Transport Support#16
renecannao merged 13 commits intoProxySQL:v3.1-vecfrom
rahim-kanji:v3.1_mcp-http-ssl-toggle

Conversation

@rahim-kanji
Copy link
Copy Markdown

Overview

This PR introduces significant enhancements to the MCP (Model Context Protocol) server implementation in ProxySQL, including flexible HTTP/HTTPS mode support, automatic server restart on configuration changes, improved JSON-RPC 2.0 compliance, proper FTS5 full-text search implementation, and enhanced test coverage

  1. Plain HTTP Support - New mcp_use_ssl configuration variable allows switching between HTTP and HTTPS modes
  2. Automatic Server Restart - Server now automatically restarts when SSL mode or port configuration changes
  3. MCP HTTP Transport Compliance - Added GET/OPTIONS handlers for proper CORS preflight support
  4. JSON-RPC 2.0 Spec Compliance - Fixed ID parameter to support string, number, or null types
  5. FTS5 Search Fix - Catalog search now uses FTS5 MATCH with BM25 ranking instead of slow LIKE queries
  6. Enhanced Test Suite - Added 8 new FTS5-specific tests with SSL/HTTP auto-detection

Summary of Changes

1. HTTP/HTTPS Mode Toggle Support

New Configuration Variable: mcp_use_ssl

Previously, the MCP server only supported HTTPS mode, requiring valid SSL certificates to start. This added friction for development environments and made it difficult to test without proper certificate infrastructure.

What Changed:

  • Added mcp_use_ssl configuration variable (default: true for security)
  • HTTP mode now works without requiring SSL certificates
  • Clean separation between HTTP (development) and HTTPS (production) modes
  • Updated all logging to reflect the actual mode in use

Configuration:

-- Enable HTTPS mode (requires SSL certificates)
SET mcp-use_ssl = true;

-- Enable HTTP mode (no certificates required - development only)
SET mcp-use_ssl = false;

Via configure_mcp.sh:

./scripts/mcp/configure_mcp.sh --use-ssl    # HTTPS mode
./scripts/mcp/configure_mcp.sh --no-ssl     # HTTP mode

2. Automatic Server Restart on Configuration Changes

Previously, changing mcp_port or SSL settings would appear to succeed but silently fail to take effect until a manual ProxySQL restart.

What Changed:

  • Server now detects changes to mcp_port and restarts automatically
  • Server now detects changes to mcp_use_ssl and restarts automatically
  • Proper validation before restart (check SSL certificates if enabling HTTPS)
  • Graceful shutdown of old server instance before starting new one
  • Detailed logging of what changed and why restart is occurring

Files Modified:

  • lib/Admin_FlushVariables.cpp - Added restart detection logic in flush_mcp_variables___database_to_runtime() and flush_mcp_variables___runtime_to_database()

3. MCP HTTP Transport Compliance

Added GET and OPTIONS Handlers

The MCP specification defines requirements for HTTP transport beyond just POST requests. This implementation now complies with:

  • GET requests: Returns HTTP 405 Method Not Allowed per MCP spec (SSE streaming not supported)
  • OPTIONS requests: Returns proper CORS headers for cross-origin requests

What Changed:

  • MCP_JSONRPC_Resource::render_GET() - Returns 405 with Allow: POST header
  • MCP_JSONRPC_Resource::render_OPTIONS() - CORS preflight support with proper headers

Files Modified:

  • include/MCP_Endpoint.h - New method declarations
  • lib/MCP_Endpoint.cpp - Implementation with proper HTTP status codes

4. JSON-RPC 2.0 Specification Compliance

Fixed ID Parameter Type

The JSON-RPC 2.0 specification defines the id field as:

"A request identifier. This can be of type string, number, or null if included."

Previously, the implementation only accepted string IDs, causing protocol violations for numeric or null IDs.

What Changed:

  • Changed create_jsonrpc_response() and create_jsonrpc_error() to accept const json& instead of const std::string&
  • Request ID extraction now preserves the original JSON type (string, number, or null)
  • Response generation preserves the ID type from the request

Files Modified:

  • include/MCP_Endpoint.h - Updated function signatures
  • lib/MCP_Endpoint.cpp - Updated implementation

5. MCP notifications/initialized Support

The MCP specification defines notifications/initialized as a special notification sent by clients after the initialization handshake.

What Changed:

  • Added explicit handling for notifications/initialized method
  • Returns HTTP 202 Accepted with empty JSON object {} per MCP spec
  • Properly identified as a notification (no response expected)

6. FTS5 Full-Text Search Fix with BM25 Ranking

Critical Performance Fix

The catalog search was creating an FTS5 virtual table (catalog_fts) but the search() function was still using slow LIKE queries instead of FTS5's MATCH operator. This defeated the purpose of having an FTS index.

What Changed:

Before:

SELECT kind, key, document, tags, links
FROM catalog
WHERE 1=1
  AND kind = 'table'
  AND tags LIKE '%tags%'
  AND (key LIKE '%query%' OR document LIKE '%query%' OR tags LIKE '%query%')
ORDER BY updated_at DESC

After:

SELECT c.kind, c.key, c.document, c.tags, c.links
FROM catalog c
INNER JOIN catalog_fts f ON c.id = f.rowid
WHERE catalog_fts MATCH 'query'
  AND c.kind = 'table'
  AND c.tags LIKE '%tags%'
ORDER BY bm25(f) ASC, c.updated_at DESC

Benefits:

  • Full-text search using FTS5 MATCH operator
  • BM25 relevance ranking for better result quality
  • Query now required (returns empty array if no query provided)
  • Performance improvement from O(n) to O(log n) for searches

Files Modified:

  • lib/MySQL_Catalog.cpp - Updated search() function

7. Enhanced Test Suite

Catalog Test Suite Expansion

Added 9 new tests specifically for FTS5 functionality:

Test ID Test Name Description
CAT013 Multi-term search Tests AND logic (multiple terms)
CAT014 Phrase search Tests quoted phrase search
CAT015 OR search Tests Boolean OR operator
CAT016 NOT search Tests Boolean NOT operator
CAT017 Kind filter + FTS5 Tests FTS5 with kind filter
CAT018 Prefix search Tests wildcard prefix matching
CAT019 Relevance ranking Verifies BM25 ranking works
CAT020 Tags filter + FTS5 Tests FTS5 with tags filter
CAT021 Empty query Verifies empty query handling

SSL/HTTP Auto-Detection:

  • Test script now auto-detects whether server uses HTTP or HTTPS
  • New --ssl and --no-ssl flags for explicit mode selection
  • MCP_USE_SSL environment variable support

Files Modified:

  • scripts/mcp/test_catalog.sh - 409 lines of new test code
  • scripts/mcp/configure_mcp.sh - SSL mode configuration support

Files Changed

File Lines Changed Description
lib/Admin_FlushVariables.cpp +126/-8 Auto-restart logic, SSL mode validation
lib/ProxySQL_MCP_Server.cpp +113/-35 HTTP/HTTPS mode, cleanup improvements
lib/MCP_Endpoint.cpp +75/-8 GET/OPTIONS handlers, JSON-RPC fixes
scripts/mcp/test_catalog.sh +409/-2 FTS5 tests, SSL auto-detection
scripts/mcp/configure_mcp.sh +49/-2 SSL configuration flags
lib/MySQL_Catalog.cpp +34/-25 FTS5 search with BM25 ranking
lib/MCP_Thread.cpp +21/-1 mcp_use_ssl variable handling
include/MCP_Endpoint.h +37/-4 New method declarations
include/ProxySQL_MCP_Server.hpp +27/-2 SSL tracking methods
include/MCP_Thread.h +9/-3 mcp_use_ssl variable
src/proxysql.cfg +3/-1 Configuration example

Configuration Examples

Development Mode (HTTP)

# Via configuration script
./scripts/mcp/configure_mcp.sh --no-ssl --enable

# Via SQL
mysql -h 127.0.0.1 -P 6032 -u admin -padmin
> SET mcp-use_ssl = false;
> LOAD MCP VARIABLES TO RUNTIME;

Production Mode (HTTPS)

# Via configuration script
./scripts/mcp/configure_mcp.sh --use-ssl --enable

# Via SQL
mysql -h 127.0.0.1 -P 6032 -u admin -padmin
> SET mcp-use_ssl = true;
> LOAD MCP VARIABLES TO RUNTIME;

Port Change (Auto-Restart)

-- Changing port now automatically restarts the server
SET mcp-port = 7071;
LOAD MCP VARIABLES TO RUNTIME;
-- Server restarts automatically on port 7071

Testing

Run All Catalog Tests (Auto-Detect SSL)

./scripts/mcp/test_catalog.sh

Force HTTP Mode

./scripts/mcp/test_catalog.sh --no-ssl

Force HTTPS Mode

./scripts/mcp/test_catalog.sh --ssl

Verbose Output

./scripts/mcp/test_catalog.sh -v

Backwards Compatibility

  • Default behavior: mcp_use_ssl=true maintains security default
  • Existing configurations: No breaking changes - HTTPS remains default
  • API compatibility: JSON-RPC responses remain compatible
  • Client requirements: No client changes required

Security Considerations

  • HTTP mode is explicitly labeled as "unencrypted" in all logs
  • Default to HTTPS: mcp_use_ssl=true is the default for security
  • SSL validation: Server refuses to start in HTTPS mode without certificates
  • Clear warnings: Log messages warn when using HTTP mode

Related Documentation


…e the

   MCP server was already running. The issue was caused by improper cleanup of
   handler objects during reinitialization.

   Root cause:
   - ProxySQL_MCP_Server destructor deletes mysql_tool_handler
   - The old code tried to delete handlers again after deleting the server,
     causing double-free corruption

   The fix properly handles handler lifecycle during reinitialization:
   1. Delete Query_Tool_Handler first (server destructor doesn't clean this)
   2. Delete the server (which also deletes MySQL_Tool_Handler via destructor)
   3. Delete other handlers (config/admin/cache/observe) created by old server
   4. Create new MySQL_Tool_Handler with updated configuration
   5. Create new Query_Tool_Handler
   6. Create new server (recreates all handlers with new endpoints)

   This ensures proper cleanup and prevents double-free issues while allowing
   runtime reconfiguration of MySQL connection parameters.
* Add full support for both HTTP and HTTPS modes in MCP server via the mcp_use_ssl configuration variable, enabling plain HTTP for development and HTTPS for production with proper certificate validation
* Server now automatically restarts when SSL mode or port configuration changes, fixing silent configuration failures where changes appeared to succeed but didn't take effect until manual restart.

Features:
   - Explicit support for HTTP mode (mcp_use_ssl=false) without SSL certificates
   - Explicit support for HTTPS mode (mcp_use_ssl=true) with certificate validation
   - Configurable via configure_mcp.sh with --no-ssl or --use-ssl flags
   - Settable via admin interface: SET mcp-use_ssl=true/false
   - Automatic restart detection for SSL mode changes (HTTP ↔ HTTPS)
   - Automatic restart detection for port changes (mcp_port)
- Add render_GET() returning 405 Method Not Allowed
- Add render_OPTIONS()
Change id parameter from string to json& to support JSON-RPC 2.0 spec (id can be string, number, or null)
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @rahim-kanji, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the MCP server in ProxySQL by introducing a flexible HTTP/HTTPS toggle, enabling automatic server restarts upon configuration changes, and improving compliance with MCP and JSON-RPC 2.0 specifications. A major performance enhancement was implemented for the catalog's full-text search, now leveraging FTS5 with BM25 ranking. These changes aim to improve usability, stability, and performance, supported by an expanded test suite.

Highlights

  • Flexible HTTP/HTTPS Mode for MCP Server: Introduced a new configuration variable mcp_use_ssl (defaulting to true) to allow switching the MCP server between HTTPS (secure) and plain HTTP (unencrypted) modes, simplifying development and testing without requiring SSL certificates.
  • Automatic Server Restart on Configuration Changes: The MCP server now automatically detects changes to mcp_port or mcp_use_ssl and gracefully restarts, ensuring configuration updates take effect immediately without manual intervention. This includes validation for SSL certificates if enabling HTTPS.
  • Enhanced MCP HTTP Transport Compliance: Added proper handling for HTTP GET and OPTIONS requests to comply with the MCP specification. GET requests now return a 405 Method Not Allowed, and OPTIONS requests provide correct CORS headers for preflight checks.
  • JSON-RPC 2.0 Specification Compliance: The JSON-RPC id parameter now correctly supports string, number, or null types, aligning with the JSON-RPC 2.0 specification and preventing protocol violations.
  • FTS5 Full-Text Search Fix with BM25 Ranking: Resolved a critical performance issue in the catalog search by switching from slow LIKE queries to the efficient FTS5 MATCH operator with BM25 relevance ranking, significantly improving search performance and result quality. An empty query now returns an error.
  • Support for 'notifications/initialized': Explicit handling for the notifications/initialized method has been added, returning an HTTP 202 Accepted response with an empty JSON object, as per the MCP specification for client initialization notifications.
  • Expanded Test Suite: The test suite has been significantly enhanced with 9 new FTS5-specific tests covering various search functionalities (multi-term, phrase, boolean, prefix, ranking, filtering). The test script also includes SSL/HTTP auto-detection and explicit mode selection flags.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 19, 2026

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Configurable MCP protocol (HTTP or HTTPS) with SSL toggle and status reporting.
    • Proper handling for GET (405), DELETE (405) and OPTIONS (CORS) requests.
    • JSON-RPC id accepts string, number, or null; responses omit id when null.
    • Empty prompts/resources lists returned where unsupported.
  • Bug Fixes

    • Reliable full server restart when port or SSL mode change.
    • Improved handler cleanup and clearer startup/shutdown logs.
  • Tests/Chores

    • CLI, config and test scripts detect/select SSL mode and report endpoint URL.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Adds nullable JSON id handling and explicit GET/OPTIONS/DELETE handlers to the MCP JSON‑RPC endpoint; introduces configurable MCP SSL (mcp_use_ssl) with conditional HTTP/HTTPS server creation and full server restart on port/SSL changes; enhances catalog FTS search and updates MCP scripts/tests for SSL and new tests.

Changes

Cohort / File(s) Summary
JSON‑RPC endpoint
include/MCP_Endpoint.h, lib/MCP_Endpoint.cpp
Change create_jsonrpc_response/create_jsonrpc_error id param from string to json (nullable). Add render_GET(), render_OPTIONS(), render_DELETE() handlers; parse/store id as JSON and echo it in errors/results; add private handlers for prompts/list and resources/list.
MCP SSL config & thread vars
include/MCP_Thread.h, lib/MCP_Thread.cpp, src/proxysql.cfg
Add exported mcp_use_ssl / use_ssl variable (default true in code, default false in config). Extend get/set variable logic to read/write use_ssl.
MCP server HTTP/HTTPS mode
include/ProxySQL_MCP_Server.hpp, lib/ProxySQL_MCP_Server.cpp
Add use_ssl member and accessors; conditionally create HTTPS (in‑memory certs) or HTTP server based on flag; update startup/stop logging; destructor now cleans all tool handlers.
Server lifecycle & restart
lib/Admin_FlushVariables.cpp
On port or SSL changes, stop, delete and recreate the MCP server instance (full restart) and log reason; only require certs when SSL enabled; update start/stop messages to reflect HTTP vs HTTPS.
Catalog full‑text search
lib/MySQL_Catalog.cpp
Require non‑empty query; escape for MATCH, use catalog_fts MATCH in prepared statement, bind params (kind, tags, limit, offset), order by BM25 then recency, safely parse document JSON per row.
Config & scripts
scripts/mcp/configure_mcp.sh, scripts/mcp/test_catalog.sh
Add MCP_USE_SSL CLI/options (--use-ssl/--no-ssl), persist mcp-use_ssl, auto/explicit SSL detection in tests, protocol-aware curl usage; test_catalog.sh adds many new FTS5 tests (CAT013–CAT021).
Misc / tool includes & handlers
lib/Admin_FlushVariables.cpp, lib/ProxySQL_MCP_Server.cpp
Add tool handler includes; rely on server destructor to clean handlers and recreate them on full server restart; adjust logging and restart semantics.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant MCP_Endpoint
  participant ToolHandlers
  Note right of MCP_Endpoint: rgba(100,149,237,0.5) HTTP handler (GET/OPTIONS/POST/DELETE)
  Client->>MCP_Endpoint: OPTIONS /mcp
  MCP_Endpoint-->>Client: 200 + CORS, Content-Type: application/json
  Client->>MCP_Endpoint: GET /mcp
  MCP_Endpoint-->>Client: 405 + Allow: POST
  Client->>MCP_Endpoint: POST (JSON‑RPC, id: json|null)
  MCP_Endpoint->>ToolHandlers: dispatch method (id passed as json)
  ToolHandlers-->>MCP_Endpoint: result or error (id preserved)
  MCP_Endpoint-->>Client: 200 + JSON‑RPC response (id echoed if not null)
Loading
sequenceDiagram
  participant AdminCLI
  participant ConfigStore
  participant MCP_Server
  participant ToolHandlers
  Note right of MCP_Server: rgba(144,238,144,0.5) Mode: use_ssl, port
  AdminCLI->>ConfigStore: set mcp_use_ssl / mcp_port
  ConfigStore-->>MCP_Server: notify config change
  MCP_Server->>MCP_Server: detect changed port or use_ssl
  MCP_Server->>MCP_Server: stop & delete webserver instance
  MCP_Server->>ToolHandlers: destructor cleans handlers
  MCP_Server->>MCP_Server: create webserver (HTTP or HTTPS)
  MCP_Server->>ToolHandlers: reinitialize handlers and bind endpoints
  MCP_Server-->>AdminCLI: "Server restarted" status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Poem

🐇 I hopped through headers, swapped string ids for json delight,
OPTIONS waved hello, GET said “not tonight.”
SSL found a burrow; when flags flipped, we rebuilt the gate,
Full‑text carrots scattered — search returns first rate.
Hop, restart, respond — the MCP meadow’s feeling great!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.15% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the three main components of the PR: MCP Server HTTP/SSL toggle, FTS5 search fix, and enhanced transport support.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, covering all major modifications including HTTP/SSL support, automatic restart, transport compliance, JSON-RPC fixes, FTS5 improvements, and test enhancements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces significant and valuable enhancements, including a flexible HTTP/HTTPS mode for the MCP server, automatic restarts on configuration changes, and a critical performance fix for FTS5 search. The increased compliance with JSON-RPC 2.0 and the MCP specification, along with the expanded test suite, are also great improvements.

However, I have identified a critical double-free bug in the server restart logic and a high-severity SQL injection vulnerability in the new FTS5 search implementation. Additionally, there are opportunities to reduce code duplication and remove a hardcoded path from the configuration file. Please address these issues to ensure the stability and security of the new features.

Comment on lines +1617 to +1648
// 1. Delete Query_Tool_Handler first (server destructor doesn't delete this)
if (GloMCPH->query_tool_handler) {
proxy_info("MCP: Deleting old Query Tool Handler\n");
delete GloMCPH->query_tool_handler;
GloMCPH->query_tool_handler = NULL;
}

// Create new tool handler with current configuration
proxy_info("MCP: Reinitializing MySQL Tool Handler with current configuration\n");
// 2. Stop and delete the server (server destructor also deletes MySQL_Tool_Handler)
proxy_info("MCP: Stopping and deleting old server\n");
delete GloMCPH->mcp_server;
GloMCPH->mcp_server = NULL;
// Note: mysql_tool_handler is already deleted by server destructor and set to NULL
proxy_info("MCP: Old server deleted\n");

// 3. Delete other handlers that were created by old server
// The server destructor doesn't clean these up, so we need to do it manually
if (GloMCPH->config_tool_handler) {
delete GloMCPH->config_tool_handler;
GloMCPH->config_tool_handler = NULL;
}
if (GloMCPH->admin_tool_handler) {
delete GloMCPH->admin_tool_handler;
GloMCPH->admin_tool_handler = NULL;
}
if (GloMCPH->cache_tool_handler) {
delete GloMCPH->cache_tool_handler;
GloMCPH->cache_tool_handler = NULL;
}
if (GloMCPH->observe_tool_handler) {
delete GloMCPH->observe_tool_handler;
GloMCPH->observe_tool_handler = NULL;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

This block of code manually deletes several tool handlers (query_tool_handler, config_tool_handler, etc.) before and after deleting GloMCPH->mcp_server. The destructor for ProxySQL_MCP_Server (in lib/ProxySQL_MCP_Server.cpp) has been updated in this same PR to clean up all these handlers. This will lead to the handlers being freed twice, which is a double-free bug.

The comments here are also misleading. For instance, line 1632 states the destructor doesn't clean up handlers, but the updated destructor does.

To fix this, you should remove this manual handler deletion logic. The call to delete GloMCPH->mcp_server; on line 1626 is sufficient to trigger the cleanup of all associated handlers.

Comment on lines +203 to 216
sql << "SELECT c.kind, c.key, c.document, c.tags, c.links "
<< "FROM catalog c "
<< "INNER JOIN catalog_fts f ON c.id = f.rowid "
<< "WHERE catalog_fts MATCH '" << query << "'";

// Add kind filter
if (!kind.empty()) {
sql << " AND kind = '" << kind << "'";
sql << " AND c.kind = '" << kind << "'";
}

// Add tags filter
if (!tags.empty()) {
sql << " AND tags LIKE '%" << tags << "%'";
sql << " AND c.tags LIKE '%" << tags << "%'";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The query, kind, and tags parameters are directly concatenated into the SQL query string, which creates a SQL injection vulnerability. An attacker could provide a malicious string to manipulate the query. For example, a query string like foo' OR 1=1 -- would alter the logic of the WHERE clause.

To fix this, you should use prepared statements with parameter binding. The SQLite3DB class appears to support this via prepare_v2 and bind functions. If that's not feasible, you must at least escape the input strings to prevent injection. A simple way to do this for SQLite is to replace every single quote (') with two single quotes ('').

src/proxysql.cfg Outdated
restart_on_missing_heartbeats=10
#set_thread_name=false
datadir="/var/lib/proxysql"
datadir="/home/rkanji/claude_code/proxysql_vec/proxysql-vec/src/"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This change hardcodes a user-specific absolute path for datadir. Default configuration files should use generic or relative paths to be portable across different environments. Please revert this to the original /var/lib/proxysql or another suitable default.

datadir="/var/lib/proxysql"

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@lib/Admin_FlushVariables.cpp`:
- Around line 1668-1692: The code currently constructs a Query_Tool_Handler
(GloMCPH->query_tool_handler = new Query_Tool_Handler(...)) and then later
constructs a ProxySQL_MCP_Server which also creates a Query_Tool_Handler when
mysql_tool_handler exists, causing a leak/overwrite; fix by removing the manual
creation of GloMCPH->query_tool_handler (or alternatively, add a null-check
guard in the server constructor so it doesn't overwrite an existing handler).
Concretely: either delete the block that new's Query_Tool_Handler and let
ProxySQL_MCP_Server(port, GloMCPH) instantiate it when needed, or modify
ProxySQL_MCP_Server's constructor to only create a Query_Tool_Handler if
GloMCPH->query_tool_handler is NULL, and ensure any allocated instance is
properly deleted before overwrite.

In `@lib/MySQL_Catalog.cpp`:
- Around line 191-219: The search(), list(), and delete_entry() methods
currently build SQL by concatenating user input (query, kind, tags, limit,
offset, key) which is vulnerable to SQL injection; change these to use
db->prepare_v2() with parameter placeholders (e.g., "WHERE catalog_fts MATCH ?
AND c.kind = ? AND c.tags LIKE ? ORDER BY bm25(f) ASC, c.updated_at DESC LIMIT ?
OFFSET ?"), then bind values with proxy_sqlite3_bind_text() for strings (wrap
tags with %...% before binding) and proxy_sqlite3_bind_int() for numeric
limit/offset, execute, and finalize the statement; follow the existing upsert()
and get() patterns for error checking and resource cleanup so no user input is
ever concatenated into the SQL string.

In `@src/proxysql.cfg`:
- Line 11: The sample config currently hardcodes a developer-specific datadir
value for the datadir setting; change the datadir entry (datadir=...) back to a
standard, portable default (e.g., the typical /var/lib/proxysql or another
project-standard placeholder) so the example config is not tied to a user path
and is portable for other environments.
- Line 64: The config sets mcp_use_ssl=false which conflicts with the documented
and runtime default of true; change the mcp_use_ssl setting to true and update
the inline comment to correctly state the secure default (e.g., "Enable/disable
SSL/TLS (default: true for security)"), and scan nearby related MySQL/MCP SSL
settings to ensure consistency with the runtime defaults and secure
configuration expectations.

Comment on lines +191 to +219
// FTS5 search requires a query
if (query.empty()) {
proxy_error("Catalog search requires a query parameter\n");
return "[]";
}

std::ostringstream sql;
sql << "SELECT kind, key, document, tags, links FROM catalog WHERE 1=1";
char* error = NULL;
int cols = 0, affected = 0;
SQLite3_result* resultset = NULL;

// FTS5 search with BM25 ranking
sql << "SELECT c.kind, c.key, c.document, c.tags, c.links "
<< "FROM catalog c "
<< "INNER JOIN catalog_fts f ON c.id = f.rowid "
<< "WHERE catalog_fts MATCH '" << query << "'";

// Add kind filter
if (!kind.empty()) {
sql << " AND kind = '" << kind << "'";
sql << " AND c.kind = '" << kind << "'";
}

// Add tags filter
if (!tags.empty()) {
sql << " AND tags LIKE '%" << tags << "%'";
sql << " AND c.tags LIKE '%" << tags << "%'";
}

// Add search query
if (!query.empty()) {
sql << " AND (key LIKE '%" << query << "%' "
<< "OR document LIKE '%" << query << "%' "
<< "OR tags LIKE '%" << query << "%')";
}

sql << " ORDER BY updated_at DESC LIMIT " << limit << " OFFSET " << offset;

char* error = NULL;
int cols = 0, affected = 0;
SQLite3_result* resultset = NULL;
// Order by relevance (BM25) and recency
sql << " ORDER BY bm25(f) ASC, c.updated_at DESC LIMIT " << limit << " OFFSET " << offset;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, locate and examine the file
cd /tmp/repo 2>/dev/null || find . -name "MySQL_Catalog.cpp" -type f | head -5

Repository: ProxySQL/proxysql-vec

Length of output: 88


🏁 Script executed:

# Read the file to verify the code context and surrounding function
find . -name "MySQL_Catalog.cpp" -type f -exec wc -l {} \;

Repository: ProxySQL/proxysql-vec

Length of output: 92


🏁 Script executed:

# Get the actual code around the specified lines
find . -name "MySQL_Catalog.cpp" -type f -exec sed -n '160,230p' {} +

Repository: ProxySQL/proxysql-vec

Length of output: 1806


🏁 Script executed:

# Search for the function containing this code to understand parameter sources
find . -name "MySQL_Catalog.cpp" -type f -exec sed -n '100,200p' {} +

Repository: ProxySQL/proxysql-vec

Length of output: 2681


🏁 Script executed:

# Check for any escaping/sanitization functions in the codebase
rg "escape|sanitize|prepared|bind" lib/ --type cpp -B 2 -A 2

Repository: ProxySQL/proxysql-vec

Length of output: 50378


🏁 Script executed:

# Look for other SQL query patterns in the same file
rg "ostringstream.*sql|sql <<|MATCH|FTS5" lib/MySQL_Catalog.cpp -B 2 -A 2

Repository: ProxySQL/proxysql-vec

Length of output: 1553


Use prepared statements with parameter binding instead of raw SQL concatenation for query, kind, tags, and limit/offset.

The search() method at lines 206, 210, and 215 concatenates user input directly into SQL, creating a SQL injection vulnerability. The same file already demonstrates the correct pattern in upsert() and get() methods using db->prepare_v2() and proxy_sqlite3_bind_text(). Apply the same approach here.

Additionally, the list() and delete_entry() methods have identical vulnerabilities with raw concatenation of kind and key parameters.

🤖 Prompt for AI Agents
In `@lib/MySQL_Catalog.cpp` around lines 191 - 219, The search(), list(), and
delete_entry() methods currently build SQL by concatenating user input (query,
kind, tags, limit, offset, key) which is vulnerable to SQL injection; change
these to use db->prepare_v2() with parameter placeholders (e.g., "WHERE
catalog_fts MATCH ? AND c.kind = ? AND c.tags LIKE ? ORDER BY bm25(f) ASC,
c.updated_at DESC LIMIT ? OFFSET ?"), then bind values with
proxy_sqlite3_bind_text() for strings (wrap tags with %...% before binding) and
proxy_sqlite3_bind_int() for numeric limit/offset, execute, and finalize the
statement; follow the existing upsert() and get() patterns for error checking
and resource cleanup so no user input is ever concatenated into the SQL string.

{
mcp_enabled=false
mcp_port=6071
mcp_use_ssl=false # Enable/disable SSL/TLS (default: true for security)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Align mcp_use_ssl with secure defaults and documentation.

Line 64 sets mcp_use_ssl=false but the comment says default is true. This also contradicts the runtime default (true) and weakens security for anyone using this example config.

🔐 Proposed fix
-mcp_use_ssl=false              # Enable/disable SSL/TLS (default: true for security)
+mcp_use_ssl=true               # Enable/disable SSL/TLS (default: true for security)
🤖 Prompt for AI Agents
In `@src/proxysql.cfg` at line 64, The config sets mcp_use_ssl=false which
conflicts with the documented and runtime default of true; change the
mcp_use_ssl setting to true and update the inline comment to correctly state the
secure default (e.g., "Enable/disable SSL/TLS (default: true for security)"),
and scan nearby related MySQL/MCP SSL settings to ensure consistency with the
runtime defaults and secure configuration expectations.

The catalog_fts FTS5 virtual table was being created but the search() function was using slow LIKE queries instead of FTS5 MATCH operator.

Changes to lib/MySQL_Catalog.cpp:
  - Use FTS5 MATCH with INNER JOIN to catalog_fts when query provided
  - Add BM25 relevance ranking (ORDER BY bm25(f) ASC)
  - Significant performance improvement: O(log n) vs O(n)

Changes to scripts/mcp/test_catalog.sh:
  - Add 8 new FTS5-specific tests (CAT013-CAT020):
    - Multi-term search (AND logic)
    - Phrase search with quotes
    - Boolean operators (OR, NOT)
    - Prefix search with wildcards
    - Kind and tags filter combinations
    - Relevance ranking verification
  - Add SSL/HTTP support with auto-detection
  - New options: --ssl, --no-ssl, MCP_USE_SSL env var
  - Fix endpoint path: /query -> /mcp/querywq
@rahim-kanji rahim-kanji force-pushed the v3.1_mcp-http-ssl-toggle branch from 11143d2 to f7397f6 Compare January 19, 2026 07:59
…ded path

This commit addresses three issues identified by code review:

1. CRITICAL: Fix double-free bug in MCP server restart logic
   - Remove manual handler deletions in Admin_FlushVariables.cpp
   - ProxySQL_MCP_Server destructor already properly cleans up all handlers
   - Previously caused crashes when toggling SSL mode or changing port
   - Simplified restart: delete server (destructor cleanup) → create new server
   - Verified with 10+ rapid SSL toggles without crashes

2. HIGH: Fix SQL injection vulnerability in catalog search
   - Rewrite MySQL_Catalog::search() to use prepared statements
   - Use parameter binding (proxy_sqlite3_bind_text/bind_int) for user input
   - Escape single quotes in FTS5 MATCH clause (doesn't support parameters)
   - Tested against multiple injection attempts (single quote, backslash,
     comments, UNION SELECT, kind/tags parameter injection)
   - All 21 catalog tests still pass with new implementation

3. MEDIUM: Fix hardcoded user-specific path in config
   - Revert datadir from user-specific absolute path to /var/lib/proxysql
   - Ensures portability across different environments

Testing:
- SSL toggle: 7 tests passed (HTTP↔HTTPS, port changes, stress test)
- SQL injection: 10 tests passed (various injection attempts blocked)
- Catalog functionality: 21 tests passed (FTS5, BM25 ranking, etc.)
- Total: 38 tests passed, 0 failed

Fixes issues identified in GitHub PR sysown#16 review.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@lib/Admin_FlushVariables.cpp`:
- Around line 1613-1636: The reinitialization path creates a new
ProxySQL_MCP_Server unconditionally and can start HTTPS without validating
certificates; before allocating GloMCPH->mcp_server in the else branch, check
GloMCPH->variables.mcp_use_ssl and perform the same certificate
loading/validation used in the server-NULL branch (the SSL cert checks around
the earlier create/start logic), log an error and skip creating/starting the
server if certs are missing or invalid, and ensure you do not call start() when
validation fails (update the else-branch around ProxySQL_MCP_Server construction
where GloMCPH->mcp_server is set).

In `@lib/MySQL_Catalog.cpp`:
- Around line 271-277: The JSON string fields are written without escaping in
the json-building expression in MySQL_Catalog.cpp (the stream that writes
"kind","key","tags","links"), so implement a helper like escape_json(const char*
str) that returns a std::string with quotes, backslashes, control chars
(newline, carriage return, tab) and other <0x20 bytes escaped (use \", \\, \n,
\r, \t, and \uXXXX for control bytes), then replace direct uses of kind_val,
key_val, tags_val, and links_val in the json << ... expression with
escape_json(kind_val), escape_json(key_val), escape_json(tags_val), and
escape_json(links_val) respectively so all string values are safe and JSON
remains well-formed.
♻️ Duplicate comments (1)
lib/MySQL_Catalog.cpp (1)

298-302: Note: SQL injection vulnerability remains in unchanged methods.

While the search() method now uses prepared statements for kind and tags, the list() method (here) and remove() method (line 387-391) still concatenate parameters directly into SQL. Consider addressing these in a follow-up.

🧹 Nitpick comments (3)
lib/MySQL_Catalog.cpp (2)

197-211: FTS5 query syntax characters are not escaped.

The escape_sql lambda only escapes single quotes for the SQL string literal, but FTS5 MATCH has its own query syntax with special operators (*, ", -, +, ^, AND, OR, NOT, NEAR, parentheses). User input like foo* or foo" OR bar could produce unexpected search behavior or FTS5 parse errors.

Consider stripping or escaping FTS5 metacharacters, or wrapping the query in double quotes to treat it as a literal phrase:

escaped_query = "\"" + escape_sql(query) + "\"";

Alternatively, filter to alphanumeric and whitespace only for stricter sanitization.


220-220: Redundant initialization.

param_count is initialized to 1 here but immediately reassigned on line 244. Remove this dead initialization.

♻️ Suggested fix
-	int param_count = 1;  // Track parameter binding position
-
 	// Add kind filter with parameter placeholder
 	if (!kind.empty()) {
 		sql << " AND c.kind = ?";
 	}
lib/Admin_FlushVariables.cpp (1)

1577-1647: Consider extracting MCP server lifecycle management into a helper function.

The server startup, restart, and stop logic is duplicated between flush_mcp_variables___database_to_runtime (lines 1371-1478) and flush_mcp_variables___runtime_to_database (lines 1577-1647). This duplication contributed to the SSL validation being missed in one path. A helper function like manage_mcp_server_state(bool enabled) would consolidate this logic and reduce maintenance burden.

Comment on lines 1613 to 1636
} else {
// Server is already running - check if MySQL configuration changed
// and reinitialize the tool handler if needed
proxy_info("MCP: Server already running, checking MySQL tool handler reinitialization\n");
if (GloMCPH->mysql_tool_handler) {
// Delete old handler
delete GloMCPH->mysql_tool_handler;
GloMCPH->mysql_tool_handler = NULL;
}
// Server is already running - need to stop, delete server, and recreate everything
proxy_info("MCP: Server already running, reinitializing\n");

// Create new tool handler with current configuration
proxy_info("MCP: Reinitializing MySQL Tool Handler with current configuration\n");
GloMCPH->mysql_tool_handler = new MySQL_Tool_Handler(
GloMCPH->variables.mcp_mysql_hosts ? GloMCPH->variables.mcp_mysql_hosts : "",
GloMCPH->variables.mcp_mysql_ports ? GloMCPH->variables.mcp_mysql_ports : "",
GloMCPH->variables.mcp_mysql_user ? GloMCPH->variables.mcp_mysql_user : "",
GloMCPH->variables.mcp_mysql_password ? GloMCPH->variables.mcp_mysql_password : "",
GloMCPH->variables.mcp_mysql_schema ? GloMCPH->variables.mcp_mysql_schema : "",
GloMCPH->variables.mcp_catalog_path ? GloMCPH->variables.mcp_catalog_path : ""
);

if (GloMCPH->mysql_tool_handler->init() != 0) {
proxy_error("MCP: Failed to reinitialize MySQL Tool Handler\n");
delete GloMCPH->mysql_tool_handler;
GloMCPH->mysql_tool_handler = NULL;
// 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_info("MCP: MySQL Tool Handler reinitialized successfully\n");
proxy_error("MCP: Failed to create new server instance\n");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing SSL certificate validation when reinitializing server.

When the server is already running and gets reinitialized, this code path unconditionally creates a new server without validating SSL certificates. Compare this to:

  • Lines 1584-1612 (server NULL case in same function): properly validates SSL certs
  • Lines 1440-1465 (database_to_runtime restart): properly validates SSL certs

If mcp_use_ssl=true but certificates are not loaded, this path will attempt to start an HTTPS server without the required certificates.

🐛 Proposed fix to add SSL certificate validation
 			} 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
 				proxy_info("MCP: Stopping and deleting old server\n");
 				delete GloMCPH->mcp_server;
 				GloMCPH->mcp_server = NULL;
 				proxy_info("MCP: Old server deleted\n");
 
 				// Create and start new server with current configuration
-				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");
+				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 {
+						proxy_info("MCP: Creating and starting new HTTPS 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: New server created and started successfully\n");
+						} else {
+							proxy_error("MCP: Failed to create new server instance\n");
+						}
+					}
 				} else {
-					proxy_error("MCP: Failed to create new server instance\n");
+					proxy_info("MCP: Creating and starting new 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: New server created and started successfully\n");
+					} else {
+						proxy_error("MCP: Failed to create new server instance\n");
+					}
 				}
 			}
🤖 Prompt for AI Agents
In `@lib/Admin_FlushVariables.cpp` around lines 1613 - 1636, The reinitialization
path creates a new ProxySQL_MCP_Server unconditionally and can start HTTPS
without validating certificates; before allocating GloMCPH->mcp_server in the
else branch, check GloMCPH->variables.mcp_use_ssl and perform the same
certificate loading/validation used in the server-NULL branch (the SSL cert
checks around the earlier create/start logic), log an error and skip
creating/starting the server if certs are missing or invalid, and ensure you do
not call start() when validation fails (update the else-branch around
ProxySQL_MCP_Server construction where GloMCPH->mcp_server is set).

rahim-kanji and others added 6 commits January 19, 2026 23:55
- Version 2024-11-05 only supports HTTP_SSE as transport.
- ProxySQL's MCP implemenation aligns more with the StreamableHTTP
  transport specified in version 2025-06-18.
- Support for SSE in StreamableHTTP transport is optional.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
- Fix incorrect method name for notification
- Handle all notification messages in a generic way
- Respond with HTTP 202 Accepted (no response body)

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
Issue
-----
- ProxySQL only supports the `tools` feature of the MCP protocol
  and does not support features such as `prompts` and `resources`.

- Although ProxySQL expresses this in its `initialize` response,
  (server capabilities list contains only the `tools` object),
  clients such as Warp Terminal ignore it and continue to send
  requests for methods such `prompts/list` and `resources/list`.

- Any response other than `HTTP 200 OK` is treated as an error
  and client fails to initialize.

Fix
---
- Handle prompt and resource list requests by returning an empty array.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
- Respond with 405 Method Not Allowed, when clients send DELETE
  request for session termination.

Signed-off-by: Wazir Ahmed <wazir@proxysql.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@lib/MCP_Endpoint.cpp`:
- Line 117: The Allow header values are inconsistent: update the
response->with_header("Allow", ...) calls so render_GET and render_DELETE
advertise the same allowed methods; locate the calls in render_GET and
render_DELETE and change the header value to the common set (e.g., "POST,
OPTIONS") so both handlers return identical Allow listings.
🧹 Nitpick comments (1)
lib/MCP_Endpoint.cpp (1)

126-148: Consider security implications of wildcard CORS origin.

The Access-Control-Allow-Origin: "*" allows any origin to make requests to this endpoint. While this may be intentional for broad MCP client compatibility, consider whether a configurable or more restrictive origin policy is appropriate for your deployment scenarios.

Also, the static analysis hint about null pointer at line 138 is a false positive—C++ new throws std::bad_alloc on allocation failure rather than returning null.

"",
http::http_utils::http_method_not_allowed // 405
));
response->with_header("Allow", "POST"); // Tell client what IS allowed
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Inconsistent Allow header values between GET and DELETE handlers.

render_GET returns Allow: POST while render_DELETE returns Allow: POST, OPTIONS. For consistency and accuracy, both should advertise the same set of allowed methods.

Proposed fix
 	auto response = std::shared_ptr<http_response>(new string_response(
 		"",
 		http::http_utils::http_method_not_allowed  // 405
 	));
-	response->with_header("Allow", "POST");  // Tell client what IS allowed
+	response->with_header("Allow", "POST, OPTIONS");  // Tell client what IS allowed

Also applies to: 162-162

🤖 Prompt for AI Agents
In `@lib/MCP_Endpoint.cpp` at line 117, The Allow header values are inconsistent:
update the response->with_header("Allow", ...) calls so render_GET and
render_DELETE advertise the same allowed methods; locate the calls in render_GET
and render_DELETE and change the header value to the common set (e.g., "POST,
OPTIONS") so both handlers return identical Allow listings.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds flexible HTTP/HTTPS mode support to the MCP server, implements automatic server restart on configuration changes, improves JSON-RPC 2.0 compliance, fixes FTS5 full-text search implementation, and significantly enhances test coverage. The changes enable development-friendly HTTP mode while maintaining production-ready HTTPS as the default, with comprehensive protocol compliance improvements.

Changes:

  • Added mcp_use_ssl configuration variable to toggle between HTTP and HTTPS modes
  • Implemented automatic server restart when port or SSL mode changes
  • Fixed FTS5 search to use MATCH operator with BM25 ranking instead of LIKE queries
  • Added GET/OPTIONS/DELETE handlers for proper MCP HTTP transport compliance
  • Added 9 new FTS5-specific tests with SSL/HTTP auto-detection

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/proxysql.cfg Added mcp_use_ssl configuration variable with example
scripts/mcp/test_catalog.sh Added SSL auto-detection and 9 new FTS5 tests (CAT013-CAT021)
scripts/mcp/configure_mcp.sh Added --use-ssl/--no-ssl flags and SSL-aware testing
lib/ProxySQL_MCP_Server.cpp Implemented conditional HTTP/HTTPS server creation based on mcp_use_ssl
lib/MySQL_Catalog.cpp Replaced LIKE queries with FTS5 MATCH and BM25 ranking
lib/MCP_Endpoint.cpp Added GET/OPTIONS/DELETE handlers, fixed JSON-RPC ID type handling, added prompts/list and resources/list
lib/Admin_FlushVariables.cpp Added automatic restart detection for port and SSL mode changes
lib/MCP_Thread.cpp Added mcp_use_ssl variable getter/setter with true/false/1/0 support
include/ProxySQL_MCP_Server.hpp Added get_port() and is_using_ssl() methods, updated documentation
include/MCP_Thread.h Added mcp_use_ssl variable declaration
include/MCP_Endpoint.h Updated create_jsonrpc_response/error signatures to accept json ID type, added new method declarations

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

));
response->with_header("Content-Type", "application/json");
return response;
return response;
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inconsistent indentation: the return statement has an extra tab compared to the lines above it. Should align with the previous lines at the same indentation level.

Suggested change
return response;
return response;

Copilot uses AI. Check for mistakes.
{
mcp_enabled=false
mcp_port=6071
mcp_use_ssl=false # Enable/disable SSL/TLS (default: true for security)
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default value for mcp_use_ssl is set to false, but the comment states "default: true for security". This contradicts the code in lib/MCP_Thread.cpp line 46 which sets the default to true, and also contradicts the security-first principle mentioned throughout the PR description.

Setting the default to false in the configuration file is dangerous as it means users who copy this example config will unknowingly run the MCP server in unencrypted HTTP mode. The value should be set to true to match the code default and the security intent.

Suggested change
mcp_use_ssl=false # Enable/disable SSL/TLS (default: true for security)
mcp_use_ssl=true # Enable/disable SSL/TLS (default: true for security)

Copilot uses AI. Check for mistakes.
Comment on lines +198 to +219
// Helper lambda to escape single quotes for SQLite SQL literals
auto escape_sql = [](const std::string& str) -> std::string {
std::string result;
result.reserve(str.length() * 2); // Reserve space for potential escaping
for (char c : str) {
if (c == '\'') {
result += '\''; // Escape single quote by doubling it
}
result += c;
}
return result;
};

// Escape query for use in FTS5 MATCH (MATCH doesn't support parameter binding)
std::string escaped_query = escape_sql(query);

// Build SQL query with placeholders for parameters
std::ostringstream sql;
sql << "SELECT kind, key, document, tags, links FROM catalog WHERE 1=1";
sql << "SELECT c.kind, c.key, c.document, c.tags, c.links "
<< "FROM catalog c "
<< "INNER JOIN catalog_fts f ON c.id = f.rowid "
<< "WHERE catalog_fts MATCH '" << escaped_query << "'";
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The custom SQL escaping implementation for FTS5 MATCH queries is insufficient and potentially vulnerable to FTS5 injection attacks. While doubling single quotes prevents basic SQL injection, FTS5 has its own query syntax with special operators (OR, AND, NOT, *, quotes, parentheses, etc.) that can be exploited.

For example, a malicious query like OR 1=1 or * could alter the search semantics. The comment states "MATCH doesn't support parameter binding," but this is incorrect. SQLite FTS5 MATCH does support parameter binding using the following pattern:

WHERE catalog_fts MATCH ?

Then bind the query parameter normally. This is the recommended and secure approach per SQLite documentation.

Copilot uses AI. Check for mistakes.
result["protocolVersion"] = "2025-06-18";
result["capabilities"]["tools"] = json::object(); // Explicitly declare tools support
result["serverInfo"] = {
{"name", "proxysql-mcp-mcp-mysql-tools"},
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a typo in the server name. It should be "proxysql-mcp-mysql-tools" but it currently says "proxysql-mcp-mcp-mysql-tools" with a duplicated "mcp".

Suggested change
{"name", "proxysql-mcp-mcp-mysql-tools"},
{"name", "proxysql-mcp-mysql-tools"},

Copilot uses AI. Check for mistakes.
Comment on lines 1628 to 1651
} else {
// Server is already running - check if MySQL configuration changed
// and reinitialize the tool handler if needed
proxy_info("MCP: Server already running, checking MySQL tool handler reinitialization\n");
if (GloMCPH->mysql_tool_handler) {
// Delete old handler
delete GloMCPH->mysql_tool_handler;
GloMCPH->mysql_tool_handler = NULL;
}
// Server is already running - need to stop, delete server, and recreate everything
proxy_info("MCP: Server already running, reinitializing\n");

// Create new tool handler with current configuration
proxy_info("MCP: Reinitializing MySQL Tool Handler with current configuration\n");
GloMCPH->mysql_tool_handler = new MySQL_Tool_Handler(
GloMCPH->variables.mcp_mysql_hosts ? GloMCPH->variables.mcp_mysql_hosts : "",
GloMCPH->variables.mcp_mysql_ports ? GloMCPH->variables.mcp_mysql_ports : "",
GloMCPH->variables.mcp_mysql_user ? GloMCPH->variables.mcp_mysql_user : "",
GloMCPH->variables.mcp_mysql_password ? GloMCPH->variables.mcp_mysql_password : "",
GloMCPH->variables.mcp_mysql_schema ? GloMCPH->variables.mcp_mysql_schema : "",
GloMCPH->variables.mcp_catalog_path ? GloMCPH->variables.mcp_catalog_path : ""
);

if (GloMCPH->mysql_tool_handler->init() != 0) {
proxy_error("MCP: Failed to reinitialize MySQL Tool Handler\n");
delete GloMCPH->mysql_tool_handler;
GloMCPH->mysql_tool_handler = NULL;
// 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_info("MCP: MySQL Tool Handler reinitialized successfully\n");
proxy_error("MCP: Failed to create new server instance\n");
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The runtime_to_database flush function unconditionally recreates the MCP server when it's already running, without checking if SSL certificates are available when SSL mode is enabled. This differs from the database_to_runtime function (lines 1418-1483) which properly validates SSL certificates before restarting.

The server recreation at lines 1644-1645 should be wrapped in SSL certificate validation logic similar to the database_to_runtime flow, to prevent the server from failing to start after deletion if SSL certificates aren't available.

Copilot uses AI. Check for mistakes.
Comment on lines +173 to 175
response=$(curl -s -X POST "${proto}://${PROXYSQL_ADMIN_HOST}:${MCP_PORT}/mcp/config" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","method":"ping","id":1}' 2>/dev/null || echo "")
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The curl command for testing the MCP server in HTTPS mode is missing the -k flag to allow self-signed certificates. This will cause the test to fail when using self-signed certificates (which is the typical development setup). The test should use -k flag when proto is "https", similar to how it's done in test_catalog.sh at line 82.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +56
if (!ssl_key || !ssl_cert) {
proxy_error("Cannot start MCP 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;
}
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When SSL certificates are not available in SSL mode, the constructor returns early (line 55) but continues to initialize tool handlers (lines 77-189) even though the webserver (ws) was never created. This results in an object in an inconsistent state where tool handlers exist but the webserver doesn't.

While start() and stop() methods safely check for null ws, this wastes resources by creating handlers for a server that cannot start. Consider returning early before initializing any tool handlers, or wrapping all handler initialization in a check that ws was successfully created.

Copilot uses AI. Check for mistakes.
@renecannao renecannao merged commit 9311477 into ProxySQL:v3.1-vec Jan 20, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants