Skip to main content

bear_mcp_server/
mcp.rs

1use crate::bear::BearDatabase;
2use rmcp::{
3    ErrorData as McpError,
4    handler::server::tool::{Parameters, ToolRouter},
5    model::*,
6    tool, tool_handler, tool_router,
7};
8use std::sync::Arc;
9use tokio::sync::Mutex;
10
11#[derive(Clone)]
12pub struct BearMcpServer {
13    bear_database: Arc<Mutex<BearDatabase>>,
14    tool_router: ToolRouter<Self>,
15}
16
17#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
18pub struct SearchRequest {
19    #[schemars(description = "search query")]
20    pub query: Option<String>,
21    #[schemars(description = "tag")]
22    pub tag: Option<String>,
23}
24
25#[tool_router]
26impl BearMcpServer {
27    pub async fn new(db_path: std::path::PathBuf) -> crate::Result<Self> {
28        let bear_database = Arc::new(Mutex::new(BearDatabase::new(db_path).await?));
29        Ok(Self {
30            bear_database,
31            tool_router: Self::tool_router(),
32        })
33    }
34
35    #[tool(description = "Search all notes")]
36    async fn list_notes(
37        &self,
38        Parameters(SearchRequest { query, tag }): Parameters<SearchRequest>,
39    ) -> std::result::Result<CallToolResult, McpError> {
40        #[derive(serde::Serialize)]
41        struct NotesResponse<T> {
42            notes: T,
43        }
44
45        let tag_noramlized = tag.as_deref().map(|tag| tag.trim_start_matches('#'));
46
47        let database = self.bear_database.lock().await;
48        let notes = database
49            .list_notes(query.as_deref(), tag_noramlized)
50            .await?;
51        let response = NotesResponse { notes };
52        Ok(CallToolResult::success(vec![Content::json(&response)?]))
53    }
54
55    #[tool(description = "List tags")]
56    async fn list_tags(&self) -> std::result::Result<CallToolResult, McpError> {
57        #[derive(serde::Serialize)]
58        struct TagsResponse<T> {
59            tags: T,
60        }
61
62        let database = self.bear_database.lock().await;
63        let tags = database.list_tags().await?;
64        let response = TagsResponse { tags };
65        Ok(CallToolResult::success(vec![Content::json(&response)?]))
66    }
67}
68
69#[tool_handler]
70impl rmcp::ServerHandler for BearMcpServer {
71    fn get_info(&self) -> ServerInfo {
72        ServerInfo {
73            protocol_version: ProtocolVersion::V_2025_03_26,
74            capabilities: ServerCapabilities::builder().enable_tools().build(),
75            server_info: Implementation::from_build_env(),
76            instructions: Some("A server for notes stored in the Bear app".into()),
77        }
78    }
79}