1use 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)]
26pub struct DatabaseInfoTool;
31
32impl SqliteServerTool for DatabaseInfoTool {
33 const NAME: &str = "database_info";
34
35 type Context = McpServerSqlite;
36 type Error = ToolError<DatabaseInfoError>;
37
38 type Input = DatabaseInfoInput;
39 type Output = DatabaseInfoOutput;
40
41 fn handle(
42 ctx: &Self::Context,
43 _input: Self::Input,
44 ) -> Result<Self::Output, Self::Error> {
45 let conn = ctx
46 .connection()
47 .map_err(|source| ToolError::Connection { source })?;
48
49 let query_err =
50 |source| ToolError::Tool(DatabaseInfoError::Query { source });
51
52 let sqlite_version: String = conn
53 .query_row("SELECT sqlite_version()", [], |row| row.get(0))
54 .map_err(query_err)?;
55
56 let page_size: i64 = conn
57 .query_row("PRAGMA page_size", [], |row| row.get(0))
58 .map_err(query_err)?;
59
60 let page_count: i64 = conn
61 .query_row("PRAGMA page_count", [], |row| row.get(0))
62 .map_err(query_err)?;
63
64 let journal_mode: String = conn
65 .query_row("PRAGMA journal_mode", [], |row| row.get(0))
66 .map_err(query_err)?;
67
68 let wal_checkpoint = if journal_mode == "wal" {
69 let info = conn
70 .query_row("PRAGMA wal_checkpoint(PASSIVE)", [], |row| {
71 Ok(WalCheckpointInfo {
72 busy: row.get(0)?,
73 log_pages: row.get(1)?,
74 checkpointed_pages: row.get(2)?,
75 })
76 })
77 .map_err(query_err)?;
78 Some(info)
79 } else {
80 None
81 };
82
83 let database_size_bytes = page_size * page_count;
84
85 let freelist_count: i64 = conn
86 .query_row("PRAGMA freelist_count", [], |row| row.get(0))
87 .map_err(query_err)?;
88
89 let table_count: i64 = conn
90 .query_row(
91 "SELECT COUNT(*) FROM sqlite_master \
92 WHERE type = 'table'",
93 [],
94 |row| row.get(0),
95 )
96 .map_err(query_err)?;
97
98 let index_count: i64 = conn
99 .query_row(
100 "SELECT COUNT(*) FROM sqlite_master \
101 WHERE type = 'index'",
102 [],
103 |row| row.get(0),
104 )
105 .map_err(query_err)?;
106
107 Ok(DatabaseInfoOutput {
108 sqlite_version,
109 page_size,
110 page_count,
111 journal_mode,
112 wal_checkpoint,
113 database_size_bytes,
114 freelist_count,
115 table_count,
116 index_count,
117 })
118 }
119}
120
121#[derive(
123 Clone,
124 Copy,
125 Debug,
126 Default,
127 PartialEq,
128 Eq,
129 PartialOrd,
130 Ord,
131 Hash,
132 Serialize,
133 Deserialize,
134 schemars::JsonSchema,
135)]
136pub struct DatabaseInfoInput {}
137
138#[derive(
140 Clone,
141 Debug,
142 PartialEq,
143 Eq,
144 PartialOrd,
145 Ord,
146 Hash,
147 Serialize,
148 Deserialize,
149 schemars::JsonSchema,
150)]
151pub struct DatabaseInfoOutput {
152 pub sqlite_version: String,
154 pub page_size: i64,
156 pub page_count: i64,
159 pub journal_mode: String,
161 pub wal_checkpoint: Option<WalCheckpointInfo>,
164 pub database_size_bytes: i64,
166 pub freelist_count: i64,
169 pub table_count: i64,
171 pub index_count: i64,
173}
174
175#[derive(
178 Clone,
179 Copy,
180 Debug,
181 PartialEq,
182 Eq,
183 PartialOrd,
184 Ord,
185 Hash,
186 Serialize,
187 Deserialize,
188 schemars::JsonSchema,
189)]
190pub struct WalCheckpointInfo {
191 pub busy: i64,
193 pub log_pages: i64,
195 pub checkpointed_pages: i64,
198}
199
200#[derive(Debug, thiserror::Error)]
202pub enum DatabaseInfoError {
203 #[error("failed to retrieve database info: {source}")]
205 Query {
206 source: rusqlite::Error,
208 },
209}
210
211impl IntoContents for DatabaseInfoError {
214 fn into_contents(self) -> Vec<Content> {
215 vec![Content::text(self.to_string())]
216 }
217}