Skip to main content

dbmcp_postgres/tools/
create_database.rs

1//! MCP tool: `createDatabase`.
2
3use dbmcp_server::types::{CreateDatabaseRequest, MessageResponse};
4use dbmcp_sql::SqlError;
5
6use super::prelude::*;
7use crate::connection::quote_ident;
8
9const NAME: &str = "createDatabase";
10const TITLE: &str = "Create Database";
11const DESCRIPTION: &str = include_str!("../../assets/tools/create_database/default.md");
12
13/// Marker type for the `createDatabase` MCP tool.
14pub(crate) struct CreateDatabaseTool;
15
16impl ToolBase for CreateDatabaseTool {
17    type Parameter = CreateDatabaseRequest;
18    type Output = MessageResponse;
19    type Error = ErrorData;
20
21    fn name() -> Cow<'static, str> {
22        NAME.into()
23    }
24
25    fn title() -> Option<String> {
26        Some(TITLE.into())
27    }
28
29    fn description() -> Option<Cow<'static, str>> {
30        Some(DESCRIPTION.into())
31    }
32
33    fn annotations() -> Option<ToolAnnotations> {
34        Some(
35            ToolAnnotations::new()
36                .read_only(false)
37                .destructive(false)
38                .idempotent(false)
39                .open_world(false),
40        )
41    }
42
43    fn input_schema() -> Option<Arc<JsonObject>> {
44        Some(input_schema::<Self::Parameter>(false))
45    }
46
47    fn output_schema() -> Option<Arc<JsonObject>> {
48        Some(output_schema::<Self::Output>())
49    }
50}
51
52impl AsyncTool<PostgresHandler> for CreateDatabaseTool {
53    async fn invoke(handler: &PostgresHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
54        handler.create_database(params).await
55    }
56}
57
58impl PostgresHandler {
59    /// Creates a database if it doesn't exist.
60    ///
61    /// # Errors
62    ///
63    /// Returns [`SqlError`] if read-only or the query fails.
64    pub async fn create_database(
65        &self,
66        CreateDatabaseRequest { database }: CreateDatabaseRequest,
67    ) -> Result<MessageResponse, ErrorData> {
68        if self.config.read_only {
69            return Err(SqlError::ReadOnlyViolation.into());
70        }
71
72        let create_sql = format!("CREATE DATABASE {}", quote_ident(&database));
73        self.connection.execute(create_sql.as_str(), None).await.map_err(|e| {
74            let msg = e.to_string();
75            if msg.contains("already exists") {
76                return SqlError::Query(format!("Database '{database}' already exists."));
77            }
78            e
79        })?;
80
81        Ok(MessageResponse {
82            message: format!("Database '{database}' created successfully."),
83        })
84    }
85}