Skip to main content

database_mcp_server/
tools.rs

1//! Shared tool implementation functions.
2//!
3//! Extracts the common logging, validation, and serialization logic
4//! from per-backend MCP tool handlers into reusable functions.
5
6use database_mcp_backend::error::AppError;
7use rmcp::model::ErrorData;
8use serde::Serialize;
9use serde_json::Value;
10use tracing::info;
11
12use crate::map_error;
13
14/// Executes a `list_databases` tool call.
15///
16/// # Errors
17///
18/// Returns [`ErrorData`] if the backend query or JSON serialization fails.
19pub async fn list_databases(list_fn: impl Future<Output = Result<Vec<String>, AppError>>) -> Result<String, ErrorData> {
20    info!("TOOL: list_databases called");
21    let db_list = list_fn.await.map_err(map_error)?;
22    info!("TOOL: list_databases completed. Databases found: {}", db_list.len());
23    serde_json::to_string_pretty(&db_list).map_err(map_error)
24}
25
26/// Executes a `list_tables` tool call.
27///
28/// # Errors
29///
30/// Returns [`ErrorData`] if the backend query or JSON serialization fails.
31pub async fn list_tables(
32    list_fn: impl Future<Output = Result<Vec<String>, AppError>>,
33    database_name: &str,
34) -> Result<String, ErrorData> {
35    info!("TOOL: list_tables called. database_name={database_name}");
36    let table_list = list_fn.await.map_err(map_error)?;
37    info!("TOOL: list_tables completed. Tables found: {}", table_list.len());
38    serde_json::to_string_pretty(&table_list).map_err(map_error)
39}
40
41/// Executes a `get_table_schema` tool call.
42///
43/// # Errors
44///
45/// Returns [`ErrorData`] if the backend query or JSON serialization fails.
46pub async fn get_table_schema(
47    schema_fn: impl Future<Output = Result<impl Serialize, AppError>>,
48    database_name: &str,
49    table_name: &str,
50) -> Result<String, ErrorData> {
51    info!("TOOL: get_table_schema called. database_name={database_name}, table_name={table_name}");
52    let schema = schema_fn.await.map_err(map_error)?;
53    info!("TOOL: get_table_schema completed");
54    serde_json::to_string_pretty(&schema).map_err(map_error)
55}
56
57/// Executes a `read_query` tool call with read-only validation.
58///
59/// The `validate` closure performs backend-specific SQL validation
60/// (e.g. read-only enforcement with the appropriate SQL dialect).
61///
62/// # Errors
63///
64/// Returns [`ErrorData`] if validation, the backend query, or JSON serialization fails.
65pub async fn read_query(
66    query_fn: impl Future<Output = Result<Value, AppError>>,
67    sql_query: &str,
68    database_name: &str,
69    validate: impl FnOnce(&str) -> Result<(), AppError>,
70) -> Result<String, ErrorData> {
71    info!(
72        "TOOL: execute_sql called. database_name={database_name}, sql_query={}",
73        &sql_query[..sql_query.len().min(100)]
74    );
75
76    validate(sql_query).map_err(map_error)?;
77
78    let results = query_fn.await.map_err(map_error)?;
79    let row_count = results.as_array().map_or(0, Vec::len);
80    info!("TOOL: execute_sql completed. Rows returned: {row_count}");
81    serde_json::to_string_pretty(&results).map_err(map_error)
82}
83
84/// Executes a `write_query` tool call.
85///
86/// # Errors
87///
88/// Returns [`ErrorData`] if the backend query or JSON serialization fails.
89pub async fn write_query(
90    query_fn: impl Future<Output = Result<Value, AppError>>,
91    sql_query: &str,
92    database_name: &str,
93) -> Result<String, ErrorData> {
94    info!(
95        "TOOL: execute_sql called. database_name={database_name}, sql_query={}",
96        &sql_query[..sql_query.len().min(100)]
97    );
98
99    let results = query_fn.await.map_err(map_error)?;
100    let row_count = results.as_array().map_or(0, Vec::len);
101    info!("TOOL: execute_sql completed. Rows returned: {row_count}");
102    serde_json::to_string_pretty(&results).map_err(map_error)
103}
104
105/// Executes a `create_database` tool call.
106///
107/// # Errors
108///
109/// Returns [`ErrorData`] if the backend query or JSON serialization fails.
110pub async fn create_database(
111    create_fn: impl Future<Output = Result<Value, AppError>>,
112    database_name: &str,
113) -> Result<String, ErrorData> {
114    info!("TOOL: create_database called for database: '{database_name}'");
115    let result = create_fn.await.map_err(map_error)?;
116    info!("TOOL: create_database completed");
117    serde_json::to_string_pretty(&result).map_err(map_error)
118}
119
120/// Resolves an empty database name to `None`.
121#[must_use]
122pub fn resolve_database(name: &str) -> Option<&str> {
123    if name.is_empty() { None } else { Some(name) }
124}