Skip to content

Comments

Add MCP endpoint for stats#5341

Open
wazir-ahmed wants to merge 10 commits intov3.0from
v4.0-mcp-stats
Open

Add MCP endpoint for stats#5341
wazir-ahmed wants to merge 10 commits intov3.0from
v4.0-mcp-stats

Conversation

@wazir-ahmed
Copy link
Member

@wazir-ahmed wazir-ahmed commented Feb 4, 2026

  • Introduces a new /mcp/stats MCP endpoint (replacing the previous /mcp/observe endpoint) that provides 19 tools for monitoring, diagnostics, and performance analysis of ProxySQL through the MCP protocol.

Live Data Tools

  1. show_status - Global status variables
  2. show_processlist - Active sessions
  3. show_queries - Query digest stats
  4. show_commands - Command execution stats
  5. show_connections - Backend connection pool metrics
  6. show_errors - Error tracking stats
  7. show_users - Per-user connection stats
  8. show_client_cache - Client host cache
  9. show_query_rules - Query rule hit stats
  10. show_prepared_statements - Prepared statement info
  11. show_gtid - GTID replication info
  12. show_cluster - Cluster node health

Historical Data Tools

  1. show_system_history - CPU/memory usage trends
  2. show_query_cache_history - Query cache metrics
  3. show_connection_history - Historical connection metrics
  4. show_query_history - Historical query digests

Utility Tools

  1. flush_query_log - Flush query events to tables
  2. show_query_log - Individual query events
  3. flush_queries - Save digests to disk

Summary by CodeRabbit

  • New Features

    • Added a /mcp/stats endpoint exposing a comprehensive JSON-centric monitoring toolkit (health, metrics, queries, connections, errors, users, cache, GTID, rules, cluster, historical analysis, aggregations).
  • Configuration

    • Replaced the observe endpoint with stats, added /mcp/ai and RAG auth entries, and introduced a consolidated GenAI feature flag with a CLI option.
  • Utilities

    • Added SQL-escaping and histogram-percentile utilities and improved resultset→JSON conversion.
  • Documentation

    • Added full specs, implementation guidance, and stats/tables schema docs.
  • Tests

    • Updated tests to reflect the stats endpoint and new auth vars.

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

coderabbitai bot commented Feb 4, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Replaces the Observe tool with a new Stats tool and wiring (endpoint, auth var, handler), adds a full in-process Stats_Tool_Handler implementation and header, SQLite→JSON helper, SQL/util helpers, GenAI guards/flag consolidation, three large documentation files, and related config/test/build updates. No public API signatures were removed; many new handler methods were added.

Changes

Cohort / File(s) Summary
Documentation
doc/MCP/MCP_Stats_Tools_Spec.md, doc/MCP/MCP_Stats_Implementation_Plan.md, doc/Stats_Tables.md
Adds three comprehensive docs: tools spec, implementation plan, and stats table/schema reference for the new /mcp/stats tools.
New Handler Declaration
include/Stats_Tool_Handler.h
Adds Stats_Tool_Handler class declaration: lifecycle, mutex, many tool handler method signatures, and helper method signatures.
Stats Tool Implementation
lib/Stats_Tool_Handler.cpp
New large implementation providing dispatch, admin/history DB helpers, many tool handlers (health, metrics, queries, connections, errors, history, cluster, utilities), JSON shaping, and centralized error responses.
SQLite→JSON Helper
include/MCP_Tool_Handler.h, lib/MCP_Tool_Handler.cpp
Adds resultset_to_json(SQLite3_result*, int) declaration and implementation to convert SQLite3_result rows into nlohmann::json arrays.
MCP & Endpoint Wiring
lib/ProxySQL_MCP_Server.cpp, lib/MCP_Thread.cpp, lib/MCP_Endpoint.cpp, include/MCP_Thread.h
Replaces Observe handler with Stats handler across init/registration/cleanup; renames endpoint and auth variable from observe→stats; adds /mcp/ai auth var; updates get/set variable handling.
Utilities
include/proxysql_utils.h, lib/proxysql_utils.cpp
Introduces sql_escape() and calculate_percentile_from_histogram() declarations/implementations for SQL quoting and histogram percentile calculation.
Build & Includes
lib/Makefile, include/cpp.h, include/proxysql.h, include/proxysql_glovars.hpp
Updates GenAI build objects to include MCP_Tool_Handler/MCP bits, swaps Observe→Stats includes, guards AI headers with PROXYSQLGENAI, and consolidates GenAI flags into global.genai.
Config & Tests
src/proxysql.cfg, test/tap/tests/mcp_module-t.cpp, lib/Admin_FlushVariables.cpp, lib/Config_Tool_Handler.cpp
Renames config key to mcp_stats_endpoint_auth, adds mcp_ai_endpoint_auth/mcp_rag_endpoint_auth, updates tests and variable counts, and adjusts include ordering/removals.
SQLite/Tool plumbing
include/MCP_Tool_Handler.h, lib/MCP_Tool_Handler.cpp, lib/MCP_Tool_Handler.cpp
Adds SQLite result parsing utility into MCP tool handler code and exposes it for reuse by Stats handler.
Startup & GenAI flow
lib/ProxySQL_GloVars.cpp, src/main.cpp
Adds --genai CLI option and global.genai; reorders GenAI/MCP init gating and adds GenAI module init function.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client
  participant MCP_Server as MCP_Server
  participant Stats_Handler as Stats_Handler
  participant Admin_DB as Admin_DB
  participant Stats_DB as Stats_DB
  participant JSON_Helper as JSON_Helper

  Client->>MCP_Server: JSON-RPC execute_tool(tool, args)
  MCP_Server->>Stats_Handler: execute_tool(tool, args)
  activate Stats_Handler
  Stats_Handler->>Stats_Handler: lock mutex
  alt live data tool
    Stats_Handler->>Admin_DB: execute_admin_query(sql)
    Admin_DB-->>Stats_Handler: SQLite3_result
  else historical data tool
    Stats_Handler->>Stats_DB: execute_statsdb_disk_query(sql)
    Stats_DB-->>Stats_Handler: SQLite3_result
  end
  Stats_Handler->>JSON_Helper: resultset_to_json(resultset, cols)
  JSON_Helper-->>Stats_Handler: json array
  Stats_Handler->>Stats_Handler: unlock mutex
  deactivate Stats_Handler
  Stats_Handler-->>MCP_Server: JSON-RPC result
  MCP_Server-->>Client: response
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • rahim-kanji

Poem

🐰
I hopped from Observe to Stats with glee,
Mutex snug, I guard each query,
Digests, history, metrics bright,
Docs and SQL through the night,
JSON carrots make my day 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 65.31% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add MCP endpoint for stats' clearly and specifically describes the main change: adding a new MCP endpoint for stats (replacing observe), which is the central focus of this large changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch v4.0-mcp-stats

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@gemini-code-assist
Copy link

Summary of Changes

Hello @wazir-ahmed, 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 enhances ProxySQL's monitoring and diagnostic capabilities by introducing a dedicated /mcp/stats endpoint. This new endpoint consolidates and expands upon existing statistics, offering a rich set of tools for real-time operational insights and historical performance analysis. The changes involve refactoring the MCP handler structure, adding new utility functions, and implementing a wide array of specialized data retrieval and aggregation functionalities.

Highlights

  • New MCP Endpoint for Stats: Introduced a new /mcp/stats endpoint, replacing the previous /mcp/observe endpoint, to provide comprehensive monitoring and diagnostic tools for ProxySQL.
  • Comprehensive Monitoring Tools: Added 17 new tools accessible via the /mcp/stats endpoint, covering areas such as health checks, process list, metrics, query digests, connection pools, error tracking, and cluster status.
  • Advanced Analysis & Historical Data: New tools include command execution statistics with latency percentiles, per-user connection stats, client host cache analysis, GTID replication info, query rule hit counts, and historical connection/query digest trends.
  • SQL Utility Functions: Implemented sql_escape for safe SQL string insertion and calculate_percentile_from_histogram for deriving percentiles from latency buckets.
  • Generic Resultset to JSON Conversion: Added a static utility method resultset_to_json to MCP_Tool_Handler for converting SQLite3 query results into a structured JSON array, simplifying data output for all tools.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • include/MCP_Thread.h
    • Renamed Observe_Tool_Handler class to Stats_Tool_Handler.
    • Updated mcp_observe_endpoint_auth member to mcp_stats_endpoint_auth.
    • Updated observe_tool_handler pointer to stats_tool_handler.
  • include/MCP_Tool_Handler.h
    • Reordered include directives for cpp.h and <memory>.
    • Updated comments to reflect the new stats endpoint instead of observe.
    • Added declaration for static json resultset_to_json(SQLite3_result* resultset, int cols);.
  • include/Stats_Tool_Handler.h
    • Added new header file defining the Stats_Tool_Handler class.
    • Declared 17 new tool handling methods (e.g., handle_get_health, handle_show_processlist).
    • Declared helper methods for executing admin/statsdb queries and parsing global stats.
  • include/proxysql_utils.h
    • Added declaration for std::string sql_escape(const std::string& input);.
    • Added declaration for int calculate_percentile_from_histogram(...).
  • lib/Admin_FlushVariables.cpp
    • Removed several specific MCP_Tool_Handler includes (MySQL_Tool_Handler, Query_Tool_Handler, Config_Tool_Handler, Admin_Tool_Handler, Cache_Tool_Handler, Observe_Tool_Handler) as they are no longer directly needed in this file.
  • lib/MCP_Endpoint.cpp
    • Changed endpoint name check from "observe" to "stats" for authentication.
    • Updated reference to handler->variables.mcp_observe_endpoint_auth to handler->variables.mcp_stats_endpoint_auth.
  • lib/MCP_Thread.cpp
    • Updated include from Observe_Tool_Handler.h to Stats_Tool_Handler.h.
    • Renamed mcp_observe_endpoint_auth in mcp_thread_variables_names to mcp_stats_endpoint_auth.
    • Initialized variables.mcp_stats_endpoint_auth instead of variables.mcp_observe_endpoint_auth.
    • Updated observe_tool_handler pointer to stats_tool_handler in constructor and destructor.
    • Modified get_variable and set_variable logic to use stats_endpoint_auth instead of observe_endpoint_auth.
  • lib/MCP_Tool_Handler.cpp
    • Added new source file implementing MCP_Tool_Handler::resultset_to_json.
    • This function converts SQLite3 results into a JSON array of objects, handling numeric and null values appropriately.
  • lib/Makefile
    • Added MCP_Tool_Handler.oo to the _OBJ_CXX list.
    • Replaced Observe_Tool_Handler.oo with Stats_Tool_Handler.oo in the _OBJ_CXX list.
  • lib/ProxySQL_MCP_Server.cpp
    • Updated include from Observe_Tool_Handler.h to Stats_Tool_Handler.h.
    • Changed initialization of handler->observe_tool_handler to handler->stats_tool_handler.
    • Updated endpoint registration from /mcp/observe to /mcp/stats.
    • Modified endpoint list string from /mcp/observe to /mcp/stats.
    • Updated cleanup logic to delete handler->stats_tool_handler instead of handler->observe_tool_handler.
  • lib/Stats_Tool_Handler.cpp
    • Added new source file implementing the Stats_Tool_Handler class.
    • Includes implementations for all 17 new stats tools (e.g., handle_get_health, handle_show_processlist, handle_show_queries).
    • Implements helper methods execute_admin_query, execute_statsdb_disk_query, parse_global_stats, and is_valid_stats_table.
  • lib/proxysql_utils.cpp
    • Implemented sql_escape function to safely escape single quotes and backslashes in strings for SQL insertion.
    • Implemented calculate_percentile_from_histogram function to approximate percentile values from histogram bucket counts.
  • src/proxysql.cfg
    • Updated configuration variable mcp_observe_endpoint_auth to mcp_stats_endpoint_auth.
  • test/tap/tests/mcp_module-t.cpp
    • Updated test setup to use mcp-stats_endpoint_auth instead of mcp-observe_endpoint_auth.
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.

@wazir-ahmed wazir-ahmed changed the title [WIP] Add MCP endpoint for stats [WIP][skip-ci] Add MCP endpoint for stats Feb 4, 2026
@wazir-ahmed wazir-ahmed changed the title [WIP][skip-ci] Add MCP endpoint for stats [WIP] Add MCP endpoint for stats Feb 4, 2026
@wazir-ahmed wazir-ahmed changed the title [WIP] Add MCP endpoint for stats [WIP] [skip-ci] Add MCP endpoint for stats Feb 4, 2026
Copy link

@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 a new /mcp/stats endpoint, replacing the old /mcp/observe endpoint. This is a substantial feature addition, providing a wide range of tools for monitoring and diagnostics. The implementation is extensive, adding a new Stats_Tool_Handler with 17 distinct tools. My review focuses on the security, correctness, and maintainability of this new implementation. I've identified a critical SQL injection vulnerability in the get_stats tool that needs to be addressed. Additionally, there are a few other issues related to incorrect identifier escaping, robustness, and code structure that I've commented on. Overall, this is a great feature, but the security concerns should be prioritized before merging.

@renecannao renecannao changed the base branch from v4.0 to v3.0 February 5, 2026 11:49
…entation

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

@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: 10

🤖 Fix all issues with AI agents
In `@doc/MCP/MCP_Stats_Implementation_Plan.md`:
- Around line 878-895: The pseudocode for calculate_percentile is missing the
safety bounds check and empty-bucket fallback used in the real implementation;
update the pseudocode (or add a note) to mirror
calculate_percentile_from_histogram by checking i < thresholds.size() when
indexing bucket_thresholds and by handling the empty/bucket-less case before
returning bucket_thresholds.back() (e.g., verify thresholds is non-empty and
return a sensible default or error if it is empty). Ensure references to
calculate_percentile, calculate_percentile_from_histogram, bucket_thresholds,
and thresholds.size() are used so readers can locate the corresponding real-code
checks.

In `@doc/Stats_Tables.md`:
- Line 269: The link to [Source References](`#source-references`) will not resolve
because the target uses a bold line with an explicit anchor syntax; change the
target from the bold line "**Source References** {`#source-references`}" to a real
Markdown heading (e.g., "#### Source References") so the anchor
"#source-references" is generated consistently and the in-page link resolves
across renderers; ensure the heading text matches the link fragment or update
the link fragment to match the new heading.

In `@lib/proxysql_utils.cpp`:
- Around line 760-781: In calculate_percentile_from_histogram change the
accumulator types to a 64-bit integer (e.g., int64_t or long long) to avoid
overflow: make `total` and `cumulative` 64-bit, compute `target` as a 64-bit
value derived from total and `percentile` (cast/round appropriately), and use
64-bit arithmetic when adding bucket values and comparing cumulative >= target;
keep the function return type as int and return thresholds[i] as before. This
fixes potential overflow in the loops that use `total`, `cumulative`, `target`,
and `buckets`.

In `@lib/Stats_Tool_Handler.cpp`:
- Around line 1388-1399: The issue is that total_queries is incremented with
count_star before applying the min_time_us filter, causing a mismatch between
total_queries and the summed queries returned; move the accumulation of
total_queries so it only happens after the min_time_us check (i.e., after
computing avg_time and after the "if (min_time_us > 0 && avg_time < min_time_us)
continue;" line) or alternatively add a separate filtered_total variable and
increment that after the filter while keeping total_queries as the unfiltered
sum — adjust uses of total_queries accordingly (refer to total_queries,
count_star, min_time_us, avg_time, and the queries aggregation logic).
- Around line 808-811: The handler currently ignores the severity_threshold
parameter declared in the schema: update Stats_Tool_Handler::handle_get_health
to read severity_threshold from arguments (e.g., int or string using
arguments.value("severity_threshold", <default>)), validate/normalize it, and
apply it when assembling or filtering the alert/health results (where alerts are
collected in this function) so only alerts at or above the threshold are
returned; ensure the parameter name matches the schema and preserve existing
database and include_backend behavior in the same function.
- Around line 1061-1067: The backend filter currently takes port as a raw
substring and concatenates it into the SQL (see backend_filter handling and sql
construction using srv_port), which allows SQL injection; update the code that
extracts port (the block that creates host and port from backend_filter) to
validate that port contains only digits (and optionally check range 1-65535),
and if not numeric reject the filter or skip adding the srv_port clause;
alternatively use a parameterized query or cast the validated integer into the
SQL rather than direct string concatenation; apply the same numeric
validation/fix in handle_show_connections where the same pattern is used.
- Around line 1688-1691: The code is converting PostgreSQL sqlstate into an int
via std::stoi (error[errno_col] = ... std::stoi(...)) which will throw or
truncate; change the handler to treat sqlstate as a string for pgsql — assign
the raw string to error[errno_col] (or a new error["sqlstate"] key) instead of
calling std::stoi, and update any filtering logic that compares the errno
parameter to be type-aware so that for PostgreSQL it performs string comparison
against sqlstate (rather than integer comparison); also consider adding a
separate sqlstate string parameter to the tool description and use that
parameter in the filter branch that currently applies the errno filter.
- Around line 964-992: The current branch that builds the SQL table name chooses
only one pool (stats_pgsql_connection_pool vs stats_mysql_connection_pool) based
on database == "pgsql", so database == "all" returns only MySQL; change the
logic in the include_backend block to query both pools when database == "all"
(e.g., build a list of table names or run two queries), call execute_admin_query
for each table (using the existing resultset, cols, sql, err variables), loop
over each successful resultset to populate the backends array (same parsing code
for fields and computing utilization_pct), push results into
health_data["backend_servers"], and delete/free each resultset after processing;
keep the single-query behavior when database is "mysql" or "pgsql".
- Around line 2048-2076: The code concatenates untrusted inputs into SQL via
columns_str (built with sql_escape), arguments["where"], and
arguments["order_by"] when building `sql` (SELECT ... FROM ...), which allows
SQL injection; fix by validating and whitelisting identifiers and restricting
allowed syntax: (1) For columns_str / arguments["columns"] (in the loop that
builds columns_str), validate each entry is a simple identifier or a permitted
wildcard (e.g., match /^[A-Za-z_][A-Za-z0-9_]*$/ or a single '*'), reject or map
anything else to an error; (2) For arguments["where"] and arguments["order_by"],
do not concatenate raw strings—either build these clauses from validated
structured input (e.g., column/operator/value triples that bind values with
prepared statements) or at minimum reject dangerous characters/patterns
(semicolon, --, /* */, unbalanced quotes) and only allow a strict grammar
(column names from the same whitelist and a limited set of operators); and (3)
use parameterized queries / prepared statements for any values instead of
embedding literals; update the code paths that read arguments["where"],
arguments["order_by"], and the columns_str construction to perform these
validations and to return an error when input is not strictly allowed.
🧹 Nitpick comments (8)
lib/MCP_Tool_Handler.cpp (3)

1-6: Redundant includes and macros already provided by the header.

json.hpp, using json, and #define PROXYJSON are already included/defined in MCP_Tool_Handler.h (lines 7–9). While self-contained .cpp files are fine in principle, the duplication of #define PROXYJSON (a non-idempotent macro) could mask ordering issues if the header landscape changes.


15-43: Consider guarding against null row or null column_definition entries.

Line 18 dereferences resultset->column_definition[i]->name and line 19 dereferences row->fields[i] without null-checking the intermediate pointers. If a column_definition entry or a row pointer is ever null (e.g., due to a partial/corrupt result set), this will segfault.

Given that this is defensive utility code consumed by many tools, a brief null guard would be cheap insurance:

🛡️ Suggested defensive guards
 	for (const auto& row : resultset->rows) {
+		if (!row) continue;
 		json obj = json::object();
 		for (int i = 0; i < cols && i < (int)resultset->column_definition.size(); i++) {
-			const char* col_name = resultset->column_definition[i]->name;
+			if (!resultset->column_definition[i]) continue;
+			const char* col_name = resultset->column_definition[i]->name;
+			if (!col_name) continue;
 			const char* val = row->fields[i];

29-41: strtod will parse "NaN", "inf", and "-inf" as numeric doubles.

If any stats value happens to contain these strings (or e.g. "Infinity"), they will be stored as JSON doubles (NaN/Inf), which are not valid JSON values and will cause serialization issues with nlohmann::json (it replaces them with null by default). This is unlikely in normal ProxySQL stats but worth noting.

If you want to be safe, add a std::isfinite(d) check:

Proposed guard
 			double d = strtod(val, &end);
-			if (end != val && *end == '\0') {
+			if (end != val && *end == '\0' && std::isfinite(d)) {
 				obj[col_name] = d;
doc/MCP/MCP_Stats_Tools_Spec.md (1)

71-84: Section numbering: "2.1" is a subsection of "2" but uses a different heading format.

Section "2.1 Time Field Conventions" reads as a subsection of "2. Response Format Convention" but is not listed in the Table of Contents. This is minor — consider adding it to the TOC or renumbering as a standalone section for discoverability.

doc/Stats_Tables.md (1)

1729-1737: Missing language specifier on fenced code block.

This data flow diagram uses a fenced code block without a language identifier, which triggers markdownlint MD040. Add text as the language identifier for plain-text diagrams.

Proposed fix
-```
+```text
 Query executes
     ↓
 MySQL_Logger::log_request() adds event to circular buffer (MyLogCB)
doc/MCP/MCP_Stats_Implementation_Plan.md (1)

149-178: Missing language specifier on ASCII art flow diagrams.

Three fenced code blocks (lines 149, 214, 271) containing flow diagrams lack a language identifier, triggering markdownlint MD040. Add text as the language specifier.

Also applies to: 214-240, 271-313

lib/ProxySQL_MCP_Server.cpp (1)

125-129: Missing cleanup on init failure, inconsistent with other handlers.

Config (Line 83), Query (Line 105), and AI (Line 135) handlers all delete and NULL the handler on init() failure. Stats follows the Admin/Cache pattern of silently ignoring failure, which could leave a partially-initialized handler in play.

Proposed fix
 	// 5. Stats Tool Handler
 	handler->stats_tool_handler = new Stats_Tool_Handler(handler);
 	if (handler->stats_tool_handler->init() == 0) {
 		proxy_info("Stats Tool Handler initialized\n");
+	} else {
+		proxy_error("Failed to initialize Stats Tool Handler\n");
+		delete handler->stats_tool_handler;
+		handler->stats_tool_handler = NULL;
 	}
lib/Stats_Tool_Handler.cpp (1)

2677-2687: Integer division in avg aggregation truncates the result.

Line 2680: agg_result = sum / (long long)values.size() performs integer division. For example, averaging values [3, 4] yields 3 instead of 3.5. Since agg_result is long long, the fractional part is always lost.

Consider either returning the average as a double in the JSON, or documenting this as integer-truncated average.

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

@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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/MCP_Endpoint.cpp (1)

319-324: ⚠️ Potential issue | 🟡 Minor

Typo in server name: "proxysql-mcp-mcp-mysql-tools" contains a duplicated mcp- prefix.

This string is returned to MCP clients during the initialize handshake. It likely should be "proxysql-mcp-mysql-tools" or "proxysql-mcp-stats" (or similar). The current value may confuse clients and monitoring tools.

Proposed fix
 		result["serverInfo"] = {
-			{"name", "proxysql-mcp-mcp-mysql-tools"},
+			{"name", "proxysql-mcp-server"},
 			{"version", MCP_THREAD_VERSION}
 		};
🤖 Fix all issues with AI agents
In `@doc/MCP/MCP_Stats_Tools_Spec.md`:
- Around line 28-31: The docs list mutating tools flush_query_log and
flush_queries under the /stats monitoring endpoint which is misleading; update
the spec so these write-capable tools are either moved to the admin surface
(/mcp/admin) or annotated with explicit write warnings and auth requirements.
Edit the MCP_Stats_Tools_Spec to: 1) remove or relocate entries for
flush_query_log and flush_queries from the /stats section and add them under
/mcp/admin (or add a clear “write-capable” subsection), 2) add text describing
their destructive effects (drains circular buffer, resets in-memory counters)
and required elevated auth, and 3) update any cross-references to point to the
new location or the authorization note so readers won’t assume /stats is
read-only.
- Around line 70-72: The section header "## 2.1 Time Field Conventions" is
numbered as a subsection but is placed between top-level sections and missing
from the TOC; fix by either promoting it to a top-level header (change "## 2.1
Time Field Conventions" to "## 3. Time Field Conventions" or the appropriate
top-level numbering and renumber subsequent sections) or by adding an entry for
"2.1 Time Field Conventions" to the document's Table of Contents; update any
cross-references and the TOC entries so numbering and placement are consistent
with the surrounding headers ("2. Response Format Convention" and "3. Tool
Categories").

In `@include/MCP_Tool_Handler.h`:
- Around line 185-197: MCP_Tool_Handler.h uses the type SQLite3_result* (in the
declaration of resultset_to_json) but never forward-declares or includes it,
creating a fragile include-order dependency; to fix, add a forward declaration
"class SQLite3_result;" near the top of MCP_Tool_Handler.h (after the existing
includes) so the declaration of resultset_to_json and any other uses of
SQLite3_result* compile even if sqlite3db.h isn’t included first.

In `@include/proxysql_glovars.hpp`:
- Around line 123-125: The field global.genai is uninitialized unless --genai is
passed; update the struct/class constructor that initializes other flags (same
place where gdbg, nostart, foreground are set) to explicitly set global.genai =
false so it has a defined default value before process_opts_pre() and any reads
in main.cpp; touch the constructor in include/proxysql_glovars.hpp where other
booleans are initialized and add the initialization for genai.

In `@include/Stats_Tool_Handler.h`:
- Around line 11-41: The Doxygen block in Stats_Tool_Handler.h lists a different
set of tools (e.g., show_status, show_prepared_statements, flush_query_log,
show_query_log, flush_queries) than the PR description (e.g., get_health,
show_metrics, list_stats, get_stats, aggregate_metrics); reconcile them by
choosing the authoritative toolset, then update the Doxygen comment in
Stats_Tool_Handler.h to exactly match the implemented/advertised MCP endpoints
and tool names (or vice versa update the implementation/PR text to match the
docs). Ensure every tool listed (show_status, show_processlist, show_queries,
show_commands, show_connections, show_errors, show_users, show_client_cache,
show_query_rules, show_prepared_statements, show_gtid, show_cluster,
show_system_history, show_query_cache_history, show_connection_history,
show_query_history, flush_query_log, show_query_log, flush_queries or the PR
equivalents get_health, show_metrics, list_stats, get_stats, aggregate_metrics)
corresponds to an implemented handler function or is removed, and keep the
Doxygen summary, counts (Live/Historical/Utility) and names consistent.

In `@lib/ProxySQL_GloVars.cpp`:
- Around line 434-438: The constructor for the global GloVars instance is
missing explicit initialization for the boolean global.genai; update the GloVars
constructor (the same place where global.gdbg, global.nostart,
global.foreground, global.sqlite3_server, global.clickhouse_server, etc. are
set) to explicitly set global.genai = false (or the desired default) alongside
those members — this ensures consistency and avoids relying on implicit
zero-initialization; locate the init block around the existing boolean
assignments (near the sqlite3_server initialization) and add global.genai
initialization there.
🧹 Nitpick comments (7)
include/proxysql_utils.h (1)

376-380: Consider using int64_t for bucket counts to avoid overflow on busy instances.

In calculate_percentile_from_histogram, both buckets and the internal total accumulator are int. On a high-traffic ProxySQL instance, command counters (e.g., Total_cnt) can exceed INT_MAX (~2.1B). The total * percentile multiplication would also overflow. If this is expected to only handle per-command histogram buckets (which are typically smaller), this may be acceptable — but worth a note.

doc/MCP/MCP_Stats_Implementation_Plan.md (1)

175-204: Add language identifiers to ASCII-art fenced code blocks.

The diagram blocks at lines 175, 245, and 310 lack language specifiers, triggering markdownlint MD040. Use ```text for plain-text diagrams.

lib/MCP_Tool_Handler.cpp (1)

28-43: Consider checking errno for strtoll/strtod overflow.

If a numeric string overflows long long, strtoll returns LLONG_MAX/LLONG_MIN and sets errno = ERANGE, but the code doesn't check for this. Similarly for strtod with extreme values. For ProxySQL stats data this is unlikely to matter, but for correctness:

Suggested improvement
 			char* end = nullptr;
+			errno = 0;
 			long long ll = strtoll(val, &end, 10);
-			if (end != val && *end == '\0') {
+			if (end != val && *end == '\0' && errno != ERANGE) {
 				obj[col_name] = ll;
 			} else {
 				// Not a plain integer; try floating-point
+				errno = 0;
 				double d = strtod(val, &end);
-				if (end != val && *end == '\0') {
+				if (end != val && *end == '\0' && errno != ERANGE) {
 					obj[col_name] = d;
 				} else {
 					obj[col_name] = std::string(val);
src/main.cpp (1)

1671-1676: Misleading indentation: GloAdmin->init_mcp_variables() appears to be outside the if (GloMCPH) guard.

Line 1675 is the body of the if (GloMCPH) on line 1674, but its indentation makes it appear to be at the same level as the if. This is a readability trap that could lead to future maintenance mistakes. Adding braces would clarify intent.

Proposed fix
 	if (GloVars.global.genai) {
-		if (GloGATH)
+		if (GloGATH) {
 			GloAdmin->init_genai_variables();
-		if (GloMCPH)
-		GloAdmin->init_mcp_variables();
+		}
+		if (GloMCPH) {
+			GloAdmin->init_mcp_variables();
+		}
 	}
lib/ProxySQL_MCP_Server.cpp (1)

127-131: Missing error handling for Stats Tool Handler initialization failure.

If init() returns non-zero, the handler is left allocated but potentially in an invalid state. Compare with Config/Query handlers (lines 84-91, 107-113) which log an error, delete the handler, and set it to NULL on failure.

This is consistent with the Admin and Cache handlers (which also lack error handling), but it would be safer to handle failures uniformly.

Add error handling consistent with Config/Query handlers
 	// 5. Stats Tool Handler
 	handler->stats_tool_handler = new Stats_Tool_Handler(handler);
 	if (handler->stats_tool_handler->init() == 0) {
 		proxy_info("Stats Tool Handler initialized\n");
+	} else {
+		proxy_error("Failed to initialize Stats Tool Handler\n");
+		delete handler->stats_tool_handler;
+		handler->stats_tool_handler = NULL;
 	}
include/Stats_Tool_Handler.h (2)

6-9: Missing direct includes for <string> and <vector>.

The class declarations use std::string, std::vector<int>, and json but only <map> is directly included. The others are resolved transitively through MCP_Tool_Handler.h / MCP_Thread.h, which is fragile — if those headers change their includes, this file breaks.

Proposed fix
 `#include` <map>
+#include <string>
+#include <vector>
 
 `#include` "MCP_Tool_Handler.h"
 `#include` "MCP_Thread.h"

177-193: Consider documenting or enforcing RAII for SQLite3_result** output parameters.

Both execute_admin_query and execute_statsdb_disk_query require the caller to delete the resultset. This is a common leak vector, especially if early returns or exceptions occur between allocation and cleanup. If ProxySQL's codebase doesn't already have a RAII wrapper for SQLite3_result, a small unique_ptr deleter alias would prevent leaks in all 19+ tool handlers.

Comment on lines +28 to +31
- [6. Utility Tools](#6-utility-tools)
- [6.1 flush_query_log](#61-flush_query_log)
- [6.2 show_query_log](#62-show_query_log)
- [6.3 flush_queries](#63-flush_queries)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mutating tools (flush_query_log, flush_queries) on a monitoring/stats endpoint may violate principle of least surprise.

The /stats endpoint is semantically a read-only monitoring endpoint. Including tools that flush data (flush_query_log drains the circular buffer, flush_queries resets in-memory counters) means a monitoring-only auth token could inadvertently allow data-destructive operations. Consider either:

  1. Moving these to the /mcp/admin endpoint, or
  2. Clearly documenting the write-capable nature and ensuring the auth model accounts for it.
🤖 Prompt for AI Agents
In `@doc/MCP/MCP_Stats_Tools_Spec.md` around lines 28 - 31, The docs list mutating
tools flush_query_log and flush_queries under the /stats monitoring endpoint
which is misleading; update the spec so these write-capable tools are either
moved to the admin surface (/mcp/admin) or annotated with explicit write
warnings and auth requirements. Edit the MCP_Stats_Tools_Spec to: 1) remove or
relocate entries for flush_query_log and flush_queries from the /stats section
and add them under /mcp/admin (or add a clear “write-capable” subsection), 2)
add text describing their destructive effects (drains circular buffer, resets
in-memory counters) and required elevated auth, and 3) update any
cross-references to point to the new location or the authorization note so
readers won’t assume /stats is read-only.

@wazir-ahmed wazir-ahmed changed the title [WIP] [skip-ci] Add MCP endpoint for stats Add MCP endpoint for stats Feb 10, 2026
@wazir-ahmed
Copy link
Member Author

retest this please

@wazir-ahmed
Copy link
Member Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

✅ Actions performed

Full review triggered.

Copy link

@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 `@doc/Stats_Tables.md`:
- Around line 1728-1736: The fenced code block showing the data flow lacks a
language identifier; update the block to include "text" (e.g., ```text) so
linters render it consistently. Locate the diagram block that mentions
MySQL_Logger::log_request(), MyLogCB, mysql_eventslog_table_memory_size and the
Manual DUMP command and change the opening fence to ```text while keeping the
content unchanged.

@wazir-ahmed
Copy link
Member Author

@gemini-code-assist full review

@wazir-ahmed
Copy link
Member Author

@coderabbitai full review

@coderabbitai
Copy link

coderabbitai bot commented Feb 11, 2026

✅ Actions performed

Full review triggered.

Copy link

@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 a new /mcp/stats endpoint, replacing /mcp/observe and adding a comprehensive set of 19 tools for monitoring and diagnostics. The changes are extensive, including new documentation, a new Stats_Tool_Handler, and updates across the MCP framework to support the new endpoint. The implementation is well-structured, and the documentation is excellent. I've found one potential data corruption bug in the JSON serialization logic and a minor fragility in the implementation plan documentation. Overall, this is a great enhancement to ProxySQL's observability capabilities.

Comment on lines +31 to +43
char* end = nullptr;
long long ll = strtoll(val, &end, 10);
if (end != val && *end == '\0') {
obj[col_name] = ll;
} else {
// Not a plain integer; try floating-point
double d = strtod(val, &end);
if (end != val && *end == '\0') {
obj[col_name] = d;
} else {
obj[col_name] = std::string(val);
}
}

Choose a reason for hiding this comment

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

high

The current numeric parsing logic using strtoll can incorrectly handle large unsigned 64-bit integers, which are common for statistics counters. When strtoll is given a value larger than LLONG_MAX, it returns LLONG_MAX and sets errno to ERANGE. Since errno is not checked, this leads to silent data corruption, where large counters are reported as LLONG_MAX.

A more robust approach is to first attempt parsing as unsigned long long for non-negative values, and then fall back to other types, while also checking errno for range errors.

            char* end = nullptr;
            bool parsed = false;

            // Try unsigned long long for non-negative numbers, which is common for stats counters.
            if (val[0] != '-') {
                errno = 0;
                unsigned long long ull = strtoull(val, &end, 10);
                if (end != val && *end == '\0' && errno != ERANGE) {
                    obj[col_name] = ull;
                    parsed = true;
                }
            }

            // Try long long if not parsed yet (e.g., for negative numbers).
            if (!parsed) {
                errno = 0;
                end = nullptr;
                long long ll = strtoll(val, &end, 10);
                if (end != val && *end == '\0' && errno != ERANGE) {
                    obj[col_name] = ll;
                    parsed = true;
                }
            }

            // Fallback to double if it's not an integer.
            if (!parsed) {
                end = nullptr;
                double d = strtod(val, &end);
                if (end != val && *end == '\0') {
                    obj[col_name] = d;
                    parsed = true;
                }
            }

            // Finally, treat as a string if no numeric conversion worked.
            if (!parsed) {
                obj[col_name] = std::string(val);
            }

Comment on lines +940 to +951
atoi(row->fields[3]), // cnt_100us
atoi(row->fields[4]), // cnt_500us
atoi(row->fields[5]), // cnt_1ms
atoi(row->fields[6]), // cnt_5ms
atoi(row->fields[7]), // cnt_10ms
atoi(row->fields[8]), // cnt_50ms
atoi(row->fields[9]), // cnt_100ms
atoi(row->fields[10]), // cnt_500ms
atoi(row->fields[11]), // cnt_1s
atoi(row->fields[12]), // cnt_5s
atoi(row->fields[13]), // cnt_10s
atoi(row->fields[14]) // cnt_INFs

Choose a reason for hiding this comment

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

medium

Accessing result set fields by hardcoded numeric indices (row->fields[3], row->fields[4], etc.) is fragile. If the column order in the SELECT query for stats_mysql_commands_counters changes in the future, this code will lead to incorrect data or crashes. It would be more robust to look up column indices by their names from the SQLite3_result->column_definition at the beginning of the result processing loop. This would make the implementation more maintainable and resilient to schema changes.

Copy link

@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 `@doc/Stats_Tables.md`:
- Around line 57-74: The CREATE TABLE for stats_mysql_commands_counters is
missing a type (and NOT NULL) for the cnt_INFs column; update the column
definition for cnt_INFs to match the other counters (e.g., "INT NOT NULL") in
the stats_mysql_commands_counters table and apply the same fix to the PostgreSQL
variant where cnt_INFs is likewise missing its type to keep the schema
consistent.

In `@lib/ProxySQL_MCP_Server.cpp`:
- Around line 127-131: The Stats_Tool_Handler allocation/initialization lacks
failure handling: when handler->stats_tool_handler->init() != 0 you must mirror
the pattern used by the Config and Query handlers by deleting the
partially-constructed handler, setting handler->stats_tool_handler = nullptr,
and not registering the /mcp/stats endpoint; update the code around
Stats_Tool_Handler, and also review Admin/Cache handler initializations to
ensure they similarly clean up on init failure and guard their endpoint
registrations so endpoints are only registered when init() succeeds.
🧹 Nitpick comments (2)
doc/MCP/MCP_Stats_Implementation_Plan.md (1)

412-420: Note: build_time_range_query uses string concatenation for SQL construction.

The pseudocode builds SQL via string concatenation. While std::to_string(start) and std::to_string(now) are safe here (integer timestamps, not user input), the actual implementation should prefer parameterized queries for consistency with the rest of the codebase, where parameterized patterns (? OR ? IS NULL) are already demonstrated (e.g., lines 502–506).

src/main.cpp (1)

1671-1676: Inconsistent indentation on Line 1675 — GloAdmin->init_mcp_variables() appears at the same level as its if guard.

While functionally correct (braceless if in C++ takes the next statement), the indentation doesn't match the pattern on Line 1673. This is a pre-existing line, but the new if (GloMCPH) guard on Line 1674 makes the visual inconsistency more noticeable.

Suggested fix
 		if (GloMCPH)
-		GloAdmin->init_mcp_variables();
+			GloAdmin->init_mcp_variables();

int port = std::stoi(port_str);
sql += " AND srv_host = '" + sql_escape(host) + "' AND srv_port = " + std::to_string(port);
} catch (...) {
// Invalid port - skip server filter
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to log this.
It should never happen, I am tempted to put an assert() here

int port = std::stoi(port_str);
sql += " AND srv_host = '" + sql_escape(host) + "' AND srv_port = " + std::to_string(port);
} catch (...) {
// Invalid port - skip server filter
Copy link
Contributor

Choose a reason for hiding this comment

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

As before, we need to log this.
It should never happen, I am tempted to put an assert() here

SQLite3_result* count_rs = NULL;
int count_cols = 0;
int total_sessions = 0;
execute_admin_query(count_sql.c_str(), &count_rs, &count_cols);
Copy link
Contributor

Choose a reason for hiding this comment

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

We need to log if any error happens.
Although syntax errors shouldn't happen, some tables may exist only if certain modules are loaded. Unless we filter certain tablename based on what module is not loaded, we should log an error AND return an error to the client.
I see there are multiple cases like this in the code, like at line 1190 , 1617 , etc

conditions.push_back("Variable_Name LIKE '" + sql_escape(variable_name) + "'");
} else if (!category.empty()) {
auto it = CATEGORY_PREFIXES.find(category);
if (it != CATEGORY_PREFIXES.end()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

What happens when a category is not found?

@renecannao
Copy link
Contributor

Added some comments.
We can start introducing testing.

Signed-off-by: René Cannaò <rene@proxysql.com>
Copy link

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/MCP_Endpoint.cpp (1)

37-52: ⚠️ Potential issue | 🟠 Major

The "ai" endpoint is missing from the authentication chain and is permanently inaccessible.

The "ai" endpoint is registered in ProxySQL_MCP_Server.cpp (lines 171–177) but has no case in the if-else chain at lib/MCP_Endpoint.cpp lines 37–48. Every POST to /mcp/ai hits the else branch, which logs an error and returns false, causing HTTP 401 for all requests.

Add the missing `"ai"` case
 	} else if (endpoint_name == "rag") {
 		expected_token = handler->variables.mcp_rag_endpoint_auth;
+	} else if (endpoint_name == "ai") {
+		expected_token = handler->variables.mcp_ai_endpoint_auth;
 	} else {
 		proxy_error("MCP authentication on %s: unknown endpoint\n", endpoint_name.c_str());

This requires creating a corresponding mcp_ai_endpoint_auth variable in MCP_Threads_Handler::variables (matching the pattern of other auth vars), or set expected_token = nullptr if unauthenticated access is intended.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MCP_Endpoint.cpp` around lines 37 - 52, The auth branch is missing a case
for endpoint_name == "ai", causing /mcp/ai requests to fall into the unknown
endpoint path; add an else-if branch similar to the other endpoints that sets
expected_token = handler->variables.mcp_ai_endpoint_auth (or expected_token =
nullptr if the AI endpoint should be unauthenticated), and ensure
MCP_Threads_Handler::variables defines mcp_ai_endpoint_auth with the same
type/pattern as the other mcp_*_endpoint_auth members so the symbol exists when
referenced.
🧹 Nitpick comments (2)
src/main.cpp (1)

1668-1673: Missing braces on if (GloMCPH) body makes intent unclear.

Line 1671 guards init_mcp_variables() without braces, and the indentation makes it look like init_mcp_variables() is unguarded. Add braces to make the guard explicit.

🔧 Proposed fix
 	if (GloVars.global.genai) {
 		if (GloGATH)
 			GloAdmin->init_genai_variables();
-		if (GloMCPH)
-		GloAdmin->init_mcp_variables();
+		if (GloMCPH) {
+			GloAdmin->init_mcp_variables();
+		}
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.cpp` around lines 1668 - 1673, The if-statement guarding
init_mcp_variables() lacks braces making the intent ambiguous; update the
conditional that checks GloMCPH so its body is wrapped in braces—i.e., change
the block containing GloMCPH and the call to GloAdmin->init_mcp_variables() to
use { } so the call is clearly executed only when GloMCPH is true and aligns
with the existing GloGATH branch that calls GloAdmin->init_genai_variables().
lib/ProxySQL_MCP_Server.cpp (1)

129-129: Duplicate extern AI_Features_Manager *GloAI; declaration in the same constructor.

The same extern declaration appears again at line 180. The first declaration already brings GloAI into scope for the entire function body; the second is dead code.

🔧 Remove the duplicate extern
-	// 7. RAG endpoint (for Retrieval-Augmented Generation)
-	extern AI_Features_Manager *GloAI;
 	if (GloAI) {

The extern AI_Features_Manager *GloAI; on line 180 can be removed since GloAI is already in scope from line 129.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ProxySQL_MCP_Server.cpp` at line 129, Remove the redundant extern
declaration for the global pointer by deleting the second "extern
AI_Features_Manager *GloAI;" occurrence inside the same constructor/function;
keep the first declaration (the one already at the top of the function) so GloAI
remains in scope and remove the duplicate near line 180 to avoid dead/redundant
code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@include/proxysql_utils.h`:
- Around line 369-381: Validate inputs and fix the P0 edge case in
calculate_percentile_from_histogram: first assert or clamp percentile to
[0.0,1.0] and verify buckets.size() == thresholds.size() and non-empty
buckets/thresholds; if percentile == 0.0, return the threshold of the first
bucket with a non-zero count (and return 0 if all counts are zero) instead of
using the cumulative >= 0 check; otherwise compute the target rank from
percentile and total count and iterate cumulative counts to find the bucket
whose cumulative meets or exceeds that target (return thresholds.back() if
target exceeds total).
- Around line 362-367: The sql_escape function currently doubles backslashes in
addition to doubling single quotes, which is incorrect for SQLite and causes
mismatches (e.g., "C:\Users\test" -> "C:\\Users\\test"); modify the
implementation of sql_escape to stop escaping/backslash-doubling and only
replace single-quote characters (') with two single quotes ('') per SQL/SQLite
rules, update the function's documentation comment to reflect this
SQLite-correct behavior, and run/verify the callers (notably usages in
Stats_Tool_Handler.cpp that query stats.* tables) still behave as expected.

---

Outside diff comments:
In `@lib/MCP_Endpoint.cpp`:
- Around line 37-52: The auth branch is missing a case for endpoint_name ==
"ai", causing /mcp/ai requests to fall into the unknown endpoint path; add an
else-if branch similar to the other endpoints that sets expected_token =
handler->variables.mcp_ai_endpoint_auth (or expected_token = nullptr if the AI
endpoint should be unauthenticated), and ensure MCP_Threads_Handler::variables
defines mcp_ai_endpoint_auth with the same type/pattern as the other
mcp_*_endpoint_auth members so the symbol exists when referenced.

---

Duplicate comments:
In `@lib/ProxySQL_MCP_Server.cpp`:
- Around line 122-126: The Stats_Tool_Handler initialization lacks failure
handling: after constructing handler->stats_tool_handler and calling
handler->stats_tool_handler->init(), check for a non-zero return and on failure
log an error (e.g., proxy_err or proxy_info with clear message including "Stats
Tool Handler init failed"), delete or free handler->stats_tool_handler and set
it to nullptr, and propagate the error (return failure or set an appropriate
status) so the caller knows initialization failed; update the block around
Stats_Tool_Handler, its init() call, and any cleanup paths to handle this case.

---

Nitpick comments:
In `@lib/ProxySQL_MCP_Server.cpp`:
- Line 129: Remove the redundant extern declaration for the global pointer by
deleting the second "extern AI_Features_Manager *GloAI;" occurrence inside the
same constructor/function; keep the first declaration (the one already at the
top of the function) so GloAI remains in scope and remove the duplicate near
line 180 to avoid dead/redundant code.

In `@src/main.cpp`:
- Around line 1668-1673: The if-statement guarding init_mcp_variables() lacks
braces making the intent ambiguous; update the conditional that checks GloMCPH
so its body is wrapped in braces—i.e., change the block containing GloMCPH and
the call to GloAdmin->init_mcp_variables() to use { } so the call is clearly
executed only when GloMCPH is true and aligns with the existing GloGATH branch
that calls GloAdmin->init_genai_variables().

Comment on lines +369 to +381
/**
* @brief Calculate an approximate percentile value from histogram bucket counts.
* @param buckets Vector of counts per histogram bucket.
* @param thresholds Vector of upper-bound threshold values for each bucket (same length as buckets).
* @param percentile The percentile to calculate, in the range [0.0, 1.0].
* @return The threshold value of the bucket in which the target percentile falls,
* or 0 if the buckets are empty.
*/
int calculate_percentile_from_histogram(
const std::vector<int>& buckets,
const std::vector<int>& thresholds,
double percentile
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

calculate_percentile_from_histogram: missing input validation and P0 edge-case quirk.

Two minor concerns:

  1. No range check on percentile: Values outside [0.0, 1.0] silently produce thresholds.back() (target exceeds total). Adding an assert or clamp would make misuse visible early.

  2. P0 always returns thresholds[0]: When percentile == 0.0, target = 0; after the first cumulative += buckets[0], the condition cumulative >= 0 is immediately satisfied regardless of whether that bucket contains any counts. For sparse histograms (e.g., first several buckets are zero) this returns the wrong threshold.

💡 Suggested guard and doc note
 int calculate_percentile_from_histogram(
 	const std::vector<int>& buckets,
 	const std::vector<int>& thresholds,
 	double percentile
 ) {
+	assert(percentile >= 0.0 && percentile <= 1.0);
 	long long total = 0;
 	for (int b : buckets) total += b;
 
 	if (total == 0) return 0;
 
 	long long target = (long long)(total * percentile);
+	if (target == 0) target = 1; // ensure at least one count is crossed for P0
 	long long cumulative = 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/proxysql_utils.h` around lines 369 - 381, Validate inputs and fix the
P0 edge case in calculate_percentile_from_histogram: first assert or clamp
percentile to [0.0,1.0] and verify buckets.size() == thresholds.size() and
non-empty buckets/thresholds; if percentile == 0.0, return the threshold of the
first bucket with a non-zero count (and return 0 if all counts are zero) instead
of using the cumulative >= 0 check; otherwise compute the target rank from
percentile and total count and iterate cumulative counts to find the bucket
whose cumulative meets or exceeds that target (return thresholds.back() if
target exceeds total).

Copy link

@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.

🧹 Nitpick comments (4)
include/Stats_Tool_Handler.h (1)

213-225: calculate_percentile return type may truncate; vector parameters should be const&.

Two related issues with this signature:

  1. Return type: The function returns int (signed 32-bit, capped at ~2.1 billion µs ≈ 35 minutes). ProxySQL's histogram has an open-ended "INF" bucket; an interpolated value within it, or any extreme latency, would silently truncate. Using int64_t eliminates the risk with no ABI cost at this internal scope.

  2. Parameter passing: buckets and thresholds are passed by value (heap-allocated vector copies) for read-only inputs. They should be const std::vector<int>&.

♻️ Proposed fix
-	int calculate_percentile(const std::vector<int>& buckets, const std::vector<int>& thresholds, double percentile);
+	int64_t calculate_percentile(const std::vector<int64_t>& buckets, const std::vector<int64_t>& thresholds, double percentile);

(bucket counts are already described as 64-bit in the doc-comment; aligning the threshold vector element type avoids a mixed-width comparison in the implementation.)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@include/Stats_Tool_Handler.h` around lines 213 - 225, Change the signature of
calculate_percentile to return int64_t and accept the vectors by const reference
to avoid truncation and unnecessary copies: update calculate_percentile(const
std::vector<int>& buckets, const std::vector<int>& thresholds, double
percentile) to use int64_t for the return and make buckets and thresholds const
std::vector<int64_t>& (or at least const std::vector<int64_t>& for buckets and
thresholds to match 64-bit accumulation referenced in the doc and avoid
mixed-width comparisons), and propagate that type change through any callers and
implementations of calculate_percentile.
lib/ProxySQL_MCP_Server.cpp (1)

176-203: AI and RAG endpoints bypass the new register_endpoint lambda.

These two endpoints duplicate the registration pattern (construct unique_ptr, call register_resource, push to _endpoints) rather than reusing the lambda introduced at line 158. Consistency would also close the gap where the RAG handler init failure (proxy_error / delete / null) isn't mirrored for the successful-init path's endpoint registration.

♻️ Suggested refactoring for AI/RAG endpoints to use the lambda
-	if (handler->ai_tool_handler) {
-		std::unique_ptr<httpserver::http_resource> ai_resource =
-			std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, handler->ai_tool_handler, "ai"));
-		ws->register_resource("/mcp/ai", ai_resource.get(), true);
-		_endpoints.push_back({"/mcp/ai", std::move(ai_resource)});
-	}
+	register_endpoint("/mcp/ai", handler->ai_tool_handler, "ai");

And after the RAG handler init block:

-		std::unique_ptr<httpserver::http_resource> rag_resource =
-			std::unique_ptr<httpserver::http_resource>(new MCP_JSONRPC_Resource(handler, handler->rag_tool_handler, "rag"));
-		ws->register_resource("/mcp/rag", rag_resource.get(), true);
-		_endpoints.push_back({"/mcp/rag", std::move(rag_resource)});
-		proxy_info("RAG Tool Handler initialized\n");
+		proxy_info("RAG Tool Handler initialized\n");
+		register_endpoint("/mcp/rag", handler->rag_tool_handler, "rag");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/ProxySQL_MCP_Server.cpp` around lines 176 - 203, The AI and RAG endpoint
blocks duplicate manual resource creation/registration instead of using the
existing register_endpoint lambda (the helper that constructs a unique_ptr,
calls ws->register_resource and pushes into _endpoints); change both AI and RAG
branches to call that lambda with MCP_JSONRPC_Resource(...) and the path
("/mcp/ai" and "/mcp/rag") so registration is consistent and centralized, and
ensure the RAG success/failure symmetry: only create/register the resource after
rag_tool_handler->init() returns success, and on failure call proxy_error,
delete handler->rag_tool_handler and set it to NULL exactly as the failure
branch already does. Reference symbols: MCP_JSONRPC_Resource,
handler->ai_tool_handler, handler->rag_tool_handler, RAG_Tool_Handler, GloAI,
_endpoints, ws->register_resource, and the register_endpoint lambda.
lib/MCP_Tool_Handler.cpp (2)

1-12: Add direct include of MCP_Tool_Handler.h for explicit self-inclusion.

MCP_Tool_Handler.h (which declares MCP_Tool_Handler::resultset_to_json) is not directly #included in this translation unit. While compilation succeeds due to the transitive path (cpp.hMCP_Tool_Handler.h), explicit self-inclusion is the recommended practice and provides protection against future header refactors.

Add #include "MCP_Tool_Handler.h" after the existing includes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MCP_Tool_Handler.cpp` around lines 1 - 12, Add a direct include for the
header that declares this unit’s API: insert `#include` "MCP_Tool_Handler.h" among
the existing includes (after the other project headers like "cpp.h" or
"sqlite3db.h") so the translation unit explicitly includes the declaration for
MCP_Tool_Handler::resultset_to_json and related symbols instead of relying on
transitive includes.

20-24: Add row->cnt guard before accessing row->fields[i].

The inner loop bounds check uses min(cols, column_definition.size()) but does not validate the actual field count of the row. The cnt field tracks the number of fields in each row instance and can be less than expected columns if rows are malformed or truncated. Other code paths in the codebase (e.g., MCP_Thread.cpp line 420 and Query_Tool_Handler.cpp) consistently guard field access with row->cnt before indexing into fields. Without this guard, accessing row->fields[i] for i >= row->cnt is out-of-bounds.

Suggested defensive guard
 for (int i = 0; i < cols && i < (int)resultset->column_definition.size(); i++) {
+    if (i >= row->cnt) break;
     const char* col_name = resultset->column_definition[i]->name;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/MCP_Tool_Handler.cpp` around lines 20 - 24, The loop iterating
resultset->rows must check each row's actual field count before indexing into
row->fields; modify the inner loop (the loop that currently uses cols and
resultset->column_definition.size()) to also ensure i < row->cnt (i.e., guard
with row->cnt) before accessing row->fields[i], and skip or set null for missing
fields so you never read out-of-bounds from row->fields; update the code around
resultset, row, cols, column_definition, and fields to use this defensive check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@doc/MCP/MCP_Stats_Tools_Spec.md`:
- Around line 29-32: The docs list write-capable tools under the MCP
stats/monitoring endpoint (flush_query_log, flush_queries); move these tool
entries and their sections from the stats endpoint into the MCP admin area
(e.g., /mcp/admin) and update the TOC/anchors (sections for flush_query_log and
flush_queries) accordingly, or alternatively explicitly document that these
endpoints require elevated auth by adding an authentication/authorization
subsection for flush_query_log and flush_queries (specifying admin role, scopes,
and CSRF/intent checks) and update all example paths and links to reflect the
chosen location/auth model; ensure references to flush_query_log and
flush_queries in the document and TOC are consistent.
- Around line 72-74: The section header "## 2.1 Time Field Conventions" is
mis-numbered and missing from the Table of Contents; either promote it to a
top-level section (rename the header to "## 3. Time Field Conventions" and
update any subsequent section numbering) or add an entry for "2.1 Time Field
Conventions" into the existing ToC (lines referenced as 7–32) so the document
and ToC stay consistent; update any cross-references accordingly (look for the
header string "## 2.1 Time Field Conventions" to locate the section and the ToC
block to insert or adjust entries).

---

Nitpick comments:
In `@include/Stats_Tool_Handler.h`:
- Around line 213-225: Change the signature of calculate_percentile to return
int64_t and accept the vectors by const reference to avoid truncation and
unnecessary copies: update calculate_percentile(const std::vector<int>& buckets,
const std::vector<int>& thresholds, double percentile) to use int64_t for the
return and make buckets and thresholds const std::vector<int64_t>& (or at least
const std::vector<int64_t>& for buckets and thresholds to match 64-bit
accumulation referenced in the doc and avoid mixed-width comparisons), and
propagate that type change through any callers and implementations of
calculate_percentile.

In `@lib/MCP_Tool_Handler.cpp`:
- Around line 1-12: Add a direct include for the header that declares this
unit’s API: insert `#include` "MCP_Tool_Handler.h" among the existing includes
(after the other project headers like "cpp.h" or "sqlite3db.h") so the
translation unit explicitly includes the declaration for
MCP_Tool_Handler::resultset_to_json and related symbols instead of relying on
transitive includes.
- Around line 20-24: The loop iterating resultset->rows must check each row's
actual field count before indexing into row->fields; modify the inner loop (the
loop that currently uses cols and resultset->column_definition.size()) to also
ensure i < row->cnt (i.e., guard with row->cnt) before accessing row->fields[i],
and skip or set null for missing fields so you never read out-of-bounds from
row->fields; update the code around resultset, row, cols, column_definition, and
fields to use this defensive check.

In `@lib/ProxySQL_MCP_Server.cpp`:
- Around line 176-203: The AI and RAG endpoint blocks duplicate manual resource
creation/registration instead of using the existing register_endpoint lambda
(the helper that constructs a unique_ptr, calls ws->register_resource and pushes
into _endpoints); change both AI and RAG branches to call that lambda with
MCP_JSONRPC_Resource(...) and the path ("/mcp/ai" and "/mcp/rag") so
registration is consistent and centralized, and ensure the RAG success/failure
symmetry: only create/register the resource after rag_tool_handler->init()
returns success, and on failure call proxy_error, delete
handler->rag_tool_handler and set it to NULL exactly as the failure branch
already does. Reference symbols: MCP_JSONRPC_Resource, handler->ai_tool_handler,
handler->rag_tool_handler, RAG_Tool_Handler, GloAI, _endpoints,
ws->register_resource, and the register_endpoint lambda.

@sonarqubecloud
Copy link

@renecannao
Copy link
Contributor

Putting this PR on hold.
The code is trying to directly access stats tables: this will not work and it is the wrong approach.
Branch v4.0-mcp-stats2 is working around that limitation with a temporary workaround, but also implement some direct access.

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.

2 participants