基于MCP协议的Agent工具接入实战:Termexo的19个工具设计
1. 从手动切窗口到Agent 自己找工具Termexo 要解决的真实痛点如果你日常的工作流里同时开着终端、文件管理器、代码编辑器、数据库客户端和一堆脚本那你一定熟悉这种场景Agent 想帮你跑个命令得先问你终端在哪个窗口想读个日志文件得让你手动把路径贴过去想批量改一批文件名你得自己写个循环再喂给它。工具是死的Agent 是瞎的——它看不见你桌面上有什么也不知道该怎么调用。Termexo 这个项目干的事情本质上就是给桌面工作台装上一层可被 Agent 发现和调用的接口。它把原本散落在各个 GUI 窗口、命令行工具、系统 API 里的能力抽象成 19 个标准化的 MCP 工具然后通过 MCP 协议暴露出去让任何支持 MCP 的 Agent 都能自动接入、自动发现、自动调用。你不再需要告诉 Agent你去打开终端然后输入 xxx你只需要说帮我把这个目录下所有临时文件清掉Agent 自己会去调对应的工具。这里的关键词是MCPModel Context Protocol和Agent 自动接入。MCP 你可以理解成一套工具说明书 调用协议的标准格式工具提供方按照这个格式描述自己能干什么、需要什么参数、返回什么结果Agent 侧按照这个格式去发现工具、理解工具、调用工具。Termexo 做的就是工具提供方这一侧的事情而且它提供的不是一两个工具是覆盖桌面工作台常见操作的 19 个工具。适合谁来参考这篇内容三类人一是想让自己的 Agent 真正能干活而不是只会聊天的开发者二是手里有一堆零散脚本、想统一封装成标准接口的工具作者三是对 MCP 协议感兴趣、想找一个完整落地案例来拆解的学习者。下面我会从工具设计、协议接入、Agent 自动发现机制、实操踩坑几个角度把这套东西拆开讲清楚。2. 19 个工具不是拍脑袋凑的桌面工作台的能力分层逻辑2.1 为什么是19 个而不是1 个万能工具很多人第一反应是为什么不做一个execute_anything的超级工具参数里传个字符串就完事我一开始也这么想但实际做下来会发现这条路走不通原因有三个。第一Agent 的工具选择依赖语义描述。MCP 协议里每个工具都有 name、description、inputSchemaAgent 是靠这些文本去判断当前任务该用哪个工具的。如果你只有一个万能工具description 就得写得无比宽泛Agent 反而不知道该不该调、怎么调命中率会大幅下降。拆成 19 个职责单一的工具后每个工具的 description 都能写得很具体Agent 的匹配准确率明显提升。第二参数校验和错误处理需要结构化。万能工具的参数是一个自由字符串你没法在 schema 层面做校验只能运行时解析出错信息也很难定位。而每个工具独立定义 inputSchema 后参数类型、必填项、枚举值都能在协议层约束住Agent 传错参数时能立刻拿到明确的报错。第三权限边界要清晰。桌面工作台涉及文件读写、进程管理、网络请求等敏感操作如果全塞进一个工具你没法做细粒度的权限控制。拆开之后你可以按工具粒度决定哪些暴露给 Agent、哪些只读、哪些需要二次确认。2.2 19 个工具的能力分层我把这 19 个工具按能力域分成了四层这个分层不是随便划的它直接决定了 Agent 在规划任务时的调用顺序。层级能力域典型工具职责调用特征感知层环境探测获取系统信息、列出目录、查询进程只读、高频、无副作用操作层文件与进程读写文件、移动复制、启动终止进程有副作用、需权限控制执行层命令与脚本执行 shell 命令、运行脚本、捕获输出高风险、需沙箱或确认协同层数据与转换格式转换、文本处理、批量重命名组合调用、幂等优先感知层的工具是 Agent 的眼睛它必须先知道当前环境长什么样才能规划后续操作。操作层是手执行层是肌肉协同层是技巧。这个分层的好处是Agent 在规划时天然会先调感知层、再调操作层形成合理的调用链而不是一上来就执行危险命令。2.3 工具粒度的取舍经验粒度太粗Agent 不会用粒度太细工具数量爆炸Agent 选择困难。我在设计时遵循一条经验法则一个工具对应一个人类会单独说出口的动作。比如列出目录内容是一个独立动作读取文件内容是另一个写入文件又是另一个——这三件事人在指挥别人做的时候会分开说那就应该拆成三个工具。反过来读取文件并统计行数就不该是一个工具因为人不会这么说人会先说读这个文件再说数一下多少行。后者应该由 Agent 组合两个工具来完成。这条法则帮我砍掉了很多看似方便、实则破坏 Agent 规划能力的组合工具。3. MCP 工具描述怎么写Agent 才真的会用3.1 description 是给模型看的不是给人看的这是最容易踩的坑。很多人写工具 description 的时候习惯写成给人看的文档风格比如本工具用于对指定路径下的文件进行读取操作支持多种编码格式。这种写法对模型来说信息密度太低模型抓不住什么时候该用它。正确的写法是面向调用场景把什么情况下该调这个工具直接写进 description。比如读取文件的工具description 我会写成读取指定路径的文本文件内容。当用户要求查看、分析、总结某个文件的内容时使用。如果只是想确认文件是否存在请改用 check_path 工具。这样模型在规划时能直接对上号还能被引导到更合适的工具上。3.2 inputSchema 的字段设计细节inputSchema 是 JSON Schema 格式字段设计有几个实操要点。必填项要真的必填。required数组里只放绝对不能省的字段。我见过把可选参数也塞进 required 的结果 Agent 每次都得瞎编一个值填进去反而出错。枚举值要穷举。比如编码格式这种字段用enum把常见值列出来模型就不会自由发挥写出utf8-with-bom这种不存在的值。默认值写在 description 里。JSON Schema 的default字段很多模型不认但你在 description 里写不传则默认为当前工作目录模型是能理解的。路径类参数要说明相对基准。是相对于工作目录还是绝对路径必须写清楚否则 Agent 传相对路径时你会解析到意想不到的位置。下面是一个读取文件工具的 schema 示例注意 description 的写法{ name: read_text_file, description: 读取指定路径的文本文件内容。当用户要求查看、分析或总结某个文件内容时使用。若仅需确认文件是否存在请改用 check_path。, inputSchema: { type: object, properties: { path: { type: string, description: 文件路径。支持绝对路径或相对于当前工作目录的相对路径。 }, encoding: { type: string, enum: [utf-8, gbk, latin-1], description: 文件编码不传默认为 utf-8。 }, max_bytes: { type: integer, description: 最多读取的字节数不传则读取全部。大文件建议设置此值避免超长输出。 } }, required: [path] } }3.3 返回值结构要稳定工具返回给 Agent 的内容结构一定要稳定。我建议统一返回一个对象包含ok、data、error三个字段。ok是布尔值表示成功与否data是成功时的结果error是失败时的错误信息。这样 Agent 处理返回值时逻辑统一不用为每个工具写不同的解析分支。注意错误信息不要直接抛原始堆栈那对模型来说是噪音。把错误翻译成一句人话比如路径不存在/tmp/xxx模型拿到后能自己决定是重试还是换路径。4. Agent 自动接入的完整链路从握手到调用4.1 MCP 的握手与能力协商Agent 接入 Termexo 的第一步是握手。MCP 基于 JSON-RPC客户端Agent 侧会先发initialize请求带上自己支持的协议版本和能力服务端Termexo 侧返回自己支持的版本、能力列表以及 serverInfo。这一步的关键是版本要对齐如果双方协议版本不兼容后续的tools/list和tools/call都会失败。握手完成后客户端会发notifications/initialized通知表示初始化完成。之后就可以正式调用了。整个链路是客户端发initialize请求服务端返回能力与版本信息客户端发initialized通知客户端发tools/list拉取工具清单客户端根据清单构建工具调用能力客户端发tools/call执行具体工具4.2 tools/list 返回什么Agent 才能自动发现tools/list返回的是一个工具数组每个元素包含 name、description、inputSchema。Agent 拿到这个数组后会把它转换成自己内部的可调用函数表示。不同 Agent 框架的转换方式不同但核心都是把 description 和 schema 拼进模型的上下文让模型知道有这么些工具可用。这里有个实操细节工具数量会影响上下文长度。19 个工具的完整描述拼起来可能有好几千 token如果 Agent 的上下文预算紧张会挤占对话空间。我的做法是给每个工具的 description 控制在 100 字以内schema 里只保留必要字段把详细文档放到工具执行时的错误提示里按需返回。4.3 自动接入的两种模式Agent 自动接入 Termexo 有两种常见模式各有适用场景。模式一启动时全量拉取。Agent 启动时调一次tools/list把 19 个工具全部加载进上下文。优点是调用时无需再查询延迟低缺点是占用上下文且工具更新后需要重启 Agent 才能感知。模式二按需动态发现。Agent 先只加载工具分类的摘要真正需要某类能力时再调tools/list拉取该类工具的详情。优点是上下文占用小、支持热更新缺点是实现复杂且模型需要多一轮规划。我实测下来如果 Agent 的上下文窗口在 32k 以上直接用模式一更省心如果窗口紧张或者工具会频繁变动模式二更合适。Termexo 本身对两种模式都支持因为它只是按协议返回数据怎么用是客户端的事。5. 实操中真正会卡住你的几个坑5.1 路径解析的基准目录问题这是最高频的坑。Agent 传过来的路径可能是相对路径而你的服务进程的工作目录和用户以为的工作目录往往不一致。我踩过一次Agent 传了./logs/app.log服务进程的工作目录是安装目录结果读到了完全无关的文件。解决方案是在服务启动时显式固定一个工作目录并在所有路径类工具的 description 里写明相对路径基于此目录解析。同时对传入的路径做一次规范化resolve把..和符号链接都处理掉避免路径穿越。5.2 长输出把上下文撑爆执行 shell 命令、读取大文件这类工具输出可能非常长。如果不做限制一次调用就能把 Agent 的上下文塞满后续对话直接崩。我的做法是给所有可能产生长输出的工具加一个max_bytes或max_lines参数默认值设一个保守的数比如 64KB并在 description 里提示输出被截断时请用更精确的参数重试。5.3 危险操作的确认机制执行层工具跑命令、删文件风险最高。MCP 协议本身没有内置的确认机制但你可以通过工具设计来实现把危险操作拆成预检和执行两步预检工具返回将要执行的操作描述Agent 把它展示给用户确认后再调执行工具。这样既符合协议又给了用户拦截的机会。提示不要指望模型自己会谨慎。模型在完成任务的驱动下倾向于直接调执行工具。确认机制必须做在工具层面而不是靠提示词约束。5.4 并发调用时的状态冲突Agent 可能会并行调用多个工具比如同时读两个文件。如果你的工具实现里有共享的可变状态比如一个全局的当前目录变量并发时就会互相干扰。原则是工具实现尽量无状态所有上下文通过参数传入需要共享的状态放到请求级别而不是进程级别。6. 把 19 个工具串成工作流几个真实场景的调用链6.1 场景一清理项目临时文件用户说帮我把这个项目里的临时文件清掉。Agent 的调用链大致是先调感知层的目录列举工具扫描出*.tmp、*.log、__pycache__等再调协同层的过滤工具筛出目标然后调操作层的删除工具逐个删除最后调感知层工具复查确认。整个链路里Agent 自己完成了扫描—筛选—删除—验证的规划你只给了一句自然语言指令。这个场景能跑通的关键是感知层工具返回的目录结构要足够结构化比如返回 JSON 数组而不是纯文本Agent 才能可靠地做后续筛选。6.2 场景二批量重命名并生成报告用户说把这个目录下的图片按拍摄日期重命名并给我一份对照表。Agent 会先调感知层获取文件列表和元数据再调协同层的日期解析工具然后调操作层的重命名工具最后调协同层的表格生成工具输出对照表。这里协同层的工具是幂等的即使 Agent 重试也不会产生副作用这是设计时刻意保证的。6.3 场景三跑测试并分析失败原因用户说跑一下测试看看哪些挂了。Agent 调执行层工具跑测试命令拿到输出后如果输出很长会先调协同层的文本过滤工具提取失败用例再调感知层工具读取相关日志文件最后汇总给用户。这个链路里执行层工具的输出截断策略很关键——如果测试输出被截断在关键信息之前Agent 就得重跑浪费一轮。7. 工具数量继续增长时怎么保持 Agent 不选择困难19 个工具已经不算少了如果后续扩展到 50 个、100 个Agent 的工具选择准确率会下降。我在设计时预留了几个应对手段。第一是命名前缀分组。工具名用fs_、proc_、exec_、util_这样的前缀分组模型在规划时能通过前缀快速缩小范围。第二是description 里写什么时候不要用我主动把模型引导到更合适的工具上。第三是分层加载前面提到的按需动态发现模式在工具数量大时几乎是必须的。还有一个经验定期清理僵尸工具。有些工具上线后调用率极低可能是 description 写得不好也可能是职责和其他工具重叠。与其留着干扰模型不如合并或下线。我每隔一段时间会看一遍工具的调用日志把长期零调用的工具拿出来重新评估。8. 我在实际搭建中总结的几条硬经验第一条先把感知层做扎实。很多人的顺序是反的先做执行工具结果 Agent 因为看不见环境而乱调。感知层是地基地基不稳上面全是空中楼阁。第二条description 要反复打磨。我每个工具的 description 平均改了五遍以上每次都是拿真实任务去测看 Agent 有没有选对工具。选错了就回去改 description而不是改模型或改提示词。第三条错误信息是给模型看的第二份文档。工具执行失败时返回的错误信息模型会当成上下文的一部分来决策。所以错误信息要写得像 description 一样讲究告诉模型为什么失败、可以怎么补救。第四条别怕工具多怕的是职责不清。19 个工具如果每个职责都清晰Agent 用起来很顺3 个工具如果职责重叠Agent 反而懵。工具设计的核心不是数量是边界。第五条留一个逃生舱工具。我保留了一个通用的 shell 执行工具作为兜底当 Agent 发现现有工具都搞不定时可以用它。但这个工具的 description 里明确写了优先使用专用工具仅在其他工具都无法完成时使用避免它被滥用。这套东西搭下来最大的感受是Agent 能不能真正干活不取决于模型多聪明而取决于你给它的工具接口设计得多清楚。Termexo 的 19 个工具只是载体背后那套让 Agent 看得见、选得对、调得动的设计思路才是真正值得复用的部分。后续如果要把这套模式迁移到别的领域比如设计工具、数据分析工具思路是一样的先分层、再定粒度、然后死磕 description最后用真实任务反复验证。