database_mcp_mysql/tools/
write_query.rs1use std::borrow::Cow;
4
5use database_mcp_server::types::{QueryRequest, QueryResponse};
6use database_mcp_sql::Connection as _;
7use database_mcp_sql::SqlError;
8use database_mcp_sql::sanitize::validate_ident;
9use rmcp::handler::server::router::tool::{AsyncTool, ToolBase};
10use rmcp::model::{ErrorData, ToolAnnotations};
11
12use crate::MysqlHandler;
13
14pub(crate) struct WriteQueryTool;
16
17impl WriteQueryTool {
18 const NAME: &'static str = "writeQuery";
19 const TITLE: &'static str = "Write Query";
20 const DESCRIPTION: &'static str = r#"Execute a write SQL query (INSERT, UPDATE, DELETE, CREATE, ALTER, DROP).
21
22<usecase>
23Use when:
24- Inserting, updating, or deleting rows
25- Creating or altering tables, indexes, views, or other schema objects
26- Any data modification operation
27</usecase>
28
29<when_not_to_use>
30- Read-only queries (SELECT, SHOW) → use readQuery
31- Query performance analysis → use explainQuery
32- Creating/dropping entire databases → use createDatabase or dropDatabase
33</when_not_to_use>
34
35<examples>
36✓ "INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')"
37✓ "UPDATE orders SET status = 'shipped' WHERE id = 42"
38✓ "CREATE TABLE logs (id INT PRIMARY KEY, message TEXT)"
39✗ "SELECT * FROM users" → use readQuery
40</examples>
41
42<what_it_returns>
43A JSON array of affected/returning row objects, each keyed by column name.
44</what_it_returns>"#;
45}
46
47impl ToolBase for WriteQueryTool {
48 type Parameter = QueryRequest;
49 type Output = QueryResponse;
50 type Error = ErrorData;
51
52 fn name() -> Cow<'static, str> {
53 Self::NAME.into()
54 }
55
56 fn title() -> Option<String> {
57 Some(Self::TITLE.into())
58 }
59
60 fn description() -> Option<Cow<'static, str>> {
61 Some(Self::DESCRIPTION.into())
62 }
63
64 fn annotations() -> Option<ToolAnnotations> {
65 Some(
66 ToolAnnotations::new()
67 .read_only(false)
68 .destructive(true)
69 .idempotent(false)
70 .open_world(true),
71 )
72 }
73}
74
75impl AsyncTool<MysqlHandler> for WriteQueryTool {
76 async fn invoke(handler: &MysqlHandler, params: Self::Parameter) -> Result<Self::Output, Self::Error> {
77 Ok(handler.write_query(params).await?)
78 }
79}
80
81impl MysqlHandler {
82 pub async fn write_query(&self, QueryRequest { query, database }: QueryRequest) -> Result<QueryResponse, SqlError> {
88 let db = Some(database.trim()).filter(|s| !s.is_empty());
89
90 if let Some(name) = &db {
91 validate_ident(name)?;
92 }
93
94 let rows = self.connection.fetch_json(query.as_str(), db).await?;
95
96 Ok(QueryResponse { rows })
97 }
98}