database_mcp_sqlite/
handler.rs1use database_mcp_backend::types::{GetTableSchemaRequest, ListTablesRequest, QueryRequest};
7use database_mcp_config::DatabaseConfig;
8use database_mcp_server::tools;
9use rmcp::ServerHandler;
10use rmcp::handler::server::router::tool::ToolRouter;
11use rmcp::handler::server::wrapper::Parameters;
12use rmcp::model::ErrorData;
13
14use super::SqliteBackend;
15
16#[derive(Clone, Debug)]
21pub struct SqliteHandler {
22 backend: SqliteBackend,
23 tool_router: ToolRouter<Self>,
24}
25
26impl SqliteHandler {
27 pub async fn new(config: &DatabaseConfig) -> Result<Self, database_mcp_backend::AppError> {
33 let backend = SqliteBackend::new(config).await?;
34 let mut tool_router = Self::tool_router();
35 if backend.read_only {
36 tool_router.remove_route("write_query");
37 }
38 Ok(Self { backend, tool_router })
39 }
40}
41
42#[rmcp::tool_router]
43impl SqliteHandler {
44 #[rmcp::tool(
46 name = "list_tables",
47 description = "List all tables in a specific database. Requires database_name from list_databases.",
48 annotations(
49 read_only_hint = true,
50 destructive_hint = false,
51 idempotent_hint = true,
52 open_world_hint = false
53 )
54 )]
55 async fn list_tables(&self, Parameters(req): Parameters<ListTablesRequest>) -> Result<String, ErrorData> {
56 tools::list_tables(self.backend.list_tables(&req.database_name), &req.database_name).await
57 }
58
59 #[rmcp::tool(
61 name = "get_table_schema",
62 description = "Get column definitions (type, nullable, key, default) and foreign key relationships for a table. Requires database_name and table_name.",
63 annotations(
64 read_only_hint = true,
65 destructive_hint = false,
66 idempotent_hint = true,
67 open_world_hint = false
68 )
69 )]
70 async fn get_table_schema(&self, Parameters(req): Parameters<GetTableSchemaRequest>) -> Result<String, ErrorData> {
71 tools::get_table_schema(
72 self.backend.get_table_schema(&req.database_name, &req.table_name),
73 &req.database_name,
74 &req.table_name,
75 )
76 .await
77 }
78
79 #[rmcp::tool(
81 name = "read_query",
82 description = "Execute a read-only SQL query (SELECT, SHOW, DESCRIBE, USE, EXPLAIN).",
83 annotations(
84 read_only_hint = true,
85 destructive_hint = false,
86 idempotent_hint = true,
87 open_world_hint = true
88 )
89 )]
90 async fn read_query(&self, Parameters(req): Parameters<QueryRequest>) -> Result<String, ErrorData> {
91 let db = tools::resolve_database(&req.database_name);
92 tools::read_query(
93 self.backend.execute_query(&req.sql_query, db),
94 &req.sql_query,
95 &req.database_name,
96 |sql| {
97 database_mcp_backend::validation::validate_read_only_with_dialect(
98 sql,
99 &sqlparser::dialect::SQLiteDialect {},
100 )
101 },
102 )
103 .await
104 }
105
106 #[rmcp::tool(
108 name = "write_query",
109 description = "Execute a write SQL query (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP).",
110 annotations(
111 read_only_hint = false,
112 destructive_hint = true,
113 idempotent_hint = false,
114 open_world_hint = true
115 )
116 )]
117 async fn write_query(&self, Parameters(req): Parameters<QueryRequest>) -> Result<String, ErrorData> {
118 let db = tools::resolve_database(&req.database_name);
119 tools::write_query(
120 self.backend.execute_query(&req.sql_query, db),
121 &req.sql_query,
122 &req.database_name,
123 )
124 .await
125 }
126}
127
128#[rmcp::tool_handler(router = self.tool_router)]
129impl ServerHandler for SqliteHandler {
130 fn get_info(&self) -> rmcp::model::ServerInfo {
131 database_mcp_server::server_info()
132 }
133}