database_mcp_postgres/tools/
list_tables.rs1use std::borrow::Cow;
4
5use database_mcp_server::AppError;
6use database_mcp_server::types::{ListTablesRequest, ListTablesResponse};
7use database_mcp_sql::Connection as _;
8use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
9use rmcp::model::{ErrorData, ToolAnnotations};
10use serde_json::Value;
11
12use crate::PostgresHandler;
13
14pub(crate) struct ListTablesTool;
16
17impl ListTablesTool {
18 const NAME: &'static str = "list_tables";
19 const DESCRIPTION: &'static str = r#"List all tables in a specific database. Requires `database_name` — call `list_databases` first to discover available databases.
20
21<usecase>
22Use when:
23- Exploring a database to find relevant tables
24- Verifying a table exists before querying or inspecting it
25- The user asks what tables are in a database
26</usecase>
27
28<examples>
29✓ "What tables are in the mydb database?" → list_tables(database_name="mydb")
30✓ "Does a users table exist?" → list_tables to check
31✗ "Show me the columns of users" → use get_table_schema instead
32</examples>
33
34<what_it_returns>
35A sorted JSON array of table name strings.
36</what_it_returns>"#;
37}
38
39impl ToolBase for ListTablesTool {
40 type Parameter = ListTablesRequest;
41 type Output = ListTablesResponse;
42 type Error = ErrorData;
43
44 fn name() -> Cow<'static, str> {
45 Self::NAME.into()
46 }
47
48 fn description() -> Option<Cow<'static, str>> {
49 Some(Self::DESCRIPTION.into())
50 }
51
52 fn annotations() -> Option<ToolAnnotations> {
53 Some(
54 ToolAnnotations::new()
55 .read_only(true)
56 .destructive(false)
57 .idempotent(true)
58 .open_world(false),
59 )
60 }
61}
62
63impl AsyncTool<PostgresHandler> for ListTablesTool {
64 async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
65 Ok(handler.list_tables(¶ms).await?)
66 }
67}
68
69impl PostgresHandler {
70 pub async fn list_tables(&self, request: &ListTablesRequest) -> Result<ListTablesResponse, AppError> {
76 let db = if request.database_name.is_empty() {
77 None
78 } else {
79 Some(request.database_name.as_str())
80 };
81 let sql = "SELECT tablename FROM pg_tables WHERE schemaname = 'public' ORDER BY tablename";
82 let rows = self.connection.fetch(sql, db).await?;
83 Ok(ListTablesResponse {
84 tables: rows
85 .iter()
86 .filter_map(|r| r.get("tablename").and_then(Value::as_str).map(str::to_owned))
87 .collect(),
88 })
89 }
90}