Skip to main content

database_mcp_postgres/tools/
drop_database.rs

1//! MCP tool: `drop_database`.
2
3use std::borrow::Cow;
4
5use database_mcp_server::AppError;
6use database_mcp_server::types::{DropDatabaseRequest, MessageResponse};
7use database_mcp_sql::identifier::validate_identifier;
8use database_mcp_sql::timeout::execute_with_timeout;
9use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
10use rmcp::model::{ErrorData, ToolAnnotations};
11
12use crate::PostgresHandler;
13
14/// Marker type for the `drop_database` MCP tool.
15pub(crate) struct DropDatabaseTool;
16
17impl DropDatabaseTool {
18    const NAME: &'static str = "drop_database";
19    const DESCRIPTION: &'static str = "Drop an existing database. Cannot drop the currently connected database.";
20}
21
22impl ToolBase for DropDatabaseTool {
23    type Parameter = DropDatabaseRequest;
24    type Output = MessageResponse;
25    type Error = ErrorData;
26
27    fn name() -> Cow<'static, str> {
28        Self::NAME.into()
29    }
30
31    fn description() -> Option<Cow<'static, str>> {
32        Some(Self::DESCRIPTION.into())
33    }
34
35    fn annotations() -> Option<ToolAnnotations> {
36        Some(
37            ToolAnnotations::new()
38                .read_only(false)
39                .destructive(true)
40                .idempotent(false)
41                .open_world(false),
42        )
43    }
44}
45
46impl AsyncTool<PostgresHandler> for DropDatabaseTool {
47    async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
48        Ok(handler.drop_database(&params).await?)
49    }
50}
51
52impl PostgresHandler {
53    /// Drops an existing database.
54    ///
55    /// Refuses to drop the currently connected (default) database and
56    /// evicts the corresponding pool cache entry after a successful drop.
57    ///
58    /// # Errors
59    ///
60    /// Returns [`AppError::ReadOnlyViolation`] in read-only mode,
61    /// [`AppError::InvalidIdentifier`] for invalid names,
62    /// or [`AppError::Query`] if the target is the active database
63    /// or the backend reports an error.
64    pub async fn drop_database(&self, request: &DropDatabaseRequest) -> Result<MessageResponse, AppError> {
65        if self.config.read_only {
66            return Err(AppError::ReadOnlyViolation);
67        }
68        let name = &request.database_name;
69        validate_identifier(name)?;
70
71        // Guard: prevent dropping the currently connected database.
72        if self.default_db == *name {
73            return Err(AppError::Query(format!(
74                "Cannot drop the currently connected database '{name}'."
75            )));
76        }
77
78        let pool = self.get_pool(None).await?;
79
80        let drop_sql = format!("DROP DATABASE {}", Self::quote_identifier(name));
81        execute_with_timeout(
82            self.config.query_timeout,
83            &drop_sql,
84            sqlx::query(&drop_sql).execute(&pool),
85        )
86        .await?;
87
88        // Evict the pool for the dropped database so stale connections
89        // are not reused.
90        self.pools.invalidate(name).await;
91
92        Ok(MessageResponse {
93            message: format!("Database '{name}' dropped successfully."),
94        })
95    }
96}