org_mcp_server/tools/
org_search.rs1use org_core::OrgModeError;
2use rmcp::{
3 ErrorData as McpError,
4 handler::server::wrapper::Parameters,
5 model::{CallToolResult, Content, ErrorCode},
6 schemars, tool, tool_router,
7};
8
9use crate::core::OrgModeRouter;
10
11#[derive(Debug, schemars::JsonSchema, serde::Deserialize)]
12pub struct SearchRequest {
13 #[schemars(description = "Search query string to find in org file content")]
14 pub query: String,
15 #[schemars(description = "Maximum number of search results to return (optional)")]
16 pub limit: Option<usize>,
17 #[schemars(description = "Maximum snippet size in characters (optional, default: 100)")]
18 pub snippet_max_size: Option<usize>,
19 #[schemars(
20 description = "Filter results by tags (optional, matches any of the provided tags)"
21 )]
22 pub tags: Option<Vec<String>>,
23}
24
25#[tool_router(router = "tool_router_search", vis = "pub(crate)")]
26impl OrgModeRouter {
27 #[tool(
28 name = "org-search",
29 description = "Search for text content across all org files using fuzzy matching",
30 annotations(title = "org-search tool")
31 )]
32 async fn tool_search(
33 &self,
34 Parameters(SearchRequest {
35 query,
36 limit,
37 snippet_max_size,
38 tags,
39 }): Parameters<SearchRequest>,
40 ) -> Result<CallToolResult, McpError> {
41 let org_mode = self.org_mode.lock().await;
42
43 let results = if tags.is_some() {
44 org_mode.search_with_tags(&query, tags.as_deref(), limit, snippet_max_size)
45 } else {
46 org_mode.search(&query, limit, snippet_max_size)
47 };
48
49 match results {
50 Ok(results) => match Content::json(results) {
51 Ok(serialized) => Ok(CallToolResult::success(vec![serialized])),
52 Err(e) => Err(McpError {
53 code: ErrorCode::INTERNAL_ERROR,
54 message: format!("Failed to serialize search results: {e}").into(),
55 data: None,
56 }),
57 },
58 Err(e) => {
59 let error_code = match &e {
60 OrgModeError::InvalidDirectory(_) => ErrorCode::INVALID_PARAMS,
61 OrgModeError::WalkError(_) => ErrorCode::INTERNAL_ERROR,
62 OrgModeError::IoError(_) => ErrorCode::INTERNAL_ERROR,
63 _ => ErrorCode::INTERNAL_ERROR,
64 };
65 Err(McpError {
66 code: error_code,
67 message: format!("Failed to search: {e}").into(),
68 data: None,
69 })
70 }
71 }
72 }
73}