Skip to main content

database_mcp_mysql/tools/
write_query.rs

1//! MCP tool: `writeQuery`.
2
3use 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
14/// Marker type for the `writeQuery` MCP tool.
15pub(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    /// Executes a write SQL query.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`SqlError`] if the query fails.
87    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}