How MCP Tools Are Implemented in ai-memory: Request Routing Flow Explained
The ai-memory project exposes its memory operations as MCP tools through the rmcp crate by using the #[tool_handler] procedural macro to auto-register handlers in a ToolRouter, which then dispatches incoming JSON-RPC requests via the ServerHandler::call_tool implementation in AiMemoryServer.
The akitaonrails/ai-memory codebase leverages the rmcp crate to turn core memory functions into protocol-compliant MCP tools. Understanding the MCP tools implementation and request routing flow inside this Rust project reveals a clean three-layer architecture: macro-generated handlers, a compile-time ToolRouter, and a transport-agnostic ServerHandler dispatch loop. Every stage of the pipeline is defined inside crates/ai-memory-mcp/src/server.rs, making it the authoritative source for how client requests reach the business logic.
Declaring MCP Tools with the #[tool_handler] Macro
Each public operation—such as memory_query or memory_write_page—is declared as an async Rust function decorated with the #[tool_handler] attribute. According to the ai-memory source code, this macro expands the function into a JSON-RPC-compatible handler, automatically derives a JSON Schema from the argument struct, and registers the tool name with the router.
In crates/ai-memory-mcp/src/server.rs around lines 3835–3860, a tool definition looks like this:
#[tool_handler] // ← marks the function as an MCP tool
async fn memory_query(
ctx: RequestContext<Self>,
args: QueryArgs,
) -> Result<CallToolResult, McpError> {
// ‑‑‑ argument validation, store access, business logic ‑‑‑
// Build a `CallToolResult` containing the JSON payload the client expects.
}
The macro performs three critical tasks behind the scenes. First, it generates a JSON Schema for the argument struct—in this case, QueryArgs. Second, it registers the function under the exact RPC method name memory_query. Third, it wraps the function body so that any Result<_, McpError> is converted into a proper MCP CallToolResult before leaving the server boundary.
Building the ToolRouter Inside AiMemoryServer
The dispatcher itself lives inside the AiMemoryServer struct as a ToolRouter<Self>. In crates/ai-memory-mcp/src/server.rs at lines 48–52, the server stores the router as a field:
pub struct AiMemoryServer {
…
#[allow(dead_code)]
tool_router: ToolRouter<Self>, // ← the router that knows all tools
}
The ToolRouter type is imported from rmcp::handler::server::router::tool::ToolRouter and acts as a compile-time generated dispatcher. When AiMemoryServer::new runs, the router is populated with every function bearing the #[tool_handler] attribute, creating an internal map from tool name to handler. This design keeps routing logic decoupled from transport concerns.
MCP Request Routing Flow Step by Step
The journey from a client request to a business-logic response follows five precise stages inside the ai-memory MCP server.
Step 1: Transport Reception and JSON-RPC Decoding
A client connects through either the built-in stdio transport—used by ai-memory-cli commands mcp_bridge—or the streamable-http server exposed via ai-memory serve. The chosen transport decodes the raw incoming bytes into an rmcp::model::CallToolRequestParams object. Because routing lives entirely inside AiMemoryServer, the same request structure works regardless of transport.
Step 2: ServerHandler::call_tool Entry Point
The rmcp runtime forwards the decoded request to AiMemoryServer::call_tool(request, ctx). In the impl ServerHandler for AiMemoryServer block around line 3836 of crates/ai-memory-mcp/src/server.rs, the server creates a ToolCallContext by calling rmcp::handler::server::tool::ToolCallContext::new(self, request, ctx). This context object binds the server instance, the request parameters, and the rmcp context into a single dispatch unit.
Step 3: ToolRouter Dispatch
Inside the tool call context, the request reaches self.tool_router.call(&request, &ctx). The router performs a lookup on request.tool—for example, "memory_query"—finds the matching generated handler, and invokes it with the deserialized arguments. This step is entirely internal to crates/ai-memory-mcp/src/server.rs and happens without hand-written dispatch logic.
Step 4: Handler Execution
The matched handler—the same function annotated with #[tool_handler]—executes the core business logic. This may involve querying the SQLite store, applying retention rules, or invoking LLM providers. The handler ultimately builds and returns a CallToolResult containing the JSON payload and optional metadata.
Step 5: Response Serialization and Client Delivery
The ToolCallContext converts the handler's result into an rmcp JSON-RPC response modeled by rmcp::model::CallToolResult. The transport layer then serializes the response and sends it back across stdio or HTTP. Because the router and handler are transport-agnostic, no tool-level code changes are required to support new transports.
Client Example: Invoking an MCP Tool
The following Rust snippet demonstrates how a client constructs a request and sends it over the stdio transport. The same CallToolRequestParams object works identically over HTTP because the routing logic remains confined to AiMemoryServer.
use rmcp::transport::stdio;
use rmcp::model::CallToolRequestParams;
// Build a request to call the `memory_query` tool
let request = CallToolRequestParams {
tool: "memory_query".into(),
params: serde_json::json!({ "q": "ai‑memory architecture", "n": 5 }),
..Default::default()
};
// Send it over the stdio transport
let response = stdio::client::call(request).await?;
println!("Got hits: {}", response.result);
Additional transport bridging logic can be found in crates/ai-memory-cli/src/commands/mcp_bridge.rs, which shows how users can invoke these MCP tools across different transport boundaries.
Key Files in the MCP Implementation
Several source files work together to define, register, and test the MCP tools implementation and request routing flow in ai-memory:
crates/ai-memory-mcp/src/server.rs– DefinesAiMemoryServer, stores theToolRouter, and contains theimpl ServerHandlerdispatch logic.crates/ai-memory-mcp/src/lib.rs– Public re-exports and the crate entry point for the MCP layer.crates/ai-memory-mcp/src/actor.rs– Helper for extracting request parts from rmcp extensions, consumed by tool handlers.crates/ai-memory-cli/src/commands/mcp_bridge.rs– Example client bridging stdio and HTTP transports.crates/ai-memory-mcp/tests/slot_identity.rs– Unit test exercising the router and tool-call flow with a localLocalSessionManager.
Summary
ai-memoryexposes its memory API as MCP tools using the rmcp crate and the#[tool_handler]procedural macro.- Each tool is auto-registered in a compile-time
ToolRouter<Self>stored insideAiMemoryServer. - Incoming JSON-RPC requests enter through
ServerHandler::call_tool, which creates aToolCallContextand forwards the call to the router. - The router looks up the tool by name, executes the corresponding handler, and returns a
CallToolResultthat is serialized back to the client. - The entire routing stack is transport-agnostic, supporting both stdio and streamable HTTP without tool-level changes.
Frequently Asked Questions
What crate does ai-memory use to implement MCP server functionality?
The project uses the rmcp crate to handle the MCP protocol. This includes the ToolRouter, ServerHandler trait, ToolCallContext, and the #[tool_handler] macro machinery that generates JSON Schema and registers handlers.
Where is the tool routing logic located in the ai-memory repository?
All core routing logic lives in crates/ai-memory-mcp/src/server.rs. This file contains the AiMemoryServer struct with its tool_router: ToolRouter<Self> field, the #[tool_handler] function definitions, and the impl ServerHandler for AiMemoryServer block that performs dispatch.
How does the #[tool_handler] macro affect a Rust function?
The macro expands the decorated function into a JSON-RPC-compatible handler, automatically derives a JSON Schema from the function's argument struct, and registers the tool name with the ToolRouter. It also wraps the return type so that Result<_, McpError> is converted into a standard MCP CallToolResult.
Can ai-memory MCP tools work over both stdio and HTTP transports?
Yes. The routing and handler layers are completely transport-agnostic. The same AiMemoryServer and ToolRouter logic serves requests whether they arrive via the stdio transport—used by ai-memory-cli commands mcp_bridge—or the streamable HTTP server started with ai-memory serve.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →