# PegBoard Implementation - Key Changes
## Latest Update: Added `select_tools` Method
**New method for batch tool retrieval**
Added `select_tools(&[&str]) -> Option<Vec<Tool>>` method to efficiently retrieve multiple tools in one call.
### What's New:
- **All-or-nothing semantics**: Returns `Some(Vec<Tool>)` only if ALL requested tools exist
- **Returns `None` if ANY tool is missing** - makes it easy to check for required tool sets
- **Use case**: When you need a specific subset of tools for a specialized task
- **Example**: Select only the tools needed for a particular operation instead of getting all tools
```rust
// Get specific tools for a task
let tools = pegboard.select_tools(&["web-search", "fs-read", "db-write"]);
if let Some(tools) = tools {
// All 3 tools found, can use them safely
send_to_llm(&tools).await?;
} else {
// One or more tools missing
println!("Missing required tools");
}
```
### API Addition:
- Location: `magi-tool/src/pegboard.rs:274`
- Documentation: `magi-tool/docs/PEGBOARD_DESIGN.md` (Tool Retrieval section)
---
## Previous Update: Automatic Tool Discovery
✅ **`list_tools()` is now fully implemented!**
The PegBoard now automatically calls `list_tools()` on each MCP service during registration to discover all available tools. This is a required method in the rmcp protocol.
### What Changed:
- Removed TODO comment - `list_tools()` is always available in rmcp
- Automatically calls `service.list_tools(None)` to get all tools (no pagination)
- Converts rmcp tool format to PegBoard's Tool format
- Tools are discovered and registered in one seamless operation
## Summary
Updated PegBoard implementation to match the actual use case: **automatic tool name prefixing to avoid conflicts when sending to LLM**.
## The Problem You Described
- Multiple MCP servers may have tools with the same name (e.g., `search()`)
- When sending tools to LLM, names must be unique
- Need to route LLM's tool calls back to the correct MCP service
## The Solution
### Automatic Name Prefixing
When you register an MCP service with a namespace (e.g., "web_search"), the PegBoard:
1. **Prefixes all tool names**: `search` → `web_search-search`
2. **Modifies the Tool struct**: Changes `tool.name` to the prefixed version
3. **Maintains routing info**: Stores mapping of `"web_search-search"` → `(service_index, "search")`
4. **Returns prefixed tools to LLM**: When you call `get_all_tools()`, tools have unique prefixed names
### Example Flow
```rust
// Register service "web_search" that has tool "search"
pegboard.add_service("web_search".to_string(), service).await?;
// Get tools for LLM - name is already "web_search-search"
let tools = pegboard.get_all_tools();
// tools[0].name == "web_search-search" ✅
// LLM calls "web_search-search"
let (service_idx, original_name) = pegboard.get_tool_route("web_search-search").unwrap();
// service_idx = 0, original_name = "search"
// Call the service with original name
services[service_idx].call_tool("search", params).await?;
```
## Key Changes from Initial Implementation
### Before (Wrong)
- Used `namespace::tool_name` as internal key only
- Tool's `name` field was unchanged
- Would have sent duplicate names to LLM ❌
### After (Correct)
- Uses `namespace-tool_name` format (with dash)
- Tool's `name` field is **modified** to use prefixed name
- LLM receives unique tool names ✅
- Routing maintains original name for service calls ✅
## API Changes
### Main Methods
1. **`add_service(namespace, service)`** - Automatic discovery + prefixing
```rust
pegboard.add_service("web".to_string(), service).await?;
```
2. **`get_tool(prefixed_name)`** - Get by prefixed name
```rust
let tool = pegboard.get_tool("web-search").unwrap();
assert_eq!(tool.name, "web-search"); ```
3. **`get_tool_route(prefixed_name)`** - NEW! Critical for routing
```rust
let (service_idx, original_name) = pegboard.get_tool_route("web-search").unwrap();
```
4. **`get_all_tools()`** - Tools ready for LLM
```rust
let tools = pegboard.get_all_tools();
```
## Data Structures
### PegBoard
```rust
pub struct PegBoard {
// Tools with prefixed names (e.g., "web-search")
tools: DashMap<String, Tool>,
// Services with namespaces
services: Vec<(String, RunningService<...>)>,
// Routing: "web-search" → (service_idx, "search")
tool_routing: DashMap<String, ToolRoute>,
// Namespace → prefixed tool names
namespace_tools: DashMap<String, Vec<String>>,
}
```
### ToolRoute (NEW)
```rust
struct ToolRoute {
service_index: usize, // Which service
original_name: String, // Original tool name
}
```
## Complete Workflow
```
1. Register Service
↓
Service has: ["search", "fetch"]
↓
PegBoard stores: ["web-search", "web-fetch"]
↓
2. Get Tools for LLM
↓
tools = pegboard.get_all_tools()
↓
tools[0].name == "web-search" (prefixed!)
↓
3. Send to LLM
↓
LLM sees unique names
↓
4. LLM Calls "web-search"
↓
(service_idx, original_name) = pegboard.get_tool_route("web-search")
↓
Returns: (0, "search")
↓
5. Execute
↓
services[0].call_tool("search", params)
```
## Why `DashMap<String, Tool>` is Better
✅ **Direct O(1) lookup** by prefixed name (what LLM uses)
✅ **Single source of truth** - tools already have prefixed names
✅ **No synchronization issues** - one data structure
✅ **Thread-safe** - DashMap handles concurrency
✅ **Easy to add/remove** - no reindexing needed
❌ Vec approach would require:
- Maintaining Vec and DashMap in sync
- Extra indirection (name → index → tool)
- Complexity when removing tools
## Testing
All 12 tests pass:
- Name prefixing logic
- Tool registration with namespaces
- Routing information retrieval
- Namespace operations
- Error handling
Run: `cargo test -p magi-tool`
## Documentation
See `PEGBOARD_DESIGN.md` for:
- Complete API reference
- Usage examples
- Architecture decisions
- Future enhancements