Skip to main content

things_mcp/tools/
search.rs

1//! MCP tool: free-text + structured search over to-dos.
2
3use schemars::JsonSchema;
4use serde::{Deserialize, Serialize};
5
6use crate::core::reader::queries::{search, SearchParams};
7use crate::core::types::TodoList;
8use crate::state::AppState;
9use crate::tools::lists::ProjectStatusArg;
10
11#[derive(Debug, Clone, Deserialize, Serialize, JsonSchema, Default)]
12pub struct SearchArgs {
13    /// Free-text query, matched against the to-do's title and notes
14    /// (case-insensitive substring; no boolean / wildcard syntax).
15    #[serde(default)]
16    pub query: Option<String>,
17    /// Tag titles or UUIDs to match. OR-semantic — an item with any listed tag is returned.
18    #[serde(default)]
19    pub tags: Vec<String>,
20    /// Restrict to to-dos in a specific area (directly or via project). Optional.
21    #[serde(default)]
22    pub area_id: Option<String>,
23    /// Restrict to to-dos in a specific project. Optional.
24    #[serde(default)]
25    pub project_id: Option<String>,
26    /// `open` (default), `done`, or `all`.
27    #[serde(default)]
28    pub status: Option<ProjectStatusArg>,
29    /// ISO `YYYY-MM-DD`. Inclusive upper bound on `deadline`. Optional.
30    #[serde(default)]
31    pub due_before: Option<String>,
32    /// ISO `YYYY-MM-DD`. Inclusive lower bound on `deadline`. Optional.
33    #[serde(default)]
34    pub due_after: Option<String>,
35    /// ISO `YYYY-MM-DD`. Inclusive upper bound on `startDate`. Optional.
36    #[serde(default)]
37    pub scheduled_before: Option<String>,
38    /// ISO `YYYY-MM-DD`. Inclusive lower bound on `startDate`. Optional.
39    #[serde(default)]
40    pub scheduled_after: Option<String>,
41    /// Cap on returned rows. Defaults to 50.
42    #[serde(default)]
43    pub limit: Option<u32>,
44}
45
46pub async fn things_search(
47    state: AppState,
48    args: SearchArgs,
49) -> anyhow::Result<TodoList> {
50    let params = SearchParams {
51        query: args.query,
52        tags: args.tags,
53        area_id: args.area_id,
54        project_id: args.project_id,
55        status: args.status.unwrap_or_default().into(),
56        due_before: args.due_before,
57        due_after: args.due_after,
58        scheduled_before: args.scheduled_before,
59        scheduled_after: args.scheduled_after,
60        limit: args.limit.unwrap_or(50),
61    };
62    let items = search(&state.pool, params).await?;
63    Ok(TodoList { items })
64}