mcp-server-sqlite 1.0.0

An MCP server for SQLite with fine-grained access control
Documentation
//! The `database_info` tool: returns metadata about the SQLite database
//! including file size, page metrics, journal mode, WAL status, version, and
//! object counts.

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,
)]
/// Return metadata about the database: SQLite version, page size, page count,
/// journal mode, WAL checkpoint status, total size in bytes, freelist (unused)
/// page count, and the number of tables and indexes. Useful for monitoring
/// database health and understanding storage characteristics.
pub struct DatabaseInfoTool;

impl SqliteServerTool for DatabaseInfoTool {
    const NAME: &str = "database_info";

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

    type Input = DatabaseInfoInput;
    type Output = DatabaseInfoOutput;

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

        let query_err =
            |source| ToolError::Tool(DatabaseInfoError::Query { source });

        let sqlite_version: String = conn
            .query_row("SELECT sqlite_version()", [], |row| row.get(0))
            .map_err(query_err)?;

        let page_size: i64 = conn
            .query_row("PRAGMA page_size", [], |row| row.get(0))
            .map_err(query_err)?;

        let page_count: i64 = conn
            .query_row("PRAGMA page_count", [], |row| row.get(0))
            .map_err(query_err)?;

        let journal_mode: String = conn
            .query_row("PRAGMA journal_mode", [], |row| row.get(0))
            .map_err(query_err)?;

        let wal_checkpoint = if journal_mode == "wal" {
            let info = conn
                .query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |row| {
                    Ok(WalCheckpointInfo {
                        busy: row.get(0)?,
                        log_pages: row.get(1)?,
                        checkpointed_pages: row.get(2)?,
                    })
                })
                .map_err(query_err)?;
            Some(info)
        } else {
            None
        };

        let database_size_bytes = page_size * page_count;

        let freelist_count: i64 = conn
            .query_row("PRAGMA freelist_count", [], |row| row.get(0))
            .map_err(query_err)?;

        let table_count: i64 = conn
            .query_row(
                "SELECT COUNT(*) FROM sqlite_master \
                 WHERE type = 'table'",
                [],
                |row| row.get(0),
            )
            .map_err(query_err)?;

        let index_count: i64 = conn
            .query_row(
                "SELECT COUNT(*) FROM sqlite_master \
                 WHERE type = 'index'",
                [],
                |row| row.get(0),
            )
            .map_err(query_err)?;

        Ok(DatabaseInfoOutput {
            sqlite_version,
            page_size,
            page_count,
            journal_mode,
            wal_checkpoint,
            database_size_bytes,
            freelist_count,
            table_count,
            index_count,
        })
    }
}

/// The input parameters for the `database_info` tool.
#[derive(
    Clone,
    Copy,
    Debug,
    Default,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Serialize,
    Deserialize,
    schemars::JsonSchema,
)]
pub struct DatabaseInfoInput {}

/// The result of querying database metadata.
#[derive(
    Clone,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Serialize,
    Deserialize,
    schemars::JsonSchema,
)]
pub struct DatabaseInfoOutput {
    /// The SQLite library version string (e.g. `"3.45.1"`).
    pub sqlite_version: String,
    /// The database page size in bytes, as configured by `PRAGMA page_size`.
    pub page_size: i64,
    /// The total number of pages in the database file, as reported by `PRAGMA
    /// page_count`.
    pub page_count: i64,
    /// The active journal mode (e.g. `"delete"`, `"wal"`, `"memory"`).
    pub journal_mode: String,
    /// WAL checkpoint information, present only when the journal mode is
    /// `"wal"`. Contains the result of `PRAGMA wal_checkpoint(PASSIVE)`.
    pub wal_checkpoint: Option<WalCheckpointInfo>,
    /// The total database size in bytes, computed as `page_size * page_count`.
    pub database_size_bytes: i64,
    /// The number of unused pages on the freelist, as reported by `PRAGMA
    /// freelist_count`.
    pub freelist_count: i64,
    /// The number of tables in the database, counted from `sqlite_master`.
    pub table_count: i64,
    /// The number of indexes in the database, counted from `sqlite_master`.
    pub index_count: i64,
}

/// Information from a passive WAL checkpoint, as returned by `PRAGMA
/// wal_checkpoint(PASSIVE)`.
#[derive(
    Clone,
    Copy,
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Serialize,
    Deserialize,
    schemars::JsonSchema,
)]
pub struct WalCheckpointInfo {
    /// Non-zero if the checkpoint was blocked by a concurrent reader or writer.
    pub busy: i64,
    /// The number of pages in the WAL log file.
    pub log_pages: i64,
    /// The number of pages successfully checkpointed back to the main database
    /// file.
    pub checkpointed_pages: i64,
}

/// Errors specific to the `database_info` tool.
#[derive(Debug, thiserror::Error)]
pub enum DatabaseInfoError {
    /// A query for database metadata failed.
    #[error("failed to retrieve database info: {source}")]
    Query {
        /// The underlying rusqlite error.
        source: rusqlite::Error,
    },
}

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