# MCP Tool Router Pattern Guide

A practical guide for building MCP servers with 50-500+ tools without destroying context windows or confusing the LLM.

---

## The Problem

When your MCP server exposes too many tools:

1. **Token bloat**: 50 tools ≈ 30-50K tokens consumed before the user says anything
2. **Selection errors**: Claude sees `add_component`, `place_component`, `add_footprint`, `place_footprint` and guesses wrong
3. **Context starvation**: Less room for actual conversation and results
4. **Accuracy degradation**: More tools = more confusion about which to use

Real-world example: A KiCAD MCP server with 52 tools consumes ~40K tokens. An IDA Pro MCP server could easily hit 100+ tools.

---

## Solution Overview

Implement a **router pattern** within your MCP server that:

1. Exposes only essential tools directly (10-15 tools)
2. Provides discovery tools for everything else
3. Routes execution through a single `execute_tool` endpoint

**Result**: Claude sees ~18 tools instead of 100+, but can still access everything.

---

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                     MCP Client (Claude)                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                      Your MCP Server                         │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              DIRECT TOOLS (Always Visible)              ││
│  │  • create_project    • open_project    • save_project   ││
│  │  • add_component     • add_track       • get_info       ││
│  │  • (10-15 high-frequency tools)                         ││
│  └─────────────────────────────────────────────────────────┘│
│  ┌─────────────────────────────────────────────────────────┐│
│  │                    ROUTER TOOLS                         ││
│  │  • list_tool_categories   • get_category_tools          ││
│  │  • execute_tool           • search_tools                ││
│  └─────────────────────────────────────────────────────────┘│
│                              │                               │
│                              ▼                               │
│  ┌─────────────────────────────────────────────────────────┐│
│  │              ROUTED TOOLS (Hidden)                      ││
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐   ││
│  │  │  export  │ │   drc    │ │  zones   │ │ advanced │   ││
│  │  │ (8 tools)│ │(5 tools) │ │(6 tools) │ │(12 tools)│   ││
│  │  └──────────┘ └──────────┘ └──────────┘ └──────────┘   ││
│  │  ┌──────────┐ ┌──────────┐ ┌──────────┐                ││
│  │  │schematic │ │  layers  │ │ graphics │  ... more      ││
│  │  │(15 tools)│ │(4 tools) │ │(10 tools)│                ││
│  │  └──────────┘ └──────────┘ └──────────┘                ││
│  └─────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────┘
```

---

## Implementation

### Step 1: Define Your Tool Registry

Create a central registry that organizes all tools by category.

```typescript
// src/tools/registry.ts

export interface ToolDefinition {
  name: string;
  description: string;
  inputSchema: {
    type: "object";
    properties: Record<string, unknown>;
    required?: string[];
  };
  handler: (params: any) => Promise<any>;
}

export interface ToolCategory {
  name: string;
  description: string;
  tools: ToolDefinition[];
}

// Define all your categories
export const toolCategories: ToolCategory[] = [
  {
    name: "export",
    description: "Generate output files: Gerber, drill, BOM, PDF, 3D models (STEP/VRML), SVG",
    tools: [
      {
        name: "export_gerber",
        description: "Export Gerber files for PCB fabrication",
        inputSchema: {
          type: "object",
          properties: {
            output_dir: {
              type: "string",
              description: "Output directory path",
            },
            layers: {
              type: "array",
              items: { type: "string" },
              description: "Layers to export (default: all copper + silkscreen + mask)",
            },
            format: {
              type: "string",
              enum: ["rs274x", "x2"],
              description: "Gerber format version",
            },
          },
          required: ["output_dir"],
        },
        handler: async (params) => {
          // Your implementation
          return { success: true, files: ["..."] };
        },
      },
      {
        name: "export_drill",
        description: "Export Excellon drill files",
        inputSchema: {
          type: "object",
          properties: {
            output_dir: { type: "string" },
            format: { type: "string", enum: ["excellon", "excellon2"] },
          },
          required: ["output_dir"],
        },
        handler: async (params) => {
          /* ... */
        },
      },
      {
        name: "export_bom",
        description: "Export bill of materials as CSV or XML",
        inputSchema: {
          /* ... */
        },
        handler: async (params) => {
          /* ... */
        },
      },
      // ... more export tools
    ],
  },
  {
    name: "drc",
    description:
      "Design rule checking: clearance validation, electrical rules, manufacturing constraints",
    tools: [
      {
        name: "run_drc",
        description: "Run full design rule check on current board",
        inputSchema: {
          type: "object",
          properties: {
            report_all: {
              type: "boolean",
              description: "Report all violations or stop at first",
            },
          },
        },
        handler: async (params) => {
          /* ... */
        },
      },
      {
        name: "get_drc_errors",
        description: "Get current DRC violations without re-running check",
        inputSchema: { type: "object", properties: {} },
        handler: async (params) => {
          /* ... */
        },
      },
      {
        name: "set_design_rules",
        description: "Configure design rules: clearance, track width, via size, etc.",
        inputSchema: {
          type: "object",
          properties: {
            min_clearance: { type: "number", description: "Minimum clearance in mm" },
            min_track_width: { type: "number", description: "Minimum track width in mm" },
            min_via_diameter: { type: "number", description: "Minimum via diameter in mm" },
            min_via_drill: { type: "number", description: "Minimum via drill size in mm" },
          },
        },
        handler: async (params) => {
          /* ... */
        },
      },
    ],
  },
  {
    name: "zones",
    description: "Copper zones/pours: ground planes, power fills, keep-out areas",
    tools: [
      {
        name: "add_zone",
        description: "Add copper zone/pour to PCB",
        inputSchema: {
          type: "object",
          properties: {
            net_name: { type: "string", description: "Net to connect (e.g., 'GND', 'VCC')" },
            layer: { type: "string", description: "Layer name (e.g., 'F.Cu', 'B.Cu')" },
            points: {
              type: "array",
              items: {
                type: "object",
                properties: {
                  x: { type: "number" },
                  y: { type: "number" },
                },
              },
              description: "Polygon vertices in mm",
            },
            priority: { type: "number", description: "Fill priority (higher fills first)" },
          },
          required: ["net_name", "layer", "points"],
        },
        handler: async (params) => {
          /* ... */
        },
      },
      {
        name: "fill_zones",
        description: "Recalculate and fill all copper zones",
        inputSchema: { type: "object", properties: {} },
        handler: async (params) => {
          /* ... */
        },
      },
      {
        name: "remove_zone",
        description: "Remove a copper zone by ID",
        inputSchema: {
          type: "object",
          properties: {
            zone_id: { type: "string", description: "Zone identifier" },
          },
          required: ["zone_id"],
        },
        handler: async (params) => {
          /* ... */
        },
      },
    ],
  },
  // Add more categories...
];
```

### Step 2: Build Lookup Maps

Create efficient lookups for routing.

```typescript
// src/tools/registry.ts (continued)

// Build lookup maps at module load time
const categoryMap = new Map<string, ToolCategory>();
const toolMap = new Map<string, { category: string; tool: ToolDefinition }>();

export function initializeRegistry() {
  for (const category of toolCategories) {
    categoryMap.set(category.name, category);
    for (const tool of category.tools) {
      toolMap.set(tool.name, { category: category.name, tool });
    }
  }
}

export function getCategory(name: string): ToolCategory | undefined {
  return categoryMap.get(name);
}

export function getTool(name: string): { category: string; tool: ToolDefinition } | undefined {
  return toolMap.get(name);
}

export function getAllCategories(): ToolCategory[] {
  return toolCategories;
}

export function searchTools(query: string): Array<{
  category: string;
  tool: string;
  description: string;
}> {
  const q = query.toLowerCase();
  const matches: Array<{ category: string; tool: string; description: string }> = [];

  for (const category of toolCategories) {
    for (const tool of category.tools) {
      if (
        tool.name.toLowerCase().includes(q) ||
        tool.description.toLowerCase().includes(q) ||
        category.name.toLowerCase().includes(q)
      ) {
        matches.push({
          category: category.name,
          tool: tool.name,
          description: tool.description,
        });
      }
    }
  }

  return matches;
}

// Initialize on load
initializeRegistry();
```

### Step 3: Create Router Tools

These are the tools that enable discovery and execution.

```typescript
// src/tools/router.ts

import { getAllCategories, getCategory, getTool, searchTools } from "./registry.js";

export const routerTools = {
  list_tool_categories: {
    name: "list_tool_categories",
    description:
      "List all available tool categories. Use this to discover what operations " +
      "are available beyond the basic tools exposed directly.",
    inputSchema: {
      type: "object" as const,
      properties: {},
      required: [],
    },
    handler: async () => {
      const categories = getAllCategories();
      return {
        total_categories: categories.length,
        total_tools: categories.reduce((sum, c) => sum + c.tools.length, 0),
        categories: categories.map((c) => ({
          name: c.name,
          description: c.description,
          tool_count: c.tools.length,
        })),
      };
    },
  },

  get_category_tools: {
    name: "get_category_tools",
    description:
      "Get detailed information about tools in a specific category, " +
      "including their parameters. Use after list_tool_categories to " +
      "see what's available in a category.",
    inputSchema: {
      type: "object" as const,
      properties: {
        category: {
          type: "string",
          description: "Category name from list_tool_categories",
        },
      },
      required: ["category"],
    },
    handler: async (params: { category: string }) => {
      const category = getCategory(params.category);
      if (!category) {
        return {
          error: `Unknown category: ${params.category}`,
          available_categories: getAllCategories().map((c) => c.name),
        };
      }
      return {
        category: category.name,
        description: category.description,
        tools: category.tools.map((t) => ({
          name: t.name,
          description: t.description,
          parameters: t.inputSchema,
        })),
      };
    },
  },

  execute_tool: {
    name: "execute_tool",
    description:
      "Execute a tool from any category. First use list_tool_categories " +
      "and get_category_tools to discover available tools and their parameters.",
    inputSchema: {
      type: "object" as const,
      properties: {
        tool_name: {
          type: "string",
          description: "Tool name (from get_category_tools)",
        },
        params: {
          type: "object",
          description: "Tool parameters (see get_category_tools for schema)",
        },
      },
      required: ["tool_name"],
    },
    handler: async (input: { tool_name: string; params?: Record<string, unknown> }) => {
      const entry = getTool(input.tool_name);
      if (!entry) {
        return {
          error: `Unknown tool: ${input.tool_name}`,
          hint: "Use list_tool_categories and get_category_tools to find available tools",
        };
      }

      try {
        const result = await entry.tool.handler(input.params || {});
        return {
          tool: input.tool_name,
          category: entry.category,
          result,
        };
      } catch (err) {
        return {
          error: `Tool execution failed: ${(err as Error).message}`,
          tool: input.tool_name,
          category: entry.category,
        };
      }
    },
  },

  search_tools: {
    name: "search_tools",
    description:
      "Search for tools by keyword across all categories. " +
      "Useful when you know what you want to do but not which category it's in.",
    inputSchema: {
      type: "object" as const,
      properties: {
        query: {
          type: "string",
          description: "Search term (e.g., 'gerber', 'zone', 'differential', 'export')",
        },
      },
      required: ["query"],
    },
    handler: async (params: { query: string }) => {
      const matches = searchTools(params.query);
      return {
        query: params.query,
        count: matches.length,
        matches: matches.slice(0, 20), // Limit results
      };
    },
  },
};
```

### Step 4: Define Direct Tools

These are your high-frequency tools that stay visible always.

```typescript
// src/tools/direct.ts

import { ToolDefinition } from "./registry.js";

// These tools are ALWAYS visible to Claude - no routing needed
// Pick your 10-15 most common operations

export const directTools: ToolDefinition[] = [
  // === PROJECT LIFECYCLE ===
  {
    name: "create_project",
    description: "Create a new project with initial files and configuration",
    inputSchema: {
      type: "object",
      properties: {
        name: { type: "string", description: "Project name" },
        path: { type: "string", description: "Directory path for project" },
        template: {
          type: "string",
          description: "Optional template to use",
          enum: ["blank", "arduino", "raspberry-pi"],
        },
      },
      required: ["name", "path"],
    },
    handler: async (params) => {
      // Implementation
      return { success: true, project_path: `${params.path}/${params.name}` };
    },
  },
  {
    name: "open_project",
    description: "Open an existing project",
    inputSchema: {
      type: "object",
      properties: {
        path: { type: "string", description: "Path to project file or directory" },
      },
      required: ["path"],
    },
    handler: async (params) => {
      /* ... */
    },
  },
  {
    name: "save_project",
    description: "Save all project files",
    inputSchema: {
      type: "object",
      properties: {},
    },
    handler: async (params) => {
      /* ... */
    },
  },
  {
    name: "get_project_info",
    description: "Get current project information: path, files, status",
    inputSchema: {
      type: "object",
      properties: {},
    },
    handler: async (params) => {
      /* ... */
    },
  },

  // === PRIMARY OPERATIONS (your core workflow) ===
  {
    name: "add_component",
    description: "Add a component at specified position",
    inputSchema: {
      type: "object",
      properties: {
        type: { type: "string", description: "Component type or library reference" },
        reference: { type: "string", description: "Reference designator (e.g., R1, U1)" },
        x: { type: "number", description: "X position" },
        y: { type: "number", description: "Y position" },
        rotation: { type: "number", description: "Rotation in degrees", default: 0 },
      },
      required: ["type", "reference", "x", "y"],
    },
    handler: async (params) => {
      /* ... */
    },
  },
  {
    name: "move_component",
    description: "Move a component to new position",
    inputSchema: {
      type: "object",
      properties: {
        reference: { type: "string", description: "Component reference (e.g., R1)" },
        x: { type: "number", description: "New X position" },
        y: { type: "number", description: "New Y position" },
      },
      required: ["reference", "x", "y"],
    },
    handler: async (params) => {
      /* ... */
    },
  },
  {
    name: "list_components",
    description: "List all components with their positions and properties",
    inputSchema: {
      type: "object",
      properties: {
        filter: { type: "string", description: "Optional filter (e.g., 'R*' for resistors)" },
      },
    },
    handler: async (params) => {
      /* ... */
    },
  },

  // === SECONDARY OPERATIONS (still common) ===
  {
    name: "add_connection",
    description: "Add a connection/trace between two points",
    inputSchema: {
      type: "object",
      properties: {
        start: {
          type: "object",
          properties: { x: { type: "number" }, y: { type: "number" } },
        },
        end: {
          type: "object",
          properties: { x: { type: "number" }, y: { type: "number" } },
        },
        net: { type: "string", description: "Net name" },
      },
      required: ["start", "end"],
    },
    handler: async (params) => {
      /* ... */
    },
  },
  {
    name: "list_nets",
    description: "List all nets/connections",
    inputSchema: {
      type: "object",
      properties: {},
    },
    handler: async (params) => {
      /* ... */
    },
  },
  {
    name: "get_info",
    description: "Get general information about current state",
    inputSchema: {
      type: "object",
      properties: {},
    },
    handler: async (params) => {
      /* ... */
    },
  },
];
```

### Step 5: Register with MCP Server

Wire everything together in your main server file.

```typescript
// src/index.ts

import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";

import { directTools } from "./tools/direct.js";
import { routerTools } from "./tools/router.js";
import { initializeRegistry } from "./tools/registry.js";

// Initialize the tool registry
initializeRegistry();

const server = new Server(
  {
    name: "your-mcp-server",
    version: "1.0.0",
  },
  {
    capabilities: {
      tools: {},
    },
  },
);

// Combine all visible tools
const allVisibleTools = [...directTools, ...Object.values(routerTools)];

// Build a handler map for quick lookup
const toolHandlers = new Map<string, (params: any) => Promise<any>>();

for (const tool of directTools) {
  toolHandlers.set(tool.name, tool.handler);
}
for (const tool of Object.values(routerTools)) {
  toolHandlers.set(tool.name, tool.handler);
}

// List tools handler - returns only direct + router tools
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: allVisibleTools.map((tool) => ({
      name: tool.name,
      description: tool.description,
      inputSchema: tool.inputSchema,
    })),
  };
});

// Call tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  const handler = toolHandlers.get(name);
  if (!handler) {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            error: `Unknown tool: ${name}`,
            hint: "Use list_tool_categories and search_tools to find available tools",
          }),
        },
      ],
      isError: true,
    };
  }

  try {
    const result = await handler(args || {});
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(result, null, 2),
        },
      ],
    };
  } catch (error) {
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify({
            error: `Tool execution failed: ${(error as Error).message}`,
          }),
        },
      ],
      isError: true,
    };
  }
});

// Start the server
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("MCP Server running on stdio");
}

main().catch(console.error);
```

---

## Choosing Direct vs Routed Tools

### Direct Tools (Always Visible)

Include tools that are:

- Used in 80%+ of sessions
- Essential for basic workflows
- Required for project lifecycle (open, save, create)
- Needed for the core operation loop

**Examples by domain:**

| Domain       | Direct Tools                                                                                                                     |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **KiCAD**    | create_project, open_project, save_project, add_component, move_component, add_track, list_components, list_nets, get_board_info |
| **IDA Pro**  | open_database, save_database, get_function, list_functions, add_comment, rename, get_xrefs, decompile                            |
| **Git**      | status, add, commit, push, pull, checkout, branch, log                                                                           |
| **Database** | connect, query, list_tables, describe_table                                                                                      |

### Routed Tools (Hidden Behind Router)

Include tools that are:

- Used in <50% of sessions
- Part of specialized workflows
- End-of-workflow operations (export, report)
- Advanced features
- Bulk or batch operations

**Examples:**

| Category          | Why Route It                 |
| ----------------- | ---------------------------- |
| `export`          | Only used at end of workflow |
| `drc/validation`  | Used during review phase     |
| `advanced_*`      | Specialty operations         |
| `bulk_*`          | Batch operations             |
| `config/settings` | One-time setup               |

---

## Category Design Guidelines

### Good Category Names

- Short, memorable (1-2 words)
- Verb-oriented or domain-specific
- Clear scope

```
✓ export, import, drc, zones, routing, schematic, layers, analysis
✗ miscellaneous, utilities, other, stuff, tools2
```

### Good Category Descriptions

Include:

- What the category does
- Key operations included
- When you'd use it

```typescript
// Good
{
  name: "export",
  description: "Generate manufacturing files: Gerber, drill, BOM, PDF, 3D models (STEP/VRML), SVG"
}

// Bad
{
  name: "export",
  description: "Export tools"
}
```

### Suggested Category Structure (Generic)

```typescript
const categories = [
  // Core operations (might be direct tools instead)
  { name: "project", description: "Project lifecycle: create, open, save, close" },

  // Domain-specific operations
  { name: "analysis", description: "Analyze and inspect: find patterns, validate, check" },
  { name: "modification", description: "Modify and transform: edit, rename, restructure" },
  { name: "navigation", description: "Navigate and search: find, list, filter, locate" },

  // Output operations
  { name: "export", description: "Export and generate: reports, files, documentation" },
  { name: "import", description: "Import from external sources: files, formats, APIs" },

  // Configuration
  { name: "config", description: "Configuration and settings: preferences, rules, templates" },

  // Advanced/specialized
  { name: "advanced", description: "Advanced operations: batch processing, automation, scripting" },
];
```

---

## IDA Pro Example Structure

For an IDA Pro MCP server with 100+ tools:

### Direct Tools (~12)

```typescript
const directTools = [
  "open_database", // Load IDB
  "save_database", // Save IDB
  "get_function", // Get function by address/name
  "list_functions", // List all functions
  "decompile", // Decompile function (Hex-Rays)
  "add_comment", // Add comment at address
  "rename", // Rename address/function
  "get_xrefs_to", // Get cross-references to address
  "get_xrefs_from", // Get cross-references from address
  "get_strings", // List strings
  "search_bytes", // Search for byte pattern
  "get_segments", // List segments
];
```

### Routed Categories

```typescript
const categories = [
  {
    name: "disassembly",
    description: "Disassembly operations: undefine, make code/data, change types",
    tools: ["make_code", "make_data", "undefine", "set_type", "make_array", "make_struct"],
  },
  {
    name: "functions",
    description: "Function management: create, delete, modify boundaries, set types",
    tools: ["create_function", "delete_function", "set_func_end", "set_func_type", "add_func_arg"],
  },
  {
    name: "types",
    description: "Type system: structs, enums, typedefs, parse headers",
    tools: ["create_struct", "add_struct_member", "create_enum", "parse_header", "import_types"],
  },
  {
    name: "patching",
    description: "Binary patching: modify bytes, assemble, apply patches",
    tools: ["patch_bytes", "patch_word", "patch_dword", "assemble", "apply_patches"],
  },
  {
    name: "scripting",
    description: "IDAPython scripting: run scripts, evaluate expressions",
    tools: ["run_script", "eval_python", "get_global", "set_global"],
  },
  {
    name: "signatures",
    description: "Signatures and patterns: FLIRT, Lumina, create signatures",
    tools: ["apply_flirt", "query_lumina", "create_sig", "find_pattern"],
  },
  {
    name: "debugging",
    description: "Debugger control: breakpoints, stepping, memory",
    tools: ["set_breakpoint", "step_into", "step_over", "read_memory", "write_memory", "get_regs"],
  },
  {
    name: "export",
    description: "Export: ASM listing, pseudocode, database info, reports",
    tools: ["export_asm", "export_c", "export_json", "generate_report"],
  },
  {
    name: "import",
    description: "Import: symbols, types, comments from external sources",
    tools: ["import_symbols", "import_pdb", "import_map", "import_comments"],
  },
  {
    name: "analysis",
    description: "Analysis control: reanalyze, find patterns, auto-analysis settings",
    tools: ["reanalyze", "find_crypto", "find_strings", "analyze_calls", "set_analysis_options"],
  },
];
```

---

## Usage Flow Examples

### Example 1: User wants to export Gerbers

```
User: "Export gerbers for this board"

Claude's thinking:
1. Don't see an export_gerber tool directly
2. Call search_tools({ query: "gerber" })
   → Returns: { category: "export", tool: "export_gerber", ... }
3. Call execute_tool({ tool_name: "export_gerber", params: { output_dir: "./gerbers" }})
   → Returns: { success: true, files: [...] }

Claude: "I've exported the Gerber files to ./gerbers/"
```

### Example 2: User wants to place a component

```
User: "Add a 0805 resistor at position 10,20"

Claude's thinking:
1. See add_component in direct tools
2. Call add_component({ type: "R_0805", reference: "R1", x: 10, y: 20 })
   → Returns: { success: true, reference: "R1" }

Claude: "Added R1 (0805 resistor) at position (10, 20)"
```

### Example 3: User wants to do something unfamiliar

```
User: "I need to add length matching to these traces"

Claude's thinking:
1. Don't see length matching in direct tools
2. Call search_tools({ query: "length matching" })
   → Returns: { category: "advanced_routing", tool: "add_length_tuning", ... }
3. Call get_category_tools({ category: "advanced_routing" })
   → Returns full parameter schema for add_length_tuning
4. Call execute_tool({ tool_name: "add_length_tuning", params: {...} })

Claude: "I've added length tuning meanders to match the trace lengths"
```

---

## Testing Your Router

### Unit Tests

```typescript
// tests/router.test.ts

import { describe, it, expect } from "vitest";
import { searchTools, getCategory, getTool, getAllCategories } from "../src/tools/registry.js";
import { routerTools } from "../src/tools/router.js";

describe("Tool Registry", () => {
  it("should find tools by keyword", () => {
    const results = searchTools("export");
    expect(results.length).toBeGreaterThan(0);
    expect(results.some((r) => r.tool.includes("export"))).toBe(true);
  });

  it("should return category info", () => {
    const category = getCategory("export");
    expect(category).toBeDefined();
    expect(category!.tools.length).toBeGreaterThan(0);
  });

  it("should return tool info with category", () => {
    const tool = getTool("export_gerber");
    expect(tool).toBeDefined();
    expect(tool!.category).toBe("export");
  });
});

describe("Router Tools", () => {
  it("list_tool_categories returns all categories", async () => {
    const result = await routerTools.list_tool_categories.handler({});
    expect(result.categories.length).toBe(getAllCategories().length);
  });

  it("get_category_tools returns tools for valid category", async () => {
    const result = await routerTools.get_category_tools.handler({
      category: "export",
    });
    expect(result.tools).toBeDefined();
    expect(result.tools.length).toBeGreaterThan(0);
  });

  it("get_category_tools returns error for invalid category", async () => {
    const result = await routerTools.get_category_tools.handler({
      category: "nonexistent",
    });
    expect(result.error).toBeDefined();
  });

  it("execute_tool runs valid tool", async () => {
    const result = await routerTools.execute_tool.handler({
      tool_name: "export_gerber",
      params: { output_dir: "/tmp/test" },
    });
    expect(result.error).toBeUndefined();
  });

  it("execute_tool returns error for invalid tool", async () => {
    const result = await routerTools.execute_tool.handler({
      tool_name: "nonexistent_tool",
      params: {},
    });
    expect(result.error).toBeDefined();
  });
});
```

### Integration Test

Test with an actual MCP client:

```bash
# Using MCP Inspector
npx @modelcontextprotocol/inspector your-server

# Or with Claude Desktop - add to config
{
  "mcpServers": {
    "your-server": {
      "command": "node",
      "args": ["./dist/index.js"]
    }
  }
}
```

---

## Advanced: Combining with Anthropic's Tool Search Tool

If you control the client (building your own app with Claude API), you can use Anthropic's native Tool Search Tool for even better results:

```python
import anthropic

client = anthropic.Anthropic()

response = client.beta.messages.create(
    model="claude-sonnet-4-5-20250929",
    max_tokens=4096,
    betas=["advanced-tool-use-2025-11-20"],
    tools=[
        # Tool search tool for dynamic discovery
        {
            "type": "tool_search_tool_regex_20251119",
            "name": "tool_search_tool_regex"
        },
        # Always-loaded tools
        {
            "name": "create_project",
            "description": "Create new project",
            "input_schema": {...},
            "defer_loading": False  # Always visible
        },
        # Deferred tools - only loaded when searched
        {
            "name": "export_gerber",
            "description": "Export Gerber files for PCB fabrication",
            "input_schema": {...},
            "defer_loading": True  # Hidden until searched
        },
        # ... 100 more deferred tools
    ],
    messages=[{"role": "user", "content": "Export gerbers for my board"}]
)
```

This approach works at the API level rather than MCP level, giving you:

- Native search with regex or BM25
- Automatic tool expansion
- Works with any MCP client

See [Tool Search Tool Documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool) for details.

---

## References

### MCP Documentation

- [MCP Specification](https://modelcontextprotocol.io/specification/2025-03-26)
- [MCP Tools Documentation](https://modelcontextprotocol.info/docs/concepts/tools/)
- [MCP TypeScript SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [MCP Python SDK](https://github.com/modelcontextprotocol/python-sdk)
- [MCP GitHub Organization](https://github.com/modelcontextprotocol)

### Anthropic Advanced Tool Use

- [Advanced Tool Use Blog Post](https://www.anthropic.com/engineering/advanced-tool-use)
- [Tool Search Tool Documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool)
- [Programmatic Tool Calling Documentation](https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling)
- [Claude Code Issue: Tool Search Support](https://github.com/anthropics/claude-code/issues/12836)

### Example Implementations

- [KiCAD MCP Server](https://github.com/mixelpixx/KiCAD-MCP-Server) - 52+ tools with natural language PCB design
- [MCP Servers Repository](https://github.com/modelcontextprotocol/servers) - Official reference implementations

---

## Checklist

Before shipping your router-based MCP server:

- [ ] Direct tools cover 80% of common use cases
- [ ] Categories have clear, descriptive names
- [ ] Category descriptions explain what's included
- [ ] All tools have good descriptions
- [ ] `search_tools` finds tools by common keywords
- [ ] `execute_tool` handles errors gracefully
- [ ] Unit tests pass for registry and router
- [ ] Tested with actual MCP client (Claude Desktop, Cline, etc.)
- [ ] README documents the router pattern for users

---

## Summary

| Before                   | After                 |
| ------------------------ | --------------------- |
| 100 tools visible        | 15-18 tools visible   |
| ~60K+ tokens consumed    | ~10K tokens consumed  |
| Tool selection confusion | Clear categories      |
| Context starvation       | Room for conversation |

The router pattern gives you unlimited tool capacity while keeping Claude focused and efficient.
