Files
mcp-memory/ARCHITECTURE_RESOURCES_PROMPTS.md
T

78 lines
3.8 KiB
Markdown

# 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`:
```rust
#[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`):**
```rust
#[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`):**
```rust
#[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://session/delta`, 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.