5 分钟构建你的第一个 MCP Server:Node.js 快速入门教程
使用 Node.js + 官方 TypeScript SDK,你可以在 5 分钟内构建一个功能完整的 MCP Server,并在 Claude Desktop 中直接调用它。本文提供从项目初始化到工具注册、本地调试、客户端对接的完整流程,附带每一步的可运行代码。

核心要点
•
5 分钟上手:从 npm init 到 Claude Desktop 成功调用,整个流程不超过 5 分钟
•
零协议知识要求:官方 SDK 封装了 JSON-RPC 2.0 通信细节,你只需关注业务逻辑
•
stdio 传输:本教程使用最简单的 stdio 模式,无需网络配置,Claude Desktop 原生支持
•
完整可运行:每段代码都经过验证,复制即用,不是伪代码
•
调试友好:包含 MCP Inspector 工具的使用方法和常见报错排查方案
前置准备
开始之前,确保你的开发环境满足以下要求:
#JRPRafzCCANH text { white-space: pre; }#JRPRafzCCANH .fb0 { stroke: var(--N900-FG, 0.15); }#JRPRafzCCANH .fb0 {stroke: rgba(31,35,41, 0.15)}#JRPRafzCCANH .fx0 { stroke-miterlimit: 10; }#JRPRafzCCANH .fx0 {stroke-miterlimit: 10}#JRPRafzCCANH .fa1 { fill: rgb(187, 191, 196); }#JRPRafzCCANH .fa1 {fill: rgb(187, 191, 196)}#JRPRafzCCANH .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#JRPRafzCCANH .fx1 {font-style: normal}#JRPRafzCCANH .fx2 { text-anchor: middle; }#JRPRafzCCANH .fx2 {text-anchor: middle}#JRPRafzCCANH .fx3 { dominant-baseline: central; }#JRPRafzCCANH .fx3 {dominant-baseline: central}#JRPRafzCCANH .fx4 { stroke: none; }#JRPRafzCCANH .fx4 {stroke: none}#JRPRafzCCANH .fc0 { fill: var(--text-caption); }#JRPRafzCCANH .fc0 {fill: #646a73}#JRPRafzCCANH .fa2 { fill: var(--text-link-disabled); }#JRPRafzCCANH .fa2 {fill: rgb(187, 191, 196)}#JRPRafzCCANH .fa3 { fill: rgb(246, 248, 250); }#JRPRafzCCANH .fa3 {fill: rgb(246, 248, 250)}#JRPRafzCCANH .fx5 { font: 400 16px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#JRPRafzCCANH .fx5 {font-style: normal}#JRPRafzCCANH .fx6 { text-anchor: start; }#JRPRafzCCANH .fx6 {text-anchor: start}#JRPRafzCCANH .fx7 { dominant-baseline: alphabetic; }#JRPRafzCCANH .fx7 {dominant-baseline: alphabetic}#JRPRafzCCANH .fc1 { fill: rgb(51, 51, 51); }#JRPRafzCCANH .fc1 {fill: rgb(51, 51, 51)}#JRPRafzCCANH .fx8 { font: 400 14px LarkHackSafariFont, LarkEmojiFont, "Fira Code", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, "Noto Sans", Tahoma, "Nimbus Sans L", Arial, "Liberation Sans", "PingFang SC", "Hiragino Sans GB", "Noto Sans CJK SC", "Source Han Sans SC", "Source Han Sans CN", "Microsoft YaHei", "Wenquanyi Micro Hei", "WenQuanYi Zen Hei", STHeiti, SimHei, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji", system-ui; }#JRPRafzCCANH .fx8 {font-style: normal}#JRPRafzCCANH .fc2 { fill: rgb(139, 92, 246); }#JRPRafzCCANH .fc2 {fill: rgb(139, 92, 246)}#JRPRafzCCANH .fb1 { fill: var(--N900-FG, 0.15); }#JRPRafzCCANH .fb1 {fill: rgba(31,35,41, 0.15)}#JRPRafzCCANH .fb2 { fill: rgb(225, 228, 232); }#JRPRafzCCANH .fb2 {fill: rgb(225, 228, 232)}#JRPRafzCCANH .fa4 { fill: var(--bg-body); }#JRPRafzCCANH .fa4 {fill: #fff}ABC1234依赖Node.jsnpmTypeScript最低版本18.0+9.0+5.0+检查命令全局安装或项目内安装均可node --versionnpm --version
本教程的所有代码基于 @modelcontextprotocol/sdk 官方 TypeScript SDK 最新版本。确认 Node.js 版本后,我们直接开始。
第一步:初始化项目
创建一个新的 Node.js 项目并安装 MCP SDK:
代码块
Bash
mkdir my-first-mcp-server
这里安装了三个关键依赖:
•
@modelcontextprotocol/sdk:MCP 官方 SDK,处理所有协议层通信
•
zod:运行时 Schema 验证库,用于定义 Tool 的输入参数类型
•
typescript:编译器,SDK 的类型推断需要它
初始化 TypeScript 配置:
代码块
Bash
npx tsc --init
修改生成的 tsconfig.json,确保以下关键配置:
代码块
JSON
{"compilerOptions": {"target": "ES2022","module": "Node16","moduleResolution": "Node16","outDir": "./dist","rootDir": "./src","strict": true,"esModuleInterop": true,"skipLibCheck": true},"include": ["src/**/*"]}
同时更新 package.json,添加 type 字段和构建脚本:
代码块
JSON
{"type": "module","scripts": {"build": "tsc","start": "node dist/index.js"}}
项目骨架搭建完成。整个过程不到 1 分钟。
第二步:编写 MCP Server 核心代码
在 src/index.ts 中编写完整的 Server 代码。我们从最小可用版本开始——注册一个 Tool,让 AI Agent 能够调用它。
代码块
TypeScript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 1. 创建 Server 实例const server = new McpServer({
name: "my-first-mcp-server",
version: "1.0.0",
});
// 2. 注册一个 Tool
server.tool(
"get-weather",
"获取指定城市的当前天气信息",
{
city: z.string().describe("城市名称,例如 Beijing、Tokyo、New York"),
},
async ({ city }) => {
// 这里用模拟数据演示,实际项目中替换为真实 API 调用const weatherData: Record<string, { temp: number; condition: string }> = {
Beijing: { temp: 22, condition: "晴" },
这段代码做了三件事:
创建 McpServer 实例:指定 Server 名称和版本号,这些信息会在客户端的 initialize 握手中使用
注册 Tool:通过 server.tool() 声明一个名为 get-weather 的工具。第二个参数是自然语言描述——LLM 会根据这段描述决定何时调用该工具。第三个参数用 zod 定义参数的 JSON Schema,SDK 会自动将其转换为 MCP 协议要求的 inputSchema 格式
连接传输层:使用 StdioServerTransport 通过标准输入/输出与客户端通信
关于 Tool 描述的编写技巧
Tool 的 description 字段直接影响 LLM 的 Tool Use 决策。好的描述应该:
•
明确说明工具的功能("获取天气"而不是"天气工具")
•
包含关键约束("需要传入城市名称")
•
避免歧义(如果有多个类似工具,描述中应体现差异)
这与 Function Calling 中函数描述的编写原则一致——Prompt Engineering 的基本功在这里同样适用。
第三步:编译并本地测试
编译 TypeScript 代码:
代码块
Bash
npx tsc
编译成功后,dist/index.js 就是可运行的 Server。先在终端手动验证它能正常启动:
代码块
Bash
node dist/index.js
如果没有任何输出且进程没有退出,说明 Server 已在等待 stdin 输入——这是 stdio 模式的正常行为。按 Ctrl+C 退出。
使用 MCP Inspector 调试
手动构造 JSON-RPC 消息来测试 Server 既繁琐又容易出错。MCP 官方提供了一个可视化调试工具——MCP Inspector:
代码块
Bash
npx @modelcontextprotocol/inspector node dist/index.js
Inspector 启动后会在浏览器中打开一个调试界面,你可以:
•
查看 Server 注册的所有 Tools、Resources 和 Prompts
•
手动输入参数调用 Tool 并查看返回结果
•
查看完整的 JSON-RPC 消息收发日志
在 Inspector 的 Tools 面板中,你应该能看到 get-weather 工具。填入 city: "Beijing" 点击调用,预期返回:
代码块
JSON
{"content": [{"type": "text","text": "Beijing 当前天气:晴,温度 22°C"}]}
调试确认无误后,进入下一步。
第四步:对接 Claude Desktop
Claude Desktop 是目前最成熟的 MCP 客户端之一。配置方法如下:
找到配置文件
macOS 的配置文件路径:
代码块
Plain Text
~/Library/Application Support/Claude/claude_desktop_config.json
Windows 的配置文件路径:
代码块
Plain Text
%APPDATA%\Claude\claude_desktop_config.json
如果文件不存在,手动创建即可。
添加 Server 配置
在配置文件中注册你的 MCP Server:
代码块
JSON
注意两个关键点:
•
command 和 args 中的路径必须是绝对路径。相对路径会导致 Claude Desktop 无法找到可执行文件
•
如果你使用 nvm 管理 Node.js 版本,command 应指向 nvm 环境下的 node 绝对路径(通过 which node 查看)
重启 Claude Desktop
完全退出 Claude Desktop 后重新打开(macOS 上需要从 Dock 栏右键退出,仅关闭窗口不会重新加载配置)。
重启后,在 Claude 的对话输入框旁边应该能看到一个工具图标(锤子形状),点击展开可以看到 get-weather 工具已注册。
测试对话
在 Claude Desktop 中输入:
北京今天天气怎么样?
Claude 会识别出这个问题可以通过 get-weather 工具回答,自动调用你的 MCP Server,并将结果整合到回复中。整个 Tool Use 流程对用户完全透明。
扩展:添加更多 Tools
一个 MCP Server 可以注册任意数量的 Tool。我们继续添加两个实用工具来演示更复杂的场景。
带有多参数的 Tool
代码块
TypeScript
server.tool(
"calculate-bmi",
"根据身高和体重计算 BMI 指数",
{
height: z.number().describe("身高,单位厘米"),
weight: z.number().describe("体重,单位千克"),
},
返回结构化数据的 Tool
代码块
TypeScript
server.tool(
"generate-uuid",
"生成一个或多个 UUID v4",
{
count: z.number().min(1).max(50).default(1).describe("生成数量,1-50"),
},
async ({ count }) => {
const uuids = Array.from({ length: count }, () => crypto.randomUUID());
return {
content: [
{
type: "text" as const,
text: uuids.join("\n"),
},
注意 count 参数使用了 z.number().min(1).max(50).default(1)——zod 的链式校验会自动转换为 JSON Schema 中的 minimum、maximum 和 default 约束。SDK 在收到客户端请求时会自动执行输入验证,不合法的参数会返回标准的 JSON-RPC 错误响应。
添加 Resources 和 Prompts
除了 Tools,MCP 协议还定义了另外两个核心原语。
Resource:暴露数据给 AI
Resource 让 AI Agent 能够读取你的 Server 中的数据,类似于一个只读的数据接口:
代码块
TypeScript
Prompt:预置对话模板
Prompt 是可复用的对话模板,客户端可以让用户选择并填充参数:
代码块
TypeScript
server.prompt(
"weather-report",
{ city: z.string() },
({ city }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `请查询 ${city} 的天气,并用简洁的方式报告当前温度和天气状况。`,
},
},
],
})
);
Tools、Resources、Prompts 这三个原语的协作方式在 MCP 协议进阶实战中有更深入的讲解。
完整项目结构
完成以上步骤后,你的项目结构应如下所示:
代码块
Plain Text
my-first-mcp-server/
├── src/
│ └── index.ts # Server 主文件
├── dist/
│ └── index.js # 编译产物
├── package.json
├── tsconfig.json
└── node_modules/
这是一个最小化的结构。随着 Server 复杂度增长,你可以将不同的 Tool 拆分到独立模块中:
代码块
Plain Text
调试与排错
常见报错及解决方案
1. Error: Cannot find module '@modelcontextprotocol/sdk/server/mcp.js'
原因:TypeScript 的 moduleResolution 配置不正确。确保 tsconfig.json 中设置为 "Node16" 或 "NodeNext",且 package.json 中有 "type": "module"。
Claude Desktop 中看不到工具
排查步骤:
•
检查 claude_desktop_config.json 的 JSON 格式是否合法(多余逗号是常见错误)
•
确认 args 中的路径是绝对路径
•
在终端手动运行 node /absolute/path/to/dist/index.js,确认没有报错
•
完全退出并重启 Claude Desktop
Tool 调用返回空结果
检查 Tool handler 函数是否正确返回了 content 数组。MCP 协议要求返回格式必须包含 content 字段,每个 content 项必须有 type 和 text(或其他支持的 MIME 类型)。
查看日志
Claude Desktop 的 MCP 相关日志路径:
代码块
Bash
# macOStail -f ~/Library/Logs/Claude/mcp*.log# Windows# %APPDATA%\Claude\Logs\mcp*.log
日志中可以看到完整的 JSON-RPC 消息交互过程,是排查问题的第一手资料。
生产环境注意事项
本教程构建的是一个学习用的最小 Server。投入生产前,你需要关注以下几点:
错误边界:为每个 Tool handler 添加 try-catch,防止单个工具的异常导致整个 Server 进程崩溃。
输入校验:虽然 zod 提供了基本的类型校验,但对于涉及文件系统、网络请求或数据库操作的 Tool,需要额外的安全校验——防止路径遍历、注入攻击等。
超时控制:长时间运行的 Tool 应设置超时。LLM 客户端通常有请求超时限制,你的 Tool 如果执行时间过长会导致请求失败。
传输层选型:如果你的 Server 需要远程访问或供多个客户端同时使用,应从 stdio 切换到 SSE 传输。关于 SSE 传输层的实现细节,参见 用 Go 从零实现 MCP 协议 SSE 传输层。关于高并发场景下的网关设计,参见 MCP 网关高并发架构设计。
性能考量:当 Server 的 Tool 数量增多或调用频率变高时,Node.js 的单线程模型可能成为瓶颈。此时可以考虑 Go 实现——详见 MCP Server 性能对比:Node.js vs Go。
总结
本文从零开始,用 Node.js 和官方 TypeScript SDK 构建了一个包含天气查询、BMI 计算、UUID 生成三个 Tool 的 MCP Server,并成功对接了 Claude Desktop。整个过程涵盖了 MCP Server 开发的核心流程:
项目初始化:安装 SDK 和 zod,配置 TypeScript
Tool 注册:使用 server.tool() 声明工具名称、描述、参数 Schema 和处理函数
本地调试:通过 MCP Inspector 可视化测试
客户端对接:配置 claude_desktop_config.json 实现 Claude Desktop 集成
扩展能力:添加 Resources 和 Prompts 丰富 Server 功能
MCP 协议正在成为 AI Agent 连接外部世界的标准方式。掌握 MCP Server 开发,意味着你编写的任何能力都可以被整个 MCP 生态中的客户端复用——这正是 MCP 最大的价值所在。