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>>toMemoryHandler. - In
handle_request("resources/list"), iterate overself.resources.values()and build the JSON payload. - In
handle_request("resources/read"), lookup the requested URI inself.resourcesand call.read(state).await. - Move the existing
memory://graph/entitieslogic 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>>toMemoryHandler. - In
handle_request("prompts/list"), map overself.prompts.values(). - In
handle_request("prompts/get"), call.get(args, state).await.
4. Execution Plan
- Refactor
router.rs(No functional changes yet): Define theMcpResourceandMcpPrompttraits. Update theMemoryHandlerstruct to hold these HashMaps. Migrate the existing hardcoded stubs (memory://graph/entitiesandanalyze_tech_debt) into structs implementing these traits. - Expand Resources (Phase 1): Add new handlers for
memory://tasks/active,memory://pinned_files, etc. - 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.