Files
mcp-memory/ARCHITECTURE_RESOURCES_PROMPTS.md
T

3.8 KiB

Architecture Design: MCP Resources & Prompts

1. Current Architecture (Tools)

Currently, the mcp-memory server handles MCP tools using an elegant trait-based approach in router.rs:

#[async_trait]
pub trait McpTool: Send + Sync {
    fn name(&self) -> &'static str;
    fn schema(&self) -> Value;
    async fn execute(&self, args: Value, state: Arc<MemoryState>) -> Result<String, String>;
}

Tools are registered into a HashMap<String, Box<dyn McpTool>> within the MemoryHandler. This prevents the main JSON-RPC match block from becoming a monolithic switch statement.

2. The Problem

Currently, the resources/list, resources/read, prompts/list, and prompts/get endpoints are hardcoded directly inside the MemoryHandler::handle_request match block in router.rs. As we expand our usage of Resources (to expose the database state dynamically) and Prompts (to bundle complex workflows), continuing to hardcode them in router.rs will result in massive code duplication and tearup.

3. The Proposed Solution (Trait Extensibility)

We will replicate the success of the McpTool trait by introducing McpResource and McpPrompt traits.

A. MCP Resources

Trait Definition (router.rs or resources.rs):

#[async_trait]
pub trait McpResource: Send + Sync {
    /// The exact URI the client requests (e.g. "memory://tasks/active")
    fn uri(&self) -> &'static str;
    
    /// Human-readable name for the client UI
    fn name(&self) -> &'static str;
    
    /// Description for the client UI
    fn description(&self) -> Option<&'static str> { None }
    
    /// Mime type of the content (usually "application/json" or "text/markdown")
    fn mime_type(&self) -> Option<&'static str> { Some("application/json") }
    
    /// Retrieve the resource content
    async fn read(&self, state: Arc<MemoryState>) -> Result<String, String>;
}

Implementation:

  • Add pub resources: std::collections::HashMap<String, Box<dyn McpResource>> to MemoryHandler.
  • In handle_request("resources/list"), iterate over self.resources.values() and build the JSON payload.
  • In handle_request("resources/read"), lookup the requested URI in self.resources and call .read(state).await.
  • Move the existing memory://graph/entities logic into its own handler struct.

B. MCP Prompts

Trait Definition (router.rs or prompts.rs):

#[async_trait]
pub trait McpPrompt: Send + Sync {
    /// The unique name of the prompt (e.g. "analyze_tech_debt")
    fn name(&self) -> &'static str;
    
    /// Description for the client UI
    fn description(&self) -> Option<&'static str> { None }
    
    /// Schema or array defining arguments (can default to empty)
    fn arguments(&self) -> serde_json::Value { serde_json::json!([]) }
    
    /// Execute the prompt and return the `messages` array payload
    async fn get(&self, args: Value, state: Arc<MemoryState>) -> Result<serde_json::Value, String>;
}

Implementation:

  • Add pub prompts: std::collections::HashMap<String, Box<dyn McpPrompt>> to MemoryHandler.
  • In handle_request("prompts/list"), map over self.prompts.values().
  • In handle_request("prompts/get"), call .get(args, state).await.

4. Execution Plan

  1. Refactor router.rs (No functional changes yet): Define the McpResource and McpPrompt traits. Update the MemoryHandler struct to hold these HashMaps. Migrate the existing hardcoded stubs (memory://graph/entities and analyze_tech_debt) into structs implementing these traits.
  2. Expand Resources (Phase 1): Add new handlers for memory://tasks/active, memory://pinned_files, etc.
  3. Expand Prompts (Phase 2): Add new handlers for handoff_routine, etc.

This design guarantees we do not needlessly tear up code—we merely extend the existing robust McpTool pattern to the rest of the protocol.