magi-tool 0.0.3

provide tools for Magi AI agents
Documentation
# Optional Namespace Feature

## Summary

Updated PegBoard to make namespace **optional** when registering MCP services. Users can now choose whether to prefix tool names based on their specific needs.

## The Change

### API Update

```rust
// BEFORE: Namespace was required
pub async fn add_service(
    &mut self,
    namespace: String,  // Required
    service: RunningService<...>,
) -> Result<usize, PegBoardError>

// AFTER: Namespace is optional
pub async fn add_service(
    &mut self,
    namespace: Option<String>,  // Optional
    service: RunningService<...>,
) -> Result<usize, PegBoardError>
```

### Behavior

**With namespace** (`Some("web")`):
- Tools get prefixed: `search``web-search`
- Tool's `name` field is modified
- Namespace is tracked for queries

**Without namespace** (`None` or `Some("")`):
- Tools keep original names: `search``search`
- Tool's `name` field is unchanged
- No namespace tracking

## When to Use Each Approach

### ✅ Use Namespace (Prefixing) When:

1. **Multiple services with conflicting tool names**
   ```rust
   // Both have "search" - must use namespaces
   pegboard.add_service(Some("web".to_string()), web_service).await?;
   pegboard.add_service(Some("files".to_string()), file_service).await?;
   // Result: "web-search", "files-search"
   ```

2. **Want clear service identification**
   ```rust
   // Prefer explicit naming for clarity
   pegboard.add_service(Some("github".to_string()), github_service).await?;
   // "github-create_issue" is clearer than just "create_issue"
   ```

### ❌ Skip Namespace (No Prefixing) When:

1. **Single MCP service**
   ```rust
   // Only one service, no conflicts possible
   pegboard.add_service(None, calculator_service).await?;
   // Tools: "add", "subtract", "multiply", "divide"
   ```

2. **Known unique tool names**
   ```rust
   // Calculator + Weather - no overlap
   pegboard.add_service(None, calculator_service).await?;
   pegboard.add_service(None, weather_service).await?;
   // Tools: "add", "subtract", "get_forecast", "get_alerts"
   ```

3. **Prefer clean tool names**
   ```rust
   // If you don't want prefixes and are confident about uniqueness
   pegboard.add_service(None, service).await?;
   ```

## Usage Examples

### Example 1: Mixed Approach

```rust
let mut pegboard = PegBoard::new();

// Services with conflicts - use namespaces
pegboard.add_service(Some("fs".to_string()), fs_service).await?;
pegboard.add_service(Some("db".to_string()), db_service).await?;
// Both have "read", "write" → "fs-read", "fs-write", "db-read", "db-write"

// Standalone service - no namespace
pegboard.add_service(None, calculator_service).await?;
// Unique tools → "add", "subtract", "multiply", "divide"

// Get all tools for LLM (mixed prefixed + original names)
let tools = pegboard.get_all_tools();
// ["fs-read", "fs-write", "db-read", "db-write", "add", "subtract", "multiply", "divide"]
```

### Example 2: All Without Namespace

```rust
let mut pegboard = PegBoard::new();

// When you're confident there are no conflicts
pegboard.add_service(None, weather_service).await?;
pegboard.add_service(None, calculator_service).await?;
pegboard.add_service(None, timer_service).await?;

// All tools keep original names
let tools = pegboard.get_all_tools();
// ["get_forecast", "add", "subtract", "set_timer", "cancel_timer"]
```

### Example 3: All With Namespace

```rust
let mut pegboard = PegBoard::new();

// When you want consistent prefixing for clarity
pegboard.add_service(Some("weather".to_string()), weather_service).await?;
pegboard.add_service(Some("calc".to_string()), calculator_service).await?;
pegboard.add_service(Some("timer".to_string()), timer_service).await?;

// All tools are prefixed
let tools = pegboard.get_all_tools();
// ["weather-get_forecast", "calc-add", "calc-subtract", "timer-set_timer", "timer-cancel_timer"]
```

## Routing Works the Same

Whether tools are prefixed or not, routing works identically:

```rust
// For prefixed tool "web-search"
let (service_idx, original_name) = pegboard.get_tool_route("web-search").unwrap();
// Returns: (0, "search")
// Call: services[0].call_tool("search", params)

// For non-prefixed tool "add"
let (service_idx, original_name) = pegboard.get_tool_route("add").unwrap();
// Returns: (1, "add")
// Call: services[1].call_tool("add", params)
```

## Decision Flowchart

```
┌─────────────────────────────────────┐
│ Are you registering multiple        │
│ MCP services?                       │
└────────────┬────────────────────────┘
        No   │   Yes
      ┌──────┴──────┐
      │             │
      ▼             ▼
   ┌────┐      ┌────────────────────┐
   │ No │      │ Could any services │
   │ NS │      │ have conflicting   │
   └────┘      │ tool names?        │
              └──────┬─────────────┘
                No   │   Yes/Unsure
              ┌──────┴──────┐
              │             │
              ▼             ▼
           ┌────┐      ┌──────┐
           │ No │      │ Use  │
           │ NS │      │ NS   │
           └────┘      └──────┘

NS = Namespace
```

## Implementation Details

### Empty String Treated as No Namespace

Both `None` and `Some("")` mean no namespace:

```rust
// These are equivalent
pegboard.add_service(None, service).await?;
pegboard.add_service(Some(String::new()), service).await?;

// Internally normalized to empty string
let namespace_str = namespace.unwrap_or_default();
let has_namespace = !namespace_str.is_empty();
```

### Namespace Tracking

Tools without namespace are NOT tracked in `namespace_tools` map:

```rust
// With namespace
pegboard.add_service(Some("web".to_string()), service).await?;
pegboard.list_namespaces(); // ["web"]
pegboard.list_tools_in_namespace("web"); // ["web-search", "web-fetch"]

// Without namespace
pegboard.add_service(None, service).await?;
pegboard.list_namespaces(); // [] (not tracked)
// Access via get_all_tools() instead
```

## Migration Guide

If you were using the old API with required namespace:

```rust
// OLD (required namespace)
pegboard.add_service("web".to_string(), service).await?;

// NEW (wrap in Some)
pegboard.add_service(Some("web".to_string()), service).await?;

// NEW (or use None if no conflicts)
pegboard.add_service(None, service).await?;
```

## Testing

All 14 tests pass, including new tests for optional namespace:

```bash
cargo test -p magi-tool --lib
```

New tests:
- `test_optional_namespace` - Verifies None and empty string handling
- `test_prefix_only_when_namespace_provided` - Confirms conditional prefixing
- Updated existing tests to use `Option<String>` API

## Benefits

1. **Flexibility** - Use prefixing only when needed
2. **Simplicity** - Single-service use cases don't need namespace overhead
3. **Cleaner names** - Avoid unnecessary prefixes when no conflicts exist
4. **Backward compatible** - Just wrap existing strings in `Some()`
5. **Clear intent** - `None` explicitly signals "no conflicts expected"

## Documentation

Updated files:
- `PEGBOARD_DESIGN.md` - Complete API reference with examples
- `OPTIONAL_NAMESPACE.md` - This guide
- Inline code documentation

See `PEGBOARD_DESIGN.md` for full usage examples and decision guide.