MCP服务器的优化设计思路:Domain Facade设计

我们把60个工具精简到了9个。 功能完全不变。 上下文开销减少了85%。
REST接口规范对人类开发者来说堪称完美——我们读一遍文档,就能记住各种接口端点,受用终身。
但如今,调用你API的不再是人类了。
而是上下文窗口仅有20万token的大语言模型(LLM)——它每一轮交互都会重新读取所有工具的描述,而且你还要为每一个token付费。
请再读一遍这句话:每一轮交互,所有工具描述都要重新读一遍。
这意味着,我们需要一套全新的设计思路。
2 核心问题:工具数量泛滥
MCP(模型上下文协议)能让我们给AI助手扩展自定义工具,但大家很容易陷入一个误区:把接口拆解得过于细碎,比如针对“记忆功能”就做出了一堆接口:
memory_add(添加记忆)memory_get(获取记忆)memory_list(列出记忆)memory_update(更新记忆)memory_delete(删除记忆)memory_pin(置顶记忆)memory_archive(归档记忆)memory_link(关联记忆)memory_unlink(取消记忆关联)memory_search(搜索记忆)memory_embed(生成记忆向量)...如果把这套细碎的接口逻辑套用到项目管理、任务跟踪、文档、文件、数据库等不同领域,很快就会冒出60多个工具。每个工具都要写描述、定义参数格式、给示例——这些都会变成token消耗。
结果就是:LLM每一轮交互都要处理12000个token的工具描述。
最终的问题显而易见:响应速度变慢、成本飙升,甚至AI还会因为60个相似工具傻傻分不清,想调用memory_upsert(新增/更新记忆),结果选错成了memory_update(仅更新记忆)。
3 真实案例:优化前后对比
3.1 第一版:细碎化设计(节选)
{"tools":[{"name":"MemoriesAdd","description":"Add a new memory to the system","inputSchema":{"type":"object","properties":{"projectKey":{},// 项目标识"title":{},// 记忆标题"body":{},// 记忆内容"scope":{},// 记忆生效范围"memoryType":{},// 记忆类型"tags":{},// 标签"importance":{},// 重要程度"pinned":{},// 是否置顶"ttlIso":{},// 有效期(ISO格式)"userId":{},// 用户ID"chatId":{},// 对话ID"sourceKind":{},// 来源类型"sourceRef":{}// 来源引用},"required":["projectKey","title","body"]}},{"name":"MemoriesSearch","description":"Search memories using hybrid FTS + semantic search","inputSchema":{ ... }},// 混合全文+语义搜索记忆{"name":"MemoriesList","description":"List memories with filtering and pagination","inputSchema":{ ... }},// 分页筛选列出记忆{"name":"MemoriesGet","description":"Get a specific memory by ID","inputSchema":{ ... }},// 按ID获取单条记忆{"name":"MemoriesUpdate","description":"Update an existing memory","inputSchema":{ ... }},// 更新已有记忆{"name":"MemoriesPin","description":"Pin or unpin a memory","inputSchema":{ ... }},// 置顶/取消置顶记忆{"name":"MemoriesArchive","description":"Archive a memory (soft delete)","inputSchema":{ ... }},// 归档记忆(软删除){"name":"MemoriesDelete","description":"Permanently delete a memory","inputSchema":{ ... }},// 永久删除记忆{"name":"MemoriesLink","description":"Link two memories","inputSchema":{ ... }},// 关联两条记忆{"name":"MemoriesUnlink","description":"Remove a link between memories","inputSchema":{ ... }},// 取消记忆关联{"name":"MemoriesRelated","description":"Get related memories","inputSchema":{ ... }},// 获取相关记忆{"name":"MemoriesPrune","description":"Archive expired memories","inputSchema":{ ... }},// 归档过期记忆{"name":"MemoriesEmbed","description":"Generate embeddings","inputSchema":{ ... }},// 生成记忆向量嵌入{"name":"MemoriesStats","description":"Get memory statistics","inputSchema":{ ... }},// 获取记忆统计数据{"name":"ProjectsList","description":"List all projects","inputSchema":{ ... }},// 列出所有项目{"name":"ProjectsGet","description":"Get a project by key","inputSchema":{ ... }},// 按标识获取项目{"name":"DocsList","description":"List docs for a project","inputSchema":{ ... }},// 列出项目下的文档{"name":"DocsSearch","description":"Search docs via FTS","inputSchema":{ ... }},// 全文搜索文档{"name":"FilesList","description":"List files","inputSchema":{ ... }},// 列出文件{"name":"FilesRead","description":"Read a file","inputSchema":{ ... }},// 读取文件{"name":"FilesWrite","description":"Write a file","inputSchema":{ ... }},// 写入文件{"name":"DbTables","description":"List SQLite tables","inputSchema":{ ... }},// 列出SQLite数据表{"name":"DbQuery","description":"Run a SELECT","inputSchema":{ ... }},// 执行SELECT查询{"name":"DbExec","description":"Execute SQL","inputSchema":{ ... }},// 执行SQL语句// ... 还有35+个工具]}每次交互要消耗约12000个token。每一次都要。
3.2 第二版:领域门面设计(完整)
{"tools":[{"name":"MemoryExecute",// 记忆功能总入口"description":"神经记忆系统。支持指令: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"]// 必须传cmd参数}},{"name":"ProjectsExecute","description":"项目管理。支持指令:list(列出)、get(获取)、upsert(新增/更新)、archive(归档)、stats(统计)","inputSchema":{ ... }},{"name":"TasksExecute","description":"任务跟踪。支持指令:list(列出)、get(获取)、upsert(新增/更新)、delete(删除)、set_status(修改状态)","inputSchema":{ ... }},{"name":"DocsExecute","description":"文档管理。支持指令:list(列出)、get(获取)、upsert(新增/更新)、delete(删除)、search(搜索)、pin(置顶)","inputSchema":{ ... }},{"name":"FilesExecute","description":"文件操作。支持指令:list(列出)、get(读取)、put(写入)、delete(删除)、roundtrip_*(原子编辑相关)","inputSchema":{ ... }},{"name":"DatabaseExecute","description":"SQL访问。支持指令:query(查询)、exec(执行)、schema(查结构)、tables(列表)、stats(统计)","inputSchema":{ ... }},{"name":"ArtifactsExecute","description":"内容存储。支持指令:get(获取)、search(搜索)、upsert(新增/更新)","inputSchema":{ ... }},{"name":"HydrationExecute","description":"AI上下文。支持指令:hydrate(加载上下文)、persona_*(人设相关)、identity_*(身份相关)","inputSchema":{ ... }},{"name":"DeepSearch","description":"外部搜索:谷歌、GitHub、维基百科、黑客新闻","inputSchema":{ ... }}]}每次交互仅消耗约2000个token。功能完全不变,这就是全部的工具列表。
4 核心设计思路:按领域封装,一个领域一个工具
原本14个记忆相关工具,现在整合成1个工具,内置14个指令:
// 优化前:14个工具,14段描述,14套参数格式MemoriesAdd({ title, body, ... }) // 新增记忆MemoriesSearch({ query, topK, ... }) // 搜索记忆MemoriesPin({ id, pinned }) // 置顶记忆...// 优化后:1个工具,1段描述,指令作为参数传入MemoryExecute({ cmd: "add", params: { title, body, ... }}) // 执行新增记忆指令MemoryExecute({ cmd: "search", params: { query, topK, ... }}) // 执行搜索记忆指令MemoryExecute({ cmd: "pin", params: { id, pinned }}) // 执行置顶记忆指令AI只需要思考9个领域,而不是60个零散的操作动词。
比如它想“搜索记忆”,直接调用MemoryExecute并传入cmd: "search"就行,简单直接。
5 具体实现方式
每个领域的封装逻辑都遵循统一结构(以C#为例):
///<summary>/// 领域指令执行入口///</summary>///<param name="command">领域指令对象(包含指令名+参数)</param>///<returns>领域操作响应结果</returns>publicasync Task<DomainResponse> ExecuteAsync(DomainCommand command){// 把指令名转为小写,匹配对应的处理逻辑return command.Cmd.ToLowerInvariant() switch {"add" => await AddAsync(command), // 处理新增指令"get" => await GetAsync(command), // 处理获取指令"list" => await ListAsync(command), // 处理列出指令"search" => await SearchAsync(command), // 处理搜索指令"update" => await UpdateAsync(command), // 处理更新指令"delete" => await DeleteAsync(command), // 处理删除指令 _ => DomainResponse.Failure(command.Cmd, "Unknown command") // 未知指令返回失败 };}5.1 统一的请求/响应格式
请求示例:
{"cmd":"search",// 要执行的指令:搜索"detail":"standard",// 返回标准详细程度的内容"params":{"projectId":1,"query":"authentication","topK":10}// 搜索参数:项目1、关键词“认证”、返回前10条}响应示例:
{"ok":true,// 操作是否成功"cmd":"search",// 回显指令名,方便AI关联请求和响应"data":[...],// 搜索结果数据"count":10,// 结果数量"error":null// 错误信息(成功时为null)}响应里回显指令名很重要——AI同时处理多个操作时,能清晰对应每一个请求的结果。
5.2 可配置的返回详细程度
用一个参数就能控制返回内容的多少,避免不必要的token消耗:
| 详细程度 | 返回内容 | 使用场景 |
|---|---|---|
minimal | 仅返回ID、标题 | 列表展示、计数统计、快速校验 |
standard | 核心字段、内容摘要 | 日常通用场景 |
full | 所有字段和内容 | 深度查看、调试排错 |
AI可以根据需求选择合适的详细程度,比如只想知道数量时,就不用解析50KB的完整响应内容了。
6 最终的9个核心工具
| 工具名 | 支持的指令 | 用途 |
|---|---|---|
MemoryExecute | add、get、list、search、update、pin、delete、link、unlink、embed、stats、prune | 带混合搜索的神经记忆管理 |
ProjectsExecute | list、get、upsert、archive、stats、get_tree | 工作空间(项目)管理 |
TasksExecute | list、get、upsert、delete、set_status、add_note | 任务跟踪管理 |
DocsExecute | list、get、upsert、delete、search、pin、embed | 文档管理 |
FilesExecute | list、get、put、delete、mkdir、roundtrip_* | 文件操作 |
DatabaseExecute | query、exec、schema、tables、stats | 直接访问SQL数据库 |
ArtifactsExecute | get、search、upsert | 内容寻址存储(CAS)管理 |
HydrationExecute | hydrate、persona_、identity_、preferences_* | AI上下文加载(人设、身份、偏好等) |
DeepSearch | (聚合指令) | 外部平台搜索(谷歌、GitHub、维基百科、黑客新闻) |
60+个操作,最终只封装成9个工具,功能完全没丢。
7 为什么这种设计有效?
- 降低AI的认知负担
- 接口风格统一
- 极致省token
- 扩展更灵活
- 减少选错工具的概率
8 优化后的核心指标对比
| 指标 | 优化前(60个工具) | 优化后(9个工具) |
|---|---|---|
| 工具列表token消耗 | ~12000 | ~2000 |
| 选错工具的概率 | 频繁发生 | 几乎没有 |
| 响应延迟 | 较高 | 更低 |
| 月度API成本 | 高($$$) | 低($) |
9 额外技巧:基于清单的往返编辑模式(Manifest-Based Roundtripping)
还有一个实用的设计思路值得分享:原子化多文件编辑。
9.1 原痛点所在
LLM逐个编辑文件时,会出现这些问题:
PUT /file/a.cs → 写入内容PUT /file/b.cs → 写入内容PUT /file/c.cs → 写入内容三次API调用,没有原子性(要么全成、要么全败),也没有冲突检测——如果用户在AI编辑时改了某个文件,AI会无声地覆盖掉用户的修改。
9.2 解决方案
设计一套“往返编辑”模式,流程就变成了这样:
// 第一步:发起编辑请求:`roundtrip_start({ paths: ["a.cs", "b.cs", "c.cs"] })`roundtrip_start({ paths: ["a.cs", "b.cs", "c.cs"] }) → 返回结果:包含文件原始哈希值的“清单”(manifest) + 原始文件的压缩包(ZIP)// 第二步:AI在本地编辑压缩包里的文件(不用直接操作原文件)[AI edits files in ZIP]// 第三步:预览修改效果:`roundtrip_preview({ manifestId, modifiedZip })`roundtrip_preview({ manifestId, modifiedZip }) → 返回结果:修改对比(diff) + 冲突警告(如果有)// 确认提交修改:`roundtrip_commit({ manifestId, zip, mode: "replace" })`roundtrip_commit({ manifestId, zip, mode: "replace" }) → 原子化应用:要么所有修改都生效,要么全不生效9.3 核心:用“清单”记录原始状态
这个“清单”就像一张“文件快照凭证”,会记录每个文件的关键信息,确保后续能验证文件是否被改动过:
{"manifestId":"rtp_2024-01-15T10-30-00Z_a1b2c3d4",// 清单唯一ID(方便后续关联)"entries":[// 每个文件的记录:路径、SHA256哈希值(文件内容的“指纹”)、文件大小{"path":"src/auth/login.cs","sha256":"abc123...","size":2048},{"path":"src/auth/logout.cs","sha256":"def456...","size":1024}]}这里的SHA256哈希值很关键——它就像文件的“数字指纹”:只要文件内容有一点点变化,哈希值就会完全不同。
9.4 提交时的冲突检测
提交修改时,系统会自动验证文件是否被篡改,逻辑很简单:
// 计算当前服务器上文件的实际哈希值(相当于“当前指纹”)var currentSha256 = ComputeHash(physicalPath);// 对比清单里记录的“原始指纹”和“当前指纹”if (currentSha256 != manifestEntry.Sha256)// 指纹不一致 → 说明文件被外部修改过,添加冲突警告 conflicts.Add($"文件已被外部修改:{virtualPath}");9.5 三种提交模式(按需选择)
| 模式(mode) | 已存在的文件 | 新文件 | 适用场景 |
|---|---|---|---|
replace | 覆盖原有内容 | 新建文件 | 完全同步AI的修改(比如重构后的文件) |
add_only | 跳过不修改 | 新建文件 | 安全搭建框架(比如新增配置文件,不碰已有代码) |
update_only | 覆盖原有内容 | 跳过不创建 | 定向修复(比如只改已有文件的bug,不新增文件) |
这种模式的好处很明显:一次原子化操作(要么全成要么全败)、省流量(传压缩包比逐个传文件高效)、不丢数据(冲突会提前警告)。而“清单”就是你的“安全 checkpoint”——你能明确知道自己是基于哪个版本的文件开始编辑的。
10 什么时候不适合用这种模式?
- 简单服务器
- 无状态工具
- 面向开发者的接口
这个模式专门针对LLM的特点设计:LLM的上下文窗口有限、每处理一个字符都要花钱,所以需要尽量减少它的“思考负担”。
11 总结
MCP 还是个新兴领域,最佳实践还在不断形成中。但有一点已经很明确:为人类开发者设计的接口,不一定适合LLM使用。
人类看一次文档就记住了,LLM每次对话都要重新“读”文档;人类能记住不同的接口地址,LLM处理的字符越多越贵;人类喜欢精细的操作选项,LLM面对60个相似的功能动词会产生困惑。
“上下文优化型接口”(Context-Optimized APIs)颠覆了传统的设计思路:不再纠结“是否符合REST规范”,而是聚焦“如何在保证功能的同时,最小化LLM的上下文负担”。
对我们来说,答案是“领域门面模式”(Domain Facades):一个领域对应一个工具,用参数指定具体命令,统一的数据格式,可配置的细节展示级别。
最终效果:60个工具 → 9个工具;12000个字符的上下文 → 2000个字符;功能完全不变。
AI的响应更快、使用成本更低,还能更准确地选对工具。
有时候,最好的接口设计,恰恰是最能尊重使用者(这里是LLM)约束的设计。
本文内容仅供参考,不构成任何专业建议。使用本文提供的信息时,请自行判断并承担相应风险。



