mcp_server_sqlite/tools/
explain_query_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 ExplainQueryTool;
28
29impl SqliteServerTool for ExplainQueryTool {
30 const NAME: &str = "explain_query";
31
32 type Context = McpServerSqlite;
33 type Error = ToolError<ExplainQueryError>;
34
35 type Input = ExplainQueryInput;
36 type Output = ExplainQueryOutput;
37
38 fn handle(
39 ctx: &Self::Context,
40 input: Self::Input,
41 ) -> Result<Self::Output, Self::Error> {
42 let conn = ctx
43 .connection()
44 .map_err(|source| ToolError::Connection { source })?;
45
46 let explain_sql = format!("EXPLAIN QUERY PLAN {}", input.query);
47
48 let mut stmt =
49 conn.prepare(&explain_sql).map_err(|source| {
50 if matches!(
51 source,
52 rusqlite::Error::SqliteFailure(
53 rusqlite::ffi::Error {
54 code: rusqlite::ffi::ErrorCode::AuthorizationForStatementDenied,
55 ..
56 },
57 _,
58 )
59 ) {
60 ToolError::AccessDenied {
61 message: format!(
62 "the configured access control policy \
63 denied this statement: {}",
64 input.query,
65 ),
66 }
67 } else {
68 ToolError::Tool(ExplainQueryError::Query { source })
69 }
70 })?;
71
72 let plan = stmt
73 .query_map([], |row| {
74 Ok(PlanNode {
75 id: row.get(0)?,
76 parent: row.get(1)?,
77 detail: row.get(3)?,
78 })
79 })
80 .map_err(|source| {
81 ToolError::Tool(ExplainQueryError::Query { source })
82 })?
83 .collect::<Result<Vec<_>, _>>()
84 .map_err(|source| {
85 ToolError::Tool(ExplainQueryError::Query { source })
86 })?;
87
88 Ok(ExplainQueryOutput { plan })
89 }
90}
91
92#[derive(
94 Clone,
95 Debug,
96 PartialEq,
97 Eq,
98 PartialOrd,
99 Ord,
100 Hash,
101 Serialize,
102 Deserialize,
103 schemars::JsonSchema,
104)]
105pub struct ExplainQueryInput {
106 #[schemars(description = "The SQL query to explain")]
108 pub query: String,
109}
110
111#[derive(
113 Clone,
114 Debug,
115 PartialEq,
116 Eq,
117 PartialOrd,
118 Ord,
119 Hash,
120 Serialize,
121 Deserialize,
122 schemars::JsonSchema,
123)]
124pub struct ExplainQueryOutput {
125 pub plan: Vec<PlanNode>,
128}
129
130#[derive(
132 Clone,
133 Debug,
134 PartialEq,
135 Eq,
136 PartialOrd,
137 Ord,
138 Hash,
139 Serialize,
140 Deserialize,
141 schemars::JsonSchema,
142)]
143pub struct PlanNode {
144 pub id: i64,
146 pub parent: i64,
148 pub detail: String,
151}
152
153#[derive(Debug, thiserror::Error)]
155pub enum ExplainQueryError {
156 #[error("failed to explain query: {source}")]
159 Query {
160 source: rusqlite::Error,
162 },
163}
164
165impl IntoContents for ExplainQueryError {
168 fn into_contents(self) -> Vec<Content> {
169 vec![Content::text(self.to_string())]
170 }
171}