Skip to main content

mcp_server_sqlite/tools/
create_fts_index_tool.rs

1//! The `create_fts_index` tool: creates a full-text search virtual table backed
2//! by FTS5 over columns of an existing table.
3
4use rmcp::model::{Content, IntoContents};
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8use super::ToolError;
9use crate::{mcp::McpServerSqlite, traits::SqliteServerTool};
10
11#[derive(
12    Clone,
13    Copy,
14    Debug,
15    PartialEq,
16    Eq,
17    PartialOrd,
18    Ord,
19    Hash,
20    Default,
21    Serialize,
22    Deserialize,
23    JsonSchema,
24)]
25/// Create a full-text search index on an existing table using SQLite's FTS5
26/// module. Specify the source table and the columns to index. The virtual table
27/// is named `{table}_fts` by default and can be queried with the MATCH
28/// operator.
29pub struct CreateFtsIndexTool;
30
31impl SqliteServerTool for CreateFtsIndexTool {
32    const NAME: &str = "create_fts_index";
33
34    type Context = McpServerSqlite;
35    type Error = ToolError<CreateFtsIndexError>;
36
37    type Input = CreateFtsIndexInput;
38    type Output = CreateFtsIndexOutput;
39
40    fn handle(
41        ctx: &Self::Context,
42        input: Self::Input,
43    ) -> Result<Self::Output, Self::Error> {
44        if input.columns.is_empty() {
45            return Err(ToolError::Tool(CreateFtsIndexError::NoColumns));
46        }
47
48        let conn = ctx
49            .connection()
50            .map_err(|source| ToolError::Connection { source })?;
51
52        let fts_table = input
53            .fts_table_name
54            .unwrap_or_else(|| format!("{}_fts", input.table_name));
55
56        let columns = input.columns.join(", ");
57        let sql = format!(
58            "CREATE VIRTUAL TABLE [{}] USING fts5({}, content=[{}])",
59            fts_table, columns, input.table_name,
60        );
61
62        conn.execute_batch(&sql).map_err(|source| {
63            ToolError::Tool(CreateFtsIndexError::Create { source })
64        })?;
65
66        Ok(CreateFtsIndexOutput {
67            fts_table_name: fts_table,
68        })
69    }
70}
71
72/// The input parameters for the `create_fts_index` tool.
73#[derive(
74    Clone,
75    Debug,
76    PartialEq,
77    Eq,
78    PartialOrd,
79    Ord,
80    Hash,
81    Serialize,
82    Deserialize,
83    schemars::JsonSchema,
84)]
85pub struct CreateFtsIndexInput {
86    /// The name of the existing table to index.
87    #[schemars(description = "The source table to create an FTS index over")]
88    pub table_name: String,
89    /// The columns to include in the full-text index.
90    #[schemars(description = "The columns to include in the full-text index")]
91    pub columns: Vec<String>,
92    /// Optional name for the FTS virtual table. Defaults to `{table_name}_fts`
93    /// if not provided.
94    #[schemars(
95        description = "Optional name for the FTS virtual table (defaults to {table}_fts)"
96    )]
97    pub fts_table_name: Option<String>,
98}
99
100/// The result of creating an FTS index.
101#[derive(
102    Clone,
103    Debug,
104    PartialEq,
105    Eq,
106    PartialOrd,
107    Ord,
108    Hash,
109    Serialize,
110    Deserialize,
111    schemars::JsonSchema,
112)]
113pub struct CreateFtsIndexOutput {
114    /// The name of the created FTS virtual table.
115    pub fts_table_name: String,
116}
117
118/// Errors specific to the `create_fts_index` tool.
119#[derive(Debug, thiserror::Error)]
120pub enum CreateFtsIndexError {
121    /// No columns were specified for the full-text index.
122    #[error("no columns specified for the FTS index")]
123    NoColumns,
124    /// SQLite failed to create the FTS virtual table.
125    #[error("failed to create FTS index: {source}")]
126    Create {
127        /// The underlying rusqlite error.
128        source: rusqlite::Error,
129    },
130}
131
132/// Converts the error into MCP content by rendering the display string as text.
133impl IntoContents for CreateFtsIndexError {
134    fn into_contents(self) -> Vec<Content> {
135        vec![Content::text(self.to_string())]
136    }
137}