Skip to main content
DeployStack Satellite provides a second MCP access method alongside the hierarchical router: path-based instance routing. This enables standard MCP clients to connect directly to individual instances using simple token authentication, without OAuth2 setup or meta-tool discovery.
Use Case: Standard MCP clients that need direct access to a specific instance’s tools without the complexity of OAuth2 or the two-step discovery pattern of the hierarchical router.
For AI agents and applications requiring access to multiple instances, see Hierarchical Router which uses OAuth2 and provides 2 meta-tools for dynamic tool discovery.

The Problem It Solves

OAuth2 Complexity for Direct Integration

Standard MCP clients (libraries, scripts, custom applications) face challenges with OAuth2: Traditional OAuth2 Requirements:
  • Browser-based authorization flow
  • Token refresh management
  • Client ID/secret configuration
  • Redirect URL handling
  • State management
Impact on Simple Clients:
// ❌ Complex: OAuth2 flow for simple script
const oauth = new OAuth2Client(clientId, clientSecret, redirectUrl);
const authUrl = oauth.generateAuthUrl();
// User must open browser, authorize, get code...
const tokens = await oauth.getTokens(authorizationCode);
// Refresh token management, expiry handling...

The Instance Router Solution

Path-based routing with token authentication provides direct access:
// ✅ Simple: Direct connection with token
const client = new Client({ name: "my-script", version: "1.0.0" }, {});

await client.connect(new StreamableHTTPClientTransport({
  url: "https://satellite.example.com/i/bold-penguin-42a3/mcp?token=ds_inst_abc123..."
}));

// Done! Start using tools immediately
Benefits:
  • No OAuth2 flow required
  • Single token in URL
  • Works in scripts, CLIs, automation
  • Standard MCP client compatibility
  • No browser required

Architecture Overview

Two Parallel Routers

The satellite operates two independent MCP routers simultaneously:
┌─────────────────────────────────────────────────────────────┐
│                    Satellite Server                          │
│                                                              │
│  ┌──────────────────────────────────────────────┐          │
│  │      Hierarchical Router (/mcp)               │          │
│  │  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │          │
│  │  Auth: OAuth2 Bearer token                    │          │
│  │  Scope: All user's instances                  │          │
│  │  Tools: 2 meta-tools (discover + execute)     │          │
│  │  Use: AI agents (Claude, Cursor)              │          │
│  └────────────────┬─────────────────────────────┘          │
│                   │                                          │
│                   │ shares                                   │
│                   ▼                                          │
│  ┌──────────────────────────────────────────────┐          │
│  │       McpToolExecutor (SHARED)                │          │
│  │  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │          │
│  │  • stdio tool execution                       │          │
│  │  • HTTP/SSE tool execution                    │          │
│  │  • OAuth token injection                      │          │
│  │  • Request logging & batching                 │          │
│  │  • Error handling & recovery                  │          │
│  └────────────────▲─────────────────────────────┘          │
│                   │                                          │
│                   │ shares                                   │
│                   │                                          │
│  ┌────────────────┴─────────────────────────────┐          │
│  │      Instance Router (/i/:path/mcp)           │          │
│  │  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  │          │
│  │  Auth: Query param ?token=ds_inst_...         │          │
│  │  Scope: Single specific instance              │          │
│  │  Tools: ALL tools from that instance          │          │
│  │  Use: Direct MCP clients                      │          │
│  └──────────────────────────────────────────────┘          │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Key Design Principles

Shared Execution, Separate Sessions:
  • Both routers share the same McpToolExecutor for consistent tool execution
  • OAuth token injection, retry logic, and recovery are shared
  • Each router maintains its own session manager to prevent collision
  • Independent authentication mechanisms (OAuth2 vs token)
Single Responsibility:
  • Hierarchical Router: Multi-instance access for AI agents
  • Instance Router: Single-instance access for direct clients

Route Endpoints

The instance router exposes three standard MCP endpoints:

POST /i/:instancePath/mcp

Purpose: Client-to-server MCP messages (initialize, tools/list, tools/call) URL Format:
POST https://satellite.example.com/i/:instancePath/mcp?token=<instance_token>
Parameters:
  • :instancePath - URL path parameter (e.g., bold-penguin-42a3)
  • token - Query parameter (e.g., ds_inst_abc123...)
Headers:
  • Content-Type: application/json
  • mcp-session-id: <session-id> (optional, for session reuse)
Body: JSON-RPC 2.0 request Examples:
  • Initialize: {"method": "initialize", ...}
  • List tools: {"method": "tools/list", ...}
  • Call tool: {"method": "tools/call", "params": {"name": "create_issue", "arguments": {...}}}

GET /i/:instancePath/mcp

Purpose: Server-to-client notifications via Server-Sent Events (SSE) URL Format:
GET https://satellite.example.com/i/:instancePath/mcp?token=<instance_token>
Headers:
  • mcp-session-id: <session-id> (required)
Response: SSE stream with MCP notifications

DELETE /i/:instancePath/mcp

Purpose: Session termination URL Format:
DELETE https://satellite.example.com/i/:instancePath/mcp?token=<instance_token>
Headers:
  • mcp-session-id: <session-id> (required)
Response: Session closed

Authentication Flow

Token Format

Instance tokens follow a specific format for easy identification:
ds_inst_<64 hexadecimal characters>
Example:
ds_inst_a1b2c3d4e5f6789012345678901234567890abcdef1234567890abcdef123456
Components:
  • ds_inst_ - Prefix for token type identification (12 characters)
  • <64 hex> - Cryptographically random token (64 characters)
  • Total Length: 71 characters

SHA-256 Hash Validation

Tokens are validated using SHA-256 hash comparison: Storage:
  • Backend generates token during instance creation
  • SHA-256 hash stored in database (instance_token_hash column)
  • Plain token shown to user ONCE (copy before closing)
  • Hash included in satellite configuration
Validation Process:
// 1. Extract token from query parameter
const token = request.query.token; // "ds_inst_abc123..."

// 2. Validate format
if (!token || !token.startsWith('ds_inst_')) {
  return 401; // Invalid token format
}

// 3. Hash incoming token
const hash = crypto.createHash('sha256').update(token).digest('hex');

// 4. Compare with stored hash
if (hash !== config.instance_token_hash) {
  return 401; // Invalid token
}

// 5. Store auth context
request.instanceAuth = { processId, serverConfig, instancePath };

Authentication Error Responses

404 Not Found:
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32000,
    "message": "Instance not found: instance-path-xyz"
  },
  "id": null
}
401 Unauthorized (Missing Token):
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32000,
    "message": "Missing or invalid token format"
  },
  "id": null
}
401 Unauthorized (Invalid Token):
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32000,
    "message": "Invalid token for instance: instance-path-xyz"
  },
  "id": null
}
500 Internal Server Error:
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "Instance missing token hash in configuration"
  },
  "id": null
}

Session Management

Session Lifecycle

The instance router supports three session modes:

1. Create New Session (Initialize)

Trigger: Client sends initialize request without existing session Process:
  1. Validate token
  2. Check if stdio process is active (respawn if dormant)
  3. Create new MCP session
  4. Generate unique session ID
  5. Set up MCP server with instance tools
  6. Return session ID in response
Client Receives:
  • Session ID in response
  • Should include in subsequent requests as mcp-session-id header

2. Reuse Existing Session

Trigger: Client sends request with valid mcp-session-id header Process:
  1. Validate token
  2. Look up session by ID
  3. Verify session exists and is active
  4. Process request using existing session
Benefits:
  • No session recreation overhead
  • Maintains state between requests
  • Faster request processing

3. Resurrect Stale Session

Trigger: Client sends request with mcp-session-id for non-existent session Process:
  1. Validate token
  2. Session not found in memory (possibly satellite restarted)
  3. Respawn stdio process if needed
  4. Create new session with same ID
  5. Send synthetic initialize request to MCP server
  6. Process client’s original request
Why This Matters:
  • Handles satellite restarts gracefully
  • Client doesn’t need to reinitialize manually
  • Maintains user experience

Session Storage

Sessions are stored in a separate McpSessionManager instance:
// Separate from hierarchical router
const instanceSessionManager = new McpSessionManager(logger);

// Session map structure
Map<sessionId, {
  transport: StreamableHTTPServerTransport,
  instancePath: string,
  processId: string,
  createdAt: Date
}>
Key Points:
  • Sessions isolated from hierarchical router
  • No collision risk between routers
  • Independent cleanup lifecycle
  • Session ID format: UUID v4

Process Respawning (stdio Only)

For stdio-based MCP servers, the instance router ensures processes are active: When Respawning Happens:
  • Initialize request AND process is dormant/crashed
  • Stale session resurrection AND stdio transport
Process:
async ensureProcessActive(processId: string): Promise<void> {
  const instance = this.processManager.getInstance(processId);

  if (!instance) return; // Process not found
  if (instance.status === 'online') return; // Already active
  if (instance.transport !== 'stdio') return; // HTTP/SSE never dormant

  // Respawn process
  await this.processManager.startProcess(processId);

  // Wait for startup (non-blocking)
  await this.processManager.waitForReady(processId, { timeout: 5000 });
}
Non-Fatal:
  • Respawn failures log warning and continue
  • Client request proceeds anyway
  • Tool execution will fail if process actually down
  • Recovery system handles permanent failures

Tool Discovery & Execution

Tool List Response

Unlike the hierarchical router’s 2 meta-tools, the instance router returns ALL actual tools from the specific instance: Hierarchical Router (2 meta-tools):
{
  "tools": [
    {"name": "discover_mcp_tools", "description": "...", "inputSchema": {...}},
    {"name": "execute_mcp_tool", "description": "...", "inputSchema": {...}}
  ]
}
Instance Router (actual tools):
{
  "tools": [
    {"name": "create_issue", "description": "Create a GitHub issue", "inputSchema": {...}},
    {"name": "get_file", "description": "Read file contents", "inputSchema": {...}},
    {"name": "list_repos", "description": "List repositories", "inputSchema": {...}},
    {"name": "create_branch", "description": "Create a new branch", "inputSchema": {...}}
  ]
}
Key Differences:
  • Tool names are original/non-namespaced (create_issue not github:create_issue)
  • Full tool definitions included (name, description, inputSchema)
  • Filtered to specific instance only (other instances’ tools hidden)
  • No search required (direct list)

Tool Name Conversion

Internally, the instance router converts tool names for execution: Client Perspective (External Format):
// Client calls tool with original name
await client.callTool({
  name: "create_issue",  // Non-namespaced
  arguments: { title: "Bug", body: "Fix" }
});
Satellite Internal (Routing Format):
// Router converts to namespaced format
const toolName = request.params.name; // "create_issue"
const namespacedTool = `${processId}:${toolName}`; // "proc_123:create_issue"

// Execute via shared executor
await toolExecutor.executeToolCall(namespacedTool, args, processId);
Why Conversion is Needed:
  • McpToolExecutor expects namespaced format for routing
  • Maintains consistency with hierarchical router’s internal format
  • Enables process-specific targeting
  • Transparent to client (automatic conversion)

Shared Tool Executor

Both routers use the same McpToolExecutor instance: Shared Functionality:
  • stdio tool execution (JSON-RPC to subprocess)
  • HTTP/SSE tool execution (HTTP requests to remote servers)
  • OAuth token injection (for servers requiring authentication)
  • Retry logic and error recovery
  • Request logging and batching
  • Status tracking integration
Benefits:
  • No code duplication
  • Consistent behavior across routers
  • Single source of truth for execution logic
  • Shared request log buffer (unified analytics)

Complete Request Flow

Step 1: Initialize Session

Request:
curl -X POST "https://satellite.example.com/i/bold-penguin-42a3/mcp?token=ds_inst_abc123..." \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "id": 0,
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {
        "name": "my-client",
        "version": "1.0.0"
      }
    }
  }'
Processing:
  1. Token validated (SHA-256 hash comparison)
  2. Instance found by path: bold-penguin-42a3
  3. stdio process respawned if dormant
  4. New session created with UUID
  5. MCP server set up with instance tools
  6. Initialize forwarded to underlying MCP server
Response:
{
  "jsonrpc": "2.0",
  "result": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "tools": {}
    },
    "serverInfo": {
      "name": "github",
      "version": "1.0.0"
    }
  },
  "id": 0
}
Client Action:
  • Extract session ID from response headers
  • Include in all subsequent requests

Step 2: List Tools

Request:
curl -X POST "https://satellite.example.com/i/bold-penguin-42a3/mcp?token=ds_inst_abc123..." \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": 1
  }'
Processing:
  1. Token validated
  2. Session reused (ID found in header)
  3. Filter cached tools by processId
  4. Return actual tool definitions (not meta-tools)
Response:
{
  "jsonrpc": "2.0",
  "result": {
    "tools": [
      {
        "name": "create_issue",
        "description": "Create a new issue in a repository",
        "inputSchema": {
          "type": "object",
          "properties": {
            "repo": {"type": "string"},
            "title": {"type": "string"},
            "body": {"type": "string"}
          },
          "required": ["repo", "title"]
        }
      },
      {
        "name": "get_file",
        "description": "Read file contents from repository",
        "inputSchema": {
          "type": "object",
          "properties": {
            "repo": {"type": "string"},
            "path": {"type": "string"}
          },
          "required": ["repo", "path"]
        }
      }
    ]
  },
  "id": 1
}

Step 3: Call Tool

Request:
curl -X POST "https://satellite.example.com/i/bold-penguin-42a3/mcp?token=ds_inst_abc123..." \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: 550e8400-e29b-41d4-a716-446655440000" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "id": 2,
    "params": {
      "name": "create_issue",
      "arguments": {
        "repo": "deploystackio/deploystack",
        "title": "Test issue",
        "body": "This is a test"
      }
    }
  }'
Processing:
  1. Token validated
  2. Session reused
  3. Tool name converted: create_issueproc_123:create_issue
  4. Routed to shared McpToolExecutor
  5. Tool executed via stdio subprocess
  6. Result returned
Response:
{
  "jsonrpc": "2.0",
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"issue_number\": 42, \"url\": \"https://github.com/deploystackio/deploystack/issues/42\"}"
      }
    ]
  },
  "id": 2
}

Comparison: Hierarchical vs Instance Router

AspectHierarchical (/mcp)Instance (/i/:path/mcp)
AuthenticationOAuth2 Bearer tokenURL query param ?token=
Token ValidationBackend API introspectionSHA-256 hash (local, fast)
Authorization ScopeAll user’s instances across teamSingle specific instance only
Tools Exposed2 meta-tools (discover + execute)ALL actual tools from instance
Tool NamesNamespaced (server:tool)Original (tool)
Tool DiscoveryFuse.js fuzzy searchDirect list (no search)
Discovery StepRequired (two-step pattern)Not required (tools in list)
Session ManagerOwn instanceOwn instance (separate)
Tool ExecutorSharedShared
Primary Use CaseAI agents (Claude, Cursor, VS Code)Direct clients (scripts, apps, CLIs)
Setup ComplexityOAuth2 flow requiredSingle token in URL
Browser RequirementYes (for OAuth authorization)No
Multi-Instance AccessYes (all user’s instances)No (one instance per connection)
Best ForInteractive AI assistanceAutomation, scripts, integrations

Client Integration Examples

TypeScript/Node.js

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamablehttp.js";

// Create MCP client
const client = new Client(
  {
    name: "my-automation-script",
    version: "1.0.0"
  },
  {
    capabilities: {}
  }
);

// Connect to specific instance via path-based router
await client.connect(
  new StreamableHTTPClientTransport({
    url: "https://satellite.example.com/i/bold-penguin-42a3/mcp?token=ds_inst_abc123def456..."
  })
);

// List available tools (returns actual tools, not meta-tools)
const { tools } = await client.listTools();
console.log(`Instance has ${tools.length} tools`);

tools.forEach(tool => {
  console.log(`- ${tool.name}: ${tool.description}`);
});

// Call a tool directly (no discovery step needed)
const result = await client.callTool({
  name: "create_issue",
  arguments: {
    repo: "deploystackio/deploystack",
    title: "Automated issue from script",
    body: "This issue was created by an automation script."
  }
});

console.log("Issue created:", result);

// Close connection when done
await client.close();

Python

from mcp import Client, StreamableHTTPClientTransport
import asyncio

async def main():
    # Create client
    client = Client(
        {
            "name": "python-automation",
            "version": "1.0.0"
        },
        {
            "capabilities": {}
        }
    )

    # Connect to instance
    transport = StreamableHTTPClientTransport(
        url="https://satellite.example.com/i/bold-penguin-42a3/mcp?token=ds_inst_abc123..."
    )

    await client.connect(transport)

    # List tools
    tools_response = await client.list_tools()
    print(f"Instance has {len(tools_response['tools'])} tools")

    # Call tool
    result = await client.call_tool(
        "create_issue",
        {
            "repo": "deploystackio/deploystack",
            "title": "Issue from Python",
            "body": "Created via Python script"
        }
    )

    print("Result:", result)

    # Cleanup
    await client.close()

asyncio.run(main())

Raw HTTP (curl)

#!/bin/bash

SATELLITE_URL="https://satellite.example.com"
INSTANCE_PATH="bold-penguin-42a3"
TOKEN="ds_inst_abc123def456..."
BASE_URL="${SATELLITE_URL}/i/${INSTANCE_PATH}/mcp?token=${TOKEN}"

# Initialize session
INIT_RESPONSE=$(curl -s -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "id": 0,
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {"name": "bash-script", "version": "1.0.0"}
    }
  }')

# Extract session ID from response headers (implementation-specific)
SESSION_ID="<extract-from-headers>"

# List tools
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: $SESSION_ID" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/list",
    "id": 1
  }'

# Call tool
curl -X POST "$BASE_URL" \
  -H "Content-Type: application/json" \
  -H "mcp-session-id: $SESSION_ID" \
  -d '{
    "jsonrpc": "2.0",
    "method": "tools/call",
    "id": 2,
    "params": {
      "name": "create_issue",
      "arguments": {
        "repo": "deploystackio/deploystack",
        "title": "Issue from bash script",
        "body": "Automation test"
      }
    }
  }'

Performance Characteristics

Token Validation Latency

SHA-256 Hash Comparison:
  • Latency: < 1ms (local computation)
  • No Network Calls: Unlike OAuth2 introspection (50-200ms)
  • CPU Overhead: Negligible (SHA-256 is fast)
Comparison:
MethodLatencyNetworkCaching
SHA-256 Hash< 1msNoN/A
OAuth2 Introspection50-200msYesPossible

Session Overhead

New Session Creation:
  • Process respawn (stdio only): 500-2000ms
  • Session setup: 5-10ms
  • MCP initialize: 10-50ms
  • Total: 15-60ms (HTTP/SSE), 515-2050ms (stdio with cold start)
Session Reuse:
  • Session lookup: < 1ms
  • No initialization overhead
  • Total: < 1ms additional latency
Stale Session Resurrection:
  • Similar to new session creation
  • Synthetic initialize: +5ms
  • Total: Same as new session

Shared Executor Benefits

Memory:
  • Single executor instance serves both routers
  • No duplication of execution logic
  • Shared request log buffer (batched emission)
Consistency:
  • Same OAuth injection logic
  • Same retry behavior
  • Same error recovery
  • Same logging format
Latency:
  • Routing overhead: < 1ms
  • Tool execution time: depends on tool (stdio: 10-500ms, HTTP: 50-2000ms)

Implementation Details

Code Locations

Main Implementation:
services/satellite/src/core/instance-router.ts (404 lines)
Shared Modules:
services/satellite/src/lib/mcp-tool-executor.ts
services/satellite/src/lib/mcp-session-manager.ts
Integration:
services/satellite/src/server.ts (lines 1262-1282, 1325-1336)

Key Classes and Methods

InstanceRouter Class:
class InstanceRouter {
  constructor({
    logger: FastifyBaseLogger,
    toolExecutor: McpToolExecutor,
    sessionManager: McpSessionManager,
    configManager: DynamicConfigManager,
    toolDiscoveryManager: UnifiedToolDiscoveryManager,
    processManager: ProcessManager
  })

  // Route registration
  setupRoutes(fastify: FastifyInstance): void

  // Authentication
  private authenticateInstance(request, reply): Promise<void>

  // Instance lookup
  private findInstanceByPath(instancePath: string): { processId, config } | null

  // MCP server setup
  private setupInstanceMcpServer(processId: string): McpServer

  // Process management
  private async ensureProcessActive(processId: string): Promise<void>
}
Key Methods:
  1. authenticateInstance() - Token validation middleware
    • Extracts token from query param
    • Validates format (ds_inst_ prefix)
    • Computes SHA-256 hash
    • Compares with stored hash
    • Stores auth context in request
  2. findInstanceByPath() - Instance lookup
    • Searches all enabled configs
    • Matches by instance_path field
    • Returns { processId, config } or null
  3. setupInstanceMcpServer() - MCP server registration
    • Registers tools/list handler (returns actual tools)
    • Registers tools/call handler (converts names, executes)
    • Filters tools by process ID
    • Returns MCP Server instance
  4. ensureProcessActive() - Process respawning
    • Checks process status
    • Respawns if dormant (stdio only)
    • Waits for ready state
    • Non-fatal on failure

Dependencies

Direct Dependencies:
  • @modelcontextprotocol/sdk - MCP protocol implementation
  • fastify - HTTP server framework
  • crypto - SHA-256 hash computation
Internal Dependencies:
  • DynamicConfigManager - Instance configuration lookup
  • UnifiedToolDiscoveryManager - Tool cache access
  • ProcessManager - stdio process lifecycle
  • McpToolExecutor - Shared tool execution
  • McpSessionManager - Session lifecycle

Security Considerations

Token Security

Storage:
  • ✅ Plain token NEVER stored in database
  • ✅ SHA-256 hash stored instead
  • ✅ Token shown to user once (must copy)
  • ❌ No token recovery if lost
Transmission:
  • ⚠️ Token in URL query parameter (visible in logs)
  • ✅ HTTPS required in production (encrypts URL)
  • ✅ No token in request body (prevents accidental logging)
Best Practices:
  • Use HTTPS in production (required)
  • Treat tokens like passwords (don’t share)
  • Rotate tokens if compromised
  • Monitor for unauthorized access attempts

Instance Isolation

Process-Level:
  • Each instance runs in separate subprocess (stdio)
  • No cross-instance tool access
  • Token scoped to specific instance
Session-Level:
  • Sessions isolated per instance
  • No cross-session data leakage
  • Independent session managers prevent collision
Configuration-Level:
  • Instance path uniqueness enforced in database
  • Token hash uniqueness enforced in database
  • No instance path collisions possible

When to Use Each Router

Use Hierarchical Router (/mcp) When:

✅ Building AI agent integrations (Claude Desktop, Cursor, VS Code) ✅ Users need access to multiple instances ✅ OAuth2 authentication is acceptable ✅ Two-step discovery pattern is okay ✅ User identity matters (per-user tool filtering)

Use Instance Router (/i/:path/mcp) When:

✅ Building automation scripts or CLIs ✅ Direct integration with standard MCP clients ✅ Single instance access is sufficient ✅ OAuth2 is too complex for use case ✅ Token-based auth is preferred ✅ Browser-less operation required ✅ Tool list should show all tools directly

Example Decision Tree

Need MCP integration?
├─ Yes → Do you need OAuth2 user context?
│        ├─ Yes → Use Hierarchical Router
│        └─ No → Do you need multiple instances?
│                 ├─ Yes → Use Hierarchical Router
│                 └─ No → Use Instance Router
└─ No → (not relevant)