Skip to main content

The Problem

When an MCP client connects, it calls tools/list to discover available tools. The response includes tool names, descriptions, and full JSON schemas. With 31 legacy action tools registered separately, that overhead adds up fast.
Without gateway:
  tools/list → 33 tools
  = 31 action tools + sdl.action.search + sdl.info
  ≈ 4,250 tokens consumed at conversation start

With gateway:
  tools/list → 6 tools
  = 4 gateway tools + sdl.action.search + sdl.info
  ≈ 725 tokens consumed at conversation start
This matters because:
  • Agents process tools/list at the start of every conversation
  • Tokens spent on tool schemas are tokens not available for code context
  • Large tool registrations cause some MCP clients to truncate or error
  • Fewer tools means fewer selection decisions for the agent (faster and more accurate)

Before and After

Flat Mode (31 tools)

sdl.repo.register, sdl.repo.status, sdl.repo.overview, sdl.index.refresh, sdl.buffer.push, sdl.buffer.checkpoint, sdl.buffer.status, sdl.code.needWindow, sdl.code.getSkeleton, sdl.code.getHotPath, sdl.agent.orchestrate, sdl.agent.feedback, sdl.runtime.execute, sdl.runtime.queryOutput, sdl.symbol.search, sdl.symbol.getCard, sdl.symbol.getCards, sdl.slice.build, sdl.slice.refresh, sdl.slice.spillover.get, sdl.delta.get, sdl.policy.get, sdl.policy.set, sdl.pr.risk.analyze, sdl.context.summary, sdl.agent.feedback.query, sdl.memory.store, sdl.memory.query, sdl.memory.remove, sdl.memory.surface, sdl.usage.stats~4,250 tokens at conversation start

Gateway Mode (4 tools)

sdl.query (9 actions), sdl.code (3 actions), sdl.repo (7 actions), sdl.agent (12 actions)Plus always-available: sdl.action.search, sdl.info~725 tokens at conversation start
MetricValue
Token reduction~83% (4,250 → 725)
Tokens saved per conversation~3,525
Character reduction~17,000 → ~2,900
Tool count reduction31 → 4

The 4 Gateway Tools

Gateway ToolActionsDomain
sdl.query9Read-only intelligence: symbol search/cards, slices, deltas, summaries, PR risk
sdl.code3Gated code access: needWindow, skeleton, hotPath
sdl.repo7Repository lifecycle: register, status, overview, index, policy, usage stats
sdl.agent12Agentic ops: orchestrate, feedback, buffers, runtime, runtime.queryOutput, memory
Outside the gateway, SDL-MCP always keeps:
  • sdl.action.search for action discovery
  • sdl.info for runtime and environment diagnostics

The Action Discriminator Pattern

Instead of calling a specific flat tool, you call the appropriate gateway tool with an action field that routes to the correct handler:
{
  "tool": "sdl.symbol.search",
  "args": {
    "repoId": "x",
    "query": "auth"
  }
}
The repoId field is hoisted to the envelope level (shared across all actions in a namespace), and the action field selects which handler processes the call.

More Gateway Examples

{
  "tool": "sdl.query",
  "args": {
    "repoId": "x",
    "action": "symbol.getCard",
    "symbolRef": { "name": "handleRequest", "file": "src/server.ts" }
  }
}
Gateway requests also accept common field aliases. For example, repo_id, repo, root_path, task_text, edited_files, symbol_id, and slice_handle all normalize to their canonical names before strict validation.

Double Zod Validation

Safety is preserved through two validation passes:
Agent Call


┌─────────────────────┐
│ Gateway Schema       │  Discriminated union on `action`
│ (cheap first-pass)   │  Catches wrong action names, type errors
└──────────┬──────────┘


┌─────────────────────┐
│ Router               │  Extracts action, merges repoId
│                      │  Looks up handler from ActionMap
└──────────┬──────────┘


┌─────────────────────┐
│ Original Zod Schema  │  Strict second-pass validation
│ (per-handler)        │  Identical to flat-mode validation
└──────────┬──────────┘


┌─────────────────────┐
│ Handler Function     │  Same handler as flat mode
│                      │  Zero behavioral difference
└─────────────────────┘
The thin wire schema catches obvious errors (wrong action name, wrong field type) cheaply. The full per-handler Zod schema then applies the same strict validation as flat mode. Handlers are identical — gateway mode changes only the registration surface, not behavior.

Token Savings Breakdown

The savings come from three techniques:
  1. Fewer tools — 4 vs 31 registration entries
  2. Compact action-aware schemas — gateway oneOf variants preserve useful field metadata without publishing every full flat schema separately
  3. $defs deduplication — repeated sub-schemas are hoisted to $defs with $ref pointers, eliminating repetition
Run the included measurement script to verify savings for your configuration:
node --experimental-strip-types scripts/measure-gateway-schema-tokens.ts
Expected output:
=== SDL-MCP Gateway Schema Token Measurement ===

Flat mode:    31 tools, ~4350 tokens (~17400 chars)
Gateway mode: 4 tools, ~725 tokens (~2900 chars)
Hybrid mode:  33 tools

Gateway is ~17% of flat mode
Estimated savings: ~3525 tokens per tools/list call

✅ Gateway schema is within target (≤40% of flat)

Configuration

{
  "gateway": {
    // Enable gateway mode (default: true)
    "enabled": true,
    // Also emit the 31 flat tool names for backward compat (default: true)
    "emitLegacyTools": true
  }
}

Registration Modes

enabledemitLegacyToolsTools RegisteredUse Case
truetrue37 (4 gateway + 31 action + 2 universal)Migration period — agents can use either style
truefalse6 (4 gateway + 2 universal)Maximum registration savings
false33 (31 flat + 2 universal)Backward compatibility, legacy agents
When emitLegacyTools: true, legacy tool names include a deprecation notice:
[Legacy — prefer sdl.query] Search for symbols by name or summary

Migration Guide

For MCP client users

If your agent currently uses flat tool names (e.g., sdl.symbol.search), you have two options:
  1. Do nothing — with emitLegacyTools: true (the default), both flat and gateway tools remain available
  2. Switch to gateway — update your agent instructions to use sdl.query with action: "symbol.search"

For agent instruction authors

Update your CLAUDE.md / AGENTS.md to use gateway-style calls:
# Tool Usage
Use `sdl.symbol.search` to find symbols.
Use `sdl.code.getSkeleton` to see code structure.
Use `sdl.slice.build` to get task context.

Disabling gateway mode

If you need backward compatibility with older MCP clients:
{
  "gateway": {
    "enabled": false
  }
}
This registers the 31 flat tools plus the universal sdl.action.search and sdl.info surfaces.

Code Mode: sdl.chain

Gateway mode optimizes tool registration overhead. Code Mode goes further by eliminating per-operation round-trip overhead — batching entire context retrieval pipelines into a single tool call. sdl.chain accepts a steps array where each step can reference results from previous steps using $N.path notation:
{
  "repoId": "my-repo",
  "steps": [
    { "fn": "symbolSearch", "args": { "query": "handleAuth", "limit": 3 } },
    { "fn": "symbolGetCard", "args": { "symbolId": "$0.symbols[0].symbolId" } },
    { "fn": "codeSkeleton", "args": { "symbolId": "$1.card.symbolId" } }
  ],
  "budget": { "maxTotalTokens": 4000 },
  "onError": "continue"
}
This search → card → skeleton pipeline runs in a single MCP call instead of three. Budget tracking, ETag caching, and context ladder validation all still apply inside sdl.chain — Code Mode does not bypass SDL-MCP’s governance layer.

Build docs developers (and LLMs) love