mcp-server-sqlite 1.0.0

An MCP server for SQLite with fine-grained access control
Documentation
//! The `describe_table` tool: returns column metadata for a given table using
//! SQLite's `PRAGMA table_info`. Useful for inspecting schema details before
//! writing queries.

use rmcp::model::{Content, IntoContents};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use super::ToolError;
use crate::{mcp::McpServerSqlite, traits::SqliteServerTool};

#[derive(
    Clone,
    Copy,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Default,
    Serialize,
    Deserialize,
    JsonSchema,
)]
/// Describe the columns of a table. Returns each column's name, type, whether
/// it is NOT NULL, its default value, and whether it is part of the primary
/// key. Useful for inspecting a table's schema before writing queries.
pub struct DescribeTableTool;

impl SqliteServerTool for DescribeTableTool {
    const NAME: &str = "describe_table";

    type Context = McpServerSqlite;
    type Error = ToolError<DescribeTableError>;

    type Input = DescribeTableInput;
    type Output = DescribeTableOutput;

    fn handle(
        ctx: &Self::Context,
        input: Self::Input,
    ) -> Result<Self::Output, Self::Error> {
        let conn = ctx
            .connection()
            .map_err(|source| ToolError::Connection { source })?;

        let mut stmt = conn
            .prepare(&format!(
                "PRAGMA table_info({})",
                enquote_identifier(&input.table_name),
            ))
            .map_err(|source| {
                ToolError::Tool(DescribeTableError::Query { source })
            })?;

        let columns = stmt
            .query_map([], |row| {
                Ok(ColumnInfo {
                    name: row.get(1)?,
                    column_type: row.get(2)?,
                    not_null: row.get::<_, i32>(3)? != 0,
                    default_value: row.get(4)?,
                    primary_key: row.get::<_, i32>(5)? != 0,
                })
            })
            .map_err(|source| {
                ToolError::Tool(DescribeTableError::Query { source })
            })?
            .collect::<Result<Vec<_>, _>>()
            .map_err(|source| {
                ToolError::Tool(DescribeTableError::Query { source })
            })?;

        Ok(DescribeTableOutput { columns })
    }
}

/// Wraps a table name in double quotes for safe use in a PRAGMA statement,
/// escaping any embedded double-quote characters by doubling them.
fn enquote_identifier(name: &str) -> String {
    format!("\"{}\"", name.replace('"', "\"\""))
}

/// The input parameters for the `describe_table` tool.
#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Serialize,
    Deserialize,
    schemars::JsonSchema,
)]
pub struct DescribeTableInput {
    /// The name of the table whose columns should be described.
    #[schemars(description = "The name of the table to describe")]
    pub table_name: String,
}

/// The result of describing a table's columns.
#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Serialize,
    Deserialize,
    schemars::JsonSchema,
)]
pub struct DescribeTableOutput {
    /// The columns of the table, each with its name, type, nullability, default
    /// value, and primary key status.
    pub columns: Vec<ColumnInfo>,
}

/// Metadata for a single column in a table, as reported by SQLite's `PRAGMA
/// table_info`.
#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Serialize,
    Deserialize,
    schemars::JsonSchema,
)]
pub struct ColumnInfo {
    /// The column name.
    pub name: String,
    /// The declared type of the column (e.g. `TEXT`, `INTEGER`). May be empty
    /// if the column was declared without a type.
    pub column_type: String,
    /// Whether the column has a `NOT NULL` constraint.
    pub not_null: bool,
    /// The default value expression, if one was specified in the column
    /// definition.
    pub default_value: Option<String>,
    /// Whether this column is part of the table's primary key.
    pub primary_key: bool,
}

/// Errors specific to the `describe_table` tool.
#[derive(Debug, thiserror::Error)]
pub enum DescribeTableError {
    /// Failed to query column information via `PRAGMA table_info`.
    #[error("failed to describe table: {source}")]
    Query {
        /// The underlying rusqlite error.
        source: rusqlite::Error,
    },
}

/// Converts the describe-table-specific error into MCP content by rendering the
/// display string as text.
impl IntoContents for DescribeTableError {
    fn into_contents(self) -> Vec<Content> {
        vec![Content::text(self.to_string())]
    }
}