从零到一:用 MCP 协议把本地工具接入 AI Agent 的完整实践
一、为什么需要 MCP
过去写一个能“干活”的 AI 应用,最麻烦的不是模型调用,而是工具接入。每个业务系统都有自己的 API 风格、鉴权方式和参数约定,开发者往往要写大量适配代码,最后得到一个臃肿而脆弱的胶水层。
Model Context Protocol(MCP)试图把这件事标准化:用一套统一的 JSON-RPC 协议描述工具(tools)、资源(resources)与提示模板(prompts),让模型与外部系统之间的接口变得可插拔。
本文不讨论协议规范细节,而是记录一次从零把本地工具接入 Agent 的完整实践,包括踩过的坑和最终的工程化方案。
二、整体架构
我们的目标很明确:让 Agent 能调用部署在本地的一批脚本能力,例如查询数据库、执行运维命令、读取日志。整体链路分三层:
- 接入层:Agent 客户端负责发起对话并维护会话上下文。
- 协议层:MCP Server 以 stdio 或 SSE 方式暴露工具清单与调用入口。
- 执行层:具体的工具实现,负责真正的业务逻辑与权限校验。
三个层次解耦之后,新增一个工具的代价从“改客户端代码 + 重新发布”降低为“在 Server 侧注册一个函数”。
三、工具注册的最小实现
一个工具在协议层的描述通常包含名称、用途说明和参数结构。用途说明写得越清楚,模型选对工具的概率越高。下面是一个简化的注册示例:
{
"name": "query_slow_log",
"description": "查询指定实例最近N分钟的慢查询日志",
"inputSchema": {
"type": "object",
"properties": {
"instance_id": { "type": "string" },
"minutes": { "type": "integer", "default": 10 }
},
"required": ["instance_id"]
}
}
这里有两个容易被忽视的细节。第一,description 不是给人看的注释,而是模型做工具选择的输入,应该写清适用场景而不是复述函数名。第二,参数尽量给出默认值和取值范围,能显著减少模型生成非法参数的概率。
四、实际踩到的四个坑
坑一:工具数量过多导致选择困难。当工具清单超过二十个之后,模型开始频繁选错。解决办法是按业务域拆分多个 Server,由客户端根据会话意图动态挂载,而不是一次性全量暴露。
坑二:返回值过大挤占上下文。某次日志查询返回了几万行,直接把上下文窗口打满。后来统一在协议层做截断与摘要,只返回前若干条并附带“已截断”标记,由模型决定是否继续拉取。
坑三:错误信息不具可操作性。最初抛出的异常只有堆栈,模型拿到后无法自行修正。改成结构化错误(错误码 + 原因 + 建议动作)之后,自愈率明显提升。
坑四:长任务的超时与幂等。某些操作耗时超过默认超时时间,重试又可能造成重复执行。最终方案是把长任务改成异步:先返回任务 ID,再由另一个查询工具轮询状态,同时要求执行层保证幂等。
五、权限与安全边界
把工具交给模型调用,本质上是把一部分执行权让渡出去,因此边界必须提前划清:
- 只读与写入工具分组暴露,写入类工具默认不挂载,需要显式开启。
- 敏感参数(如实例标识、凭据引用)由服务端补全,不经过模型。
- 所有调用落审计日志,记录调用方、参数摘要与结果状态。
- 对高危操作强制人工确认,协议层返回待确认状态而非直接执行。
这些约束看起来降低了便利性,但它们决定了这套方案能否真正上线。
六、效果与后续
完成接入后,原先需要人工在多个控制台之间来回切换的排查流程,变成了对话式的一两步操作。更重要的是工程上的变化:新增能力的边际成本大幅下降,团队成员可以独立贡献工具而不影响主程序。
后续计划有两个方向。一是把工具调用的链路做可观测化,统计每个工具的命中率与失败率,持续优化描述文案;二是引入缓存与结果复用,减少重复的远程调用。
标准化的价值往往不在第一天的惊艳,而在于第一百个工具接入时依然不需要重构。对 MCP 这类协议来说,这一点尤其成立。
- 点赞
- 收藏
- 关注作者
评论(0)