Skip to main content

dbmcp_postgres/tools/
list_databases.rs

1//! MCP tool: `listDatabases`.
2
3use dbmcp_server::pagination::Pager;
4use dbmcp_server::types::{ListDatabasesRequest, ListDatabasesResponse};
5
6use super::prelude::*;
7
8const NAME: &str = "listDatabases";
9const TITLE: &str = "List Databases";
10const DESCRIPTION: &str = include_str!("../../assets/tools/list_databases/default.md");
11
12/// Marker type for the `listDatabases` MCP tool.
13pub(crate) struct ListDatabasesTool;
14
15impl ToolBase for ListDatabasesTool {
16    type Parameter = ListDatabasesRequest;
17    type Output = ListDatabasesResponse;
18    type Error = ErrorData;
19
20    fn name() -> Cow<'static, str> {
21        NAME.into()
22    }
23
24    fn title() -> Option<String> {
25        Some(TITLE.into())
26    }
27
28    fn description() -> Option<Cow<'static, str>> {
29        Some(DESCRIPTION.into())
30    }
31
32    fn annotations() -> Option<ToolAnnotations> {
33        Some(
34            ToolAnnotations::new()
35                .read_only(true)
36                .destructive(false)
37                .idempotent(true)
38                .open_world(false),
39        )
40    }
41
42    fn input_schema() -> Option<Arc<JsonObject>> {
43        Some(input_schema::<Self::Parameter>(false))
44    }
45
46    fn output_schema() -> Option<Arc<JsonObject>> {
47        Some(output_schema::<Self::Output>())
48    }
49}
50
51impl AsyncTool<PostgresHandler> for ListDatabasesTool {
52    async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
53        handler.list_databases(params).await
54    }
55}
56
57impl PostgresHandler {
58    /// Lists one page of accessible databases.
59    ///
60    /// Uses the default pool intentionally — `pg_database` is a server-wide
61    /// catalog that returns all databases regardless of which database the
62    /// connection targets.
63    ///
64    /// # Errors
65    ///
66    /// Returns [`ErrorData`] with code `-32602` if `cursor` is malformed,
67    /// or an internal-error [`ErrorData`] if the underlying query fails.
68    pub async fn list_databases(
69        &self,
70        ListDatabasesRequest { cursor }: ListDatabasesRequest,
71    ) -> Result<ListDatabasesResponse, ErrorData> {
72        let pager = Pager::new(cursor, self.config.page_size);
73        let query = format!(
74            r"
75            SELECT datname
76            FROM pg_database
77            WHERE datistemplate = false
78            ORDER BY datname
79            LIMIT {} OFFSET {}",
80            pager.limit(),
81            pager.offset(),
82        );
83
84        let rows: Vec<String> = self.connection.fetch_scalar(query.as_str(), None).await?;
85        let (databases, next_cursor) = pager.paginate(rows);
86
87        Ok(ListDatabasesResponse { databases, next_cursor })
88    }
89}