From 2250b762a3c120255f820be6da48af3cfef0e3bf Mon Sep 17 00:00:00 2001 From: Rene Cannao Date: Sun, 18 Jan 2026 21:00:07 +0000 Subject: [PATCH] feat: Add query_tool_calls table to log MCP tool invocations Add query_tool_calls table to Discovery Schema to track all MCP tool invocations via the /mcp/query/ endpoint. Logs: - tool_name: Name of the tool that was called - schema: Schema name (nullable, empty if not applicable) - run_id: Run ID from discovery (nullable, 0 if not applicable) - start_time: Start monotonic time in microseconds - execution_time: Execution duration in microseconds - error: Error message (null if success) Modified files: - Discovery_Schema.cpp: Added table creation and log_query_tool_call function - Discovery_Schema.h: Added function declaration - Query_Tool_Handler.cpp: Added logging after each tool execution --- include/Discovery_Schema.h | 19 +++++++++++ lib/Discovery_Schema.cpp | 67 ++++++++++++++++++++++++++++++++++++++ lib/Query_Tool_Handler.cpp | 18 ++++++++++ 3 files changed, 104 insertions(+) diff --git a/include/Discovery_Schema.h b/include/Discovery_Schema.h index 887d382fb..e43142065 100644 --- a/include/Discovery_Schema.h +++ b/include/Discovery_Schema.h @@ -637,6 +637,25 @@ public: int limit = 25 ); + /** + * @brief Log MCP tool invocation via /mcp/query/ endpoint + * @param tool_name Name of the tool that was called + * @param schema Schema name (empty if not applicable) + * @param run_id Run ID (0 or -1 if not applicable) + * @param start_time Start monotonic time (microseconds) + * @param execution_time Execution duration (microseconds) + * @param error Error message (empty if success) + * @return 0 on success, -1 on error + */ + int log_query_tool_call( + const std::string& tool_name, + const std::string& schema, + int run_id, + unsigned long long start_time, + unsigned long long execution_time, + const std::string& error + ); + /** * @brief Get database handle for direct access * @return SQLite3DB pointer diff --git a/lib/Discovery_Schema.cpp b/lib/Discovery_Schema.cpp index 84ea5a0bf..57b982236 100644 --- a/lib/Discovery_Schema.cpp +++ b/lib/Discovery_Schema.cpp @@ -481,6 +481,26 @@ int Discovery_Schema::create_llm_tables() { db->execute("CREATE INDEX IF NOT EXISTS idx_llm_search_log_query ON llm_search_log(query);"); db->execute("CREATE INDEX IF NOT EXISTS idx_llm_search_log_time ON llm_search_log(searched_at);"); + // Query endpoint tool invocation log - tracks all MCP tool calls via /mcp/query/ + db->execute( + "CREATE TABLE IF NOT EXISTS query_tool_calls (" + " call_id INTEGER PRIMARY KEY AUTOINCREMENT , " + " tool_name TEXT NOT NULL , " + " schema TEXT , " + " run_id INTEGER , " + " start_time INTEGER NOT NULL , " + " execution_time INTEGER NOT NULL , " + " error TEXT , " + " called_at TEXT NOT NULL DEFAULT (datetime('now'))" + ");" + ); + proxy_debug(PROXY_DEBUG_GENERIC, 3, "Discovery_Schema: query_tool_calls table created/verified\n"); + + db->execute("CREATE INDEX IF NOT EXISTS idx_query_tool_calls_tool ON query_tool_calls(tool_name);"); + db->execute("CREATE INDEX IF NOT EXISTS idx_query_tool_calls_schema ON query_tool_calls(schema);"); + db->execute("CREATE INDEX IF NOT EXISTS idx_query_tool_calls_run ON query_tool_calls(run_id);"); + db->execute("CREATE INDEX IF NOT EXISTS idx_query_tool_calls_time ON query_tool_calls(called_at);"); + return 0; } @@ -1872,3 +1892,50 @@ int Discovery_Schema::log_llm_search( return 0; } + +int Discovery_Schema::log_query_tool_call( + const std::string& tool_name, + const std::string& schema, + int run_id, + unsigned long long start_time, + unsigned long long execution_time, + const std::string& error +) { + sqlite3_stmt* stmt = NULL; + const char* sql = "INSERT INTO query_tool_calls(tool_name, schema, run_id, start_time, execution_time, error) VALUES(?1, ?2, ?3, ?4, ?5, ?6);"; + + int rc = db->prepare_v2(sql, &stmt); + if (rc != SQLITE_OK || !stmt) { + proxy_error("Failed to prepare query_tool_calls insert: %d\n", rc); + return -1; + } + + sqlite3_bind_text(stmt, 1, tool_name.c_str(), -1, SQLITE_TRANSIENT); + if (!schema.empty()) { + sqlite3_bind_text(stmt, 2, schema.c_str(), -1, SQLITE_TRANSIENT); + } else { + sqlite3_bind_null(stmt, 2); + } + if (run_id > 0) { + sqlite3_bind_int(stmt, 3, run_id); + } else { + sqlite3_bind_null(stmt, 3); + } + sqlite3_bind_int64(stmt, 4, start_time); + sqlite3_bind_int64(stmt, 5, execution_time); + if (!error.empty()) { + sqlite3_bind_text(stmt, 6, error.c_str(), -1, SQLITE_TRANSIENT); + } else { + sqlite3_bind_null(stmt, 6); + } + + rc = sqlite3_step(stmt); + (*proxy_sqlite3_finalize)(stmt); + + if (rc != SQLITE_DONE) { + proxy_error("Failed to insert query_tool_calls: %d\n", rc); + return -1; + } + + return 0; +} diff --git a/lib/Query_Tool_Handler.cpp b/lib/Query_Tool_Handler.cpp index cad0b9b44..307750b20 100644 --- a/lib/Query_Tool_Handler.cpp +++ b/lib/Query_Tool_Handler.cpp @@ -1525,6 +1525,24 @@ json Query_Tool_Handler::execute_tool(const std::string& tool_name, const json& unsigned long long duration = monotonic_time() - start_time; track_tool_invocation(this, tool_name, schema, duration); + // Log tool invocation to catalog + int run_id = 0; + std::string run_id_str = json_string(arguments, "run_id"); + if (!run_id_str.empty()) { + run_id = catalog->resolve_run_id(run_id_str); + } + + // Extract error message if present + std::string error_msg; + if (result.contains("error") && result.contains("message")) { + const json& err = result["error"]; + if (err.contains("message") && err["message"].is_string()) { + error_msg = err["message"].get(); + } + } + + catalog->log_query_tool_call(tool_name, schema, run_id, start_time, duration, error_msg); + return result; }