
做了这么久 Claude Code,我发现大多数人对 MCP 的理解都停在「拿别人写好的工具来用」这一层。打开配置文件,粘一段 JSON,重启客户端,结束。这当然没错,但你只用到了 MCP 一半的能力。
MCP 真正值钱的地方是可扩展。你的内部 API、私有数据源、随手写的脚本,都能包装成一个 MCP Server,让 Claude 直接调用。我自己在 AI 编程的日常里反复体会到这点——一旦把领域工具接进来,Claude Code 就从通用编程助手变成了专属生产力。这篇我把亲手开发并发布一个 MCP Server 的过程完整记下来,从选题到 npm 上线,不绕弯子。
为什么我建议你自己写 MCP Server
官方那几个参考实现(filesystem、git、postgres)解决的是通用场景。但工作里你真正缺的往往是这些:
- 公司内网那套谁也不愿意碰的 REST API
- 某个垂直领域的数据源,气象、金融、医疗文献之类
- 你自己写的、只有你团队在用的 CLI 工具
这些东西官方不会管,社区也不一定有现成的。自己动手写一个,是把 AI 工作流接进自家业务最快的办法。门槛比很多人想的低。
选题:汇率查询 MCP Server
我挑了个够真实又不至于复杂的例子——调 Frankfurter API 这个免费汇率接口,做一个实时汇率查询工具。
选它的理由很直接:不用 API Key,开箱就能跑;真实涉及 HTTP 请求、参数处理、错误处理;发布完马上能在 Claude Code 里验证效果。
演示项目最怕为了简单而失真。这个例子在「够小」和「够真」之间卡得刚好。
环境准备
先确认环境:
node -v # 需要 18+
npm -v
npx @modelcontextprotocol/create-server --version # 检查 MCP SDK 可用
骨架不用手敲,直接在对话里跟 Claude Code 说清楚需求:
用 TypeScript 初始化一个 MCP Server 项目,
工具名 currency-exchange,
提供两个 tool:get_rate(查询两种货币的实时汇率)
和 list_currencies(列出所有支持的货币代码)
它会把项目结构、package.json、tsconfig.json 还有 src/index.ts 的骨架一并生成出来。这一步至少省掉二十分钟的样板代码时间,而且省下来的是最枯燥的那部分。
核心实现
在骨架基础上补完逻辑,完整的 src/index.ts 是这样:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
const BASE_URL = "https://api.frankfurter.app";
const server = new Server(
{ name: "currency-exchange", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
// 声明工具列表
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_rate",
description: "获取两种货币之间的实时汇率",
inputSchema: {
type: "object",
properties: {
from: { type: "string", description: "源货币代码,如 USD" },
to: { type: "string", description: "目标货币代码,如 CNY" },
},
required: ["from", "to"],
},
},
{
name: "list_currencies",
description: "列出所有支持的货币代码和名称",
inputSchema: { type: "object", properties: {} },
},
],
}));
// 工具调用处理
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_rate") {
const { from, to } = args as { from: string; to: string };
const res = await fetch(`${BASE_URL}/latest?from=${from}&to=${to}`);
if (!res.ok) throw new Error(`API 请求失败: ${res.status}`);
const data = await res.json();
const rate = data.rates[to.toUpperCase()];
return {
content: [{ type: "text", text: `1 ${from.toUpperCase()} = ${rate} ${to.toUpperCase()}` }],
};
}
if (name === "list_currencies") {
const res = await fetch(`${BASE_URL}/currencies`);
const data = await res.json();
const list = Object.entries(data)
.map(([code, name]) => `${code}: ${name}`)
.join("\n");
return { content: [{ type: "text", text: list }] };
}
throw new Error(`未知工具: ${name}`);
});
// 启动
const transport = new StdioServerTransport();
await server.connect(transport);
几个点值得单独说。inputSchema 必须写准,Claude 完全靠它来判断该填什么参数,含糊一点调用就会飘。错误要抛出去,别静默吞掉,MCP 客户端需要知道这次调用到底成没成。传输层用 StdioServerTransport,这是本地 MCP Server 的标准做法。
本地调试
发布前先在本地跑通。改 Claude Code 的配置文件(~/.claude/claude_desktop_config.json),加上这段:
{
"mcpServers": {
"currency-exchange": {
"command": "node",
"args": ["/你的项目路径/dist/index.js"]
}
}
}
先编译,再在 Claude Code 里问「1 美元等于多少人民币」,看到工具被调用、数据正常返回,本地联调就算过了:
npm run build
我踩过两个高频坑。一个是 inputSchema 里漏写 required,Claude 传参时有时带有时不带,行为不稳定;另一个是忘了 await server.connect(transport),进程起来一下就退了。这两类问题把报错原文丢给 Claude Code,它定位得相当准。调试这件事本身也能交给它。
发布到 npm
调试通过后,检查 package.json:
{
"name": "@yourname/currency-exchange-mcp",
"version": "1.0.0",
"description": "MCP Server for real-time currency exchange rates",
"main": "dist/index.js",
"bin": {
"currency-exchange-mcp": "dist/index.js"
},
"files": ["dist"],
"scripts": {
"build": "tsc",
"prepare": "npm run build"
}
}
bin 字段不能漏。有了它,别人用 npx @yourname/currency-exchange-mcp 就能直接跑,配置文件里不用写死本地路径。
npm login
npm publish --access public
发布完,使用者的配置改成这样:
{
"mcpServers": {
"currency-exchange": {
"command": "npx",
"args": ["-y", "@yourname/currency-exchange-mcp"]
}
}
}
几个值得注意的细节
API Key 走环境变量传,别硬编码进代码。配置文件支持 env 字段:
{
"command": "npx",
"args": ["-y", "@yourname/your-mcp"],
"env": { "API_KEY": "your-key-here" }
}
错误处理值得做细。网络超时、参数格式错、API 限流,分别给不同的提示。Claude 会把这些错误原样展示给用户,信息越清楚,来回确认越少。
工具的 description 比代码本身还重要。它直接决定 Claude 什么时候去调这个工具。写得越接近用户平时的自然语言提问,调用准确率越高。这个细节值得花时间打磨。
小结
从零到 npm 上线,整套流程大概两到三小时。骨架生成、调试定位、文档补全这几个最磨人的环节,Claude Code 全接了过去,剩下真正要动脑的部分就集中在工具设计和业务逻辑上。
MCP 生态还很早,真正有用的 Server 数量并不多。如果你手上有个内部工具或者垂直 API 一直想接进 AI 工作流,现在动手不算晚。照着这套路子走一遍,你就有自己的版本了。
关于 Easy Claude Code
如果你想用上 Claude Code,可以试试我们 ECC(Easy Claude Code)的中转服务:免🪜、低成本、快速稳定,直连官方 Claude Code。企业定制化服务可详询。