最近接了个需求,要让 OpenClaw 定期读取本地一个旧项目管理工具导出的数据文件,再在工作区里自动生成周报。翻遍现成的 MCP 仓库,工具一大堆,但大多数要么绑死了某个云服务,要么只适配特定 AI 客户端,没有一个是能直接吃我这套本地文件的。折腾到半夜,我决定不找现成了,自己动手写一个 MCP 服务。
几天下来,从对 MCP 协议一知半解,到在 OpenClaw 里稳定跑通自定义工具,整个过程踩了不少坑,也把协议的几个关键点彻底搞明白了。这篇文章就把这套流程完整写出来:MCP 和 OpenClaw 各自解决什么问题、开发环境怎么准备、协议核心逻辑是什么、怎么手写一个服务端、怎么接进 OpenClaw,以及实际调试中最容易卡住的几个地方。
适合两类人读:一类是想给 OpenClaw 加自定义工具但不知道从哪下手的;另一类是看过 MCP 文档、觉得大概懂了,但真到自己写的时候发现离跑通还差一截的。看完这篇文章,你应该能少踩一大半我踩过的坑。
1. 先弄清楚 MCP 和 OpenClaw 的分工,再决定从零造轮子
很多朋友找我聊 OpenClaw 开发,开口第一句就是"OpenClaw 支持 MCP 了,怎么装?"。这个问法其实暴露了一个常见误区:把 OpenClaw 和 MCP 当成同一个层次的东西。实际上它们是两层东西,一个相当于"协议标准",一个相当于"协议的使用方"。不把这两者的关系理清,后面写起工具来很容易被绕晕。
1.1 MCP 到底是什么,它和"插件"的本质区别
MCP 全称 Model Context Protocol,模型上下文协议,是 Anthropic 在 2024 年底提出的一个开放协议。它的核心目标特别简单:让 AI 模型能够用标准化的方式调用外部工具、读取外部数据源。你可以把它理解成 AI 应用领域的"USB 接口标准"——每个设备(工具)只要按照 USB 规范设计,插到任何电脑上都能被识别和使用。MCP 就是给 AI 模型外接各种能力的通用接口协议。
很多人第一次接触 MCP,下意识会把它类比成"AI 插件"。这个类比只能算对了三分之一。插件的特点是跟宿主强绑定:Chrome 插件只能在 Chrome 里用,换成 Edge 可能就废了。MCP 不一样,它是客户端和服务端解耦的。客户端是 Claude Desktop、OpenClaw、Cursor 这类支持 MCP 的应用,服务端是独立运行的工具进程。只要服务端实现了 MCP 协议,任何支持 MCP 的客户端都能把它拉起来用。
这意味着一个非常实际的收益:我这次为 OpenClaw 写的工具服务,以后如果我想换到别的 AI 客户端,协议层完全不用改,只需要改一下客户端的连接配置。这种一次开发、多处复用的特性,是传统插件机制给不了的。
还有一个经常和 MCP 混在一起的概念是 Computer Use。热搜里也有人问"computer use 和 mcp 的区别",这里我多说一句:Computer Use 是 AI 直接操作电脑的一套能力,截屏、识别界面控件、移动鼠标、点击输入,本质是"替你在操作系统里干活";MCP 则是"允许你调用某个具体能力"的标准接口。打个比方,Computer Use 是让 AI 亲自上手操作电脑,MCP 是给 AI 插上一堆专用工具。两者可以配合用,但不是一回事。
1.2 OpenClaw 在整条链路里的位置
OpenClaw 是一个服务端形态的 AI 自动化代理运行时。它做的事情包括:管理多个大模型提供方、维护工作区文件、执行本地命令、调度技能(Skills),以及通过 MCP 协议连接外部工具。用一句话概括:它负责"做决策",MCP 工具负责"干具体的事"。
具体到一次任务执行,大致是这个流程:用户给 OpenClaw 一个目标,OpenClaw 内部的大模型把目标拆解成步骤,然后判断每一步需要调用什么外部能力,如果需要,就通过 MCP 协议调用对应的工具服务,拿到结果后继续下一步决策。
这里有个很关键的认知:OpenClaw 本身不实现具体的业务逻辑,比如"扫描工作区文件""查询数据库""操作某个内部系统",这些都得靠工具提供。MCP 的价值就在于把这些工具标准化,让 OpenClaw 可以用统一的协议去调用,不用为每个工具单独写一套适配代码。
另外还有一个很多人问的问题:OpenClaw 和 ClawHub 有什么区别?简单说,OpenClaw 是运行时本体,ClawHub 是围绕它建立的一个分享生态,类似应用商店,里面放着别人打包好的技能和工具配置。你可以从 ClawHub 拉现成的来用,但真正贴合自己业务的工具,往往还得自己写,这也是这篇文章存在的意义。
1.3 为什么必须自己写工具,而不是攒一堆现成的
先摆结论:现成的 MCP 工具当然可以用,但遇到以下三种情况,自己写几乎是唯一选择。
第一,现成工具质量参差不齐。MCP 生态还处于早期,社区里大量工具是开发者几天内赶出来的,README 写得漂亮,实际一调就发现边界情况完全没处理。我测试过好几个号称"文件处理"的 MCP 服务,连中文路径都处理不好,更不要提大文件流式读取。
第二,数据安全问题。你要处理的数据往往在公司内网、在本地磁盘,甚至涉及不能出内网的敏感信息。把这些数据交给一个第三方维护的 MCP 服务,风险是不可控的。自己写一个本地跑的服务端,数据全程不出本机,心理踏实得多。
第三,也是最核心的一点:只有你自己知道业务长什么样。你需要的是什么数据格式、什么目录结构、什么过滤规则,这些细节只有你清楚,通用工具不可能覆盖到。MCP 服务端的开发成本其实很低,一个标准工具也就是几十到几百行代码的事,相比你花几个晚上去适配别人工具的时间,自己写反而更快。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境搭建:从命令行报错到模型接通的完整准备
OpenClaw 和 MCP 的关系理清之后,下一步就是先把环境搭起来。这一节我把从安装到模型配置的关键步骤过一遍,重点讲实操中最容易踩的坑,尤其是 Windows 环境下那一堆莫名其妙的报错。
2.1 安装 OpenClaw:Windows 上最典型的坑
如果你在 Windows 上用 PowerShell 执行安装命令后,敲 openclaw 却收到这样的报错:
code复制openclaw : 无法将“openclaw”项识别为 cmdlet、函数、脚本文件或可运行程序的名称
不用慌,这是典型的"可执行文件不在 PATH 里"导致的。出现这个问题的原因通常是两种:一是安装过程中 PATH 环境变量没刷新,重开一个终端窗口就好了;二是安装器把可执行文件放到了某个目录,但没加进系统 PATH。第二种情况需要你手动把对应目录加到环境变量里。
装的时候还会遇到一个问题:安装路径能不能指定?答案是绝大多数情况可以。Windows 下使用 PowerShell 安装脚本时,通常支持 -InstallDir 之类的参数来指定目录。如果你用的是免安装的便携包,解压到任意目录后,记得把解压目录手动加入 PATH。
另外提一句,如果你是 Linux 服务器,安装后看到提示可能存在旧版本的权限审批文件:
code复制legacy exec approvals exist at /root/.openclaw/exec-approvals.json
这是 OpenClaw 升级后把历史审批记录迁移到了新格式,不影响正常使用,可以手动删除或保留,看你自己习惯。
还有一个容易被忽略的点:OpenClaw 有稳定版和开发版两个更新渠道,命令是 openclaw update --channel stable 或 openclaw update --channel dev。作为日常开发,建议用稳定版;如果你需要测试新功能,再切换到 dev 渠道。不要长期留在 dev 渠道,我遇到过 dev 版本和 MCP SDK 协议版本不一致导致工具连接失败的情况,排查了半天,最后发现是渠道的问题。
2.2 模型接入:免费模型和 NVIDIA NIM 的配置思路
OpenClaw 本身没有推理能力,它需要接一个大模型后端来干活。好在它支持 OpenAI 兼容的接口协议,这意味着绝大多数模型服务都能接进来,不管对方是云端 API 还是本地部署的推理服务,本质上都是配置一个 baseURL 加一个 API Key。
具体的配置思路是这样:在 OpenClaw 的配置文件中指定模型提供方,把接口地址指向你的模型服务地址,填上密钥,然后指定一个具体的模型名称。如果你手头没有付费模型的额度,完全可以先用那些提供免费额度的托管服务,或者本地部署轻量模型跑起来。我第一次验证 MCP 工具能不能被模型调用时,用的就是一个免费模型的额度,完全够用。
如果你有 NVIDIA NIM 相关的环境,配置逻辑也一样。NVIDIA NIM 提供的是预优化过的推理微服务,对外暴露的就是 OpenAI 兼容接口。在 OpenClaw 配置里把 provider 指向 NIM 的 endpoint,就能直接使用。这类微服务部署方式很适合在本地或内网跑,和 MCP 服务配合起来很顺畅。
不过这里有个经验要分享:免费模型和轻量模型在工具调用上的表现差距非常大。工具调用(Function Calling)依赖模型的理解能力和结构化输出能力,太轻量的模型经常出现"明明有这个工具,就是不调用"或者"调用时参数乱传"的情况。如果你发现 OpenClaw 里 MCP 工具一直不被触发,先排查模型,别一上来就怀疑自己的代码。
2.3 workspace 目录和配置结构
安装完成后,OpenClaw 会在用户目录下生成一个 .openclaw 文件夹,这里面就是它的运行空间。最常见的默认工作区路径长这样:
code复制/root/.openclaw/workspace (Linux)
c:\users\administrator\.openclaw\workspace (Windows)
这个 workspace 目录就是 OpenClaw 的"桌面",它读写文件的默认范围都集中在这里。自己写的 MCP 工具,如果涉及文件操作,建议默认也把工作目录限定在这个 workspace 下,这样权限模型最清晰,也方便管理和备份。
.openclaw 目录下值得注意的结构大概是这样:
workspace/工作区目录,存放项目的所有工作文件skills/技能目录,放一些可复用的行为方案exec-approvals.json命令审批记录,记录哪些命令被允许自动执行- 主配置文件 全局配置,包含模型、MCP 服务等设置
对做 MCP 开发的人来说,最重要的就是主配置文件和 exec-approvals.json,后面接入工具时都要用到。
2.4 exec-approvals.json 在权限体系里的角色
这个文件值得单独说一下。OpenClaw 对命令执行有一套审批机制:当它打算在本地执行某个 shell 命令时,会先查这个命令是否在审批白名单里,如果不在,就停下来等用户确认,确认过的命令会记录到 exec-approvals.json 里,下次同类操作就可以自动放行。
这套机制对 MCP 工具也很重要。你的 MCP 服务如果内部调用了 shell 命令(比如用 child_process 去跑脚本),这部分操作同样会被 OpenClaw 的审批机制拦截。正确做法不是想着绕过它,而是在设计 MCP 工具时尽量保持最小权限,只做必要的文件读取和结构化输出,真正需要执行命令的环节再交给技能编排去处理。这样权限边界清晰,后续维护也省心。
3. MCP 协议的关键骨架:三种原语、两种传输和一次标准调用
环境准备好之后,先别急着写代码。花十分钟把 MCP 协议本身的核心机制搞清楚,后面写起来会顺很多。这一节我用最直白的方式拆解协议骨架。
3.1 三种原语:Tool、Resource、Prompt
MCP 协议定义了三个核心原语,分别是 Tool、Resource 和 Prompt。理解这三个东西,就理解了 MCP 的大部分。
- Tool(工具):可以被模型主动调用的函数。每个工具必须有一个名字、一段描述、一个 JSON Schema 格式的输入参数定义,以及一个结构化的返回值。Tool 是动词,它代表"做什么"。比如"扫描目录""查数据库""发请求"。
- Resource(资源):可被读取的数据来源,比如文件内容、数据库记录、API 返回的数据。Resource 是名词,它代表"有什么数据可以读"。模型可以通过读取 Resource 来获取上下文。
- Prompt(提示词模板):预定义好的提示词,方便用户或模型快速触发一个固定的工作流。比如"总结周报"就是一个模板,模型拿到后知道该按什么格式去总结。
实际开发中,我们 90% 的时间都在写 Tool。Resource 用的频率会低一些,但在文件类工具里很实用。Prompt 更多是配合技能去用。
3.2 两种传输方式:stdio 和 Streamable HTTP
MCP 客户端和服务端之间的通信有两种主流传输方式,对开发者的影响很直接:本地开发用哪种,云端部署用哪种。
stdio 方式:客户端把 MCP 服务作为一个子进程拉起来,通过标准输入输出传递 JSON-RPC 2.0 格式的消息。这种方式的好处是轻量、不需要开端口、天然适合本地工具,OpenClaw 默认就是这种方式。缺点是服务进程的生命周期由客户端管理,不方便跨机器访问。
Streamable HTTP 方式:服务端自己起一个 HTTP 服务,监听某个端口,客户端通过网络请求来调用。这种方式适合服务端和客户端不在同一台机器上的场景,比如云端部署。缺点是多了网络层,需要考虑鉴权、超时、并发等问题。
| 传输方式 | 适用场景 | 优点 | 需要考虑的问题 |
|---|---|---|---|
| stdio | 本地工具、同机进程 | 轻量、无网络层开销 | 进程生命周期由客户端管理 |
| HTTP | 云端服务、跨机调用 | 可远程访问、独立部署 | 鉴权、超时、端口管理 |
对绝大多数 OpenClaw 本地开发场景,用 stdio 就够了。我自己写工具时默认都是 stdio,只有需要共享给团队其他成员远程使用,我才会改成 HTTP 模式。
3.3 一次工具调用的完整生命周期
标准的 MCP 工具调用,大致经过这么几个阶段。理解这个流程,对你排查问题非常有帮助。
第一步,客户端启动服务进程(stdio 模式)或建立连接(HTTP 模式)。第二步,客户端发送 initialize 请求,服务端返回协议版本和自己的能力列表,也就是 capabilities。第三步,双方确认无误后,客户端发送 initialized 通知。第四步,客户端发送 tools/list 请求,拿到该服务端提供的全部工具清单,包括每个工具的描述和参数 Schema。
前四步都完成后,真正干活的时候到了。第五步,大模型根据用户的需求和工具清单做"意图匹配",选出一个合适的工具,通过 tools/call 请求把参数传过去。第六步,服务端执行业务逻辑,返回结果。
一个 tools/call 请求的 JSON-RPC 格式大致长这样:
json复制{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "scan_workspace",
"arguments": {
"path": "D:/work",
"maxDepth": 3
}
}
}
服务端处理完后的返回,核心是 content 字段,里面放真正的工具输出内容。整个过程中,协议本身不负责"理解用户意图",它只负责把模型选定的工具调用准确送达,再把结果准确带回来。意图识别是靠模型完成的。
3.4 工具描述写得越细,模型用得越准
在实际开发中我发现,决定一个 MCP 工具好不好用的关键,很多时候不是逻辑代码写得多漂亮,而是工具的描述和参数 Schema 写得好不好。
大模型选择工具时,靠的就是工具名、描述、参数说明这三个信息。如果你的描述语焉不详,模型就不确定该不该调用、什么时候调用;如果参数说明不清晰,模型就不知道该填什么值。这两个问题加起来,就会变成你看到的最常见的现象——工具在列表里,但模型就是不调它,或者调了但参数乱传。
我举个正反例。假设你要写一个读取文件内容的工具:
text复制差的描述:读取文件内容。
好的描述:读取指定路径的文本文件内容,适用于需要查看源码、日志、配置文件内容的场景。支持绝对路径和相对路径,相对路径会基于工作区根目录解析。文件过大时建议结合范围参数。
后者给模型提供了足够多的决策信息:什么场景用、路径怎么传、有什么限制。这些细节直接决定了工具在真实场景里的调用成功率。记住一句话:你写给模型看的描述,要比写给开发者看的文档更细致。
4. 手写"工作区文件索引"MCP 服务:完整代码与调试过程
理论部分差不多了,现在进入正题:从零手写一个 MCP 服务端。我先选一个非常实用的场景——给 OpenClaw 写一个"工作区文件索引"工具,作用是扫描指定目录、输出文件清单和大小,让 AI 快速了解工作区里有什么。这个工具无论做项目管理、代码分析还是周报生成,都用得上。
4.1 技术选型:为什么选 TypeScript + 官方 SDK
MCP 官方提供 TypeScript 和 Python 两套 SDK,我这次选了 TypeScript。原因有三点:第一,官方对 TypeScript SDK 的维护最积极,协议新特性往往先在 TS 版落地;第二,TypeScript 的类型系统对 JSON Schema 的支持很自然,写参数定义的时候不容易出错;第三,stdio 模式下 TS 编译后的产物可以直接用 Node 执行,部署简单,跨平台没有 Python 环境那种依赖问题。
如果你更熟悉 Python,用官方 Python SDK 也完全没问题,开发体验差距不大。我的建议是,你的项目栈是什么语言就用什么 SDK,不用特意为了 MCP 换语言。
先初始化项目:
bash复制mkdir workspace-indexer
cd workspace-indexer
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
这里装 zod 是因为 MCP 的 TypeScript SDK 用它来定义工具输入参数的校验规则,可以直接把 zod schema 转成 JSON Schema。
4.2 核心代码:注册一个 scan_workspace 工具
下面是最核心的代码文件 src/index.ts。我先用一个工具跑通全流程,再逐步扩展。
typescript复制import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { promises as fs } from "fs";
import path from "path";
const server = new McpServer({
name: "workspace-indexer",
version: "0.1.0"
});
server.registerTool(
"scan_workspace",
{
title: "扫描工作区文件",
description:
"递归扫描指定目录,输出文件路径、大小和总文件数,用于快速了解一个目录的内容结构。适用于查看工作区里有什么文件、文件多大、目录层级是什么样。",
inputSchema: {
path: z.string().optional().describe("要扫描的目录路径,默认是OpenClaw工作区根目录"),
maxDepth: z.number().optional().default(3).describe("扫描的最大目录深度,防止递归过深,默认3层")
}
},
async ({ path: scanPath, maxDepth }) => {
const root = scanPath || process.cwd();
const result = { root, files: [], totalFiles: 0, totalSize: 0, skippedDirs: 0 };
async function walk(dir: string, depth: number) {
if (depth > maxDepth!) return;
let entries;
try {
entries = await fs.readdir(dir, { withFileTypes: true });
} catch (e) {
return;
}
for (const entry of entries) {
// 跳过常见无关目录,避免噪音
if (entry.isDirectory() && [".git", "node_modules", "dist", "build", ".cache"].includes(entry.name)) {
result.skippedDirs++;
continue;
}
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
await walk(fullPath, depth + 1);
} else {
const stat = await fs.stat(fullPath);
result.files.push({
path: fullPath,
size: stat.size,
modified: stat.mtime.toISOString()
});
result.totalSize += stat.size;
result.totalFiles++;
}
}
}
await walk(root, 0);
return {
content: [
{
type: "text",
text: JSON.stringify(result, null, 2)
}
]
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
这里用到了 registerTool 方法,传入四个部分:工具名、工具描述和参数 Schema、以及实际执行的异步函数。如果将来你的 SDK 版本提示没有 registerTool,改用 server.tool(...) 也是一样的效果,只是写法差异。
执行逻辑本身不复杂,就是递归遍历目录,收集文件信息和大小。有个细节值得注意:我主动跳过了 .git、node_modules、dist 这些目录。这些目录文件数量庞大,会让模型一次看到一大堆没用的信息,而且会大幅拖慢扫描速度。MCP 工具的输出会完整进入模型上下文,上下文空间是很宝贵的,能过滤的噪音一定要过滤。
4.3 本地验证:用 MCP Inspector 调试工具
代码写完了,先别急着接 OpenClaw。MCP 官方提供一个叫 MCP Inspector 的调试工具,可以独立于任何 AI 客户端测试你的服务端。这一步非常关键,能帮你把"工具本身的问题"和"客户端配置的问题"隔离开。
先编译代码,然后用 Inspector 启动:
bash复制npx tsc
npx @modelcontextprotocol/inspector node dist/index.js
启动后浏览器会打开一个调试面板,你可以看到 tools/list 返回的工具列表、手动用 tools/call 调用 scan_workspace 并输入参数。我习惯在这里先把工具的正常路径和边界情况都测一遍:空目录、不存在路径、超深目录、文件非常多的情况。确认都正常了,再往下接 OpenClaw。
调试过程中有个环境相关的坑要提醒你:stdio 模式下,服务端的 console.log 输出会污染标准输出,导致协议通信解析失败。正确做法是把调试信息写到标准错误流 console.error 上,这样既能看到日志,又不干扰协议通信。
4.4 给工具加一个 Resource:读取文件内容
光能扫描目录还不够,如果模型想进一步查看某个文件的内容,还需要一个读取文件的接口。这一步我用 Resource 来实现,演示一下三种原语里 Resource 的实际用法。
typescript复制server.registerResource(
{
name: "workspace-file",
description: "读取工作区内的文本文件内容",
mimeType: "text/plain",
uriTemplate: "file:///workspace/{filename}",
parameters: {
filename: z.string().describe("相对于工作区根目录的文件路径")
}
},
async (uri, params) => {
const safePath = path.resolve(rootDir, params.filename);
// 检查路径是否在工作区内,防止越权读取
if (!safePath.startsWith(rootDir)) {
throw new Error("路径越界,只能读取工作区内的文件");
}
const content = await fs.readFile(safePath, "utf-8");
return {
contents: [
{
uri: uri.href,
mimeType: "text/plain",
text: content
}
]
};
}
);
这里特别加了一个路径校验,防止模型拼出 ../../etc/passwd 这类路径去读工作区之外的文件。这类防护是 MCP 工具开发的必备意识:你的工具暴露给模型的本质是"能力边界",边界一定要画清楚。
5. 接入 OpenClaw:配置、权限审批与连接故障排查
工具在本机已经能跑了,接下来是最关键的一步:让 OpenClaw 把这个工具用起来。这一节是实操性最强的内容,把配置方法、验证方式以及最常见的几个故障全过一遍。
5.1 MCP 服务配置:两种方式任选
OpenClaw 接入 MCP 服务,通常有两种方式。一种是直接编辑主配置文件,在 mcpServers 字段下声明一个服务条目;另一种是用命令行工具 openclaw mcp add 交互式添加,适合不太想手动改文件的场景。
这两种方式最终生成的配置都是下面这个样子:
json复制{
"mcpServers": {
"workspace-indexer": {
"command": "node",
"args": ["D:/projects/workspace-indexer/dist/index.js"],
"env": {}
}
}
}
command 是启动服务进程的命令,args 是传给这个命令的参数,env 是额外的环境变量。Windows 下路径里的反斜杠记得转义,或者直接使用正斜杠 / 让 Node 自行处理。
配置完成后重启 OpenClaw,让配置生效。然后关键一步:验证服务是否真的被拉起了。OpenClaw 启动日志里一般会打印 MCP server 的连接状态,看到类似 "MCP server connected" 的日志,说明连接成功。
5.2 验证工具是否生效:让模型自己尝试调用
连接成功不等于工具一定能被模型正确使用。我习惯用一句相当直白的话来验证:对 OpenClaw 说"扫描一下我的工作区目录,告诉我里面有什么"。如果工具真的被模型加载并且理解了它的用途,模型会主动调用 scan_workspace,然后基于返回结果回答你。
如果模型回答"我没有找到相关工具",或者答非所问,先别急。可以再用一句话引导:"你当前有哪些工具可以用?列出所有可用的 MCP 工具及其描述。"这时候模型如果列出了工具列表,说明工具加载没问题,问题出在它的意图识别上——大概率是工具描述写得不够清楚,回头改描述就好。
另外一个常见的验证方法是直接用 CLI 工具测试连接状态。OpenClaw 提供命令行接口可以列出当前已加载的 MCP 工具,具体命令各版本略有差异,在命令行里输入 openclaw mcp --help 就能看到当前版本的用法。
5.3 连接故障排查:这几个坑最常见
我把自己遇到的和帮别人排查的典型故障整理成了一张表,你在接入时如果碰到类似问题,直接对照排查:
| 故障现象 | 根本原因 | 解决办法 |
|---|---|---|
日志报 spawn node ENOENT |
OpenClaw 找不到 Node 可执行文件 | 确认 Node 在 PATH 中,或配置 env 里显式指定 PATH |
| 工具列表为空 | 服务进程启动失败或崩溃 | 先用 MCP Inspector 单独跑一遍,确认服务端本身正常 |
| 调用工具请求超时 | 服务端阻塞操作耗时太长,超过默认超时 | 给长任务增加进度反馈,或提高客户端超时设置 |
| 模型不调用工具 | 工具描述不清晰,或模型对工具调用支持差 | 优化描述;换用工具调用能力更强的模型 |
| 工具返回内容过大 | 输出超过模型上下文限制 | 在工具内部做截断、过滤,只返回必要信息 |
第一类问题最隐蔽。OpenClaw 如果是通过桌面端或服务方式启动的,它继承的环境变量可能和你终端里的不一样,导致它找不到 Node。解决办法是在 MCP 服务配置的 env 字段里显式把 PATH 环境变量传进去,确保 spawn 能正确解析到 node 命令。
5.4 exec-approvals 在 MCP 场景下的实际约束
我在前面提到了 exec-approvals.json 这个权限审批文件,在接入 MCP 工具后,它的作用会变得非常具体。
如果你的 MCP 工具内部没有执行 shell 命令,权限审批机制不会介入,OpenClaw 直接通过 MCP 协议调用工具函数,顺畅运行。但如果你的工具内部用了 child_process 去执行命令行程序——比如工具要调 ffmpeg 处理视频、或调 git 命令做版本操作——OpenClaw 的审批机制就会介入,需要用户手动确认这些命令是否允许执行。
我强烈建议,MCP 工具内部尽量少做"执行 shell 命令"这类高权限操作。把工具职责聚焦在"获取数据""整理数据""输出结构化结果"上,需要执行命令的活儿留给 OpenClaw 自身的技能编排去处理。这样权限模型干净很多,也不容易出现审批弹窗打断自动化流程的情况。
6. 进阶玩法:让 MCP 工具被 Skill 调度,实现多工具协作
工具接进来了,OpenClaw 能用它了。但单工具只是个开始,真正提效的场景是让多个工具配合、再由技能(Skill)把整套流程编排起来。这一节聊聊进阶用法。
6.1 Skill 是什么,和 MCP 工具的角色差异
前面我提到过 OpenClaw 的 skills/ 目录,里面放的是"技能"。Skill 本质上是一份结构化的行为方案文档,内容包括:这个技能用于什么场景、需要经过哪些步骤、每一步应该调用什么工具、最终的输出格式是什么。它会被作为提示词的一部分交给模型,让模型按照既定的流程去执行。
可以把 Skill 理解成"工作流剧本",MCP 工具理解成"舞台道具"。剧本负责决定什么时候用哪一个道具、道具拿来干什么,道具本身只负责把具体的事情干好。
有朋友问"skills 如何调用 mcp 工具",其实这背后的机制比我最初想象的要简单。Skill 本身是一个提示词文件,你在里面写清楚"第一步,调用 scan_workspace 工具扫描目录;第二步,调用读取文件资源查看内容",模型读到这些指令后,会自然地触发对应的 MCP 工具。它靠的是模型的能力,而不是代码层面的联动。
6.2 实战场景:周报自动生成流程
举个例子。我最初的需求是让 OpenClaw 读取旧项目管理工具的数据文件,自动生成周报。整个流程拆解下来,涉及三个工具能力的配合:
第一步,用 scan_workspace 扫描工作区,确认哪些数据文件存在、最近有没有更新。第二步,用 workspace-file 资源读取最新的数据文件内容。第三步,让模型基于读取到的内容,按周报模板生成结构化文档,写入工作区。
这三步里,前两步都是 MCP 工具在干活,第三步是 OpenClaw 自身的能力。我只需要写一个 Skill 文档,把这三步的顺序、每个步骤的要点、最终输出的格式约定写清楚,OpenClaw 就能按部就班地执行。
Skill 文档的一个简化示例,用的是 Markdown 格式,开头标注用途和触发条件,正文写步骤、工具调用要点和输出格式:
markdown复制---
name: generate-weekly-report
description: 基于工作区中的项目数据文件生成周报,适用于每周五下午自动执行。
---
# 周报生成技能
## 执行步骤
1. 调用 scan_workspace 工具,扫描工作区根目录,确认 data 目录下的周数据文件是否存在,记录文件更新时间。
2. 调用 workspace-file 资源,读取 data/weekly-data.json 的内容。
3. 读取完成后,结合数据中的项目进度、任务状态,按周报模板生成中文周报。
4. 将周报保存为工作区根目录下的 weekly-report.md。
一个很关键的细节:Skill 文档里的工具名必须和 MCP 工具注册时的名字完全一致,描述要跟工具的描述对应上,这样模型才能准确识别"这个步骤应该调用哪个工具"。我写 Skill 的时候,会先跑一遍 openclaw mcp --list 看看当前实际加载的工具名,避免凭记忆写错。
6.3 多工具协同的经验:原子化和明确边界
做过几个 Skill 之后,我总结出几条非常实际的经验,分享给你。
第一,工具要原子化。不要写一个"全能工具"把所有功能塞进去,每增加一个参数,模型出错的可能性就高一分。把功能拆成一个个单一职责的工具,让模型像搭积木一样组合它们,正确率会高很多。我最初把"扫描+读取+统计"写成了一个工具,结果模型经常漏传部分参数,拆成两个工具后,调用成功率明显上升。
第二,工具返回值要尽量精简且结构化。我之前提到过,工具的返回内容会完整进入模型的上下文窗口,上下文窗口是有成本上限的。如果一个工具返回一大堆无关字段,模型处理起来既慢又容易糊涂。我的做法是:返回 JSON 里只保留模型做决策需要的字段,其他数据一律不返回。
第三,错误信息也是给模型看的。工具内部出错时返回的错误信息,模型是能读到的,它会根据错误信息调整策略。所以错误信息的写法也应该面向模型:指出问题原因、给出可能的解决办法。比如"目录不存在,请检查路径是否完整",比一句简单的"扫描失败"有用得多。
第四,也是最容易被忽略的:给工具的输出加一个"总结提示"。我写工具时,会在返回的 JSON 最前面加一行简短的 summary,比如"共扫描 128 个文件,总大小 25MB,其中 data 目录下的 weekly-data.json 最近有更新"。这样模型即使不逐条读完整 JSON,也能靠这行 summary 快速形成判断,然后决定下一步动作。这个小习惯帮我节省了大量上下文空间,也明显提高了任务执行效率。
7. 写在工具上线的最后:个人操作体会
这一套流程走完,回头再看 OpenClaw 和 MCP 的组合,其实就是两句话:OpenClaw 负责把大模型的能力接进来做决策,MCP 负责把工具能力标准化地交到模型手上。把这两层分开理解,开发时思路会清晰很多,遇到问题也知道该去排查哪一层。
我实际用过一段时间后,最大的感触是工具描述和上下文输出的重要性不亚于工具本身的逻辑。很多人把精力花在写工具功能上,却忽略了写给模型看的那些说明文字。实际上,MCP 工具的用户不只是调用它的人,更是那个读描述、填参数、看输出的模型。把模型的体验当成产品体验来做,工具的可用性会上一个台阶。
最后再分享一个实用的小技巧:每写一个 MCP 工具,都顺手在旁边写一个对应的 Skill 文档模板。不需要多复杂,几行字说明这个工具适合什么场景、一般和哪些工具搭配使用就行。等工具多起来之后,你会发现这些文档就是你的知识库,也是团队协作时的说明书。这次从零到一完整跑通后,我给自己定了个规矩:工具代码可以粗糙,但描述和 Skill 文档一定不能省。好的工具生态,靠的不是一个完美的大工具,而是一堆边界清晰、协作顺畅的小工具,再加上能指挥它们的好剧本。
