news 2026/10/1 15:45:39

从零开始开发一个 MCP Server:用 Node.js 打通 VS Code 与 Docker 的完整实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始开发一个 MCP Server:用 Node.js 打通 VS Code 与 Docker 的完整实践

1. 为什么我要自己写一个 MCP Server

MCP Server 是什么?简单说,它是让大模型能调用外部工具的“插座”。你写一个 MCP Server,把查天气、读文件、跑命令这些能力注册进去,Claude、VS Code 里的 Agent 就能通过标准协议调用它。适合谁?适合想把内部脚本、数据库查询、运维命令接给 AI 用的开发者,也适合想搞懂 MCP 协议到底怎么跑起来的人。

我这次的目标很具体:用 Node.js 从零写一个能跑命令的 MCP Server,在 VS Code 里调试通过,最后用 Docker 打包成镜像,换台机器也能一键起。整个过程不依赖脚手架生成的黑盒代码,每一行配置我都自己写,这样出问题才知道去哪查。

为什么不用现成的 generator?脚手架确实快,但它把package.json、launch.json、mcp.json全生成了,你调试时遇到local proxy failed或者reading choices报错,根本不知道是哪个环节断的。从零手写一遍,协议握手、工具注册、stdio 通信这三块就全清楚了。

技术选型上,Node.js 的@modelcontextprotocol/sdk是目前最成熟的实现,配合zod做参数校验,VS Code Insiders 的 Agent Mode 原生支持 MCP,Docker 负责环境隔离。整条链路是:本地 Node 进程通过 stdio 和 VS Code 通信,Docker 只是把这个进程装进容器,通信方式不变。

下面我会按“初始化项目 → 写 server 入口 → 注册工具 → VS Code 接入 → Docker 打包 → 排错”的顺序走,每个配置文件都给完整内容,你复制就能用。中间涉及模型调用的部分,我会用 TaoToken 的 API 做演示,因为它兼容 Anthropic 协议,接 Claude Code 和 VS Code 插件都省事。

2. 前置准备:Node.js 环境与 TaoToken 接入配置

在写代码之前,先把两件事搞定:Node.js 环境和模型 API 的接入。MCP Server 本身不直接调模型,但你在 VS Code 里测试工具调用时,需要有一个能跑 Agent 的模型后端。我用 TaoToken 来做这一步,原因是它的 API 地址兼容 Anthropic 的 Messages 接口,Claude Code、Cline、VS Code 插件都能直接填。

先确认 Node.js 版本。打开终端跑:

node -v npm -v

MCP SDK 要求 Node 18 以上,我实测 Node 20 LTS 最稳。如果版本太低,去官网下 LTS 包重装。装完后全局装一下 TypeScript 和 tsx,后面调试要用:

npm install -g typescript tsx

接下来是 TaoToken 的 Key。访问 https://taotoken.net/api-keys 注册后创建一个 API Key,复制出来。这个 Key 后面要填到 VS Code 的 MCP 配置里,格式是sk-开头的一串字符。注意别把它提交到 Git,我一般放在.env或者系统的环境变量里。

TaoToken 的 API 基础地址是:

https://taotoken.net/api

这个地址在配置 Claude Code 或者 VS Code 插件时填到ANTHROPIC_BASE_URL或者对应的 Base URL 字段。模型 ID 填claude-sonnet-4-5或者你账号里可用的其他模型。三个要素记牢:Base URL、API Key、Model ID,缺一个都连不上。

如果你用的是 Claude Code,配置方式是在~/.claude/settings.json里写:

{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "sk-你的Key", "ANTHROPIC_MODEL": "claude-sonnet-4-5" } }

VS Code 这边,如果你用 Cline 或者 Roo Code 插件,在插件设置里选 Anthropic 兼容模式,Base URL 填https://taotoken.net/api,Key 填进去,模型选claude-sonnet-4-5。这样 Agent 就有模型可用了,接下来我们写的 MCP Server 才能被它调用。

还有一步:VS Code 要用 Insiders 版本,稳定版目前对 MCP 的支持还不完整。去 https://code.visualstudio.com/insiders/ 下载安装。装完后在设置里搜mcp,确认chat.mcp.enabled是勾上的。

环境齐了,开始建项目。

3. 从零写 MCP Server:package.json 与 server 入口完整配置

新建一个目录,比如mcp-command-runner,进去初始化:

mkdir mcp-command-runner && cd mcp-command-runner npm init -y

然后装依赖:

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

装完后改package.json,完整内容如下,你可以直接覆盖:

{ "name": "mcp-command-runner", "version": "1.0.0", "description": "A simple MCP server that runs shell commands", "type": "module", "main": "dist/index.js", "bin": { "mcp-command-runner": "dist/index.js" }, "scripts": { "build": "tsc", "dev": "tsx src/index.ts", "start": "node dist/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.0.0", "zod": "^3.23.8" }, "devDependencies": { "@types/node": "^20.11.0", "tsx": "^4.7.0", "typescript": "^5.3.3" } }

注意"type": "module"这行,MCP SDK 是 ESM 的,不加这行 import 会报错。bin字段是为了后面发布到 npm 后能用npx调用。

再建tsconfig.json:

{ "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }

现在写核心入口src/index.ts。这个文件做三件事:创建 Server 实例、注册工具、连接 stdio 传输。完整代码:

import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; import { exec } from "child_process"; import { promisify } from "util"; const execAsync = promisify(exec); const server = new Server( { name: "mcp-command-runner", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); const RunCommandSchema = z.object({ command: z.string().describe("要执行的 shell 命令"), timeout: z.number().optional().default(10000).describe("超时毫秒数"), }); server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "run_command", description: "在本地执行一条 shell 命令并返回输出", inputSchema: { type: "object", properties: { command: { type: "string", description: "要执行的 shell 命令" }, timeout: { type: "number", description: "超时毫秒数,默认 10000" }, }, required: ["command"], }, }, ], }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name !== "run_command") { throw new Error(`未知工具: ${request.params.name}`); } const parsed = RunCommandSchema.safeParse(request.params.arguments); if (!parsed.success) { return { content: [{ type: "text", text: `参数错误: ${parsed.error.message}` }], isError: true, }; } const { command, timeout } = parsed.data; try { const { stdout, stderr } = await execAsync(command, { timeout }); const output = stdout || stderr || "(无输出)"; return { content: [{ type: "text", text: output }], }; } catch (error: any) { return { content: [{ type: "text", text: `执行失败: ${error.message}` }], isError: true, }; } }); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("MCP Command Runner 已启动"); } main().catch((error) => { console.error("启动失败:", error); process.exit(1); });

几个关键点解释一下。ListToolsRequestSchema是告诉客户端“我有哪些工具”,返回的inputSchema必须是标准 JSON Schema,不能直接塞 zod 对象。CallToolRequestSchema是实际调用,这里我用 zod 做二次校验,防止模型传错参数。StdioServerTransport是通信方式,VS Code 和 Claude Desktop 都走 stdio,所以不需要开端口。

注意console.error而不是console.log。stdio 传输下,stdout 是协议数据通道,你往 stdout 打日志会污染协议,客户端直接报reading choices或者解析失败。所有调试信息走 stderr。

写完跑一下编译:

npm run build

没报错的话,dist/index.js就生成了。接下来配 VS Code 调试。

4. VS Code 接入与工具调用验证:mcp.json 配置与实测

VS Code Insiders 里 MCP Server 的配置放在.vscode/mcp.json。在项目根目录建这个文件:

{ "servers": { "command-runner": { "type": "stdio", "command": "node", "args": ["${workspaceFolder}/dist/index.js"] } } }

如果你还没编译,想直接跑 TS 源码,把command改成npx,args改成["tsx", "${workspaceFolder}/src/index.ts"]。但生产环境建议用编译后的 JS,启动快。

保存后,VS Code 会在mcp.json上方显示一个Start按钮,点它。如果配置正确,状态会变成 running。然后打开 Copilot Chat 或者 Cline 的 Agent 模式,在对话框里输入:

帮我执行 echo "hello mcp" 并告诉我输出

Agent 会识别到run_command工具,弹出确认框,点允许。几秒后你应该看到返回hello mcp。这一步成功,说明整条链路通了:VS Code → stdio → 你的 Server → 执行命令 → 返回结果。

如果没反应,先看 VS Code 的 Output 面板,选MCP频道,里面有详细日志。常见的是路径写错,${workspaceFolder}没解析对,换成绝对路径试试。

再测一个复杂点的:

列出当前目录下的文件,用 ls -la

Agent 调用run_command,参数command为ls -la,返回文件列表。到这一步,工具注册和调用验证完成。

这里插一句模型配置。VS Code 的 Agent 需要模型后端,如果你用 Cline 插件,在设置里填 TaoToken 的 Base URLhttps://taotoken.net/api、API Key 和模型 IDclaude-sonnet-4-5。这样 Agent 的推理走 TaoToken,工具调用走你本地写的 MCP Server,两边独立。想先验证模型通不通,可以去 https://taotoken.net/api 的模型对话页面发一条消息试试。

调试阶段还有个技巧:在src/index.ts里加console.error打点,比如在CallToolRequestSchema处理函数开头打console.error("收到调用:", request.params.name)。VS Code 的 MCP 日志里能看到这些 stderr 输出,方便定位是没收到请求还是执行出错。

5. Docker 容器化部署:Dockerfile 编写与常见报错排查

本地跑通后,用 Docker 打包,换机器不用重装 Node。在项目根目录建Dockerfile:

FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY tsconfig.json ./ COPY src ./src RUN npm run build FROM node:20-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --omit=dev COPY --from=builder /app/dist ./dist RUN addgroup -S mcp && adduser -S mcp -G mcp USER mcp ENTRYPOINT ["node", "dist/index.js"]

多阶段构建,builder 阶段编译 TS,最终镜像只留生产依赖和dist。用非 root 用户跑,安全一点。构建:

docker build -t mcp-command-runner:1.0.0 .

构建完测试容器能不能正常启动:

echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}' | docker run -i --rm mcp-command-runner:1.0.0

如果返回一段包含serverInfo的 JSON,说明容器里的 Server 正常。注意-i必须加,stdio 需要保持 stdin 打开。

在 VS Code 里用 Docker 版的配置改成:

{ "servers": { "command-runner-docker": { "type": "stdio", "command": "docker", "args": ["run", "-i", "--rm", "mcp-command-runner:1.0.0"] } } }

下面是我踩过的几个坑,对照报错看:

报错一:local proxy failed或连接超时。这个通常出现在模型 API 配置环节,不是 MCP Server 本身的问题。检查 TaoToken 的 Base URL 是不是https://taotoken.net/api,Key 有没有多余空格,模型 ID 拼写对不对。如果 VS Code 插件里填了代理地址,清掉。

报错二:Unexpected token或reading 'choices'。这是 stdout 被污染了。检查代码里有没有console.log,全部改成console.error。另外 npm 的一些包会在启动时往 stdout 打警告,用npm ci --silent或者直接在 Docker 里跑编译后的 JS 避免。

报错三:401 Unauthorized。API Key 无效或者没传。确认 Key 是sk-开头,在 TaoToken 控制台看余额和权限。VS Code 的 MCP 配置里如果引用了环境变量,确认变量名拼写一致。

报错四:OAuth相关错误。有些客户端默认走 OAuth 流程,但 TaoToken 用的是 API Key 模式。在插件设置里把认证方式从 OAuth 改成 API Key,或者手动填ANTHROPIC_AUTH_TOKEN。

报错五:Docker 容器启动后立即退出。多半是ENTRYPOINT路径不对,或者dist/index.js没 COPY 进去。跑docker run -it --rm mcp-command-runner:1.0.0 sh进去看看文件在不在。

报错六:Windows 上npx运行失败。这是老问题,npx在 Windows 的 cmd 里解析路径有 bug。解决办法是在mcp.json里把command写成cmd,args写成["/c", "npx", "tsx", "src/index.ts"]。或者干脆用编译后的node dist/index.js,绕开 npx。

排查顺序建议:先看 VS Code 的 MCP 日志,再看容器日志,最后看模型 API 的返回。三段分开定位,别混在一起查。

6. 把 MCP Server 接进日常开发流

工具调通、容器跑起来之后,我一般会把它接到几个固定场景。一个是让 Agent 帮我跑测试命令,比如npm test或者pytest,输出直接回给模型分析失败原因。另一个是查日志,tail -n 100 app.log这种,模型读完能直接给排查建议。这些都不需要改 Server 代码,只要在对话里描述清楚就行。

如果你想让这个 Server 更实用,可以加几个工具:read_file读指定路径文件、list_dir列目录、http_get发请求。每个工具就是一组ListTools返回定义加一个CallTool分支,照着run_command抄就行。参数校验用 zod,返回格式统一content数组。

发布到 npm 的话,package.json里的bin已经配好了,npm publish之后别人就能用npx mcp-command-runner启动。Docker Hub 就docker push你构建的镜像。发布前记得把 API Key 相关的配置全清掉,Server 本身不应该硬编码任何密钥。

长期在 VS Code 里跑 Agent 做编码任务的话,模型调用量会比较大,可以考虑用 TaoToken 的 Coding Plan,地址是 https://taotoken.net/coding-plan,按量计费比单次调用划算。接入文档在 https://taotoken.net/doc,里面有 Claude Code、Cline、Codex 各种客户端的配置示例。需要看模型列表或者调试对话,去 https://taotoken.net/api 的模型对话页面。

最后留一个我实际用下来的习惯:MCP Server 的日志全部走 stderr,并且在 Docker 里把 stderr 重定向到文件,出问题直接docker logs看。stdio 协议对输出干净度要求很高,任何多余的 stdout 输出都会让客户端解析失败,这个坑我踩过不止一次。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 15:44:15

AIGC检测免费查:汇写让你对AI生成率心中有数,投稿前先做“体检”

2024年以来,AIGC检测从一个陌生概念变成了高校和期刊社的标配。很多学校在毕业论文送审前要求提供AIGC检测报告,不少核心期刊和SCI期刊也开始在审稿流程中加入AI内容筛查。这意味着,即使你的论文文字重复率合格,如果AI生成率过高&…

作者头像 李华
网站建设 2026/10/1 15:43:46

如何买到好苹果17?

如果第一次去看二手 iPhone,不太建议一进店就只问一句:“这台多少钱?”因为二手机价格只是其中一个维度。真正需要搞明白的是:这台手机是什么状态?最近整理了一套比较简单的二手手机验机方法,如果正在广州买…

作者头像 李华
网站建设 2026/10/1 15:43:46

2026西安汽车改装靠谱门店推荐【鑫互联车改影音连锁】|15年老店专注音响、全景影像、氛围灯无损升级

导语:对于西安众多车主而言,汽车音响、720/360全景影像、车内氛围灯升级,是提升用车体验的三大热门改装项目。不管是奔驰、宝马、保时捷等高端燃油车,还是特斯拉、理想、蔚来、问界等主流新能源车型,原厂配置往往难以满…

作者头像 李华
网站建设 2026/10/1 15:43:31

IL_动作捕捉方式方法列述

IL:Imitation Learning,CP:Capture Motion 模仿学习:动作采集与捕捉方法(分类 原理 优缺点)模仿学习(IL, Imitation Learning)核心目标:从人类演示(demonst…

作者头像 李华
网站建设 2026/10/1 15:43:29

虚拟电厂电-气-碳联合优化调度的Matlab建模实现

这两年做低碳电力系统相关的网格优化,最常被问到的一个问题就是:虚拟电厂调度里同时塞进电转气、碳捕集和垃圾焚烧,到底该怎么建模?单看任何一个设备,网上的资料都不算少,但要在一套调度模型里把“电、气、…

作者头像 李华