Domain Facade Pattern for MCP: Reducing 60 Tools to 9 Cuts LLM Context by 85%
The article presents a Domain Facade design pattern for MCP servers that consolidates 60+ fine-grained tools into 9 domain-level tools, reducing per-interaction token consumption from ~12,000 to ~2,000 (85% reduction) while preserving full functionality, and introduces a manifest-based roundtripping pattern for atomic multi-file edits.
Introduction
REST APIs are designed for human developers who read documentation once and remember endpoints. However, modern API consumers are LLMs with 200k-token context windows that must re-read every tool description on every interaction — and you pay for each token.
We reduced 60 tools to 9. Functionality unchanged. Context overhead cut by 85%.
Key insight: Every interaction, all tool descriptions are re-read. This demands a new design philosophy.
Core Problem: Tool Proliferation
MCP (Model Context Protocol) lets us extend AI assistants with custom tools, but a common pitfall is over-fragmenting interfaces. For "memory" alone, teams create dozens of tools:
memory_add (add memory)
memory_get (get memory)
memory_list (list memories)
memory_update (update memory)
memory_delete (delete memory)
memory_pin (pin memory)
memory_archive (archive memory)
memory_link (link memories)
memory_unlink (unlink memories)
memory_search (search memories)
memory_embed (generate memory vectors)
...Replicated across projects, tasks, docs, files, databases, this quickly yields 60+ tools. Each needs a description, parameter schema, and examples — all consuming tokens.
Result: LLM processes ~12,000 tokens of tool descriptions every interaction.
Consequences: slower responses, soaring costs, and the AI confuses similar tools (e.g., picking memory_update instead of memory_upsert).
Real-World Before/After Comparison
Version 1: Fragmented Design (excerpt)
60+ tools defined individually, each with name, description, and inputSchema. Example tools: MemoriesAdd — "Add a new memory to the system" with 13 properties (projectKey, title, body, scope, memoryType, tags, importance, pinned, ttlIso, userId, chatId, sourceKind, sourceRef) MemoriesSearch — "Search memories using hybrid FTS + semantic search" MemoriesList, MemoriesGet, MemoriesUpdate, MemoriesPin, MemoriesArchive, MemoriesDelete, MemoriesLink, MemoriesUnlink, MemoriesRelated, MemoriesPrune, MemoriesEmbed,
MemoriesStatsProjectsList,
ProjectsGetDocsList,
DocsSearchFilesList, FilesRead,
FilesWriteDbTables, DbQuery, DbExec... 35+ more
~12,000 tokens per interaction. Every single time.
Version 2: Domain Facade Design (complete)
9 tools, each a domain entry point with sub-commands:
{
"tools": [
{
"name": "MemoryExecute",
"description": "Neural memory system. Commands: add, get, list, search, update, pin, delete, archive, link, unlink, related, embed, stats, prune",
"inputSchema": {
"type": "object",
"properties": {
"cmd": {"type": "string"},
"detail": {"enum": ["minimal", "standard", "full"]},
"params": {"type": "object"}
},
"required": ["cmd"]
}
},
{"name": "ProjectsExecute", "description": "Project management. Commands: list, get, upsert, archive, stats, get_tree"},
{"name": "TasksExecute", "description": "Task tracking. Commands: list, get, upsert, delete, set_status, add_note"},
{"name": "DocsExecute", "description": "Document management. Commands: list, get, upsert, delete, search, pin, embed"},
{"name": "FilesExecute", "description": "File operations. Commands: list, get, put, delete, mkdir, roundtrip_*"},
{"name": "DatabaseExecute", "description": "SQL access. Commands: query, exec, schema, tables, stats"},
{"name": "ArtifactsExecute", "description": "Content-addressable storage. Commands: get, search, upsert"},
{"name": "HydrationExecute", "description": "AI context. Commands: hydrate, persona_*, identity_*, preferences_*"},
{"name": "DeepSearch", "description": "External search: Google, GitHub, Wikipedia, Hacker News"}
]
}~2,000 tokens per interaction. Full functionality preserved.
Core Design: Domain Encapsulation, One Tool per Domain
Original 14 memory tools → 1 tool with 14 commands:
// Before: 14 tools, 14 descriptions, 14 param schemas
MemoriesAdd({ title, body, ... }) // add memory
MemoriesSearch({ query, topK, ... }) // search memory
MemoriesPin({ id, pinned }) // pin memory
...
// After: 1 tool, 1 description, command as parameter
MemoryExecute({ cmd: "add", params: { title, body, ... }}) // execute add command
MemoryExecute({ cmd: "search", params: { query, topK, ... }}) // execute search command
MemoryExecute({ cmd: "pin", params: { id, pinned }}) // execute pin commandAI now reasons over 9 domains instead of 60 scattered verbs. To "search memory", it calls MemoryExecute with cmd: "search" — simple and direct.
Implementation Details
Each domain follows a unified structure (C# example):
/// Domain command execution entry
/// command: Domain command object (command name + params)
/// returns: Domain operation response
public async Task<DomainResponse> ExecuteAsync(DomainCommand command)
{
// Lowercase command name, dispatch to handler
return command.Cmd.ToLowerInvariant() switch
{
"add" => await AddAsync(command), // handle add
"get" => await GetAsync(command), // handle get
"list" => await ListAsync(command), // handle list
"search" => await SearchAsync(command), // handle search
"update" => await UpdateAsync(command), // handle update
"delete" => await DeleteAsync(command), // handle delete
_ => DomainResponse.Failure(command.Cmd, "Unknown command") // unknown command
};
}Unified Request/Response Format
Request example:
{
"cmd": "search",
"detail": "standard",
"params": {
"projectId": 1,
"query": "authentication",
"topK": 10
}
}Response example:
{
"ok": true,
"cmd": "search",
"data": [...],
"count": 10,
"error": null
}Echoing the command name in the response is critical — when AI runs multiple operations concurrently, it can match each response to its request.
Configurable Detail Level
A single parameter controls response verbosity, avoiding unnecessary tokens:
minimal — Only ID, title. Use case: List views, counts, quick validation.
standard — Core fields, content summary. Use case: General daily use.
full — All fields and content. Use case: Deep inspection, debugging.
AI picks the appropriate level — e.g., minimal when only a count is needed, skipping 50KB of full payload.
Final 9 Core Tools
MemoryExecute — Commands: add, get, list, search, update, pin, delete, link, unlink, embed, stats, prune. Purpose: Neural memory with hybrid search.
ProjectsExecute — Commands: list, get, upsert, archive, stats, get_tree. Purpose: Workspace/project management.
TasksExecute — Commands: list, get, upsert, delete, set_status, add_note. Purpose: Task tracking.
DocsExecute — Commands: list, get, upsert, delete, search, pin, embed. Purpose: Document management.
FilesExecute — Commands: list, get, put, delete, mkdir, roundtrip_*. Purpose: File operations.
DatabaseExecute — Commands: query, exec, schema, tables, stats. Purpose: Direct SQL database access.
ArtifactsExecute — Commands: get, search, upsert. Purpose: Content-addressable storage (CAS).
HydrationExecute — Commands: hydrate, persona_*, identity_*, preferences_*. Purpose: AI context loading (persona, identity, preferences).
DeepSearch — Aggregate command. Purpose: External search: Google, GitHub, Wikipedia, Hacker News.
60+ operations → 9 tools. Zero functionality lost.
Why This Design Works
Lowers AI cognitive load — AI identifies 9 domains, not 60 verbs; simpler decisions.
Uniform interface style — Learn one format, apply to all domains. Every domain has list / get / search etc., consistent request/response/error formats.
Extreme token savings — "Memory" description written once, not 14 times; massive context reduction.
Flexible extensibility — New command = one switch branch; no tool re-registration, schema changes, or doc updates.
Fewer wrong-tool selections — 9 choices vs 60; AI no longer confuses MemoriesUpdate with MemoriesUpsert.
Key Metrics Comparison
Tool list token cost : Before (60 tools) ~12,000 → After (9 tools) ~2,000
Wrong-tool probability : Before frequent → After near zero
Response latency : Before higher → After lower
Monthly API cost : Before high ($$$) → After low ($)
Bonus Technique: Manifest-Based Roundtripping for Atomic Multi-File Edits
Original Pain Point
LLM edits files sequentially:
PUT /file/a.cs → write content
PUT /file/b.cs → write content
PUT /file/c.cs → write contentThree API calls, no atomicity (all-or-nothing), no conflict detection — if user modifies a file during AI editing, AI silently overwrites user changes.
Solution: Roundtrip Editing Flow
// Step 1: Start edit: roundtrip_start({ paths: ["a.cs", "b.cs", "c.cs"] })
roundtrip_start({ paths: ["a.cs", "b.cs", "c.cs"] })
→ Returns: manifest (original file hashes) + ZIP of original files
// Step 2: AI edits files inside ZIP locally (no direct file ops)
[AI edits files in ZIP]
// Step 3: Preview changes: roundtrip_preview({ manifestId, modifiedZip })
roundtrip_preview({ manifestId, modifiedZip })
→ Returns: diff + conflict warnings (if any)
// Step 4: Commit: roundtrip_commit({ manifestId, zip, mode: "replace" })
roundtrip_commit({ manifestId, zip, mode: "replace" })
→ Atomic apply: all changes succeed or none doCore: Manifest Records Original State
The manifest is a "file snapshot credential" recording each file's path, SHA256 hash (content fingerprint), and size:
{
"manifestId": "rtp_2024-01-15T10-30-00Z_a1b2c3d4",
"entries": [
{"path": "src/auth/login.cs", "sha256": "abc123...", "size": 2048},
{"path": "src/auth/logout.cs", "sha256": "def456...", "size": 1024}
]
}SHA256 is critical — any content change produces a completely different hash.
Conflict Detection on Commit
On commit, server verifies files haven't been tampered with:
// Compute current server file hash ("current fingerprint")
var currentSha256 = ComputeHash(physicalPath);
// Compare manifest's "original fingerprint" with "current fingerprint"
if (currentSha256 != manifestEntry.Sha256)
// Mismatch → file modified externally, add conflict warning
conflicts.Add($"File modified externally: {virtualPath}");Three Commit Modes
replace (full replace) — Existing files: overwrite content. New files: create. Use case: Fully sync AI modifications (e.g., refactored files).
add_only (add only) — Existing files: skip. New files: create. Use case: Safe scaffolding (e.g., add config files, don't touch existing code).
update_only (update only) — Existing files: overwrite content. New files: skip. Use case: Targeted fixes (e.g., bug fixes in existing files, no new files).
Benefits: Single atomic operation (all-or-nothing), bandwidth efficient (ZIP vs individual files), no data loss (conflicts warned upfront). The manifest serves as a "safety checkpoint" — you know exactly which file versions you started from.
When Not to Use This Pattern
Simple servers — If only 3-5 tools, unnecessary complexity.
Stateless tools — Independent operations (e.g., "generate random number", "format as JSON") don't need file state tracking.
Developer-facing APIs — Programmers prefer fine-grained file operations (e.g., REST); this packaging reduces flexibility.
This pattern is designed specifically for LLM constraints: limited context window, per-token cost, need to minimize "thinking burden".
Summary
MCP is emerging; best practices are still forming. But one thing is clear: Interfaces designed for human developers are not necessarily suitable for LLMs.
Humans read docs once and remember; LLMs re-read every conversation. Humans remember different endpoints; LLMs pay per character. Humans like granular options; LLMs get confused by 60 similar verbs.
"Context-Optimized APIs" flip traditional design: instead of "does this follow REST?", ask "how to minimize LLM context overhead while preserving functionality?"
Our answer: Domain Facades — one tool per domain, command as parameter, unified data formats, configurable detail levels.
Result: 60 tools → 9 tools; 12,000 tokens → 2,000 tokens; functionality unchanged. AI responds faster, costs less, selects tools more accurately.
Sometimes the best interface design is the one that best respects the constraints of its user — here, the LLM.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
