MCP 工具设计别只暴露接口:让 Agent 真能用起来

2026-07-10 42 预计阅读时间: 1 分钟
来源: aws.amazon.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:10 分钟

MCP 工具设计最容易出问题的地方,不是协议本身,而是把“给模型一个函数”误当成“模型能稳定完成任务”。工具名、参数形状、返回内容、错误信息、权限边界,都会直接影响 Agent 是否能做出正确决策。好的 MCP tool 设计,本质上是一次上下文工程:把模型需要的判断线索放在合适的位置,同时不要把业务系统的复杂度原封不动塞给它。

工具不是 API 镜像,而是任务接口

很多团队会把已有 REST API 或内部 SDK 原样包装成 MCP tool:getUserByIdlistOrdersupdateRecord。这对人类工程师清楚,对模型却经常不够。

模型调用工具时要回答几个问题:

  • 这个工具适合解决当前任务吗?
  • 必填参数从哪里来?
  • 如果结果为空,下一步应该怎么办?
  • 返回值里哪些字段值得继续推理?

因此,工具应该围绕任务命名,而不只是围绕数据表命名。比如客服场景里,find_customer_recent_orders 往往比 list_orders 更有用,因为前者已经编码了“客服排障”的意图。

可以这样设计工具描述:

{
  "name": "find_customer_recent_orders",
  "description": "Find recent orders for a customer when investigating delivery, refund, or payment issues. Use this before checking a single order if the user did not provide an order ID.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "customer_email": {
        "type": "string",
        "description": "Customer email address from the conversation or CRM record."
      },
      "days": {
        "type": "integer",
        "description": "Lookback window in days. Use 30 unless the user asks for a different period.",
        "default": 30
      }
    },
    "required": ["customer_email"]
  }
}

这里的重点不是 JSON 格式本身,而是描述里包含了调用时机、默认策略和参数来源。模型少猜一次,系统就少一次不稳定。

参数越贴近决策,调用越稳

一个常见反模式是把内部系统的全部参数暴露出来:分页游标、排序字段、状态枚举、内部租户 ID、调试开关。这样做看似灵活,实际会把模型拖进实现细节。

更稳的做法是把参数分成三类:

  • 任务必需参数:没有它就无法执行,比如 customer_emailorder_id
  • 可安全默认的参数:比如最近 30 天、最多返回 5 条。
  • 不应暴露给模型的参数:比如内部权限标记、数据库索引选择、低层分页策略。

如果确实需要复杂查询,可以用受控枚举,而不是任意字符串:

{
  "name": "search_support_articles",
  "description": "Search approved support articles for troubleshooting steps. Use this before giving procedural instructions to customers.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "A short search phrase, not a full customer message."
      },
      "issue_type": {
        "type": "string",
        "enum": ["delivery", "refund", "payment", "account", "technical"],
        "description": "Classify the issue so results can be filtered."
      }
    },
    "required": ["query", "issue_type"]
  }
}

这个设计牺牲了一些通用性,但换来更可预测的调用。MCP tool 的目标不是替模型打开所有门,而是给它几扇标牌清楚的门。

返回值要服务下一步推理

工具返回值也会出问题。很多实现直接返回数据库行或第三方 API 原始响应,字段多、命名内部化、失败信息含糊。模型会被迫在噪声里找信号。

更好的返回值应该做到三点:

  • 只返回完成任务需要的字段。
  • 明确区分“没有结果”和“调用失败”。
  • 给出可执行的下一步线索。

例如订单查询工具可以返回这样的结构:

{
  "status": "found",
  "orders": [
    {
      "order_id": "ord_123",
      "created_at": "2026-02-18",
      "payment_status": "paid",
      "fulfillment_status": "delayed",
      "customer_visible_summary": "Package is delayed and has not received a carrier scan in 3 days."
    }
  ],
  "next_action_hint": "If the customer asks for compensation, check refund eligibility before promising a refund."
}

这不是让工具替模型完成全部思考,而是把系统事实整理成模型能继续工作的材料。

可以这样实践:写一个最小 MCP 风格工具清单检查器

下面这个 Python 脚本不依赖真实 MCP SDK。它用于在代码评审或 CI 中检查工具定义是否包含关键上下文:描述、参数说明、必填字段和返回提示。你可以把 TOOLS 替换成项目里的 MCP tool 定义导出结果。

运行方式:保存为 check_mcp_tools.py,执行 python check_mcp_tools.py

from __future__ import annotations

TOOLS = [
    {
        "name": "find_customer_recent_orders",
        "description": "Find recent orders for a customer when investigating delivery, refund, or payment issues. Use this before checking a single order if the user did not provide an order ID.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "customer_email": {
                    "type": "string",
                    "description": "Customer email address from the conversation or CRM record.",
                },
                "days": {
                    "type": "integer",
                    "description": "Lookback window in days. Use 30 unless the user asks for a different period.",
                    "default": 30,
                },
            },
            "required": ["customer_email"],
        },
        "returns": {
            "description": "Returns matching orders plus a next_action_hint for follow-up investigation."
        },
    },
    {
        "name": "list_orders",
        "description": "List orders.",
        "inputSchema": {
            "type": "object",
            "properties": {
                "q": {"type": "string"}
            },
        },
    },
]


def audit_tool(tool: dict) -> list[str]:
    issues: list[str] = []
    name = tool.get("name", "<unnamed>")
    description = tool.get("description", "")

    if len(description.split()) < 12:
        issues.append(f"{name}: description is too short to guide tool choice")

    schema = tool.get("inputSchema", {})
    properties = schema.get("properties", {})
    required = set(schema.get("required", []))

    if not properties:
        issues.append(f"{name}: no input properties defined")

    for prop_name, prop in properties.items():
        if "description" not in prop:
            issues.append(f"{name}.{prop_name}: missing parameter description")

    for prop_name in properties:
        prop = properties[prop_name]
        has_default = "default" in prop
        if prop_name not in required and not has_default:
            issues.append(
                f"{name}.{prop_name}: optional parameter has no default; model may guess"
            )

    returns = tool.get("returns", {})
    if "description" not in returns:
        issues.append(f"{name}: missing return description or next-step guidance")

    return issues


def main() -> None:
    all_issues = []
    for tool in TOOLS:
        all_issues.extend(audit_tool(tool))

    if not all_issues:
        print("All MCP tool definitions passed the context audit.")
        return

    print("MCP tool design issues:")
    for issue in all_issues:
        print(f"- {issue}")


if __name__ == "__main__":
    main()

示例输出会指出 list_orders 的几个问题:描述太短、参数缺少说明、返回值没有上下文。这类检查不能替代真实评测,但能把最常见的设计失误提前拦下来。

取舍:少而准,还是多而全

MCP tool 设计没有唯一答案。工具太少,模型需要在一个大工具里拼复杂参数;工具太多,模型又会在选择工具时迷路。比较实用的原则是:

  • 高频任务做成明确工具,低频操作保留给人工或后台流程。
  • 有副作用的工具要更窄,比如退款、删除、发邮件,最好增加确认步骤和幂等键。
  • 查询工具可以稍宽,写入工具必须保守。
  • 工具描述要写“什么时候用”和“什么时候不用”。
  • 返回值要告诉模型事实,不要返回未经整理的内部对象。

落地时不要一次性把整个系统暴露给 Agent。先挑一个闭环任务,例如“查询订单并判断下一步客服动作”,围绕它设计 3 到 5 个工具,再用真实对话回放评估调用是否稳定。MCP 的价值不在于工具数量,而在于模型拿到的上下文是否足够清楚、边界是否足够硬。


相关推荐