# 从零手搓一个MCP服务器,我踩过的坑比写代码还多 上个月老板突然在群里丢了一句话:"我们的AI客服能不能直接查公司的数据库?" 我当时觉得这是个小事——写个function calling不就完了?结果越做越发现不对劲。先是Python写了一版,换到TypeScript项目又要重写一遍。然后产品那边说他们的AI助手也想用,好家伙,又得再来一次。 就在我准备提桶跑路的时候,同事甩了一个链接给我:"看看MCP?" 然后我就入坑了。 ## MCP到底是啥?一句话说清楚 MCP全称Model Context Protocol,是Anthropic在2024年11月搞出来的一个开放协议。 用大白话说:**它是一个标准化的插头。** 你想想USB-C——不管什么设备,只要接口是USB-C就能插。MCP就是这个思路:不管你用什么AI客户端(Claude Desktop、Cursor、VS Code Copilot),不管你后端接什么工具(数据库、GitHub、Slack),只要两边都支持MCP,就能直接连上。 以前每换一个AI客户端,就得重新写一遍工具集成。现在写一次MCP Server,所有客户端都能用。 这玩意的重要性在于——微软、OpenAI、Google全部表态支持了。这不是Anthropic一家在玩,这是整个AI行业的共识方向。 ## 和Function Calling有啥区别? 我知道你肯定在想:这不就是Function Calling换了个皮吗? 还真不是。核心区别在这: **Function Calling是"一对一"的**——你给OpenAI的API定义了一组tools,换了Claude的API就得重新定义。每个AI模型有自己的一套tool schema格式,互相不通。 **MCP是"多对多"的**——你写一个MCP Server,暴露一组标准化的工具接口。任何MCP Client(Claude、Cursor、Gemini CLI、自定义Agent)都能自动发现并调用这些工具。Server和Client完全解耦。 ``` Function Calling: AI模型 ←→ 你的代码(紧耦合) MCP: AI客户端 ←→ MCP协议 ←→ MCP服务器(松耦合) ``` 打个比方:Function Calling是你自己焊了一根充电线,只能充你的手机。MCP是一个USB-C标准,任何USB-C设备都能用。 实际开发中,这两种方式并不冲突——你的私有小工具用Function Calling就够了,跨团队跨项目的共享工具才值得上MCP。 ## 开干:6步搭建你的第一个MCP Server 我花了整整一个周末才把这个流程跑通。官方文档写得很"优雅"——优雅到看不懂。下面是我踩完坑之后的版本。 ### 第1步:环境准备 ```bash # 确保Node.js >= 18 node --version # 创建项目目录 mkdir my-first-mcp-server && cd my-first-mcp-server npm init -y npm install @modelcontextprotocol/sdk zod npm install -D typescript @types/node # 初始化TypeScript npx tsc --init ``` 对了,官方也有Python SDK,但TypeScript生态更成熟,社区里的MCP Server大部分都是TS写的。Python党别急,后面我也会提。 ### 第2步:写服务器骨架 创建 `src/index.ts`: ```typescript 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: "my-first-server", version: "1.0.0", }); // 注册一个工具 server.tool( "get_weather", // 工具名称 "获取指定城市的天气信息", // 工具描述(AI靠这个理解工具用途) { city: z.string().describe("城市名") }, // 参数schema async ({ city }) => { // 这里写实际的业务逻辑 const temp = Math.floor(Math.random() * 35 + 5); return { content: [{ type: "text", text: `${city}当前温度:${temp}°C` }], }; } ); // 启动服务器 const transport = new StdioServerTransport(); await server.connect(transport); ``` 这就是一个完整的MCP Server了。核心就三样东西: 1. `server.tool()` —— 注册工具 2. `z.object()` —— 定义参数类型 3. `async callback` —— 实现逻辑 ### 第3步:配置编译 ```json // tsconfig.json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "strict": true }, "include": ["src/**/*"] } ``` ```json // package.json 加上 { "scripts": { "build": "tsc", "start": "node build/index.js" }, "type": "module" } ``` ```bash npm run build ``` 第一次编译大概率会报错——别慌,检查一下`moduleResolution`是不是设的`Node16`。这个坑我踩了半小时。 ### 第4步:用Inspector调试 这是最爽的一步。Anthropic提供了一个可视化的调试工具: ```bash npx @modelcontextprotocol/inspector node build/index.js ``` 它会打开一个网页,你可以在里面直接测试你的工具——填参数、看返回值、调试错误。 **这一步千万别跳过。** 我之前直接配到Claude Desktop里,结果报错了完全不知道哪里出问题。Inspector能让你在本地先把逻辑跑通。 ### 第5步:接到Claude Desktop 打开Claude Desktop的配置文件: - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - Windows: `%APPDATA%\Claude\claude_desktop_config.json` ```json { "mcpServers": { "my-first-server": { "command": "node", "args": ["/绝对路径/my-first-mcp-server/build/index.js"] } } } ``` 重启Claude Desktop,打开对话,点击右下角的🔧图标——如果能看到你的服务器和工具列表,就说明连上了。 ### 第6步:试试效果 在Claude Desktop里输入:"北京今天天气怎么样?" Claude会自动调用你的`get_weather`工具,传入`city: "北京"`,然后把返回结果组织成自然语言回复给你。 **注意这里的魔法**——你从来没有告诉Claude"遇到天气问题就调用这个工具"。它靠工具的`name`和`description`自动判断什么时候该调用。所以工具的描述写得越清楚,AI调用就越准确。 ## 进阶:做一个真正有用的Server 天气查询太玩具了。接下来我带你们做一个实际项目里能用的——**GitHub代码库分析器**。 ```typescript server.tool( "analyze_repo", "分析GitHub仓库的代码统计信息", { owner: z.string().describe("仓库所有者"), repo: z.string().describe("仓库名称"), }, async ({ owner, repo }) => { try { const response = await fetch( `https://api.github.com/repos/${owner}/${repo}` ); if (!response.ok) { return { content: [{ type: "text", text: `❌ 仓库不存在或无法访问: ${response.status}` }], isError: true, }; } const data = await response.json(); const summary = [ `📦 ${data.full_name}`, `⭐ Stars: ${data.stargazers_count}`, `🍴 Forks: ${data.forks_count}`, `📝 语言: ${data.language || "未指定"}`, `📋 License: ${data.license?.name || "无"}`, `📊 Open Issues: ${data.open_issues_count}`, `📅 最后更新: ${new Date(data.updated_at).toLocaleDateString()}`, `📖 ${data.description || "无描述"}`, ].join("\n"); return { content: [{ type: "text", text: summary }], }; } catch (error) { return { content: [{ type: "text", text: `请求失败: ${error}` }], isError: true, }; } } ); ``` 这个工具不依赖任何数据库,直接调GitHub的公开API。在Claude Desktop里你可以问:"帮我分析一下facebook/react这个仓库",它就会调用这个工具帮你查。 ## MCP的三大核心概念 搞明白这三个概念,你就算真正理解MCP了: ### Tools(工具) 就是上面演示的——让AI能执行动作。查询数据库、调API、读写文件,都算工具。 ### Resources(资源) 给AI提供数据源,但不执行动作。比如: - 一个数据库表的schema - 一份API文档 - 项目配置文件 ```typescript server.resource( "config", "config://app", async () => ({ contents: [{ uri: "config://app", text: JSON.stringify({ version: "2.1", env: "production" }), }], }) ); ``` ### Prompts(提示模板) 预定义的提示词模板,带参数: ```typescript server.prompt( "code-review", "代码审查模板", { code: z.string().describe("待审查的代码") }, async ({ code }) => ({ messages: [{ role: "user", content: { type: "text", text: `请审查以下代码,关注安全性、性能和可维护性:\n\n${code}`, }, }], }) ); ``` ## 我踩过的5个大坑 ### 坑1:ESM vs CJS,配置地狱 MCP SDK v1.x只支持ESM(ECMAScript Modules)。如果你的`package.json`里没有`"type": "module"`,或者`tsconfig.json`的`module`设错了,编译能过但运行时报错。 **解决:** `package.json`必须加`"type": "module"`,`tsconfig.json`用`"module": "Node16"`。 ### 坑2:绝对路径和相对路径 Claude Desktop配置里`args`要用**绝对路径**。相对路径虽然不报错,但MCP Server会静默失败——就是连不上,也不告诉你为什么。 这个坑我卡了整整两小时。最后是在Claude Desktop的日志里(`~/Library/Logs/Claude/`)才发现的错误信息。 ### 坑3:stderr是日志通道,别乱print MCP协议通过stdout传输JSON-RPC消息。如果你在代码里用了`console.log()`调试,这些输出会被Claude Desktop当作协议消息解析,然后整个连接就炸了。 **调试输出要用`console.error()`**——stderr不会被MCP协议读取。 ```typescript // ❌ 会破坏MCP通信 console.log("调试信息"); // ✅ 正确的调试方式 console.error("调试信息"); ``` ### 坑4:超时问题 MCP工具默认有30秒超时。如果你调的外部API比较慢(比如分析一个大的GitHub仓库),可能会被截断。 **解决:** 把耗时操作拆成异步的——先返回一个"正在处理",然后通过Resource或另一个Tool查结果。或者加缓存。 ### 坑5:Schema不匹配 Zod schema定义和实际函数参数不一致的时候,MCP不会报编译错误——它会运行时静默失败。AI客户端调用你的工具,拿回一个莫名其妙的错误。 **建议:** 一定要在Inspector里手动测每一个工具,确认参数和返回值都对。 ## 谁在用MCP?生态现状 截至2026年4月,MCP的生态已经相当丰富了: **官方/大厂出品:** - Docker MCP Toolkit —— 一键管理和运行MCP Server - GitHub MCP Server —— 操作仓库、PR、Issue - Microsoft Learn Docs —— 查Azure文档 - Playwright MCP —— AI操作浏览器做UI测试 - PostgreSQL MCP —— 直接查数据库 **社区热门:** - Context7 —— 获取最新版本的库文档 - DuckDuckGo Search —— AI搜索 - Filesystem —— 安全的文件系统操作 - Notion MCP —— 读写Notion页面 有意思的是,连OpenAI都支持MCP了。他们虽然在2025年推了自己的tool calling格式,但在2026年也加上了MCP兼容层。毕竟,谁会跟行业标准对着干呢? ## Python开发者怎么办? 如果你不想用TypeScript,Anthropic也提供了Python SDK: ```bash pip install mcp ``` ```python from mcp.server.fastmcp import FastMCP mcp = FastMCP("my-python-server") @mcp.tool() def get_weather(city: str) -> str: """获取指定城市的天气信息""" temp = 15 # 假装在查API return f"{city}当前温度:{temp}°C" if __name__ == "__main__": mcp.run() ``` 比TypeScript版本简洁不少。但功能上两者完全对等,选哪个看你团队的栈。 ## 最后说两句 MCP这个东西,目前还处在"早期基础设施"阶段。就像2010年你问"REST API有啥用",很多人觉得没必要——直接RPC不就完了?但现在REST已经是行业标准了。 MCP的定位是一样的:**它是AI时代的标准接口协议。** 现在可能觉得多此一举,等你的AI工具从1个变成10个,你就会感谢这个标准化。 对了,Anthropic官方有一个免费的MCP入门课程(在Skilljar上),涵盖Tools、Resources、Prompts三个核心概念,还有实操练习。比看文档强十倍,建议去看看。 你们有没有在项目里用过MCP?遇到什么问题?评论区聊聊,我最近在研究MCP + CI/CD的玩法,想看看有没有同好。
Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐