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::{GetTableSchemaRequest, QueryRequest};
8use rmcp::handler::server::tool::ToolRouter;
9use rmcp::handler::server::wrapper::Parameters;
10use rmcp::model::{CallToolResult, Content, ErrorData};
11use rmcp::tool;
12
13use database_mcp_sql::validation::validate_read_only_with_dialect;
14
15use super::SqliteAdapter;
16
17impl SqliteAdapter {
18    /// Names of tools that require write access.
19    const WRITE_TOOL_NAMES: &[&str] = &["write_query"];
20
21    /// Builds the tool router, excluding write tools in read-only mode.
22    #[must_use]
23    pub fn build_tool_router(&self) -> ToolRouter<Self> {
24        let mut router = Self::tool_router();
25        if self.config.read_only {
26            for name in Self::WRITE_TOOL_NAMES {
27                router.remove_route(name);
28            }
29        }
30        router
31    }
32}
33
34#[rmcp::tool_router]
35impl SqliteAdapter {
36    /// List all tables in the connected `SQLite` database.
37    #[tool(
38        name = "list_tables",
39        annotations(
40            read_only_hint = true,
41            destructive_hint = false,
42            idempotent_hint = true,
43            open_world_hint = false
44        )
45    )]
46    pub async fn tool_list_tables(&self) -> Result<CallToolResult, ErrorData> {
47        let result = self.list_tables().await?;
48        Ok(CallToolResult::success(vec![Content::json(result)?]))
49    }
50
51    /// Get column definitions (type, nullable, key, default) and foreign key
52    /// relationships for a table.
53    #[tool(
54        name = "get_table_schema",
55        annotations(
56            read_only_hint = true,
57            destructive_hint = false,
58            idempotent_hint = true,
59            open_world_hint = false
60        )
61    )]
62    pub async fn tool_get_table_schema(
63        &self,
64        Parameters(request): Parameters<GetTableSchemaRequest>,
65    ) -> Result<CallToolResult, ErrorData> {
66        let result = self.get_table_schema(&request.table_name).await?;
67        Ok(CallToolResult::success(vec![Content::json(result)?]))
68    }
69
70    /// Execute a read-only SQL query (SELECT, SHOW, DESCRIBE, USE, EXPLAIN).
71    #[tool(
72        name = "read_query",
73        annotations(
74            read_only_hint = true,
75            destructive_hint = false,
76            idempotent_hint = true,
77            open_world_hint = true
78        )
79    )]
80    pub async fn tool_read_query(
81        &self,
82        Parameters(request): Parameters<QueryRequest>,
83    ) -> Result<CallToolResult, ErrorData> {
84        validate_read_only_with_dialect(&request.query, &sqlparser::dialect::SQLiteDialect {})?;
85
86        let result = self.execute_query(&request.query).await?;
87        Ok(CallToolResult::success(vec![Content::json(result)?]))
88    }
89
90    /// Execute a write SQL query (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP).
91    #[tool(
92        name = "write_query",
93        annotations(
94            read_only_hint = false,
95            destructive_hint = true,
96            idempotent_hint = false,
97            open_world_hint = true
98        )
99    )]
100    pub async fn tool_write_query(
101        &self,
102        Parameters(request): Parameters<QueryRequest>,
103    ) -> Result<CallToolResult, ErrorData> {
104        let result = self.execute_query(&request.query).await?;
105        Ok(CallToolResult::success(vec![Content::json(result)?]))
106    }
107}