# How MCP Tools Are Implemented in ai-memory: Request Routing Flow Explained

> Learn how MCP tools are implemented in ai-memory using the rmcp crate and #[tool_handler] macro. Explore the request routing flow through ToolRouter and ServerHandler::call_tool.

- Repository: [Fabio Akita/ai-memory](https://github.com/akitaonrails/ai-memory)
- Tags: internals
- Published: 2026-08-19

---

**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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs) around lines 3835–3860, a tool definition looks like this:

```rust
#[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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs) at lines 48–52, the server stores the router as a field:

```rust
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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`.

```rust
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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/src/server.rs) – Defines `AiMemoryServer`, stores the `ToolRouter`, and contains the `impl ServerHandler` dispatch logic.
- [`crates/ai-memory-mcp/src/lib.rs`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/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`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-cli/src/commands/mcp_bridge.rs) – Example client bridging stdio and HTTP transports.
- [`crates/ai-memory-mcp/tests/slot_identity.rs`](https://github.com/akitaonrails/ai-memory/blob/main/crates/ai-memory-mcp/tests/slot_identity.rs) – Unit test exercising the router and tool-call flow with a local `LocalSessionManager`.

## Summary

- `ai-memory` exposes 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 inside `AiMemoryServer`.
- Incoming JSON-RPC requests enter through **`ServerHandler::call_tool`**, which creates a `ToolCallContext` and forwards the call to the router.
- The router looks up the tool by name, executes the corresponding handler, and returns a `CallToolResult` that 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`](https://github.com/akitaonrails/ai-memory/blob/main/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`.