ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

使用TypeScript构建一个最简单的MCP服务器:从零到可运行

2026/10/4 22:55:53 拓冰建站 浏览量
使用TypeScript构建一个最简单的MCP服务器:从零到可运行 1. 从零跑通 TypeScript MCP 服务器到底难在哪如果你最近在折腾 AI 工具调用大概率听过 MCPModel Context Protocol这个词。它本质上是一套开放标准让 AI 助手能用统一的方式去调用你写的函数、读你的数据源。你可以把它理解成 AI 和外部工具之间的“翻译官”你按协议写好一个工具AI 就能在对话里直接调用它不用你再去适配每家平台的私有插件格式。但真到自己动手写第一个 MCP 服务器时很多人会卡在几个地方。第一是不知道从哪初始化项目modelcontextprotocol/sdk的导入路径和普通 npm 包不太一样mcp.js、stdio.js这些后缀容易写错。第二是工具注册的 schema 到底怎么定义参数校验用 zod 还是 JSON Schema返回结构长什么样。第三是写完之后怎么验证它真的能跑stdio 传输模式下服务器是“哑”的不接客户端你根本看不到反馈。这篇就按“最小可运行”的思路走一遍用 TypeScript 建一个计算器 MCP 服务器注册加减乘除四个工具编译后用 stdio 启动再配到支持 MCP 的客户端里实际调用一次。全程命令可复制代码可粘贴目标是让你在半小时内看到第一个工具被 AI 成功调用。适合已经会一点 TypeScript、想快速理解 MCP 工具注册机制的开发者。如果你还没配好模型侧的调用环境后面我也会给出用 TaoToken 做统一接入的配置方式省得你在多个 Key 之间来回切。先说清楚一个预期MCP 服务器本身不产生智能它只是暴露能力。真正“聪明”的部分在客户端和模型。所以我们的验证重点不是模型答得多好而是工具有没有被正确注册、参数有没有被正确解析、返回值有没有被正确回传。把这三点跑通你就掌握了 MCP 服务器开发的核心骨架。2. 前置准备项目初始化与 TaoToken 接入配置2.1 创建项目与安装依赖先建目录、初始化 npm然后装三个东西MCP SDK、zod、TypeScript 工具链。mkdir mcp-calculator-server cd mcp-calculator-server npm init -y npm install modelcontextprotocol/sdk zod npm install -D typescript types/node npx tsc --initnpx tsc --init会生成一个默认的tsconfig.json默认配置里outDir没开编译产物会和源码混在一起。建议手动改几个关键项让输出落到build/目录{ compilerOptions: { target: ES2022, module: Node16, moduleResolution: Node16, outDir: ./build, rootDir: ./src, strict: true, esModuleInterop: true, skipLibCheck: true }, include: [src/**/*] }这里module和moduleResolution都设成Node16是有原因的MCP SDK 的包导出用了exports字段老的node解析模式找不到mcp.js这个子路径会报Cannot find module。这一步踩过坑的人不少。目录结构最终长这样mcp-calculator-server/ ├── package.json ├── tsconfig.json ├── src/ │ └── server/ │ └── index.ts └── build/ # 编译输出2.2 用 TaoToken 统一模型接入MCP 服务器写完后你需要一个能调用工具的客户端来验证。如果你用的是 Claude Code、Cline 这类支持 MCP 的编码工具模型侧的接入可以走 TaoToken 的统一入口Base URL 填https://taotoken.net/apiKey 在控制台生成模型 ID 按你实际用的填。这样做的好处是MCP 服务器只管暴露工具模型调用走统一网关两边解耦换模型不用改服务器代码。具体三件套配置如下以 Claude Code 的 settings 为例{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的Key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }如果你用的是 Cline 或 Roo Code 这类插件在设置里找 “OpenAI Compatible” 或 “Anthropic” 提供商Base URL 同样填https://taotoken.net/apiKey 填控制台生成的Model ID 填你要用的模型。Key 的生成入口在控制台的 API Keys 页面文档在接入文档里模型列表可以在模型对话里先试一下通不通。注意MCP 服务器和模型接入是两件独立的事。服务器用 stdio 本地跑模型走 HTTP 网关两者通过客户端串起来。不要试图在 MCP 服务器里直接调模型 API那样会把架构搞乱。3. 可复制配置server 代码与工具注册3.1 服务器骨架新建src/server/index.ts先写导入和服务器实例。注意导入路径带.js后缀这是 Node16 模块解析的要求写.ts或省略都会在编译后报错。#!/usr/bin/env node import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { z } from zod; const server new McpServer({ name: simple-calculator, version: 1.0.0, capabilities: { resources: {}, tools: {}, }, });capabilities里声明tools: {}表示这个服务器提供工具能力。如果你后面要加资源resources或提示模板prompts也在这里声明。3.2 用 zod 定义参数 schema参数校验用 zod好处是类型推导和运行时校验一体。定义一个基础数字 schemaconst NumberSchema z.number().describe(数值参数支持整数和浮点数);.describe()里的文字会作为参数说明传给客户端模型靠它理解每个参数的含义。别偷懒不写否则模型可能把字符串塞进数字参数里。3.3 注册四个运算工具server.tool()的签名是工具名、描述、参数 schema 对象、异步处理函数。处理函数返回content数组每项有type和text。server.tool( add, 计算两个数字相加返回它们的和, { a: NumberSchema, b: NumberSchema }, async ({ a, b }) { const result a b; console.error(计算: ${a} ${b} ${result}); return { content: [{ type: text, text: ${a} ${b} ${result} }], }; } ); server.tool( subtract, 计算两个数字相减返回差值, { a: NumberSchema, b: NumberSchema }, async ({ a, b }) { const result a - b; return { content: [{ type: text, text: ${a} - ${b} ${result} }], }; } ); server.tool( multiply, 计算两个数字相乘返回积, { a: NumberSchema, b: NumberSchema }, async ({ a, b }) { const result a * b; return { content: [{ type: text, text: ${a} × ${b} ${result} }], }; } ); server.tool( divide, 计算两个数字相除包含除零检查, { dividend: NumberSchema, divisor: NumberSchema }, async ({ dividend, divisor }) { if (divisor 0) { return { content: [{ type: text, text: 错误除数不能为零 }], }; } const result dividend / divisor; return { content: [{ type: text, text: ${dividend} ÷ ${divisor} ${result} }], }; } );这里有个细节日志用console.error而不是console.log。因为 stdio 传输模式下stdout 是协议通道你往 stdout 打任何非协议内容都会污染消息流导致客户端解析失败。调试信息一律走 stderr。3.4 启动逻辑async function main() { const transport new StdioServerTransport(); await server.connect(transport); console.error(计算器 MCP 服务器已启动); } main().catch((error) { console.error(启动失败:, error); process.exit(1); });StdioServerTransport让服务器通过标准输入输出和客户端通信。客户端启动这个进程后双方用 JSON-RPC 消息在 stdin/stdout 上对话。4. 验证请求编译、启动与客户端调用4.1 编译并直接运行npx tsc node build/server/index.js如果编译没报错你会看到 stderr 输出“计算器 MCP 服务器已启动”然后进程挂起等待输入。这是正常的stdio 模式下它在等客户端发消息。按 CtrlC 退出。如果npx tsc报Cannot find module modelcontextprotocol/sdk/server/mcp.js回去检查tsconfig.json的moduleResolution是不是Node16。4.2 配置到客户端以 VS Code 的 MCP 配置为例在项目根目录建.vscode/mcp.json{ servers: { mcp-calculator: { type: stdio, command: node, args: [/绝对路径/mcp-calculator-server/build/server/index.js] } } }路径必须是编译后的.js绝对路径不能是.ts。配置参数对照如下参数作用示例值servers服务器配置容器{...}mcp-calculator服务器标识名任意字符串type通信协议类型stdiocommand启动命令nodeargs命令参数数组[.../index.js]4.3 实际调用验证重启客户端后在对话里输入“帮我算一下 128 乘以 47”。如果一切正常模型会识别出需要调用multiply工具传入a128, b47服务器返回128 × 47 6016模型再把结果组织成自然语言回复你。你也可以直接问“用计算器算 100 除以 0”验证除零分支是否返回了错误提示而不是崩溃。这一步能跑通说明工具注册、参数解析、返回值回传三个环节都通了。5. 本篇常见错误排查5.1 401 与鉴权失败如果你在客户端侧看到 401问题通常不在 MCP 服务器而在模型接入的 Key 上。检查ANTHROPIC_API_KEY或对应提供商的 Key 是否填对Base URL 是否是https://taotoken.net/api。MCP 服务器本身不涉及鉴权它只是本地进程。5.2 local proxy failed 与连接错误local proxy failed一般出现在客户端尝试连接模型网关时。先确认网络能通到 Base URL再确认 Key 有效。如果 MCP 服务器进程启动失败客户端可能报“server disconnected”这时去看 stderr 日志多半是路径写错或编译产物不存在。5.3 reading choices 与响应解析错误reading choices这类报错通常出现在 OpenAI 兼容接口的响应解析上。如果你用的客户端走的是 OpenAI 格式确认 Base URL 和模型 ID 匹配。MCP 工具调用和模型响应格式是两层别混在一起排查。5.4 OAuth 与认证流程部分客户端在接入 Anthropic 系接口时会走 OAuth 流程。如果你用的是 API Key 模式确保客户端配置里没有残留的 OAuth 设置。TaoToken 的接入以 API Key 为主配置时选对应的鉴权方式即可。5.5 工具没被调用模型没调用工具常见原因有三个工具描述太模糊模型不知道什么时候用参数 schema 的.describe()没写清楚客户端没正确加载 MCP 配置。逐个检查描述里写清“计算两个数字相加”参数说明写清“数值参数”配置文件路径用绝对路径。6. 继续往下走从计算器到真实工具计算器只是最小示例它的价值在于让你看清 MCP 服务器的骨架一个 server 实例、若干 tool 注册、一个 stdio transport。把这套骨架复制到真实场景你就能暴露文件读写、数据库查询、API 调用等能力。如果你打算长期做编码类 Agent建议把模型接入固定到 Coding Plan这样 MCP 服务器和模型调用两边都稳定不用每次换环境重新配。Key 在控制台生成文档在接入文档模型可以先在模型对话里试通再写进配置。最后留一个实用技巧调试 MCP 服务器时可以先用echo手动往 stdin 发一条 JSON-RPC 初始化消息看 stdout 返回什么。虽然麻烦但能帮你确认协议层是否正常比在客户端里盲猜快得多。