Skip to main content

task_graph_mcp/tools/
schema.rs

1//! Schema introspection tool for exposing database structure.
2
3use super::{get_bool, get_string, make_tool};
4use crate::db::Database;
5use anyhow::Result;
6use rmcp::model::Tool;
7use serde_json::{Value, json};
8
9/// Get the schema introspection tools.
10pub fn get_tools() -> Vec<Tool> {
11    vec![make_tool(
12        "get_schema",
13        "Get the task-graph database schema. Returns table names, columns (with types), indexes, foreign keys, and optionally the SQL definitions. Useful for agents writing reports or queries.",
14        json!({
15            "table": {
16                "type": "string",
17                "description": "Filter to a specific table name. If not provided, returns all tables."
18            },
19            "include_sql": {
20                "type": "boolean",
21                "description": "Include the SQL CREATE statements (default: false)"
22            }
23        }),
24        vec![],
25    )]
26}
27
28/// Get database schema information.
29pub fn get_schema(db: &Database, args: Value) -> Result<Value> {
30    let table_filter = get_string(&args, "table");
31    let include_sql = get_bool(&args, "include_sql").unwrap_or(false);
32
33    let schema = db.get_schema(include_sql)?;
34
35    // If a specific table is requested, filter the results
36    let tables = if let Some(ref table_name) = table_filter {
37        schema
38            .tables
39            .into_iter()
40            .filter(|t| t.name.eq_ignore_ascii_case(table_name))
41            .collect()
42    } else {
43        schema.tables
44    };
45
46    if let Some(table_name) = table_filter
47        && tables.is_empty()
48    {
49        return Ok(json!({
50            "error": format!("Table '{}' not found", table_name),
51            "available_tables": db.get_table_names()?
52        }));
53    }
54
55    Ok(json!({
56        "sqlite_version": schema.sqlite_version,
57        "table_count": tables.len(),
58        "tables": tables.iter().map(|t| {
59            let mut table_obj = json!({
60                "name": t.name,
61                "type": t.table_type,
62                "columns": t.columns.iter().map(|c| {
63                    json!({
64                        "name": c.name,
65                        "type": c.data_type,
66                        "nullable": c.nullable,
67                        "primary_key": c.primary_key,
68                        "default": c.default_value
69                    })
70                }).collect::<Vec<_>>()
71            });
72
73            // Only include indexes if there are any
74            if !t.indexes.is_empty() {
75                table_obj["indexes"] = json!(t.indexes.iter().map(|i| {
76                    json!({
77                        "name": i.name,
78                        "unique": i.unique,
79                        "columns": i.columns
80                    })
81                }).collect::<Vec<_>>());
82            }
83
84            // Only include foreign keys if there are any
85            if !t.foreign_keys.is_empty() {
86                table_obj["foreign_keys"] = json!(t.foreign_keys.iter().map(|fk| {
87                    json!({
88                        "from": fk.from_column,
89                        "references": format!("{}.{}", fk.to_table, fk.to_column),
90                        "on_delete": fk.on_delete,
91                        "on_update": fk.on_update
92                    })
93                }).collect::<Vec<_>>());
94            }
95
96            // Include SQL if requested
97            if let Some(ref sql) = t.sql {
98                table_obj["sql"] = json!(sql);
99            }
100
101            table_obj
102        }).collect::<Vec<_>>()
103    }))
104}