自定义工具
MCP、Skill、Slash Command,三条路径分别在什么场景下用。
三条扩展路径
想让 Claude Code 干一件官方没内置的事,有三条路径能选。它们不是替代关系,而是各占不同抽象层。
MCP server:暴露一个新的原语。数据库、API、外部服务、专门的浏览器控制,这些都是原语。它们的共同特征是"提供了 Claude 之前完全够不到的能力"。写 MCP 意味着你在给 Claude 装一只新手。
Skill:打包一段有指导意义的工作流。工具已经有了,但怎么组合怎么用需要经验。skill 就是把这些经验写成 markdown,让 Claude 在遇到类似任务时按你的方法走。skill 不加新工具,只加"怎么用工具"的知识。
Slash command:给一段 prompt 或者一段 bash 起个快捷方式。它是最轻的一层,本质上是宏。适合那种"我经常要跟 Claude 说一模一样的话"或者"我经常要跑一模一样的命令"的场景。
怎么选
问自己三个问题:
- 我要接的这个能力,Claude 目前能不能做到?做不到就写 MCP,能做到就往下看。
- 我要让 Claude 学的是一套"怎么做"的方法论吗?是就写 skill。
- 我只是想少打字?那就写 slash command。
一个具体例子。假设你想让 Claude Code 帮你自动发飞书消息通知同事 PR 已 ready:
- 如果 Claude 现在没法调飞书 API,写 MCP server,让它有
send_lark_message工具。 - 如果 Claude 已经能调(通过 MCP 或者 bash curl),但你希望它每次通知都按公司模板走,写 skill。
- 如果只是想每次自己手动触发一句"@某人 PR ready 请评审",写 slash command。
三层可以叠着用:slash command 调 skill,skill 里用 MCP tool。
一个最小 MCP tool(Python FastMCP)
FastMCP 是 Python 生态里最简单的 MCP server 框架,装完写十几行就能跑。
pip install fastmcp假设你要写一个工具,让 Claude 能把一段文本翻成盲文点字(举个不太常见的例子避免和现成 server 撞车):
# braille_server.py
from fastmcp import FastMCP
BRAILLE_MAP = {
"a": "⠁", "b": "⠃", "c": "⠉",
"d": "⠙", "e": "⠑", " ": " ",
}
mcp = FastMCP("braille")
@mcp.tool()
def to_braille(text: str) -> str:
"""把英文文本转成 Unicode 盲文点字。"""
return "".join(BRAILLE_MAP.get(c.lower(), "?") for c in text)
if __name__ == "__main__":
mcp.run(transport="stdio")在 Claude Code 里注册:
{
"mcpServers": {
"braille": {
"type": "stdio",
"command": "python",
"args": ["/absolute/path/to/braille_server.py"]
}
}
}重启 Claude Code,工具会以 mcp__braille__to_braille 出现。
一个最小 MCP tool(TypeScript SDK)
如果项目本身是 Node 栈,用官方 TypeScript SDK 更顺手:
npm install @modelcontextprotocol/sdk zod// index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({ name: "hash", version: "0.1.0" });
server.tool(
"sha256",
{ text: z.string() },
async ({ text }) => {
const crypto = await import("node:crypto");
const hex = crypto.createHash("sha256").update(text).digest("hex");
return { content: [{ type: "text", text: hex }] };
},
);
await server.connect(new StdioServerTransport());注册方式跟 Python 那版一样,只是 command 换成 node dist/index.js。
Debug tips
自定义工具写完最容易踩的坑是 Claude Code 根本没起 server、或者起了但 tool 没暴露出来。几个必查点:
开 debug 模式:
claude --debug会把每一次 MCP 握手、每一次 tool 调用的 request 和 response 打到终端。看握手阶段如果 server 没回响应,多半是 command 路径写错或 Python 环境不对。
看日志文件:Claude Code 会把每次会话的完整日志落到本地。macOS 和 Linux 在 ~/.claude/logs/,Windows 在 %APPDATA%\claude\logs\。MCP server 的 stderr 也会汇总到这里。
手动跑一遍 server:MCP server 本质上是一个可执行程序,你可以脱离 Claude Code 直接跑它,往 stdin 发 {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}} 看它有没有回响应。回不了说明代码里有问题,跟 Claude Code 没关系。
权限没放行:新工具第一次调用时 Claude Code 会弹权限提示。如果你在 --dangerously-skip-permissions 模式跑,就不会弹但也不一定放行。稳妥做法是在 settings 的 permissions.allow 里手动加。
不要把 MCP server 当轻量脚本写
每次 Claude Code 启动都会拉起所有配置的 MCP server。如果你的 server 启动要 10 秒(拉大模型、连远程数据库),整个 CLI 打开都会慢。慢启动的服务改成 lazy connect,或者干脆挪到 skill 里用 bash 触发。
什么时候把工具升级成 skill
一个 MCP tool 单独用没问题,但 Claude 每次都得自己想清楚怎么串。串对了还好,串错了就掉坑。这种时候在 .claude/skills/ 下加一个 markdown,把这个工具的正确姿势写死,Claude 遇到相关任务时会自动加载这份指导。工具管能力,skill 管方法,两层结合才是完整的扩展。
三条路径对比表
三条路径的典型区别整理一下方便查:
| 维度 | MCP server | Skill | Slash Command |
|---|---|---|---|
| 提供什么 | 新工具、新原语 | 工作流、方法论 | 快捷入口 |
| 触发方式 | 主 agent 自主调用 | 关键词 / 描述自动加载 | 用户手动输入 /xxx |
| 上下文占用 | 只在调用时占 | 加载后常驻 | 展开时替换成 prompt |
| 需要写代码 | 是 | 否,只写 markdown | 否 |
| 分发难度 | 高(要打包发布) | 中(复制目录) | 低(复制文件) |
| 团队共享 | 通过 marketplace | .claude/skills/ 入 git | .claude/commands/ 入 git |
三条路径的组合玩法
三层可以互相调用,日常项目里很常见的一种组合是:
- 一个 MCP server 提供最底层能力,比如"查我们公司的用户信息服务"。
- 一个 skill 教 Claude 什么时候该查、查完之后按什么模板生成脱敏摘要。
- 一个 slash command
/user <id>作为快捷入口,展开成一段调用 skill 的自然语言 prompt。
这种分层的好处是:MCP 只关心底层协议、skill 只关心业务方法、command 只关心用户体验,三层各自独立演化。团队里加个新同事,他只要会用 /user 就行,底下的复杂度都被封装掉了。
版本管理
自定义工具一旦被团队用起来就是准生产系统。MCP server 建议正经打包发布(PyPI、npm 或者内网仓库),版本号跟 semver,配置里锁定具体版本别写 latest。skill 和 command 是 markdown,直接跟项目 git 走,改动通过 PR 评审。这几条做好之后,Claude Code 的扩展体系才能长期维护,不会一年之后没人敢动。
一条实用建议:写完先手动跑通
自定义工具最容易出的问题是"以为 Claude 会用其实不会"。写完不要直接扔给主 agent 让它自由发挥,先自己在会话里明确说一句"用工具 X 处理 Y",看它是不是走了预期路径。走通了再放到 skill 里写方法论,让 Claude 在无提示的情况下也能想起来用。这一步能省掉后面大量的调试时间。