bear-mcp-server 0.1.1

A Model Context Protocol server for Bear.app
Documentation
use crate::bear::BearDatabase;
use rmcp::{
    ErrorData as McpError,
    handler::server::tool::{Parameters, ToolRouter},
    model::*,
    tool, tool_handler, tool_router,
};
use std::sync::Arc;
use tokio::sync::Mutex;

#[derive(Clone)]
pub struct BearMcpServer {
    bear_database: Arc<Mutex<BearDatabase>>,
    tool_router: ToolRouter<Self>,
}

#[derive(Debug, serde::Deserialize, schemars::JsonSchema)]
pub struct SearchRequest {
    #[schemars(description = "search query")]
    pub query: Option<String>,
    #[schemars(description = "tag")]
    pub tag: Option<String>,
}

#[tool_router]
impl BearMcpServer {
    pub async fn new(db_path: std::path::PathBuf) -> crate::Result<Self> {
        let bear_database = Arc::new(Mutex::new(BearDatabase::new(db_path).await?));
        Ok(Self {
            bear_database,
            tool_router: Self::tool_router(),
        })
    }

    #[tool(description = "Search all notes")]
    async fn list_notes(
        &self,
        Parameters(SearchRequest { query, tag }): Parameters<SearchRequest>,
    ) -> std::result::Result<CallToolResult, McpError> {
        #[derive(serde::Serialize)]
        struct NotesResponse<T> {
            notes: T,
        }

        let tag_noramlized = tag.as_deref().map(|tag| tag.trim_start_matches('#'));

        let database = self.bear_database.lock().await;
        let notes = database
            .list_notes(query.as_deref(), tag_noramlized)
            .await?;
        let response = NotesResponse { notes };
        Ok(CallToolResult::success(vec![Content::json(&response)?]))
    }

    #[tool(description = "List tags")]
    async fn list_tags(&self) -> std::result::Result<CallToolResult, McpError> {
        #[derive(serde::Serialize)]
        struct TagsResponse<T> {
            tags: T,
        }

        let database = self.bear_database.lock().await;
        let tags = database.list_tags().await?;
        let response = TagsResponse { tags };
        Ok(CallToolResult::success(vec![Content::json(&response)?]))
    }
}

#[tool_handler]
impl rmcp::ServerHandler for BearMcpServer {
    fn get_info(&self) -> ServerInfo {
        ServerInfo {
            protocol_version: ProtocolVersion::V_2025_03_26,
            capabilities: ServerCapabilities::builder().enable_tools().build(),
            server_info: Implementation::from_build_env(),
            instructions: Some("A server for notes stored in the Bear app".into()),
        }
    }
}