Type-safe dynamic dispatch
If you’ve ever looked at Rust MCP server code and seen ToolRouter<Self>, you might have done a double-take. How can a struct contain itself? Isn’t that circular? Let’s break down this elegant pattern that’s actually simpler than it looks.

The Pattern
struct Calculator {
router: ToolRouter<Self>,
}
The key insight: it doesn’t contain itself. The ToolRouter<Calculator> doesn’t hold a Calculator instance – it holds a list of function pointers that know how to work with a Calculator.
What’s Actually Stored
Think of ToolRouter as a HashMap:
HashMap<String, fn(&Calculator, i32, i32) -> i32>
This stores tool names mapped to function pointers. When you register tools, you’re building a dispatch table:
handlers = {
"add" -> [pointer to Calculator::add],
"multiply" -> [pointer to Calculator::multiply],
}
Why This Pattern Exists
Without the router, you’d write the same dispatch logic for every server:
match tool {
"add" => self.add(a, b),
"multiply" => self.multiply(a, b),
// 50 more lines...
}
The router pattern provides type-safe dynamic dispatch. The generic <Self> ensures all registered functions have compatible signatures – the compiler won’t let you register a function that expects &WeatherServer into a ToolRouter<Calculator>.
The Magic Moment
When a tool is called:
calculator.router.call("add", &calculator, params)
The router looks up “add” in its HashMap, retrieves the function pointer, and calls it with the Calculator instance. It’s a phonebook of methods, not a container of objects.
Why Not Just Use Traits?
You could implement impl ToolRouter for Calculator, but then where does the HashMap live? Traits define behavior, not storage. With composition, the MCP library gives you a pre-built, tested router that you just drop in.
This pattern shines in framework code where multiple server types need identical dispatch mechanisms. It’s the difference between building an app and building a library that helps others build apps.
Bottom line: ToolRouter<Self> is a type-safe registry of function pointers – a reusable dispatch table that eliminates boilerplate while maintaining Rust’s compile-time guarantees.
TL;DR: <Self> is just generic type syntax. The “list of function pointers” part is specific to how ToolRouter works internally.
TL;DR
ToolRouter<T>is generic (library accepts multiple types)ToolRouter<Self>is specific (you’re using it for ONE type: Calculator)Selfis just shorthand for “Calculator” when you’re inside the Calculator definition
We’re not making Calculator generic – we’re using a generic library with our specific type.
So… why go to this effort?
You can add new tools without modifying dispatch logic.
✅ Framework-level reuse
This is exactly how:
- MCP servers
- RPC frameworks
- plugin systems
are structured internally.
✅ Static safety + dynamic behavior
- Dynamic lookup by string
- Static, compile-time checked function signatures
