mcp_server_sqlite/tools/
execute_tool.rs1use rmcp::model::{Content, IntoContents};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use super::ToolError;
9use crate::{mcp::McpServerSqlite, traits::SqliteServerTool};
10
11#[derive(
12 Clone,
13 Copy,
14 Debug,
15 PartialEq,
16 Eq,
17 PartialOrd,
18 Ord,
19 Hash,
20 Default,
21 Serialize,
22 Deserialize,
23 JsonSchema,
24)]
25pub struct ExecuteTool;
29
30impl SqliteServerTool for ExecuteTool {
31 const NAME: &str = "execute";
32
33 type Context = McpServerSqlite;
34 type Error = ToolError<ExecuteError>;
35
36 type Input = ExecuteInput;
37 type Output = ExecuteOutput;
38
39 fn handle(
40 ctx: &Self::Context,
41 input: Self::Input,
42 ) -> Result<Self::Output, Self::Error> {
43 let conn = ctx
44 .connection()
45 .map_err(|source| ToolError::Connection { source })?;
46
47 let mut stmt = conn.prepare(&input.query).map_err(|source| {
48 if matches!(
49 source,
50 rusqlite::Error::SqliteFailure(
51 rusqlite::ffi::Error {
52 code: rusqlite::ffi::ErrorCode::AuthorizationForStatementDenied,
53 ..
54 },
55 _,
56 )
57 ) {
58 ToolError::AccessDenied {
59 message: format!(
60 "the configured access control policy denied this \
61 statement: {}",
62 input.query,
63 ),
64 }
65 } else {
66 ToolError::Tool(ExecuteError::Prepare { source })
67 }
68 })?;
69
70 let column_count = stmt.column_count();
71 let column_names = (0..column_count)
72 .map(|i| stmt.column_name(i).unwrap_or("?").to_owned())
73 .collect::<Vec<String>>();
74
75 let rows = stmt
76 .query_map([], |row: &rusqlite::Row<'_>| {
77 let columns = column_names
78 .iter()
79 .enumerate()
80 .map(|(i, name)| {
81 let value = row
82 .get::<_, rusqlite::types::Value>(i)
83 .map(Value::from)
84 .unwrap_or(Value::Null);
85 (name.clone(), value)
86 })
87 .collect();
88 Ok(Row { columns })
89 })
90 .map_err(|source| ToolError::Tool(ExecuteError::Query { source }))?
91 .collect::<Result<Vec<_>, _>>()
92 .map_err(|source| {
93 ToolError::Tool(ExecuteError::Query { source })
94 })?;
95
96 let rows_changed = conn.changes();
97
98 Ok(ExecuteOutput { rows, rows_changed })
99 }
100}
101
102#[derive(
104 Clone,
105 Debug,
106 PartialEq,
107 Eq,
108 PartialOrd,
109 Ord,
110 Hash,
111 Serialize,
112 Deserialize,
113 schemars::JsonSchema,
114)]
115pub struct ExecuteInput {
116 #[schemars(description = "The SQL query to execute")]
118 pub query: String,
119}
120
121#[derive(
123 Clone,
124 Debug,
125 PartialEq,
126 PartialOrd,
127 Serialize,
128 Deserialize,
129 schemars::JsonSchema,
130)]
131pub struct ExecuteOutput {
132 pub rows: Vec<Row>,
135 pub rows_changed: u64,
138}
139
140#[derive(
143 Clone,
144 Debug,
145 PartialEq,
146 PartialOrd,
147 Serialize,
148 Deserialize,
149 schemars::JsonSchema,
150)]
151pub struct Row {
152 pub columns: std::collections::BTreeMap<String, Value>,
155}
156
157#[serde_with::serde_as]
161#[derive(
162 Clone,
163 Debug,
164 PartialEq,
165 PartialOrd,
166 Serialize,
167 Deserialize,
168 schemars::JsonSchema,
169)]
170#[serde(tag = "kind", content = "value")]
171pub enum Value {
172 Null,
174 Integer(i64),
176 Real(f64),
178 Text(String),
180 Blob(
182 #[serde_as(as = "serde_with::hex::Hex")]
183 #[schemars(with = "String")]
184 Vec<u8>,
185 ),
186}
187
188impl From<rusqlite::types::Value> for Value {
191 fn from(value: rusqlite::types::Value) -> Self {
192 match value {
193 rusqlite::types::Value::Null => Self::Null,
194 rusqlite::types::Value::Integer(n) => Self::Integer(n),
195 rusqlite::types::Value::Real(f) => Self::Real(f),
196 rusqlite::types::Value::Text(s) => Self::Text(s),
197 rusqlite::types::Value::Blob(b) => Self::Blob(b),
198 }
199 }
200}
201
202#[derive(Debug, thiserror::Error)]
205pub enum ExecuteError {
206 #[error("failed to prepare statement: {source}")]
209 Prepare {
210 source: rusqlite::Error,
212 },
213 #[error("failed to read query results: {source}")]
215 Query {
216 source: rusqlite::Error,
218 },
219}
220
221impl IntoContents for ExecuteError {
224 fn into_contents(self) -> Vec<Content> {
225 vec![Content::text(self.to_string())]
226 }
227}