> ## Documentation Index
> Fetch the complete documentation index at: https://docs.deploystack.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Hierarchical Router Implementation

> Technical solution to MCP context window consumption. DeployStack's hierarchical router pattern reduces token usage by 99.5% using just 4 meta-tools.

DeployStack Satellite implements a hierarchical router pattern to solve the MCP context window consumption problem. Instead of exposing hundreds of tools directly to MCP clients, the satellite exposes only 4 meta-tools that enable dynamic tool discovery, execution, and resource access.

<Info>
  **Problem Solved**: Traditional MCP implementations expose all tools directly, consuming 40-80% of the context window before any actual work begins. The hierarchical router reduces this to \<1% while maintaining full functionality.
</Info>

<Note>
  **Alternative Access Method**: For direct MCP client integration without OAuth complexity, see [Instance Router](/development/satellite/instance-router) which provides token-based access to individual instances.
</Note>

For information about how tools are discovered and cached internally, see [Tool Discovery Implementation](/development/satellite/tool-discovery).

## The Context Window Problem

### Traditional MCP Architecture

When MCP clients connect to servers with many tools, the context window gets consumed by tool definitions:

```
Example with 15 MCP servers, each having 10 tools: 150 total tools
Each tool definition consumes approximately 500 tokens: 150 × 500 = 75,000 tokens
Context window total capacity: 200,000 tokens
Available for work: 125,000 tokens (62.5%)
```

**Real-world impact:**

* Claude Code: 82,000 tokens consumed by MCP tools (41% of context)
* Industry consensus: 20-40 tools before performance degrades
* Cursor hard limit: 40 tools maximum
* Critical failures: 80+ tools cause suboptimal responses

### Root Cause

MCP clients call `tools/list` to discover available tools, and servers respond with complete tool definitions including:

* Tool name
* Description (often 100+ characters)
* Input schema (JSON Schema objects)
* Parameter descriptions
* Examples and constraints

This metadata is valuable but consumes massive context space when multiplied across many tools.

## The Hierarchical Router Solution

### Architecture Overview

Instead of exposing all tools directly, the satellite exposes only 4 meta-tools:

```
┌─────────────────────────────────────────────────────────────┐
│  MCP Client (Claude Desktop / VS Code)                      │
│                                                              │
│  Connects to: http://satellite.deploystack.io/mcp          │
│                                                              │
│  Sees: "This server has 4 tools!"                          │
│        1. discover_mcp_tools                                 │
│        2. execute_mcp_tool                                   │
│        3. list_mcp_resources                                 │
│        4. read_mcp_resource                                  │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ MCP Protocol (JSON-RPC)
                              │
┌─────────────────────────────▼───────────────────────────────┐
│  DeployStack Satellite (Hierarchical Router)                │
│                                                              │
│  Exposes: 4 static meta-tools (NEVER change)               │
│                                                              │
│  Behind the scenes:                                          │
│  • Manages 20+ actual MCP servers                           │
│  • Each with 5-10 tools = 100+ real tools                   │
│  • Resources from servers that support them                  │
│  • Search index with Fuse.js                                │
│  • Client never knows about this complexity                  │
└──────────────────────────────────────────────────────────────┘
```

### Token Reduction

**Before (Traditional):**

* 150 tools × 500 tokens = 75,000 tokens
* 37.5% of 200k context consumed

**After (Hierarchical):**

* 4 meta-tools × \~500 tokens = \~2000 tokens consumed
* Result: \~1.0% of context window used
* **Token Reduction: 97.3%**

## Meta-Tool Specifications

### Tool 1: discover\_mcp\_tools

**Purpose:** Search for available tools across all connected MCP servers using natural language queries.

**Definition:**

```typescript theme={null}
{
  name: "discover_mcp_tools",
  description: "Search and discover available MCP tools across all connected servers. Use natural language to find tools (e.g., 'Figma tools', 'PostgreSQL database', 'GitHub repository'). Returns tool paths that can be executed with execute_mcp_tool.",
  inputSchema: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "Natural language search query describing what you want to do"
      },
      limit: {
        type: "number",
        description: "Maximum number of results to return (default: 10)",
        default: 10
      }
    },
    required: ["query"]
  }
}
```

**Example Request:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "tools/call",
  "params": {
    "name": "discover_mcp_tools",
    "arguments": {
      "query": "github create issue",
      "limit": 5
    }
  }
}
```

**Example Response:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "content": [{
      "type": "text",
      "text": {
        "tools": [
          {
            "tool_path": "github:create_issue",
            "description": "Create a new issue in a repository",
            "server_name": "github",
            "transport": "stdio",
            "relevance_score": 0.01
          },
          {
            "tool_path": "github:update_issue",
            "description": "Update an existing issue",
            "server_name": "github",
            "transport": "stdio",
            "relevance_score": 0.15
          }
        ],
        "total_found": 2,
        "search_time_ms": 3,
        "query": "github create issue"
      }
    }]
  }
}
```

**Implementation Details:**

* Uses Fuse.js for fuzzy full-text search
* Searches across tool names, descriptions, and server names
* Returns results ranked by relevance score
* Search time: 2-5ms for 100+ tools
* Queries [UnifiedToolDiscoveryManager](/development/satellite/tool-discovery) for tool cache

### Tool 2: execute\_mcp\_tool

**Purpose:** Execute a discovered tool by its path returned from `discover_mcp_tools`.

**Definition:**

```typescript theme={null}
{
  name: "execute_mcp_tool",
  description: "Execute a discovered MCP tool by its path. Use after discovering tools with discover_mcp_tools. The tool_path format is 'serverName:toolName' (e.g., 'figma:get_file', 'github:create_issue').",
  inputSchema: {
    type: "object",
    properties: {
      tool_path: {
        type: "string",
        description: "Full tool path from discover_mcp_tools (format: serverName:toolName)"
      },
      arguments: {
        type: "object",
        description: "Arguments to pass to the tool (schema varies by tool)"
      }
    },
    required: ["tool_path", "arguments"]
  }
}
```

**Example Request:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "2",
  "method": "tools/call",
  "params": {
    "name": "execute_mcp_tool",
    "arguments": {
      "tool_path": "github:create_issue",
      "arguments": {
        "repo": "deploystackio/deploystack",
        "title": "Test issue",
        "body": "Test body"
      }
    }
  }
}
```

**Example Response:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "2",
  "result": {
    "content": [{
      "type": "text",
      "text": {
        "issue_number": 42,
        "url": "https://github.com/deploystackio/deploystack/issues/42"
      }
    }]
  }
}
```

**Implementation Details:**

* Parses `tool_path` from external format (`server:tool`) to internal format (`server-tool`)
* Looks up tool in unified cache to determine transport type
* Routes to appropriate handler (stdio or HTTP/SSE)
* Returns result in MCP content format

### Tool 3: list\_mcp\_resources

**Purpose:** List all available resources across all connected MCP servers.

**Definition:**

```typescript theme={null}
{
  name: "list_mcp_resources",
  description: "List all available MCP resources across all connected servers. Resources are server-provided data like files, UI components, or configuration that can be read with read_mcp_resource.",
  inputSchema: {
    type: "object",
    properties: {},
    required: []
  }
}
```

**Example Request:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "3",
  "method": "tools/call",
  "params": {
    "name": "list_mcp_resources",
    "arguments": {}
  }
}
```

**Example Response:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "content": [{
      "type": "text",
      "text": {
        "resources": [
          {
            "uri": "excalidraw|ui://excalidraw/mcp-app.html",
            "name": "Excalidraw App",
            "description": "Interactive drawing canvas",
            "mimeType": "text/html",
            "server": "excalidraw",
            "_meta": {
              "ui": { "resourceUri": "excalidraw|ui://excalidraw/mcp-app.html" }
            }
          }
        ],
        "resource_templates": [],
        "total_resources": 1,
        "total_templates": 0
      }
    }]
  }
}
```

**Implementation Details:**

* Returns all resources from connected MCP servers with namespaced URIs
* Resource metadata is cached during tool discovery (no extra round-trip)
* `_meta` fields are preserved for MCP Apps support
* `_meta.ui.resourceUri` is rewritten to use the namespaced URI format
* Per-user filtering: only shows resources from the authenticated user's installations

### Tool 4: read\_mcp\_resource

**Purpose:** Read the content of a specific resource by its namespaced URI.

**Definition:**

```typescript theme={null}
{
  name: "read_mcp_resource",
  description: "Read the content of an MCP resource by its URI. Use list_mcp_resources first to discover available resources and their URIs.",
  inputSchema: {
    type: "object",
    properties: {
      uri: {
        type: "string",
        description: "The resource URI from list_mcp_resources (format: serverSlug|originalUri, e.g., 'excalidraw|ui://excalidraw/mcp-app.html')"
      }
    },
    required: ["uri"]
  }
}
```

**Example Request:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "4",
  "method": "tools/call",
  "params": {
    "name": "read_mcp_resource",
    "arguments": {
      "uri": "excalidraw|ui://excalidraw/mcp-app.html"
    }
  }
}
```

**Example Response:**

```json theme={null}
{
  "jsonrpc": "2.0",
  "id": "4",
  "result": {
    "content": [{
      "type": "text",
      "text": "<!DOCTYPE html><html>..."
    }]
  }
}
```

**Implementation Details:**

* Parses the namespaced URI (`serverSlug|originalUri`) to identify origin server
* Content is **never cached** — always proxied on-demand to the origin MCP server
* Routes to the correct transport (stdio subprocess or HTTP/SSE endpoint)
* Per-user routing: resolves the authenticated user's specific installation
* Supports both text and binary (base64) resource content

## Complete User Flow

### Step 1: Client Connects

```
MCP Client → POST /mcp
{
  "jsonrpc": "2.0",
  "method": "initialize",
  "params": {...}
}

Satellite → Client
{
  "result": {
    "capabilities": {"tools": {}, "resources": {}},
    "serverInfo": {"name": "deploystack-satellite"}
  }
}
```

**Client knows:** "I connected to a server named deploystack-satellite"

### Step 2: Client Discovers Available Tools

```
MCP Client → POST /mcp
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": {}
}

Satellite → Client
{
  "result": {
    "tools": [
      {
        "name": "discover_mcp_tools",
        "description": "Search and discover available MCP tools...",
        "inputSchema": {...}
      },
      {
        "name": "execute_mcp_tool",
        "description": "Execute a discovered MCP tool...",
        "inputSchema": {...}
      },
      {
        "name": "list_mcp_resources",
        "description": "List all available MCP resources...",
        "inputSchema": {...}
      },
      {
        "name": "read_mcp_resource",
        "description": "Read the content of an MCP resource...",
        "inputSchema": {...}
      }
    ]
  }
}
```

**Client knows:** "This server has 4 tools available"

### Step 3: User Asks for GitHub Tools

The AI agent (Claude) decides it needs GitHub tools:

```
MCP Client → POST /mcp
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "discover_mcp_tools",
    "arguments": {
      "query": "github create issue",
      "limit": 5
    }
  }
}

Satellite Internal Process:
1. Receives search query: "github create issue"
2. Searches Fuse.js index (3ms)
3. Finds matches from cached tools
4. Formats results with external tool_path format

Satellite → Client
{
  "result": {
    "content": [{
      "type": "text",
      "text": {
        "tools": [
          {
            "tool_path": "github:create_issue",
            "description": "Create a new issue",
            "relevance_score": 0.01
          }
        ]
      }
    }]
  }
}
```

**Client knows:** "The discovery tool returned some tool paths as strings"

### Step 4: User Executes Discovered Tool

The AI agent decides to use `github:create_issue`:

```
MCP Client → POST /mcp
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "execute_mcp_tool",
    "arguments": {
      "tool_path": "github:create_issue",
      "arguments": {
        "repo": "deploystackio/deploystack",
        "title": "Test issue"
      }
    }
  }
}

Satellite Internal Process:
1. Parses tool_path: "github:create_issue" → "github-create_issue"
2. Looks up in cache: transport=stdio, serverName="github-team123-alice-abc456" (user's instance)
3. Routes to ProcessManager for stdio execution (user's specific process)
4. Sends JSON-RPC to user's GitHub MCP server process
5. Returns result

Satellite → Client
{
  "result": {
    "content": [{
      "type": "text",
      "text": {
        "issue_number": 42,
        "url": "https://github.com/..."
      }
    }]
  }
}
```

**Client knows:** "The execute tool returned a result"

### Step 5: User Lists Resources

The AI agent discovers available resources:

```
MCP Client → POST /mcp
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "list_mcp_resources",
    "arguments": {}
  }
}

Satellite Internal Process:
1. Queries UnifiedResourceDiscoveryManager cache
2. Filters resources by authenticated user's installations
3. Returns namespaced URIs with metadata

Satellite → Client
{
  "result": {
    "content": [{
      "type": "text",
      "text": {
        "resources": [
          {
            "uri": "excalidraw|ui://excalidraw/mcp-app.html",
            "name": "Excalidraw App",
            "server": "excalidraw"
          }
        ],
        "total_resources": 1
      }
    }]
  }
}
```

**Client knows:** "There is 1 resource available from the excalidraw server"

### Step 6: User Reads a Resource

The AI agent reads a specific resource:

```
MCP Client → POST /mcp
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "read_mcp_resource",
    "arguments": {
      "uri": "excalidraw|ui://excalidraw/mcp-app.html"
    }
  }
}

Satellite Internal Process:
1. Parses URI: serverSlug="excalidraw", originalUri="ui://excalidraw/mcp-app.html"
2. Resolves user's specific installation
3. Proxies read request on-demand to origin MCP server
4. Returns content (never cached)

Satellite → Client
{
  "result": {
    "content": [{
      "type": "text",
      "text": "<!DOCTYPE html><html>..."
    }]
  }
}
```

**Client knows:** "The resource content was returned"

## Format Conversion

The satellite converts between user-facing format (`serverName:toolName`) and internal routing format (`serverName-toolName`) transparently during tool discovery and execution.

See [Tool Discovery - Namespacing Strategy](/development/satellite/tool-discovery#namespacing-strategy) for complete details on naming conventions and format conversion logic.

## Search Implementation

### Fuse.js Configuration

```typescript theme={null}
const fuseOptions = {
  threshold: 0.3,        // Precise matches (0.0 = exact, 1.0 = anything)
  keys: [
    { name: 'toolName', weight: 0.4 },          // 40% weight
    { name: 'description', weight: 0.35 },      // 35% weight
    { name: 'serverName', weight: 0.25 }        // 25% weight
  ],
  includeScore: true,
  minMatchCharLength: 2,
  useExtendedSearch: true
};
```

### Search Performance

* **Index Build**: On-demand per search (no stale cache)
* **Search Time**: 2-5ms for 100+ tools
* **Memory**: Zero duplication (queries UnifiedToolDiscoveryManager directly)
* **Accuracy**: Handles typos, partial matches, fuzzy matching

### Single Source of Truth

ToolSearchService queries [UnifiedToolDiscoveryManager.getAllTools()](/development/satellite/tool-discovery) directly for every search:

```typescript theme={null}
search(query: string, limit: number): SearchResult[] {
  // Get fresh tools from single source of truth
  const allTools = this.toolDiscoveryManager.getAllTools();
  
  // Build Fuse.js index on-demand
  const fuse = new Fuse(allTools, fuseOptions);
  
  // Search and return results
  return fuse.search(query).slice(0, limit);
}
```

Benefits:

* Always up-to-date (no cache invalidation needed)
* No memory duplication
* Automatic reflection of tool changes

## Why Tool and Resource Notifications Are Irrelevant

### The Question

> "Should we implement `notifications/tools/list_changed` or `notifications/resources/list_changed` when tools or resources are added or removed?"

### The Answer

**NO.** The MCP client only sees 4 static meta-tools that NEVER change:

* `discover_mcp_tools` ✅ Always available
* `execute_mcp_tool` ✅ Always available
* `list_mcp_resources` ✅ Always available
* `read_mcp_resource` ✅ Always available

**What DOES change behind the scenes:**

* New MCP servers installed → Search index and resource cache update automatically
* MCP servers removed → Search index and resource cache update automatically
* Tools/resources discovered from new servers → Caches update automatically

**But the client never needs to know!**

When a new MCP server is installed:

1. ✅ Backend sends configuration update to satellite
2. ✅ Satellite spawns new MCP server process (if stdio)
3. ✅ Tool and resource discovery runs automatically
4. ✅ Search index and resource cache refreshed
5. ❌ **Client is NOT notified** (doesn't need to be)

Next time the client calls `discover_mcp_tools` or `list_mcp_resources`, the new tools and resources are automatically included. **No notification needed.**

## Performance Characteristics

### Token Consumption

| Metric            | Traditional | Hierarchical | Reduction |
| ----------------- | ----------- | ------------ | --------- |
| Tools Exposed     | 150         | 4            | 97.3%     |
| Tokens Consumed   | 75,000      | \~2000       | 97.3%     |
| Context Available | 62.5%       | 99.0%        | +36.5%    |

### Search Performance

* **Discovery Latency**: 2-5ms (including Fuse.js index build)
* **Execution Latency**: \<1ms (routing overhead only)
* **Memory Overhead**: \~1KB per cached tool
* **Scalability**: No degradation up to 500+ tools

### Real-World Impact

**Claude Code Example:**

* Before: 82,000 tokens (41%) consumed by MCP tools
* After: \~2000 tokens (\~1.0%) consumed by 4 meta-tools
* Result: **\~80,000 tokens freed for actual work**

## Implementation Status

<Info>
  **Status**: ✅ Fully Operational

  All 4 meta-tools are implemented and production-ready:

  * `discover_mcp_tools`: Full-text search with Fuse.js
  * `execute_mcp_tool`: Complete routing for stdio and HTTP/SSE transports
  * `list_mcp_resources`: Resource listing with per-user filtering and `_meta` preservation
  * `read_mcp_resource`: On-demand resource proxying to origin servers
</Info>

### Code Locations

**Core Implementation:**

* `/services/satellite/src/core/mcp-server-wrapper.ts` - Meta-tool handlers
* `/services/satellite/src/services/tool-search-service.ts` - Search implementation
* `/services/satellite/src/services/unified-tool-discovery-manager.ts` - Tool cache
* `/services/satellite/src/services/unified-resource-discovery-manager.ts` - Resource cache
* `/services/satellite/src/lib/mcp-resource-executor.ts` - Resource read proxying

**Key Methods:**

* `setupMcpServer()` - Registers 4 meta-tools instead of all tools
* `handleDiscoverTools()` - Implements tool discovery logic
* `handleExecuteTool()` - Implements tool execution routing
* `handleListResources()` - Implements resource listing with per-user filtering
* `handleReadResource()` - Implements on-demand resource proxying

## Resource Support & \_meta Preservation

### Resource Discovery

Resources are discovered alongside tools during the same connection — no extra round-trip per server. When the satellite connects to an MCP server (stdio or HTTP/SSE), it calls both `tools/list` and `resources/list` in sequence. Resource metadata (URI, name, description, mimeType) is cached, but resource **content is never cached** — it's always proxied on-demand via `McpResourceExecutor`.

### URI Namespacing

Resources use a **pipe separator** (`|`) for URI namespacing, different from the colon (`:`) used for tools:

```
Tool format:     serverSlug:toolName        (e.g., "github:create_issue")
Resource format: serverSlug|originalUri     (e.g., "excalidraw|ui://excalidraw/mcp-app.html")
```

The pipe separator was chosen because resource URIs frequently contain colons (e.g., `ui://`, `file://`, `https://`), which would conflict with the tool namespacing format.

### \_meta Preservation

The `_meta` field is preserved through the entire cache and response chain. This is important for **MCP Apps support**, where `_meta` carries UI rendering hints (e.g., `_meta.ui.resourceUri` tells the client which resource to load for an app-like experience).

**Preservation chain:**

1. Origin MCP server returns `_meta` on tools and resources
2. Discovery managers preserve `_meta` when caching (`...(item._meta ? { _meta: item._meta } : {})`)
3. `discover_mcp_tools` includes `_meta` in tool search results
4. `list_mcp_resources` includes `_meta` in resource listings
5. `_meta.ui.resourceUri` is rewritten to use the namespaced URI format so clients can read the resource through the hierarchical router

### Resource vs Tool Flow

| Aspect              | Tools                    | Resources                        |
| ------------------- | ------------------------ | -------------------------------- |
| Discovery meta-tool | `discover_mcp_tools`     | `list_mcp_resources`             |
| Execution meta-tool | `execute_mcp_tool`       | `read_mcp_resource`              |
| URI separator       | `:` (colon)              | `\|` (pipe)                      |
| Content caching     | N/A (executed on-demand) | Never cached (proxied on-demand) |
| Metadata caching    | Yes (tool definitions)   | Yes (URI, name, mimeType)        |
| `_meta` support     | Yes                      | Yes                              |

## Benefits Summary

**For Users:**

* 95%+ more context available for actual work
* Natural language tool discovery
* No performance degradation with many tools
* Transparent - works like any MCP server

**For Developers:**

* Simple 4-tool interface
* Automatic tool discovery
* No client modifications needed
* Standard MCP protocol compliance

**For Operations:**

* Scales to hundreds of tools
* No memory explosion
* Fast search performance
* Easy to monitor and debug

## Status-Based Tool Filtering

The hierarchical router integrates with status tracking to hide tools from the user's unavailable instances and provide clear error messages when unavailable tools are executed.

<Info>
  **Per-User Filtering:** Each user sees only tools from their OWN instances that are online. Other team members' instance status doesn't affect your tool availability.
</Info>

See [Status Tracking - Tool Filtering](/development/satellite/status-tracking#tool-filtering-by-status) for complete filtering logic, execution blocking rules, and status values.

## Related Documentation

* [Tool Discovery Implementation](/development/satellite/tool-discovery) - Internal tool caching and discovery
* [Status Tracking](/development/satellite/status-tracking) - Tool filtering by per-user instance status
* [Instance Lifecycle](/development/satellite/instance-lifecycle) - Per-user instance management
* [Recovery System](/development/satellite/recovery-system) - How offline servers auto-recover
* [MCP Transport Protocols](/development/satellite/mcp-transport) - How clients connect
* [Process Management](/development/satellite/process-management) - stdio server lifecycle
* [Architecture Overview](/development/satellite/architecture) - Complete satellite design
