database_mcp_postgres/
tools.rs1use database_mcp_server::types::{CreateDatabaseRequest, GetTableSchemaRequest, ListTablesRequest, 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::PostgresAdapter;
16
17impl PostgresAdapter {
18 const WRITE_TOOL_NAMES: &[&str] = &["write_query", "create_database"];
20
21 #[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 PostgresAdapter {
36 #[tool(
39 name = "list_databases",
40 annotations(
41 read_only_hint = true,
42 destructive_hint = false,
43 idempotent_hint = true,
44 open_world_hint = false
45 )
46 )]
47 pub async fn tool_list_databases(&self) -> Result<CallToolResult, ErrorData> {
48 let result = self.list_databases().await?;
49 Ok(CallToolResult::success(vec![Content::json(result)?]))
50 }
51
52 #[tool(
55 name = "list_tables",
56 annotations(
57 read_only_hint = true,
58 destructive_hint = false,
59 idempotent_hint = true,
60 open_world_hint = false
61 )
62 )]
63 pub async fn tool_list_tables(
64 &self,
65 Parameters(request): Parameters<ListTablesRequest>,
66 ) -> Result<CallToolResult, ErrorData> {
67 let result = self.list_tables(&request.database_name).await?;
68 Ok(CallToolResult::success(vec![Content::json(result)?]))
69 }
70
71 #[tool(
74 name = "get_table_schema",
75 annotations(
76 read_only_hint = true,
77 destructive_hint = false,
78 idempotent_hint = true,
79 open_world_hint = false
80 )
81 )]
82 pub async fn tool_get_table_schema(
83 &self,
84 Parameters(request): Parameters<GetTableSchemaRequest>,
85 ) -> Result<CallToolResult, ErrorData> {
86 let result = self
87 .get_table_schema(&request.database_name, &request.table_name)
88 .await?;
89 Ok(CallToolResult::success(vec![Content::json(result)?]))
90 }
91
92 #[tool(
94 name = "read_query",
95 annotations(
96 read_only_hint = true,
97 destructive_hint = false,
98 idempotent_hint = true,
99 open_world_hint = true
100 )
101 )]
102 pub async fn tool_read_query(
103 &self,
104 Parameters(request): Parameters<QueryRequest>,
105 ) -> Result<CallToolResult, ErrorData> {
106 validate_read_only_with_dialect(&request.query, &sqlparser::dialect::PostgreSqlDialect {})?;
107
108 let db = Some(request.database_name.trim()).filter(|s| !s.is_empty());
109 let result = self.execute_query(&request.query, db).await?;
110 Ok(CallToolResult::success(vec![Content::json(result)?]))
111 }
112
113 #[tool(
115 name = "write_query",
116 annotations(
117 read_only_hint = false,
118 destructive_hint = true,
119 idempotent_hint = false,
120 open_world_hint = true
121 )
122 )]
123 pub async fn tool_write_query(
124 &self,
125 Parameters(request): Parameters<QueryRequest>,
126 ) -> Result<CallToolResult, ErrorData> {
127 let db = Some(request.database_name.trim()).filter(|s| !s.is_empty());
128 let result = self.execute_query(&request.query, db).await?;
129 Ok(CallToolResult::success(vec![Content::json(result)?]))
130 }
131
132 #[tool(
134 name = "create_database",
135 annotations(
136 read_only_hint = false,
137 destructive_hint = false,
138 idempotent_hint = false,
139 open_world_hint = false
140 )
141 )]
142 pub async fn tool_create_database(
143 &self,
144 Parameters(request): Parameters<CreateDatabaseRequest>,
145 ) -> Result<CallToolResult, ErrorData> {
146 let result = self.create_database(&request.database_name).await?;
147 Ok(CallToolResult::success(vec![Content::json(result)?]))
148 }
149}