返回文章列表
服务器

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

匿名
2025-12-02
2天前
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个核心工具

工具名支持的指令用途
MemoryExecuteadd、get、list、search、update、pin、delete、link、unlink、embed、stats、prune带混合搜索的神经记忆管理
ProjectsExecutelist、get、upsert、archive、stats、get_tree工作空间(项目)管理
TasksExecutelist、get、upsert、delete、set_status、add_note任务跟踪管理
DocsExecutelist、get、upsert、delete、search、pin、embed文档管理
FilesExecutelist、get、put、delete、mkdir、roundtrip_*文件操作
DatabaseExecutequery、exec、schema、tables、stats直接访问SQL数据库
ArtifactsExecuteget、search、upsert内容寻址存储(CAS)管理
HydrationExecutehydrate、persona_、identity_、preferences_*AI上下文加载(人设、身份、偏好等)
DeepSearch(聚合指令)外部平台搜索(谷歌、GitHub、维基百科、黑客新闻)

60+个操作,最终只封装成9个工具,功能完全没丢。

7 为什么这种设计有效?

  1. 降低AI的认知负担
  2. 接口风格统一
  3. 极致省token
  4. 扩展更灵活
  5. 减少选错工具的概率

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)约束的设计。


本文内容仅供参考,不构成任何专业建议。使用本文提供的信息时,请自行判断并承担相应风险。

分享文章
合作伙伴

本站所有广告均是第三方投放,详情请查询本站用户协议