database_mcp_mysql/tools/
create_database.rs1use std::borrow::Cow;
4
5use database_mcp_server::types::{CreateDatabaseRequest, MessageResponse};
6use database_mcp_sql::Connection as _;
7use database_mcp_sql::SqlError;
8use database_mcp_sql::sanitize::{quote_ident, quote_literal, validate_ident};
9use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
10use rmcp::model::{ErrorData, ToolAnnotations};
11use sqlparser::dialect::MySqlDialect;
12
13use crate::MysqlHandler;
14
15pub(crate) struct CreateDatabaseTool;
17
18impl CreateDatabaseTool {
19 const NAME: &'static str = "createDatabase";
20 const TITLE: &'static str = "Create Database";
21 const DESCRIPTION: &'static str = r#"Create a new database on the connected server.
22
23<usecase>
24Use when:
25- Setting up a new database for a project or application
26- The user asks to create a database
27</usecase>
28
29<examples>
30✓ "Create a database called analytics" → createDatabase(database="analytics")
31✗ "Create a table" → use writeQuery with CREATE TABLE
32</examples>
33
34<important>
35Database names must contain only alphanumeric characters and underscores.
36If the database already exists, returns a message indicating so without error.
37</important>
38
39<what_it_returns>
40A confirmation message with the created database name.
41</what_it_returns>"#;
42}
43
44impl ToolBase for CreateDatabaseTool {
45 type Parameter = CreateDatabaseRequest;
46 type Output = MessageResponse;
47 type Error = ErrorData;
48
49 fn name() -> Cow<'static, str> {
50 Self::NAME.into()
51 }
52
53 fn title() -> Option<String> {
54 Some(Self::TITLE.into())
55 }
56
57 fn description() -> Option<Cow<'static, str>> {
58 Some(Self::DESCRIPTION.into())
59 }
60
61 fn annotations() -> Option<ToolAnnotations> {
62 Some(
63 ToolAnnotations::new()
64 .read_only(false)
65 .destructive(false)
66 .idempotent(false)
67 .open_world(false),
68 )
69 }
70}
71
72impl AsyncTool<MysqlHandler> for CreateDatabaseTool {
73 async fn invoke(handler: &MysqlHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
74 Ok(handler.create_database(params).await?)
75 }
76}
77
78impl MysqlHandler {
79 pub async fn create_database(
85 &self,
86 CreateDatabaseRequest { database }: CreateDatabaseRequest,
87 ) -> Result<MessageResponse, SqlError> {
88 if self.config.read_only {
89 return Err(SqlError::ReadOnlyViolation);
90 }
91
92 validate_ident(&database)?;
93
94 let check_sql = format!(
95 r"
96 SELECT CAST(SCHEMA_NAME AS CHAR)
97 FROM information_schema.SCHEMATA
98 WHERE SCHEMA_NAME = {}",
99 quote_literal(&database),
100 );
101
102 let exists: Option<String> = self.connection.fetch_optional(check_sql.as_str(), None).await?;
103
104 if exists.is_some() {
105 return Ok(MessageResponse {
106 message: format!("Database '{database}' already exists."),
107 });
108 }
109
110 let create_sql = format!(
111 "CREATE DATABASE IF NOT EXISTS {}",
112 quote_ident(&database, &MySqlDialect {})
113 );
114
115 self.connection.execute(create_sql.as_str(), None).await?;
116
117 Ok(MessageResponse {
118 message: format!("Database '{database}' created successfully."),
119 })
120 }
121}