MCP 协议实战:从零搭建一个 MCP 服务器(Node.js 版)
一、背景
说实话,MCP(Model Context Protocol)最近是真的火。Anthropic 搞出来这个协议之后,各种 AI 工具都在接入。说白了,MCP 就是一套标准接口,让 AI 模型能直接调用外部工具、读取文件、查数据库------不再局限在聊天框里。
但我发现一个问题:网上聊 MCP 概念的文章很多,真正教你手写一个 MCP 服务器的,真没几个。今天就来填这个坑,从零开始搭一个 MCP 服务器,附带完整的 TypeScript 代码。
二、MCP 是个啥
先简单说下 MCP 的核心概念。它本质上就是一个 JSON-RPC 协议,定义了三种角色:
- Host:发起请求的一方(比如 Claude Desktop、Cursor)
- Client:在 Host 内部和 Server 建立连接
- Server:提供工具、资源、提示的服务端
MCP 支持两种传输方式:stdio (通过标准输入输出通信)和 SSE(通过 HTTP 流式通信)。我们今天用 stdio 方式,因为本地开发最方便。

三、环境准备
需要的东西很少:
- Node.js 18+
- npm 或 yarn
- 一个支持 MCP 的客户端(比如 Claude Desktop)
四、项目初始化
直接开干:
bash
mkdir mcp-demo-server
cd mcp-demo-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
这里的关键依赖是 @modelcontextprotocol/sdk,Anthropic 官方提供的 TypeScript SDK。我们直接用 tsx 来运行,省去编译步骤。
五、写一个 MCP 服务器
创建 src/index.ts:
typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// 1. 创建服务器实例
const server = new Server(
{
name: "mcp-demo-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
说白了这个代码就是注册一个 MCP 服务器,告诉客户端我叫什么名字、有什么能力。

注册工具列表
MCP 客户端会先问服务器"你有什么工具",所以我们要实现 ListTools 请求:
typescript
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "calculate",
description: "执行数学计算",
inputSchema: {
type: "object",
properties: {
expression: {
type: "string",
description: "数学表达式,如 1 + 2 * 3",
},
},
required: ["expression"],
},
},
{
name: "get_weather",
description: "获取指定城市的天气",
inputSchema: {
type: "object",
properties: {
city: {
type: "string",
description: "城市名称",
},
},
required: ["city"],
},
},
],
};
});
实现工具调用
当客户端调某个工具时,会触发 CallTool 请求:
typescript
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "calculate": {
const expression = String(args?.expression || "");
try {
// 安全起见,用 mathjs 之类的库,这里用 Function 做演示
const result = new Function(`return (${expression})`)();
return {
content: [{ type: "text", text: String(result) }],
};
} catch (e) {
return {
content: [{ type: "text", text: `计算错误: ${e}` }],
isError: true,
};
}
}
case "get_weather": {
const city = String(args?.city || "");
// 这里本该调用天气 API,但为了演示直接返回模拟数据
return {
content: [
{
type: "text",
text: `${city} 今天天气:晴,温度 28°C,湿度 65%`,
},
],
};
}
default:
throw new Error("未知工具: " + name);
}
});
启动服务器
typescript
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP 服务器已启动");
}
main().catch(console.error);
六、配置客户端
用 Claude Desktop 连接时,需要在 claude_desktop_config.json 中添加:
json
{
"mcpServers": {
"mcp-demo": {
"command": "npx",
"args": ["tsx", "src/index.ts"]
}
}
}
重启 Claude Desktop,就能看到两个新工具了。
七、踩坑记录
说几个坑:
-
SDK 版本问题 - 最新版 SDK 用
@modelcontextprotocol/sdk,别用旧版的mcp-sdk。引入路径要写全/index.js,不然会报模块找不到。 -
stdio 方式别用 console.log - MCP 用 stdio 通信,如果代码里用了 console.log,会污染通信流。统一用 console.error 或专门的 logger。
-
zod 校验 - 推荐用 zod 做输入参数校验,避免客户端传了非法参数导致服务器崩溃。
八、总结
说实话,MCP 的入门门槛比我想象的低很多。核心代码就几十行,跑通 stdio 通信之后,后面加工具就跟写 API 接口一样简单。
回顾一下我们今天做了什么:
- 创建了一个 MCP 服务器项目
- 注册了两个工具(计算器和天气查询)
- 用 stdio 方式启动
- 配置了 Claude Desktop 客户端
下一步可以试试 SSE 方式------支持远程连接,更适合生产环境部署。或者用 Python 的 SDK 再写一遍,对比一下两种语言的差异。
代码已上传到 github.com/xxx/mcp-demo-server,有用的话点个 star 吧 👇