Skip to main content

mcp_server_sqlite/tools/
describe_table_tool.rs

1//! The `describe_table` tool: returns column metadata for a given table using
2//! SQLite's `PRAGMA table_info`. Useful for inspecting schema details before
3//! writing queries.
4
5use rmcp::model::{Content, IntoContents};
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8
9use super::ToolError;
10use crate::{mcp::McpServerSqlite, traits::SqliteServerTool};
11
12#[derive(
13    Clone,
14    Copy,
15    Debug,
16    PartialEq,
17    Eq,
18    PartialOrd,
19    Ord,
20    Hash,
21    Default,
22    Serialize,
23    Deserialize,
24    JsonSchema,
25)]
26/// Describe the columns of a table. Returns each column's name, type, whether
27/// it is NOT NULL, its default value, and whether it is part of the primary
28/// key. Useful for inspecting a table's schema before writing queries.
29pub struct DescribeTableTool;
30
31impl SqliteServerTool for DescribeTableTool {
32    const NAME: &str = "describe_table";
33
34    type Context = McpServerSqlite;
35    type Error = ToolError<DescribeTableError>;
36
37    type Input = DescribeTableInput;
38    type Output = DescribeTableOutput;
39
40    fn handle(
41        ctx: &Self::Context,
42        input: Self::Input,
43    ) -> Result<Self::Output, Self::Error> {
44        let conn = ctx
45            .connection()
46            .map_err(|source| ToolError::Connection { source })?;
47
48        let mut stmt = conn
49            .prepare(&format!(
50                "PRAGMA table_info({})",
51                enquote_identifier(&input.table_name),
52            ))
53            .map_err(|source| {
54                ToolError::Tool(DescribeTableError::Query { source })
55            })?;
56
57        let columns = stmt
58            .query_map([], |row| {
59                Ok(ColumnInfo {
60                    name: row.get(1)?,
61                    column_type: row.get(2)?,
62                    not_null: row.get::<_, i32>(3)? != 0,
63                    default_value: row.get(4)?,
64                    primary_key: row.get::<_, i32>(5)? != 0,
65                })
66            })
67            .map_err(|source| {
68                ToolError::Tool(DescribeTableError::Query { source })
69            })?
70            .collect::<Result<Vec<_>, _>>()
71            .map_err(|source| {
72                ToolError::Tool(DescribeTableError::Query { source })
73            })?;
74
75        Ok(DescribeTableOutput { columns })
76    }
77}
78
79/// Wraps a table name in double quotes for safe use in a PRAGMA statement,
80/// escaping any embedded double-quote characters by doubling them.
81fn enquote_identifier(name: &str) -> String {
82    format!("\"{}\"", name.replace('"', "\"\""))
83}
84
85/// The input parameters for the `describe_table` tool.
86#[derive(
87    Clone,
88    Debug,
89    PartialEq,
90    Eq,
91    PartialOrd,
92    Ord,
93    Hash,
94    Serialize,
95    Deserialize,
96    schemars::JsonSchema,
97)]
98pub struct DescribeTableInput {
99    /// The name of the table whose columns should be described.
100    #[schemars(description = "The name of the table to describe")]
101    pub table_name: String,
102}
103
104/// The result of describing a table's columns.
105#[derive(
106    Clone,
107    Debug,
108    PartialEq,
109    Eq,
110    PartialOrd,
111    Ord,
112    Hash,
113    Serialize,
114    Deserialize,
115    schemars::JsonSchema,
116)]
117pub struct DescribeTableOutput {
118    /// The columns of the table, each with its name, type, nullability, default
119    /// value, and primary key status.
120    pub columns: Vec<ColumnInfo>,
121}
122
123/// Metadata for a single column in a table, as reported by SQLite's `PRAGMA
124/// table_info`.
125#[derive(
126    Clone,
127    Debug,
128    PartialEq,
129    Eq,
130    PartialOrd,
131    Ord,
132    Hash,
133    Serialize,
134    Deserialize,
135    schemars::JsonSchema,
136)]
137pub struct ColumnInfo {
138    /// The column name.
139    pub name: String,
140    /// The declared type of the column (e.g. `TEXT`, `INTEGER`). May be empty
141    /// if the column was declared without a type.
142    pub column_type: String,
143    /// Whether the column has a `NOT NULL` constraint.
144    pub not_null: bool,
145    /// The default value expression, if one was specified in the column
146    /// definition.
147    pub default_value: Option<String>,
148    /// Whether this column is part of the table's primary key.
149    pub primary_key: bool,
150}
151
152/// Errors specific to the `describe_table` tool.
153#[derive(Debug, thiserror::Error)]
154pub enum DescribeTableError {
155    /// Failed to query column information via `PRAGMA table_info`.
156    #[error("failed to describe table: {source}")]
157    Query {
158        /// The underlying rusqlite error.
159        source: rusqlite::Error,
160    },
161}
162
163/// Converts the describe-table-specific error into MCP content by rendering the
164/// display string as text.
165impl IntoContents for DescribeTableError {
166    fn into_contents(self) -> Vec<Content> {
167        vec![Content::text(self.to_string())]
168    }
169}