Skip to main content

dbmcp_postgres/tools/
list_views.rs

1//! MCP tool: `listViews`.
2
3use std::borrow::Cow;
4
5use dbmcp_server::pagination::Pager;
6use dbmcp_server::types::{ListViewsRequest, ListViewsResponse};
7use dbmcp_sql::Connection as _;
8use dbmcp_sql::sanitize::validate_ident;
9use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
10use rmcp::model::{ErrorData, ToolAnnotations};
11
12use crate::PostgresHandler;
13
14/// Marker type for the `listViews` MCP tool.
15pub(crate) struct ListViewsTool;
16
17impl ListViewsTool {
18    const NAME: &'static str = "listViews";
19    const TITLE: &'static str = "List Views";
20    const DESCRIPTION: &'static str = r#"List all views in a database.
21
22<usecase>
23Use when:
24- Exploring a database to find defined views alongside tables
25- Verifying a view exists before querying it
26- The user asks what views are in a database
27</usecase>
28
29<examples>
30✓ "What views are in the mydb database?" → listViews(database="mydb")
31✓ "Does an active_users view exist?" → listViews to check
32✗ "Show me the columns of a view" → use getTableSchema instead
33✗ "List materialized views" → use listMaterializedViews
34</examples>
35
36<what_it_returns>
37A sorted JSON array of view name strings in the `public` schema.
38</what_it_returns>
39
40<pagination>
41Paginated. Pass the prior response's `nextCursor` as `cursor` to fetch the next page.
42</pagination>"#;
43}
44
45impl ToolBase for ListViewsTool {
46    type Parameter = ListViewsRequest;
47    type Output = ListViewsResponse;
48    type Error = ErrorData;
49
50    fn name() -> Cow<'static, str> {
51        Self::NAME.into()
52    }
53
54    fn title() -> Option<String> {
55        Some(Self::TITLE.into())
56    }
57
58    fn description() -> Option<Cow<'static, str>> {
59        Some(Self::DESCRIPTION.into())
60    }
61
62    fn annotations() -> Option<ToolAnnotations> {
63        Some(
64            ToolAnnotations::new()
65                .read_only(true)
66                .destructive(false)
67                .idempotent(true)
68                .open_world(false),
69        )
70    }
71}
72
73impl AsyncTool<PostgresHandler> for ListViewsTool {
74    async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
75        handler.list_views(params).await
76    }
77}
78
79impl PostgresHandler {
80    /// Lists one page of views in the `public` schema of a database.
81    ///
82    /// # Errors
83    ///
84    /// Returns [`ErrorData`] with code `-32602` if `cursor` is malformed,
85    /// or an internal-error [`ErrorData`] if `database` is invalid
86    /// or the underlying query fails.
87    pub async fn list_views(
88        &self,
89        ListViewsRequest { database, cursor }: ListViewsRequest,
90    ) -> Result<ListViewsResponse, ErrorData> {
91        let database = database
92            .as_deref()
93            .map(str::trim)
94            .filter(|s| !s.is_empty())
95            .map(validate_ident)
96            .transpose()?;
97
98        let pager = Pager::new(cursor, self.config.page_size);
99        let query = format!(
100            r"
101            SELECT viewname
102            FROM pg_views
103            WHERE schemaname = 'public'
104            ORDER BY viewname
105            LIMIT {} OFFSET {}",
106            pager.limit(),
107            pager.offset(),
108        );
109
110        let rows: Vec<String> = self.connection.fetch_scalar(query.as_str(), database).await?;
111        let (views, next_cursor) = pager.finalize(rows);
112
113        Ok(ListViewsResponse { views, next_cursor })
114    }
115}