Skip to main content
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.
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.
Alternative Access Method: For direct MCP client integration without OAuth complexity, see Instance Router which provides token-based access to individual instances.
For information about how tools are discovered and cached internally, see Tool Discovery Implementation.

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:
{
  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:
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "tools/call",
  "params": {
    "name": "discover_mcp_tools",
    "arguments": {
      "query": "github create issue",
      "limit": 5
    }
  }
}
Example Response:
{
  "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 for tool cache

Tool 2: execute_mcp_tool

Purpose: Execute a discovered tool by its path returned from discover_mcp_tools. Definition:
{
  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:
{
  "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:
{
  "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:
{
  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:
{
  "jsonrpc": "2.0",
  "id": "3",
  "method": "tools/call",
  "params": {
    "name": "list_mcp_resources",
    "arguments": {}
  }
}
Example Response:
{
  "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:
{
  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:
{
  "jsonrpc": "2.0",
  "id": "4",
  "method": "tools/call",
  "params": {
    "name": "read_mcp_resource",
    "arguments": {
      "uri": "excalidraw|ui://excalidraw/mcp-app.html"
    }
  }
}
Example Response:
{
  "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 for complete details on naming conventions and format conversion logic.

Search Implementation

Fuse.js Configuration

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() directly for every search:
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

MetricTraditionalHierarchicalReduction
Tools Exposed150497.3%
Tokens Consumed75,000~200097.3%
Context Available62.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

Status: ✅ Fully OperationalAll 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

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

AspectToolsResources
Discovery meta-tooldiscover_mcp_toolslist_mcp_resources
Execution meta-toolexecute_mcp_toolread_mcp_resource
URI separator: (colon)| (pipe)
Content cachingN/A (executed on-demand)Never cached (proxied on-demand)
Metadata cachingYes (tool definitions)Yes (URI, name, mimeType)
_meta supportYesYes

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.
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.
See Status Tracking - Tool Filtering for complete filtering logic, execution blocking rules, and status values.