Skip to main content

mneme/mcp/
server.rs

1use std::sync::Arc;
2
3use rmcp::handler::server::ServerHandler;
4use rmcp::model::{
5    CallToolRequestParam, CallToolResult, Implementation, ListToolsResult, PaginatedRequestParam,
6    ServerCapabilities, ServerInfo,
7};
8use rmcp::service::{RequestContext, RoleServer};
9use tokio::sync::RwLock;
10
11use crate::config::settings::Settings;
12use crate::store::db::Database;
13use crate::store::memory::Session;
14
15/// Servidor MCP para Mneme.
16#[derive(Debug, Clone)]
17pub struct MnemeServer {
18    db: Arc<Database>,
19    #[allow(dead_code)]
20    config: Arc<Settings>,
21    current_project: Arc<RwLock<String>>,
22    #[allow(dead_code)]
23    current_session: Arc<RwLock<Option<Session>>>,
24    embeddings: Option<Arc<crate::embeddings::engine::EmbeddingEngine>>,
25    plugins: Arc<crate::plugins::PluginManager>,
26}
27
28impl MnemeServer {
29    /// Crea un nuevo MnemeServer.
30    pub fn new(
31        db: Arc<Database>,
32        config: Arc<Settings>,
33        embeddings: Option<Arc<crate::embeddings::engine::EmbeddingEngine>>,
34    ) -> Self {
35        let project = config.mcp.default_project.clone();
36        Self {
37            db,
38            config,
39            current_project: Arc::new(RwLock::new(project)),
40            current_session: Arc::new(RwLock::new(None)),
41            embeddings,
42            plugins: Arc::new(
43                crate::plugins::PluginManager::load_from_default_dir().unwrap_or_else(|e| {
44                    tracing::warn!(error = %e, "plugin loading failed, continuing without plugins");
45                    crate::plugins::PluginManager::empty()
46                }),
47            ),
48        }
49    }
50
51    /// Ejecuta el servidor MCP sobre stdio.
52    pub async fn run_stdio(self) -> crate::error::Result<()> {
53        let (stdin, stdout) = rmcp::transport::io::stdio();
54        let transport = (stdin, stdout);
55        let running = rmcp::service::serve_server(self, transport)
56            .await
57            .map_err(|e| crate::error::MnemeError::Mcp(e.to_string()))?;
58        // Wait for the server to finish (keeps the background task alive)
59        running.waiting()
60            .await
61            .map_err(|e| crate::error::MnemeError::Mcp(e.to_string()))?;
62        Ok(())
63    }
64
65    async fn current_project(&self) -> String {
66        self.current_project.read().await.clone()
67    }
68}
69
70impl ServerHandler for MnemeServer {
71    async fn call_tool(
72        &self,
73        request: CallToolRequestParam,
74        _context: RequestContext<RoleServer>,
75    ) -> Result<CallToolResult, rmcp::Error> {
76        let project = self.current_project().await;
77        Ok(crate::mcp::tools::execute_tool(
78            &self.db,
79            &request.name,
80            request.arguments,
81            &project,
82            self.embeddings.as_ref(),
83            Some(&self.plugins),
84        )
85        .await)
86    }
87
88    async fn list_tools(
89        &self,
90        _request: PaginatedRequestParam,
91        _context: RequestContext<RoleServer>,
92    ) -> Result<ListToolsResult, rmcp::Error> {
93        Ok(ListToolsResult {
94            next_cursor: None,
95            tools: crate::mcp::tools::list_tools(Some(&self.plugins)),
96        })
97    }
98
99    fn get_info(&self) -> ServerInfo {
100        ServerInfo {
101            protocol_version: rmcp::model::ProtocolVersion::default(),
102            capabilities: ServerCapabilities::builder().enable_tools().build(),
103            server_info: Implementation {
104                name: "mneme".to_string(),
105                version: env!("CARGO_PKG_VERSION").to_string(),
106            },
107            instructions: Some("Mneme MCP server — persistent memory for AI agents".to_string()),
108        }
109    }
110}