从零写一个MCP服务器:用Claude Code和官方SDK手把手搭一座桥

从零写一个MCP服务器:用Claude Code和官方SDK手把手搭一座桥
张文保 更新 25 分钟阅读 4,630 阅读
本文目录
  1. 为什么要自己写一个MCP服务器,而不是装现成的?
  2. 动手前,得先搞懂MCP到底在你和工具之间传什么?
  3. 官方SDK的包名到底是哪一个?这步错了全盘皆输
  4. 环境和项目骨架怎么搭?
  5. 核心代码:怎么注册第一个工具?
  6. 怎么编译并接进Claude Code?
  7. 除了工具,还能给服务器加点什么?
  8. 服务器不工作时,怎么调试?
  9. 写好了,怎么发布出去给别人用?
  10. 不想从零写,有没有官方脚手架?
  11. 常见问题解答
  12. 写MCP服务器到底该装哪个npm包?
  13. registerTool的inputSchema为什么不用z.object包起来?
  14. 为什么MCP服务器里不能用console.log?
  15. claude mcp add命令里的双横杠是干嘛的?
  16. 不想手写样板,有没有更快的起步方式?
  17. 自己写的MCP服务器怎么分享给别人用?
  18. 权威参考资料
摘要:网上不少“从零写MCP服务器”的教程,第一步就让你装@modelcontextprotocol/server这个包——这是个坑。当前稳定、能直接npm装上、官方推荐用于生产的TypeScript SDK是@modelcontextprotocol/sdk,从@modelcontextprotocol/sdk/server/mcp.js这种子路径导入;那个不带sdk的包名属于还在预览期的v2,照它写很可能装不上或踩到不稳定接口。这篇带你用正确的稳定版SDK,从初始化项目、写第一个工具、编译、接进Claude Code,到加Resources和Prompts、调试、发布到npm,完整走一遍,把registerTool的真实签名、为什么日志必须打到stderr、claude mcp add的双横杠分隔符这些容易翻车的细节逐个讲清,最后还告诉你一个连代码都不用自己写的官方脚手架。

为什么要自己写一个MCP服务器,而不是装现成的?

先回答一个该问的问题:GitHub、Sentry、数据库这些常用的MCP服务器都有现成的,保哥也专门盘点过最值得装的那批MCP服务器,那还有什么必要自己写一个?答案是:当你要连的,是别人没做、也不可能替你做的那套系统时。

做外贸独立站的团队,手里多半攥着一堆自家的东西:内部的选品库、自研的ERP、对接某家货代的物流查询接口、一张存着历史询盘的数据库。这些系统全世界只有你在用,社区不会有现成的MCP服务器去连它们。可你又特别希望在Claude Code里能直接问“把这批SKU的最新库存拉出来”“这个订单走到哪一步了”,而不是自己切到另一个后台去查、再把结果复制粘贴回来。这道鸿沟,只能靠你自己写一个MCP服务器来填——它就是一座桥,一头接Claude Code,一头接你那套独有的系统。

好在这座桥比想象中好搭。MCP是个标准化协议,你不需要懂AI模型的内部,只要按它的规矩把“我能提供哪些工具、每个工具收什么参数、返回什么”声明清楚,剩下的对接由协议和Claude Code自动完成。真正的难点其实不在“怎么写”,而在“别被错误的教程带歪”——这也是这篇要重点替你避开的。把SDK选对、把几个关键细节抠准,一个能用的MCP服务器,一个下午就能跑起来。

动手前,得先搞懂MCP到底在你和工具之间传什么?

不必啃协议规范,但有个心智模型会让后面省很多事。你可以把MCP理解成AI世界里的“USB接口标准”:以前每个AI工具要连一个外部系统,都得定制一根专线,N个工具连M个系统就是N乘M根线,乱成一团;MCP定了一个统一的插口,工具这边按标准做一个母口、系统这边按标准做一个公口,谁插谁都能通。你写的MCP服务器,就是给你那套独有系统做的那个标准公口。

具体到通信,MCP服务器和Claude Code之间靠一套基于JSON-RPC的消息你来我往:Claude Code问“你都有哪些工具”,服务器回一份清单;Claude Code说“调用get_weather,参数city是深圳”,服务器执行后把结果传回去。本地运行的服务器,这套消息走的是标准输入输出(stdio)——这个细节后面调试时会变成一个大坑的源头,先记住:stdout这条道是留给协议消息走的,你绝对不能往里打日志,否则等于往正经通信里塞垃圾,整个连接就乱了。

服务器能向Claude Code提供三类东西,搞清楚区别才知道该用哪个:工具(Tools)是能执行动作、有副作用的函数,比如查库存、发起一次API调用,这是最常用的;资源(Resources)是只读的数据源,像配置、文档,供模型按需读取;提示(Prompts)是预置的提示模板,会变成Claude Code里可以直接调用的命令。这篇先把最核心的工具讲透,再带你加上另外两类。如果你还分不清MCP和Skills、Hooks各自管什么,保哥另写过一篇三大扩展机制怎么选,可以先扫一眼建立全局观。

官方SDK的包名到底是哪一个?这步错了全盘皆输

这是整篇最该划重点的地方,因为不少教程在这第一步就把人带沟里了。你会看到两种写法:一种让你装@modelcontextprotocol/sdk,从@modelcontextprotocol/sdk/server/mcp.js导入;另一种让你装@modelcontextprotocol/server,从@modelcontextprotocol/server/stdio导入。它们看着只差几个字,实则是两个版本世代。

把结论先给你:当前要用于生产、能稳定npm装上的,是@modelcontextprotocol/sdk这一支(v1系列,最新已到1.29左右)官方TypeScript SDK仓库说得很清楚:仓库主分支上那套用不带sdk的新包名、写法也有变动的代码,属于还在预览期(pre-alpha)的v2,明确不建议用于生产。换句话说,那些一上来就让你npm install @modelcontextprotocol/server的教程,要么直接装不上,要么把你引到一套随时会变、不保证稳定的接口上。

两版的差异不止包名,连工具的写法都不一样,列张表你一眼就能分辨自己看的是哪一版:

对比项v1(稳定,本文用)v2(预览期,暂别用)
npm包名@modelcontextprotocol/sdk@modelcontextprotocol/server
导入路径带子路径和.js,如/server/mcp.js直接从包根或/stdio
工具入参schema裸的字段对象{ city: z.string() }z.object({ city: z.string() })
Zod版本Zod 3Zod 4
生产可用否,pre-alpha

所以你拿到任何一份MCP教程,第一眼就该核包名:装的是带sdk的那个吗、导入带.js子路径吗、inputSchema是裸字段对象吗?三个都对,才是当下能放心抄的稳定写法。这一节看着啰嗦,却能帮你省下“照着写半天发现根本装不上”的整段时间——准确,永远是技术教程最值钱的部分。下面所有代码,都按v1稳定版来写。

环境和项目骨架怎么搭?

环境要求很轻:装好Node.js 18或更高版本(自带npm),有个趁手的编辑器即可。我们用TypeScript写,类型提示能帮你少踩很多运行时的坑。先建目录、初始化项目:

mkdir weather-mcp-server && cd weather-mcp-server
npm init -y

装依赖。注意包名——装的是带sdk的那个,外加做参数校验的zod,以及TypeScript的开发依赖:

npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node

然后改package.json,有一个字段必须加,否则后面ESM导入会报错——就是"type": "module",它告诉Node这个项目用ES模块。顺手把编译脚本和入口也配上:

{
  "name": "weather-mcp-server",
  "version": "1.0.0",
  "type": "module",
  "bin": { "weather-mcp-server": "dist/index.js" },
  "scripts": { "build": "tsc" }
}

再加一个tsconfig.json,关键是target和module的选择,要让编译产物兼容Node的ESM:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "dist",
    "strict": true
  },
  "include": ["src/**/*"]
}

骨架就这些。目录里建个src文件夹,待会儿的服务器代码就放进src/index.ts。这套配置是给MCP服务器量身定的最小集,不用纠结每个选项,照抄即可,重点精力留给下一步的核心代码。

核心代码:怎么注册第一个工具?

来写真正干活的部分。打开src/index.ts,先把SDK的两个核心件导入进来——再强调一次导入路径,带子路径、带.js后缀,这是v1稳定版的正确写法:

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

创建服务器实例,给它起个名字和版本号——这个名字会显示在Claude Code的服务器列表里:

const server = new McpServer({ name: "weather-server", version: "1.0.0" });

核心来了——注册一个工具。用registerTool,它收三个参数:工具名、一个描述对象(含标题、说明和入参schema)、以及真正执行的处理函数。这里有个最易错的点:v1里inputSchema是一个裸的字段对象,形如{ city: z.string() },而不是z.object({...})包起来——这正是区分v1和v2的关键标志之一:

server.registerTool(
  "get_weather",
  {
    title: "天气查询",
    description: "查询某个城市当前的天气状况",
    inputSchema: { city: z.string().describe("城市名,如 Shenzhen") }
  },
  async ({ city }) => {
    return {
      content: [{ type: "text", text: `${city} 当前晴,气温 26°C` }]
    };
  }
);

这段不长,但每一处都有讲究。description不是写给人看的注释,它是Claude Code判断“什么时候该调这个工具”的依据,描述越准,模型越不会乱调或漏调,这点跟写Skill的description是同一个道理。inputSchema里用zod声明参数,z.string()顺手用.describe()给参数也加说明,模型填参时会更靠谱。处理函数返回的content是一个块数组,最常见的就是一个text块——真实项目里,你会把那行写死的“晴、26度”换成一次真正的天气API调用,或者一次你内部ERP的查询。

最后,把服务器接上传输通道、启动起来。本地服务器用StdioServerTransport,走标准输入输出:

const transport = new StdioServerTransport();
await server.connect(transport);

到这儿,一个能查天气的MCP服务器,代码就齐了。它现在只有一个工具,但麻雀虽小五脏俱全——加更多工具,无非是再多几个registerTool,套路完全一样。把这个最小骨架吃透,扩展是水到渠成的事。

怎么编译并接进Claude Code?

TypeScript得先编译成JavaScript才能跑。运行编译命令,产物会生成到dist目录:

npm run build

编译完,就该把这个服务器告诉Claude Code了。用claude mcp add命令,这里藏着第二个高频翻车点——那个双横杠--不能少,也不能放错位置。它的作用是把“给claude mcp add自己的选项”和“要执行的命令”分隔开,双横杠后面的整串,才是Claude Code用来启动你服务器的命令:

claude mcp add weather -- node /你的绝对路径/dist/index.js

Claude Code官方的MCP文档把这个语法规定得很死:所有选项必须放在服务器名字之前,--之后才是命令和它的参数。漏了双横杠,或者把它放错地方,命令就会被错误解析,服务器加进去也起不来。还有一点别想当然:node后面的路径建议用绝对路径,相对路径在不同工作目录下启动会找不到文件。

顺带说个作用域的事。claude mcp add默认是local作用域,也就是只在当前这个项目、只对你自己生效,配置存在你的~/.claude.json里。如果你想把这个服务器分享给团队,加--scope project,配置会写进项目根目录的.mcp.json、能跟着代码提交;想让它在你所有项目里都可用,就用--scope user。这套作用域和配置文件的细节,保哥在MCP配置指南里讲得更全,连现成服务器怎么配也一并覆盖了。加完之后,在Claude Code里输入/mcp就能看到你的服务器连上没、暴露了几个工具。

除了工具,还能给服务器加点什么?

工具是主菜,但Resources和Prompts这两道配菜,在合适场景下很提味。先看资源。资源是只读的数据源,比如你想让模型随时能读到当前项目的某份配置,就把它注册成一个资源,用registerResource,给它一个名字、一个URI、一段描述,再写读取逻辑:

server.registerResource(
  "config",
  "config://app",
  { title: "应用配置", description: "当前应用的配置数据", mimeType: "text/plain" },
  async (uri) => ({
    contents: [{ uri: uri.href, text: "这里返回配置内容" }]
  })
);

注册好后,在Claude Code里用@符号就能像引用文件一样引用这个资源。资源和工具的分界线很清楚:要执行动作、可能改变什么,用工具;只是把一份数据摆出来供读取,用资源。把查询类的只读操作做成资源还是工具,取决于它有没有副作用——纯查、纯读,资源更合适。

再看提示。提示是预置的提示模板,注册后会变成Claude Code里一个能直接敲的命令,适合把团队里反复要用的某段标准提示固化下来。用registerPromptargsSchema同样是裸字段对象:

server.registerPrompt(
  "review_code",
  { title: "代码审查", description: "审查一段代码的潜在问题", argsSchema: { code: z.string() } },
  ({ code }) => ({
    messages: [{ role: "user", content: { type: "text", text: `请审查这段代码:\n\n${code}` } }]
  })
);

这两样不是每个服务器都得有,按需取用。大多数“连自家系统”的服务器,核心还是几个工具;资源和提示是锦上添花,等你的服务器长出更多需求时再加不迟。一上来就把三样全堆上,反而容易把简单的事做复杂。

服务器不工作时,怎么调试?

第一次接MCP服务器,十有八九会遇到“加进去了但连不上”或者“工具调用就报错”。这一节专治这些,把最容易撞的几个坑摆出来。

头号大坑,前面埋过伏笔——日志必须打到stderr,绝对不能用console.log。原因前面说过:stdout这条通道是留给MCP协议消息走的,你一console.log,就把日志混进了正经的JSON-RPC消息流,协议解析直接崩。正确做法是所有调试输出都走console.error,它打到stderr,不干扰协议:

console.error("服务器已启动,等待连接……");

这个坑特别隐蔽,因为console.log在别处都是对的,唯独在MCP的stdio服务器里是致命的。很多人对着“服务器莫名其妙连不上”查半天,根子就在某行随手写的console.log上。养成习惯:写MCP服务器,日志一律console.error

第二个常用手段是查服务器状态。命令行里跑claude mcp list看所有服务器、claude mcp get weather看某个服务器的详情;在Claude Code会话里输入/mcp,能看到每个服务器连没连上、暴露了几个工具,连不上的会标出来。如果显示工具数为0,多半是注册代码有问题或者根本没编译;如果压根没出现在列表里,回头检查claude mcp add那条命令、尤其是双横杠和路径。

还有个进阶点值得知道:Claude Code启动你的服务器时,会在它的环境变量里塞一个CLAUDE_PROJECT_DIR,指向项目根目录。你的服务器代码里可以用process.env.CLAUDE_PROJECT_DIR读到它,从而稳妥地解析项目内的相对路径,不用依赖那个飘忽不定的工作目录。这个细节在“服务器需要读项目里某个文件”时特别有用,能帮你避开一类“本地跑得好、换个目录就找不到文件”的怪问题。

写好了,怎么发布出去给别人用?

自己用爽了,想分享给团队甚至社区,发布到npm是最通用的路子。先确认package.json里名字、版本、入口都对,然后登录npm、发布:

npm publish --access public

发布后,别人就能像装任何npm包一样用你的服务器了,配置时把启动命令换成npx -y你的包名即可,连本地编译都省了。如果你的服务器对接的是通用工具、有普适价值,还可以提交到社区的MCP服务器目录,或走官方的连接器目录提交流程,让更多人发现。当然,如果它连的是你公司内部那套系统,就只在团队私有仓库里分享,别公开——内部接口的细节没必要也不应该外泄。

这里多提醒一句安全。你的MCP服务器一旦能执行动作、能读数据,它就成了一个有权限的入口。发布前务必想清楚:它会不会把不该暴露的数据返回出去?要不要做调用方校验?涉及写操作、删操作的工具,是不是该加二次确认?尤其是那种会拉取外部内容的服务器,还要防着提示注入——别让一段从外部读回来的文本,变成操纵Claude Code的指令。把这些想周全,你这座桥才既好用又安全。

不想从零写,有没有官方脚手架?

读到这儿你可能会想:流程是清楚了,但手动建项目、配tsconfig、写样板还是有点烦,有没有更快的法子?有,而且是官方的。Claude Code提供了一个叫mcp-server-dev的官方插件,能让Claude直接帮你把服务器骨架搭出来。

用法分两步。先在Claude Code会话里装上这个插件,如果提示找不到市场,先把官方插件市场加进来再装:

/plugin install mcp-server-dev@claude-plugins-official

装好后,运行它带的构建技能,Claude会反过来问你的使用场景,然后替你脚手架出一个远程HTTP或本地stdio的服务器:

/mcp-server-dev:build-mcp-server

这条路适合两类人:一是想快速起步、不愿意从空目录抠样板的;二是初学者,让官方脚手架生成一份正确的起点,再对照本文去读懂每部分在干嘛,比自己摸索高效得多。不过保哥的建议是,哪怕你用脚手架,前面那套手写流程也值得至少跑一遍——只有亲手踩过包名、双横杠、stderr这几个坑,你才真正理解脚手架替你省掉了什么,日后出问题也才知道去哪儿找。工具能加速,但理解没法外包。

常见问题解答

写MCP服务器到底该装哪个npm包?

@modelcontextprotocol/sdk,这是当前稳定、官方推荐用于生产的TypeScript SDK(v1系列,最新到1.29左右),导入时走带.js的子路径如@modelcontextprotocol/sdk/server/mcp.js。那个不带sdk的@modelcontextprotocol/server是预览期的v2,不建议生产用。看到教程让你装后者,基本可以判定它过时或不准。

registerTool的inputSchema为什么不用z.object包起来?

这是v1稳定版的写法:inputSchema直接传一个裸的字段对象,比如{ city: z.string() },SDK内部会处理。用z.object({...})包起来是预览期v2的写法,在v1里会出问题。这恰好是判断一份教程对应哪个版本的快捷标志——裸字段对象是v1,z.object是v2。

为什么MCP服务器里不能用console.log?

因为本地MCP服务器靠标准输出(stdout)传输协议消息,console.log会把日志混进这条通道,污染JSON-RPC消息流,导致连接解析失败。所有调试输出必须改用console.error,它走stderr不干扰协议。很多“服务器莫名连不上”的问题,根子就是某行随手写的console.log。

claude mcp add命令里的双横杠是干嘛的?

双横杠--用来分隔“给claude mcp add的选项”和“启动服务器的命令”。所有选项必须放在服务器名字之前,--之后才是要执行的命令和参数,比如claude mcp add weather -- node /路径/dist/index.js。漏了它或放错位置,命令会被错误解析,服务器加进去也起不来,路径建议用绝对路径。

不想手写样板,有没有更快的起步方式?

有官方脚手架。在Claude Code里装mcp-server-dev插件(/plugin install mcp-server-dev@claude-plugins-official),再运行/mcp-server-dev:build-mcp-server,Claude会问你的场景并自动生成服务器骨架。适合快速起步和初学者。但建议哪怕用脚手架,也至少手写跑一遍完整流程,亲手踩过坑才真正理解每部分在干嘛。

自己写的MCP服务器怎么分享给别人用?

发布到npm最通用:确认package.json正确后跑npm publish --access public,别人用npx -y你的包名就能配上,连本地编译都省。有普适价值的可提交到社区MCP目录或官方连接器目录。但如果服务器连的是公司内部系统,就只在团队私有仓库分享,别公开,内部接口细节不该外泄。

FAQPage + Article AI 引用友好版

TL;DR · 60–80 字摘要 · 适用 ChatGPT / Perplexity / Gemini / 文心 引用

手把手用官方稳定版SDK从零开发一个MCP服务器:初始化项目、注册第一个工具、编译接入Claude Code,再加Resources和Prompts、调试、发布到npm。重点讲清正确包名、registerTool真实签名、日志为何必须走stderr、claude mcp add双横杠这些容易翻车的细节。

关键实体 · Key Entities

  • MCP
  • Claude Code
  • AI编程
  • TypeScript
  • Anthropic SDK
  • AI编程与工具链

引用元数据 · Citation Metadata

title:       从零写一个MCP服务器:用Claude Code和官方SDK手把手搭一座桥
author:      张文保 (Paul Zhang) — PatPat SEO 经理
url:         https://zhangwenbao.com/claude-code-mcp-server-tutorial.html
published:   2026-02-21
modified:    2026-06-04
source-type: First-hand expert commentary
language:    zh-CN
license:     CC BY-NC-SA 4.0 (要求保留原文链接与作者归属)
分享到
标签
版权声明

本文标题:《从零写一个MCP服务器:用Claude Code和官方SDK手把手搭一座桥》

本文链接:https://zhangwenbao.com/claude-code-mcp-server-tutorial.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交