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,
)]
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 })
}
}
fn enquote_identifier(name: &str) -> String {
format!("\"{}\"", name.replace('"', "\"\""))
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct DescribeTableInput {
#[schemars(description = "The name of the table to describe")]
pub table_name: String,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct DescribeTableOutput {
pub columns: Vec<ColumnInfo>,
}
#[derive(
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
schemars::JsonSchema,
)]
pub struct ColumnInfo {
pub name: String,
pub column_type: String,
pub not_null: bool,
pub default_value: Option<String>,
pub primary_key: bool,
}
#[derive(Debug, thiserror::Error)]
pub enum DescribeTableError {
#[error("failed to describe table: {source}")]
Query {
source: rusqlite::Error,
},
}
impl IntoContents for DescribeTableError {
fn into_contents(self) -> Vec<Content> {
vec![Content::text(self.to_string())]
}
}