Skip to main content

dbmcp_postgres/tools/
list_databases.rs

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