Skip to main content

mcp_server_sqlite/tools/
explain_query_tool.rs

1//! The `explain_query` tool: returns the EXPLAIN QUERY PLAN output for a SQL
2//! statement, showing how SQLite will execute the query.
3
4use 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)]
25/// Show the query execution plan for a SQL statement. Returns the EXPLAIN QUERY
26/// PLAN output showing how SQLite will scan tables, use indexes, and join data.
27pub 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/// The input parameters for the `explain_query` tool.
93#[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    /// The SQL query to explain.
107    #[schemars(description = "The SQL query to explain")]
108    pub query: String,
109}
110
111/// The result of explaining a SQL query's execution plan.
112#[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    /// The nodes of the query plan tree. Each node describes one step in
126    /// SQLite's execution strategy.
127    pub plan: Vec<PlanNode>,
128}
129
130/// A single node in the EXPLAIN QUERY PLAN output tree.
131#[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    /// The unique identifier for this plan node.
145    pub id: i64,
146    /// The identifier of this node's parent, or zero if this is a root node.
147    pub parent: i64,
148    /// A human-readable description of the operation performed at this step
149    /// (e.g. "SCAN users", "SEARCH users USING INDEX idx_email").
150    pub detail: String,
151}
152
153/// Errors specific to the `explain_query` tool.
154#[derive(Debug, thiserror::Error)]
155pub enum ExplainQueryError {
156    /// The EXPLAIN QUERY PLAN statement failed. This usually means the
157    /// underlying SQL is invalid or references objects that do not exist.
158    #[error("failed to explain query: {source}")]
159    Query {
160        /// The underlying rusqlite error.
161        source: rusqlite::Error,
162    },
163}
164
165/// Converts the explain-query-specific error into MCP content by rendering the
166/// display string as text.
167impl IntoContents for ExplainQueryError {
168    fn into_contents(self) -> Vec<Content> {
169        vec![Content::text(self.to_string())]
170    }
171}