MCP Function Tools 接口设计 —— 如何用有限的接口覆盖无限的意图
在 Dotamate 的开发过程中,我们遇到了许多技术挑战。以下是一些主要问题以及我们的思考和解决方案:
Q1: MCP Function Tools 接口设计 —— 如何用有限的接口覆盖无限的意图?
背景
在使用 MCP (Model Context Protocol) 为 AI 暴露 Function Tools 时,我发现一个核心矛盾:AI 的能力很大程度上取决于你提供了多少接口、接口做了什么。 这导致我们总想着提供更多的接口,因为似乎变成了——我们提供什么,AI 才知道什么。
问题场景
以 Dota2 英雄(静态资源)查询为例,用户的意图是非常多维和发散的:
- "查询有多少个英雄"
- "查询影魔的具体属性"
- "按照英雄 ID 查询"
- "查询英雄下的某个技能详情"
- 反向查询:"谁有 Toss 技能?列出所有英雄"
如果一个英雄有 100 个属性字段,我们很难写 100 个接口,也无法前置所有特定查询接口,因为:
- 总会漏掉意图 —— 用户想查什么是不确定的
- 接口膨胀 —— 会写大量零散的、功能重复的接口
- 维护成本高 —— 每增加一个字段就要加接口
解决方案
经过思考,总结出三种互补的策略:
方案一:Schema 描述增强(低成本,高收益)
在 Tool 的描述中增加结构化的文本字段说明,就像描述一张表一样,说明每个字段的含义和主要用途。
Tool: query_hero
Description: 查询 Dota2 英雄数据
Schema Fields:
- id: 英雄唯一标识 (int)
- name: 英雄英文名 (string)
- localized_name: 英雄中文名 (string)
- primary_attr: 主属性 (str/agi/int)
- attack_type: 攻击类型 (Melee/Ranged)
- roles: 角色定位列表 (Carry/Support/Nuker...)
- base_health: 基础生命值 (int)
- base_mana: 基础魔法值 (int)
- move_speed: 移动速度 (int)
- ... (完整字段列表)本质上是提示词工程 —— 这段结构化描述 AI 能读懂,它会自行判断用户意图对应哪个字段,然后构造合适的查询参数。
方案二:通用查询过滤器(一套接口覆盖所有场景)
提供一个通用的查询过滤接口,定义宽泛的查询条件协议,让 AI 自己组装过滤条件:
json
{
"tool": "query_heroes",
"parameters": {
"filters": [
{ "field": "primary_attr", "op": "eq", "value": "str" },
{ "field": "base_health", "op": "gte", "value": 600 }
],
"fields": ["id", "name", "localized_name", "base_health"],
"limit": 10,
"sort": { "field": "base_health", "order": "desc" }
}
}支持的通用操作符:eq / neq / gt / gte / lt / lte / in / contains / startswith
核心思路:不给 AI 提供具体的查询接口,而是提供一种查询能力,让 AI 自己编写过滤器。
方案三:反向映射接口(解决"倒查"场景)
对于反向查询(如"谁有 Toss 技能"),提供专门的反向索引接口:
json
{
"tool": "reverse_lookup",
"parameters": {
"target_type": "ability",
"target_value": "Toss",
"return_fields": ["hero_id", "hero_name", "ability_slot"]
}
}这类接口的关键在于建立反向索引,将属性值映射到实体,而不是从实体遍历属性。
三种方案的选择建议
| 方案 | 适用场景 | 成本 | 灵活性 |
|---|---|---|---|
| Schema 描述 | 字段含义明确、查询模式相对固定 | 低 | 中 |
| 通用过滤器 | 数据字段多、查询组合丰富 | 中 | 高 |
| 反向映射 | 倒查、属性到实体的映射 | 高 | 针对性强 |
实践中建议组合使用 先用 Schema 描述覆盖 80% 的常见查询,再用通用过滤器兜底复杂组合查询,最后为高频倒查场景单独建反向索引。