Skip to main content

database_mcp_sqlite/
tools.rs

1//! MCP tool definitions for the `SQLite` backend.
2//!
3//! Uses rmcp `#[tool]` attribute macros to define tools as methods
4//! on [`SqliteAdapter`], eliminating manual [`ToolBase`] and
5//! [`AsyncTool`] implementations.
6
7use super::types::{DropTableRequest, ExplainQueryRequest, GetTableSchemaRequest, QueryRequest};
8use database_mcp_server::types::{ListTablesResponse, MessageResponse, QueryResponse, TableSchemaResponse};
9use rmcp::handler::server::tool::ToolRouter;
10use rmcp::handler::server::wrapper::{Json, Parameters};
11use rmcp::model::ErrorData;
12use rmcp::tool;
13
14use super::SqliteAdapter;
15
16impl SqliteAdapter {
17    /// Names of tools that require write access.
18    const WRITE_TOOL_NAMES: &[&str] = &["write_query", "drop_table"];
19
20    /// Builds the tool router, excluding write tools in read-only mode.
21    #[must_use]
22    pub fn build_tool_router(&self) -> ToolRouter<Self> {
23        let mut router = Self::tool_router();
24        if self.config.read_only {
25            for name in Self::WRITE_TOOL_NAMES {
26                router.remove_route(name);
27            }
28        }
29        router
30    }
31}
32
33#[rmcp::tool_router]
34impl SqliteAdapter {
35    /// List all tables in the connected `SQLite` database.
36    #[tool(
37        name = "list_tables",
38        annotations(
39            read_only_hint = true,
40            destructive_hint = false,
41            idempotent_hint = true,
42            open_world_hint = false
43        )
44    )]
45    pub async fn tool_list_tables(&self) -> Result<Json<ListTablesResponse>, ErrorData> {
46        Ok(Json(self.list_tables().await?))
47    }
48
49    /// Get column definitions (type, nullable, key, default) and foreign key
50    /// relationships for a table.
51    #[tool(
52        name = "get_table_schema",
53        annotations(
54            read_only_hint = true,
55            destructive_hint = false,
56            idempotent_hint = true,
57            open_world_hint = false
58        )
59    )]
60    pub async fn tool_get_table_schema(
61        &self,
62        Parameters(request): Parameters<GetTableSchemaRequest>,
63    ) -> Result<Json<TableSchemaResponse>, ErrorData> {
64        Ok(Json(self.get_table_schema(&request).await?))
65    }
66
67    /// Drop a table from the database.
68    #[tool(
69        name = "drop_table",
70        annotations(
71            read_only_hint = false,
72            destructive_hint = true,
73            idempotent_hint = false,
74            open_world_hint = false
75        )
76    )]
77    pub async fn tool_drop_table(
78        &self,
79        Parameters(request): Parameters<DropTableRequest>,
80    ) -> Result<Json<MessageResponse>, ErrorData> {
81        Ok(Json(self.drop_table(&request).await?))
82    }
83
84    /// Execute a read-only SQL query (SELECT, SHOW, DESCRIBE, USE, EXPLAIN).
85    #[tool(
86        name = "read_query",
87        annotations(
88            read_only_hint = true,
89            destructive_hint = false,
90            idempotent_hint = true,
91            open_world_hint = true
92        )
93    )]
94    pub async fn tool_read_query(
95        &self,
96        Parameters(request): Parameters<QueryRequest>,
97    ) -> Result<Json<QueryResponse>, ErrorData> {
98        Ok(Json(self.read_query(&request).await?))
99    }
100
101    /// Execute a write SQL query (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP).
102    #[tool(
103        name = "write_query",
104        annotations(
105            read_only_hint = false,
106            destructive_hint = true,
107            idempotent_hint = false,
108            open_world_hint = true
109        )
110    )]
111    pub async fn tool_write_query(
112        &self,
113        Parameters(request): Parameters<QueryRequest>,
114    ) -> Result<Json<QueryResponse>, ErrorData> {
115        Ok(Json(self.write_query(&request).await?))
116    }
117
118    /// Return the execution plan for a SQL query.
119    #[tool(
120        name = "explain_query",
121        annotations(
122            read_only_hint = true,
123            destructive_hint = false,
124            idempotent_hint = true,
125            open_world_hint = true
126        )
127    )]
128    pub async fn tool_explain_query(
129        &self,
130        Parameters(request): Parameters<ExplainQueryRequest>,
131    ) -> Result<Json<QueryResponse>, ErrorData> {
132        Ok(Json(self.explain_query(&request).await?))
133    }
134}