🔥 50 行代码,我手写了一个 MCP Server 给 Claude 用!
摘要:MCP 到底是什么?和 Function Call 什么关系?本文带你用 50 行代码从零手写一个 MCP Server,边写边学,一次性把 MCP 的核心原理、调用流程、踩坑经验全搞清楚。
📌 前言
最近在学 AI 开发,到处都能看到 MCP 这个词。什么"AI 界的 USB-C"、"统一工具调用协议"......概念看了一堆,但总觉得隔着一层纱。
直到我自己动手写了一个 MCP Server,才真正搞明白它到底是怎么回事。
今天把整个学习过程分享出来,如果你也是刚接触 MCP 的新人,相信看完会有收获。
🎯 本文适合谁
- 刚接触 MCP,想搞懂它原理的 AI 新人
- 用 Claude Code / Cursor,想了解背后 MCP 机制的同学
- 想动手写一个自己的 MCP Server 的开发者
📚 MCP 到底是什么?
在写代码之前,先理清几个基本概念。
一句话理解
MCP(Model Context Protocol)是一个协议,它规定了 AI 模型和外部工具/数据之间怎么通信。
你可以把它类比成 HTTP 协议:HTTP 规定了浏览器和服务器怎么通信,MCP 规定了 AI 模型和外部工具怎么通信。
有图有真相


三个核心角色
text
┌─────────────────────────────────────────────────┐
│ MCP Host │
│ (Claude Code / Cursor) │
│ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ MCP Client │ │ MCP Client │ │
│ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │
└──────────┼──────────────────────┼────────────────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ MCP Server │ │ MCP Server │
│ (文件系统) │ │ (数据库) │
└──────────────┘ └──────────────┘
- MCP Host:宿主程序,比如 Claude Code、Cursor,我们平时用的 AI 工具
- MCP Client:配置在 Host 里,负责和 Server 通信的客户端
- MCP Server:服务端,提供具体的工具能力(比如读文件、查数据库)
我们在 .mcp.json 里配置的就是 MCP Server 的连接信息,Host 会自动创建 Client 去连接它。
MCP 和 Function Call 是什么关系?
这是我学 MCP 时最大的困惑,后来才搞明白:
MCP 底层用的就是 Function Call,它没有给模型带来新能力,而是把工具调用的方式标准化了。
先看一下 MCP Client 端是怎么调用 AI 的,帮助理解两者的关系:
javascript
// MCP 客户端调用 AI 的代码
const response = await this.openai.chat.completions.create({
model: model,
messages,
tools: this.tools, // tools 就是从 MCP Server 拿到的工具列表
});
最终还是走的 tools 参数------这就是 Function Call。
那 MCP 的价值在哪?标准化。以前每个工具都要单独写对接代码,有了 MCP,任何 MCP Server 都能被任何 MCP Client 直接调用,就像 USB-C 一样插上就能用。
MCP vs 直接用 Function Call
| 对比项 | 直接用 Function Call | 用 MCP |
|---|---|---|
| 工具定义 | 手写 JSON Schema | Server 注册,Client 自动获取 |
| 调用方式 | 代码里直接调函数 | 通过协议通信(stdio/SSE) |
| 可复用性 | 绑定在自己的项目里 | 任何 MCP Client 都能用 |
| 学习成本 | 更低 | 多了一层协议概念 |
本质上流程是一样的。 MCP 多了一层标准化的协议抽象,换来的是跨客户端的通用性。
如果你写的工具只给自己项目用,直接 Function Call 更简单。但如果你想让工具能被 Claude Code、Cursor 等多个 AI 工具复用,MCP 就有它的价值了。
你觉得 MCP 的标准化值不值?欢迎评论区讨论 💬
🔧 动手:50 行代码写一个 MCP Server
理解了概念,接下来动手实现。我写的是一个简单的文件读取 Server,功能单一但能完整展示 MCP 的核心流程。
依赖说明
json
{
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^3.23.0"
}
}
@modelcontextprotocol/sdk:MCP 官方 SDK,处理协议通信zod:数据验证库,用于定义工具参数的 Schema
初始化项目
bash
mkdir simple-read-mcp && cd simple-read-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
然后在 package.json 中添加 "type": "module"(因为 MCP SDK 用的是 ESM)。
核心代码拆解
第一步:创建 MCP Server 实例
javascript
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
const server = new Server(
{ name: "simple-read-mcp", version: "1.0.0" },
{ capabilities: { tools: {} } }
);
💡
capabilities是告诉 Client:"我这个 Server 支持哪些能力"。目前 MCP 支持三种:tools(工具调用)、resources(资源读取)、prompts(提示词模板)。我们这里只用tools。
第二步:注册工具列表(ListTools)
这一步是告诉 Client:"我有哪些工具可以用"。
javascript
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'read_file',
description: '读取指定路径的本地文本文件内容',
inputSchema: {
type: 'object',
properties: {
path: {
type: 'string',
description: '要读取的文件路径,支持绝对或相对路径'
},
},
required: ['path'],
}
}
],
}));
💡
inputSchema就是告诉 AI:"调用这个工具需要传什么参数"。AI 会根据这个 Schema 自动生成调用参数。这个 Schema 本质上和 Function Call 的 tools 定义是一样的。
第三步:处理工具调用(CallTool)
当 AI 决定要用 read_file 工具时,Client 会把请求发过来,我们在 Server 里处理:
javascript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'read_file') {
try {
const content = await fs.readFile(args.path, 'utf-8');
return { content: [{ type: 'text', text: content }] };
} catch (err) {
return { isError: true, content: [{ type: 'text', text: `读取文件失败: ${err.message}` }] };
}
}
throw new Error(`工具 ${name} 不存在`);
});
💡 返回的
content数组就是给 AI 的上下文。AI 拿到文件内容后,就能基于内容来回答问题了。
第四步:启动服务
javascript
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
}
main();
💡
StdioServerTransport表示通过标准输入输出(stdin/stdout)通信。本地 MCP Server 最常用这种方式。
就这么多! 完整代码 50 行左右,一个 MCP Server 就写好了。
🧠 搞懂:MCP 调用的完整流程
写完代码,我把整个调用流程画出来,这样理解更清晰:
text
用户: "帮我读取 readme.md"
↓
Claude Code (Host)
↓ AI 推理:需要用 read_file 工具
MCP Client
↓ ① AI 模型推理决定调用哪个工具及参数
↓ ② 把工具名和参数通过 stdio 发给 Server
MCP Server
↓ ③ 执行 read_file,读取文件内容
↓ ④ 返回文件内容给 Client
MCP Client
↓ ⑤ 把结果塞进 messages,再次调用 AI
Claude Code
↓ ⑥ AI 基于文件内容生成最终回答
用户看到结果
关键点 :MCP Server 是一个常驻进程,启动后一直在运行,随时等待 Client 的请求。不是一次性的 API 调用。
🚀 在 Claude Code 中使用 MCP
配置方式
在项目根目录创建 .mcp.json:
json
{
"mcpServers": {
"simple-read": {
"type": "stdio",
"command": "node",
"args": ["./simple-read-mcp/server.js"]
}
}
}
全局配置 (对所有项目生效)------编辑 ~/.claude.json,或通过命令添加:
bash
claude mcp add <server-name> --scope user <command> [args...]
💡 输入
/mcp命令可以查看当前所有 MCP Server 的连接状态和工具数量。
测试效果
配置好后,对 Claude Code 说:
帮我读取 D:\workspace\project\readme.md 的内容
Claude Code 会自动:发现 read_file 工具 → 生成参数 → 通过 MCP 调用 Server → 拿到文件内容 → 基于内容回答你。
整个过程对用户是透明的,你只需要提问就行。
💡 建议截图保留实际运行效果------掘金文章有视觉内容的推荐权重更高。你可以在终端里跑一遍,截一张 Claude Code 调用工具的截图放在文章里。
我在用的 MCP Server 推荐
写完自己的 Server 之后,我也装了一些社区 MCP Server 来用,效果提升明显:
| MCP Server | 功能 | 使用场景 |
|---|---|---|
| filesystem | 本地文件读写、搜索 | AI 直接操作项目文件,不用手动复制粘贴 |
| web-reader | 网页转 Markdown | AI 读取在线文档、参考文章 |
| playwright | 浏览器自动化 | AI 操作网页、截图、测试 |
| neural-memory | 持久化记忆 | 跨会话记住项目上下文和决策 |
配置方式和上面类似,都是在 .mcp.json 里添加对应的 mcpServers 配置即可,不展开赘述。
🐛 踩坑记录
学习过程中踩了几个坑,分享出来帮大家避坑:
坑 1:ESM 模块报错
现象 :SyntaxError: Cannot use import statement outside a module
原因:Node.js 默认使用 CommonJS,MCP SDK 用的是 ESM
解决 :在 package.json 中加 "type": "module"
坑 2:zod 版本不兼容
现象:启动时报 zod 相关的类型错误
原因:MCP SDK 依赖 zod 3.x,但 npm 可能装到不兼容的版本
解决 :明确安装 zod@^3.23.0,或按 SDK 文档的版本要求来
坑 3:stdio 通信看不到日志
现象:console.log 的输出干扰了 MCP 协议通信,Server 直接报错
原因:stdio 模式下 stdout 是协议通道,不能用来打日志
解决 :日志输出到 stderr:console.error('debug info')
💡 调试 MCP Server 时,推荐用官方的 MCP Inspector 工具,可以可视化查看请求和响应,比 console.log 好用得多。安装方式:
npx @modelcontextprotocol/inspector node server.js
你踩过哪些 MCP 的坑?评论区补充,我更新到文章里! 💬
⚠️ MCP 目前的局限性
学完之后我也思考了一下 MCP 的不足,客观看待才能用好它:
| 局限性 | 说明 |
|---|---|
| stdio 模式不支持远程 | 本地通信没问题,但要远程调用得用 SSE 模式,配置更复杂 |
| 工具多了影响选择准确性 | 装太多 MCP Server,AI 的工具列表变长,可能选错工具 |
| 安全权限控制不完善 | 目前权限控制主要靠 Server 自己实现,没有统一的权限模型 |
| 调试相对困难 | stdio 通信不像 HTTP 那样方便抓包,排查问题需要专门工具 |
| 没有提升模型能力 | MCP 只是协议标准化,工具调用的准确性还是取决于模型本身 |
📚 我学到的几个关键认知
1. MCP 是协议,不是产品
它不是一个新的框架或工具,而是一套通信标准。就像 HTTP 不是浏览器,但浏览器之间能互相通信靠的就是 HTTP。
2. MCP 没有提升模型的工具调用能力
很多人以为用了 MCP 模型就能更好地调用工具,其实不是。MCP 底层还是 Function Call,工具调用的准确性取决于模型本身的能力,和协议无关。
3. MCP 的价值在于标准化
举个例子:以前你想让 AI 读文件,得自己写一套 Function Call + 文件读取的代码。有了 MCP,直接装一个 @modelcontextprotocol/server-filesystem,任何 MCP Client 都能用。写一次,到处用,这就是标准化的力量。
4. 学 MCP 最好的方式就是动手写
光看文档很容易云里雾里,但自己写一个 Server 之后,整个流程就清楚了。推荐大家也试试。
💡 进阶方向
掌握了基础版之后,还可以继续探索:
| 方向 | 说明 |
|---|---|
| 多工具注册 | 一个 Server 提供多个工具(读文件 + 写文件 + 列目录) |
| Resources | 让 AI 能浏览资源列表(如数据库表结构) |
| Prompts | 预定义提示词模板 |
| 远程 Server | 用 SSE 替代 stdio,实现远程调用 |
| 权限控制 | 限制可访问的目录范围 |
🔗 参考资料
💬 交流讨论
你现在用的是 Claude Code 还是 Cursor?MCP Server 装了几个?评论区聊聊你的真实体验!
觉得有用?点个 👍 收藏 ⭐ 关注 👆,作为 AI 新人会持续分享学习笔记!
📌 标签:MCP、Claude Code、AI 开发、Node.js、Function Call