ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

MCP Server实战:用FastMCP从0到1构建并接入Claude Desktop

MCP Server实战:用FastMCP从0到1构建并接入Claude Desktop 前面两周我把团队内部那个一直靠“人肉查库、手动推送”的订单状态查询工具接进了公司自己的Agent里。做完之后最大的感受是MCP Server 这个事看着概念新实际落地起来其实就是一套标准化的工具封装流程。网上讲 MCP 概念的文章已经很多了但真正能照着手把手写一个 Server、跑起来、接进 Host 的实操教程还是偏少。所以这篇我就从开发者的角度把从 0 到 1 构建自己的 MCP Server 的完整过程写一遍包括架构理解、SDK 选型、代码实现、本地调试以及接入 Claude Desktop、浏览器的真实用法和几个容易踩的坑。这篇东西适合两种人一种是对 MCP 只有模糊概念、想快速上手写一个能跑的 Server 的开发者另一种是已经在用各种 MCP 工具但不太清楚 Host、Client、Server 之间到底怎么协作想搞明白原理好排查问题的人。内容偏实操你最好手边有一个 Python 3.10 的环境跟着代码敲一遍比单纯看十篇文章都管用。1. MCP 是干什么的先理解 Host、Client、Server 这三层1.1 没有 MCP 之前工具接入有多痛苦在 MCP 出现之前给 AI 应用接外部工具基本上是一个项目一种接法。你用 OpenAI 的函数调用就得按 function calling 的 JSON Schema 写工具定义你用自己的 Agent 框架又要按框架的 Tool 抽象来写。更麻烦的是每个工具服务都要单独实现一套鉴权、调用、错误通知机制工具多了之后全是重复劳动。我当时最烦的就是同一个组织里业务部门有查询接口、数据部门有分析服务、运维那边还有告警系统每一套都要为 AI 单独做适配。这就像每买一个新电器都得带一条专用充电线接口还不一样。MCP 想解决的就是这个问题——它把“工具接入”这件事标准化了。你的服务只要实现一次 MCP 协议任何支持 MCP 的 AI 应用也就是 Host都能直接调用不需要重复适配。1.2 MCP 的三层架构谁在说话、谁在翻译、谁在办事MCPModel Context Protocol模型上下文协议从架构上分成三层MCP Host、MCP Client、MCP Server。MCP Host 是用户直接面对的那一层比如 Claude Desktop、IDE 插件、自研的 Agent 应用。它负责和用户对话、调用大模型、决定什么时候去请求工具。它本身不直接和你的工具服务打交道。MCP Client 是 Host 内部内置的协议客户端负责建立连接、发送请求、接收结果。你可以把它理解成 Host 的“翻译官”。一个 Host 可以同时连接多个 Server也就意味着一个 Host 里可能有多个 Client 实例在同时工作。MCP Server 就是你要开发的部分。它对外暴露具体的工具、数据资源和提示词模板被客户端调用。很多人一开始搞混 Host 和 Server其实记住一句话就行Host 是“大脑”Server 是“手”。大脑决定要做什么手负责具体执行。Client 则是连接这两者的“神经”。当你在 Claude Desktop 里看到某个工具被调用链路是用户提问 → Host 让大模型判断需要调用工具 → Host 里的 Client 发起请求 → Server 执行逻辑 → 返回结构 → 大模型再把结果组织成自然语言回给用户。1.3 Tools、Resources、PromptsServer 对外暴露的三类能力MCP Server 不是只提供“工具调用”这一种能力它实际上有三类对外接口搞清楚这三者的区别你的 Server 设计会清晰很多。Tools可执行的动作一般是“让 AI 去做某件事”。比如查订单、发邮件、创建工单。Tool 需要显式调用AI 决定调用哪个、传什么参数。Resources可读取的数据一般是“让 AI 获取上下文”。比如一个订单列表、一份配置文件的 JSON、一篇文章正文。Resource 通常以 URI 的形式暴露有点像把文件系统能力开放给 AI。Prompts可复用的提示词模板。比如“生成订单周报”“写一段会议纪要开头”。Prompt 能引导模型按固定格式做事适合沉淀团队里的最佳实践。实际开发中Tools 是绝大多数场景的主角但 Resources 也很常用——如果你的 Agent 需要先读到一批背景数据再回答问题用 Resource 比让模型瞎猜强得多。后面实战部分我会三样都写一遍让你看到它们在一个项目里怎么共存。2. 动手前的准备SDK 选型与项目初始化2.1 用官方 SDK 还是 FastMCPPython 生态里现在有两条主流路线一是官方提供的mcpPython SDK另一个是社区封装FastMCP。我实际两个都试过结论是如果你是想快速做出一个能用的服务优先用 FastMCP如果是要深度定制协议细节、造轮子再看看官方 SDK。原因很简单。官方 SDK 把协议层的细节暴露得很充分灵活是灵活但写起来啰嗦。你需要自己处理 initialize 握手、工具列表声明、请求路由这些事。而 FastMCP 把这些全部简化成了装饰器风格——你写一个普通 Python 函数加一行mcp.tool()就完成了工具注册。底层还是走官方协议但开发体验完全是现代 Python 框架的感觉。FastMCP 是社区项目不是 Anthropic 官方出品但它目前维护活跃协议兼容性也跟得比较及时。我自己的判断标准是个人项目、内部工具、快速迭代的场景FastMCP 完胜你要是想给复杂分布式系统做底座或者需要非常细的协议控制再考虑官方 SDK。2.2 创建项目目录和虚拟环境我习惯用uv管 Python 环境比 pip 干净利落。不过用venv pip也一样看个人偏好。下面是 FastMCP 路线的初始化步骤。# 创建项目目录并初始化虚拟环境 mkdir order-mcp-server cd order-mcp-server python -m venv .venv source .venv/bin/activate # 安装依赖 pip install mcp[cli] fastmcp # 装完最好确认一下版本 python -c import fastmcp; print(fastmcp.__version__)这里有必要解释一下为什么需要安装mcp[cli]。虽然 FastMCP 本身会依赖官方 SDK但我们后面调试要用mcp官方提供的 Inspector那部分需要以命令行形式跑起来所以提前把官方 CLI 一起装了。常见的问题是只装 fastmcp 后发现没有mcp命令然后又回头补装白折腾一圈。2.3 两种传输方式怎么选MCP 服务端目前最常用的传输方式是 stdio 和 Streamable HTTP。stdioServer 作为子进程被 Host 拉起双方通过标准输入输出通信。这种方式配置简单适合本地运行Claude Desktop 配置里最常见。缺点是服务不能被多个进程远程共享。Streamable HTTPServer 作为一个 HTTP 服务运行支持远程访问。适合部署到服务器上给多个 Host 用。新版协议里已经废弃了老旧的 SSE-only 方式统一走 Streamable HTTP。开发初期建议先用 stdio 模式跑通整条链路因为调试起来最简单不需要考虑端口、鉴权、跨域这些问题。等逻辑稳定了再切换到 HTTP 模式部署。后面实战和问题排查的部分我会按这个顺序来。3. 写一个能用的 MCP Server从工具定义到数据暴露3.1 用 FastMCP 定义一个订单查询服务实战部分我用一个“订单查询服务”做例子背景是团队内部有一个订单系统经常需要让 AI 助手帮忙查订单状态、看物流轨迹、创建简单订单。按传统做法我会写一个 REST API 然后让 Agent 去调但现在有了 MCP我直接把业务逻辑封装成 Server。先看最基本的代码结构from datetime import datetime from fastmcp import FastMCP # 创建 Server 实例名字会显示在 Host 的可用工具列表里 mcp FastMCP(order-service) # 模拟数据库里的订单数据 ORDERS { SO-1001: { customer: 张三, status: shipped, items: [机械键盘, 显示器支架], created_at: 2025-01-10 14:30:00, tracking: SF-987654321, }, SO-1002: { customer: 李四, status: pending, items: [USB-C 扩展坞], created_at: 2025-01-12 09:15:00, tracking: None, }, } mcp.tool() def get_order(order_id: str) - dict: 按订单号查询订单状态订单号格式为 SO- 加数字例如 SO-1001 order ORDERS.get(order_id) if not order: raise ValueError(f订单 {order_id} 不存在) return order if __name__ __main__: mcp.run()这个文件只要运行起来一个最小可用的 MCP Server 就算建成了。mcp.tool()会把函数名转成工具名docstring 转成工具描述,参数注释和类型标注转成参数 Schema。所以你在写工具函数时docstring 一定要认真写清楚“这个工具干什么、参数格式有什么特殊要求”因为大模型全靠这段描述来决定什么时候调用、怎么传参。比如get_order(SO-1001)返回的就是订单字典如果传了不存在的单号我会直接抛ValueErrorHost 会把错误信息带回给大模型模型会自己决定是换一个参数重试、还是向用户道歉。这就是 MCP 工具和普通 API 一个很大的不同API 返回 404 就结束了MCP 工具的错误会成为模型上下文的一部分影响下一轮推理。运行这个 Serverpython server.pyFastMCP 默认跑在 stdio 模式上。如果你直接执行程序会挂起、等待从 stdin 读取协议消息这一般说明启动成功了。这时候不要觉得“没反应就是坏了”stdio 模式本来就不该有任何输出——如果有输出说明你代码里有 print那些内容会污染协议通信后面我会重点讲这个坑。3.2 参数校验和错误返回不要放过这个环节很多人开发 MCP Server 时会把参数校验省略觉得“模型应该传对参数”。但实测下来大模型传参远比想象中多奇怪。有一次我测试时模型把日期传成了周一把订单号里的字母 O 传成了数字 0如果服务端不做校验错误会一路流到业务系统里。FastMCP 支持 Pydantic 模型来做参数校验强烈建议参数一多就上 Pydanticfrom pydantic import BaseModel, Field class CreateOrderInput(BaseModel): customer_name: str Field(..., min_length2, max_length50, description客户姓名) items: list[str] Field(..., min_length1, description商品名称列表至少有一个商品) mcp.tool() def create_order(input: CreateOrderInput) - dict: 创建新订单返回订单号 order_id fSO-{len(ORDERS) 1002} ORDERS[order_id] { customer: input.customer_name, status: pending, items: input.items, created_at: datetime.now().strftime(%Y-%m-%d %H:%M:%S), tracking: None, } return {order_id: order_id, status: created}用 FastMCP 时把 Pydantic 模型作为参数类型SDK 会自动生成 JSON Schema并在调用时做校验。校验失败的信息会作为工具错误返回给 Host模型看到后可以自己修正参数再调一次。这其实是 MCP 流程里被低估的一环——好的校验策略能明显减少“模型对着错误参数反复试错”的情况。错误返回这块我的建议是业务性错误比如订单不存在直接抛异常即可但错误信息要写得像给同事看的提示不要写“系统错误”这种废话。比如 “订单 SO-9999 不存在当前可查询的订单号范围是 SO-1001 到 SO-1002”模型很可能会顺着这个提示改参数重试体验会好很多。3.3 再补上 Resources 和 PromptsTools 只是一个 Server 的起点。我再给这个订单服务加上一个 Resource 和一个 Prompt让读者直观感受三类能力的差异。from fastmcp import FastMCP mcp FastMCP(order-service) mcp.resource(order://{order_id}/timeline) def get_order_timeline(order_id: str) - str: 读取订单的完整状态时间线 order ORDERS.get(order_id) if not order: raise ValueError(f订单 {order_id} 不存在) lines [ f订单{order_id}, f客户{order[customer]}, f创建时间{order[created_at]}, f状态{order[status]}, ] if order.get(tracking): lines.append(f物流单号{order[tracking]}) return \n.join(lines) mcp.prompt() def order_summary(order_id: str) - str: 为指定订单生成一段周报风格的状态摘要 return f请根据订单 {order_id} 的数据生成一段适合在周报中使用的状态描述。Resource 在这个场景里说得通的地方在于它不需要模型“决定去调用”对 Host 来说更像一种数据发现机制——只要 Client 端支持模型读取上下文时可能主动去抓取它。Prompt 更简单就是给用户一个可选的提示词模板点击后自动填进对话框。从设计角度说如果你的工具服务是给公司内部 Agent 用的我建议把“高频、静态的数据”设计成 Resource“AI 判断后再执行的动作”设计成 Tool。划分清楚Host 侧的行为会好预测很多。4. 本地调试Inspector 和小型客户端4.1 用 MCP Inspector 快速验证代码写完了别急着接 Claude Desktop先用官方提供的 Inspector 跑一遍能省掉后面 80% 的排查时间。Inspector 是一个可视化调试面板可以手动选择工具、传参数、看返回结果和错误堆栈相当于 MCP Server 的 Postman。启动方式npx modelcontextprotocol/inspector python server.py这段命令的意思是用 npx 拉起 Inspector然后 Inspector 再以 stdio 子进程的方式启动python server.py。启动后浏览器会打开一个本地页面你在界面上能看到order-service里注册的get_order、create_order工具点进去手工填参数就能调用。我第一次用 Inspector 时觉得这工具太值了。以前写普通 API 可以用 curl 测MCP Server 因为走的是协议通信直接用 curl 很难模拟Inspector 把中间层全部可视化还能看到原始 JSON-RPC 消息。排查参数 Schema 问题、调试错误返回几乎全靠它。调试时一个实用技巧在 Inspector 里切换不同的输入看校验效果。比如给create_order传一个空的items列表正常情况会看到参数校验错误传一个正常参数能看到工具执行结果。这相当于把端到端的错误处理链路提前验证一遍。4.2 自己写一个 MCP 客户端做集成测试Inspector 能验证单个工具的调用但如果你想把 Server 集成到自动化测试里更靠谱的方式是写一个最小客户端。官方 SDK 提供了客户端能力代码量很小import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params StdioServerParameters( commandpython, args[server.py], cwdNone, # 如果 server 依赖相对路径这里可以指定工作目录 ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() # 列出服务器上所有工具 tools await session.list_tools() print(可用工具, [t.name for t in tools.tools]) # 调用工具 result await session.call_tool(get_order, {order_id: SO-1001}) print(调用结果, result.content) asyncio.run(main())这个脚本的好处是它和 Claude Desktop 访问 Server 的方式几乎一致都走 stdio。测试时如果脚本能跑通那基本可以断定 Server 本身没问题问题通常出在 Host 侧的配置上。我在多个项目里的习惯是把小客户端脚本放在tests/目录下配合 pytest 写成集成测试保证每次改动工具定义后至少能自动验证“工具可被发现、可被调用”。这一步投入不大但对 Server 的长期维护帮助挺大的。5. 接入真实场景Claude Desktop 与 Chrome MCP Server5.1 把 Server 配置进 Claude Desktop本地验证通过后下一步就是接进真正面向用户的 Host。这里以 Claude Desktop 为例因为它是目前对 MCP 支持最顺滑的桌面客户端之一。Claude Desktop 的配置文件在 macOS 上位于~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 在%APPDATA%\Claude\claude_desktop_config.json。往mcpServers字段里加一段你的服务{ mcpServers: { order-service: { command: python, args: [/Users/你的用户名/dev/order-mcp-server/server.py] } } }配置完重启 Claude Desktop在界面右下角或设置里能看到连接状态。如果连接成功对话时它会自动识别可用的工具在需要查询订单时会主动调用。这里有几个特别容易踩的配置坑。第一command里的python必须是命令行里能直接找到的那个解释器。如果你用的是虚拟环境建议直接把 command 写成虚拟环境里的 python 绝对路径比如/Users/xxx/dev/order-mcp-server/.venv/bin/python避免 PATH 不一致导致启动失败。第二args里 server 脚本要写绝对路径别写相对路径因为 Host 启动子进程时的工作目录不等于你的项目目录。第三fastmcp 代码里如果 import 了项目内的其他模块务必确保工作目录正确——必要时可以在 server.py 开头手动sys.path.insert(0, /项目绝对路径)这个操作不算优雅但确实能救急。5.2 Chrome MCP Server 使用教程让 AI 操作浏览器的正确姿势很多人问 Chrome MCP Server 到底怎么用网上信息比较零散我结合自己的试用经历整理一下。浏览器类 MCP 的思路是把“打开网页、点击元素、提取文本、截图、读取控制台日志”这些浏览器操作封装成一个个 MCP 工具然后让 AI 根据任务自行组合调用。典型用途包括让 AI 打开某个页面并总结内容、爬取一个列表页的数据、自动填表提交、前端页面回归测试。使用方式是分三步走的第一步安装并启动浏览器侧的 MCP 服务端。社区主流的实现方式有两种一种是基于 Chrome DevTools ProtocolCDP启动一个带调试端口的真实 Chrome然后由 MCP Server 通过 CDP 去控制浏览器另一种是安装浏览器扩展扩展通过本地回环端口与 MCP Server 通信。不管哪种装完后都需要你在浏览器侧做一次授权确认之后 MCP Server 才能拿到标签页的控制权。第二步把服务端配置进你的 MCP Host。以 Claude Desktop 为例同样是在mcpServers里加一个条目command 通常是npxargs 里写对应的包名和参数。配置完成后Host 里会出现navigate、click、extract_text、screenshot这类工具。第三步对话时让 AI 执行任务。比如你说“打开百度首页搜索 MCP Server把搜索结果第一页的标题列出来”模型会依次调用导航、输入、提取文本等工具最终返回你一份标题列表。这条经验值的部分是让 AI 操作浏览器一定要做好权限控制。我个人的安全准则是浏览器 MCP 只用于“读取信息”和“非敏感操作”凡是涉及支付、发布、删除、发送敏感数据的动作必须加入人工确认环节否则宁可不用。另外建议给浏览器 MCP 单独分配一个 Chrome profile别让它带着你的个人登录态去做自动化防止脚本行为被混入个人账号的上下文。如果你有定制化需求其实自己写一个浏览器 MCP Server 也不难。核心就是用 Playwright 或 Puppeteer 驱动浏览器把每个页面动作封装成一个 tool返回值尽量给结构化文本——比如页面核心区域文本、DOM 快照摘要不要直接返回整个 HTML否则上下文很快被撑爆。5.3 远程 MCP Server 的部署与鉴权如果你的 Server 不只是本地用需要部署到服务器给多个 Host 远程连接那就该切成 Streamable HTTP 传输方式。FastMCP 切换起来很快if __name__ __main__: mcp.run(transportstreamable-http)这样 Server 启动后会监听一个本地端口以 HTTP 协议提供 MCP 服务。你可以在前面挂一个反向代理做 TLS、加一层访问控制。远程场景下鉴权是绕不开的话题。新版 MCP 协议对远程 Server 的要求是支持 OAuth 2.0Authorization Code PKCE官方 SDK 已经内置了这套流程Host 连接时会自动拉起授权。如果你在内网或者完全信任的环境里跑可以在 Server 注册时关闭强制鉴权但我建议只在开发环境这么做。生产环境至少要有 Bearer Token 级别防护毕竟暴露在网络上的是“能执行你业务操作的工具”不是只读的静态页面。6. 踩坑实录常见问题与排查技巧6.1 启动慢、超时、连接失败MCP Server 在 stdio 模式下是一个子进程Host 启动它是有超时等待的。如果你服务里导入了很重的依赖、或者启动时连了一堆外部服务很可能 Host 已经判定超时了你的 Server 才初始化完。表现就是 Host 里看不到工具配置页显示连接失败。排查方式我自己会分三步第一步先在终端手跑一遍python server.py看启动是否有报错、日志输出是否异常第二步用 Inspector 启动一次采集启动耗时第三步启动慢的话把耗时操作挪到工具内首次调用时再做不要在 import 阶段做初始化。另外所有启动过程的日志全部打进 stderr未来排查时用21就能看到完整日志。6.2 数据返回报警与序列化问题MCP 工具返回给 Host 的数据最终要能被序列化成 JSON。如果你的代码里返回了datetime对象、bytes、或者自定义类的实例Host 侧很容易收到序列化错误。我遇到最典型的就是返回datetime没转字符串结果在 Host 里看到工具调用失败错误信息却不明显。解决办法是养成在工具返回时做一层显式转换的习惯所有时间字段先strftime所有枚举先转str所有可能为None的字段想清楚要不要带回去。这不是难事但很容易被忽略。另外大模型对超长返回的处理能力有限。如果你一个工具返回了 10 万字的日志模型很可能“看不过来”反而影响回答质量。尽量只返回模型需要的那部分信息大文件走 Resource 按需读取别一股脑塞进 tool result。6.3 工具描述和参数 Schema 对不准这是最隐蔽的坑程序不报错但模型就是调不对工具。比如你在create_order的 docstring 里写“创建订单”但没有写明订单号生成规则、不写客户姓名的格式要求模型可能随手传一个“张”这种单字名字被 Pydantic 的min_length2拦回来也可能在items参数里传一个字符串而不是字符串列表。解决办法是把 docstring 和 Pydantic 的 description 当成产品文案来写。具体说明格式规范、枚举值、边界条件。实测效果差距很明显描述写得清楚的工具模型一次调用成功的概率会高非常多。你要记住MCP 的工具 Schema 是给“一个很聪明但对你的业务完全不了解的代理”看的多给一个例子胜过一百个抽象描述。6.4 日志、并发和资源回收最后说几个平时一不注意就会翻车的点。日志污染 stdio 是我见过最多的问题。在 stdio 传输模式下stdout 是协议通道绝对不能 print。一旦 print 了普通日志Host 解析协议消息就会失败表现是工具列表加载不出来。排查时把日志全部改到 stderr或者直接配置 logging 模块输出到 stderr。并发和全局状态是另一个大坑。FastMCP 在处理并发请求时如果你的 Server 内部维护了可变全局变量比如我这个示例里的ORDERS字典多线程环境下就可能出现脏数据。简单的解决办法是给全局状态加锁或者改用数据库/缓存存储。我自己的习惯是Server 无状态化所有业务状态放外面Server 只做转发和协议适配。资源回收也要注意。如果你的工具内部需要连接数据库、Redis、外部 HTTP API记得在工具里显式关闭连接或者用连接池复用。Server 在本地跑时进程生命周期短连接泄漏不致命一旦部署成常驻 HTTP 服务连接泄漏就会慢慢把文件描述符吃光然后出现各种莫名其妙的“无法连接”“内存暴涨”。我自己线上最严重的一次就是遗漏了数据库连接关闭跑了三天把连接池打满整个服务假死。还有一个小技巧善用 Host 侧的工具调用记录。Claude Desktop 里每次工具调用都会显示输入输出摘要排查问题时先看 Host 记录再对照 Server 的 stderr 日志基本能在 10 分钟内定位绝大多数问题。我在实际操作中最深的一个体会是写 MCP Server 的技术门槛其实不高真正决定这个服务好不好用的是工具边界的定义和描述的质量。你给 AI 的工具如果说明写得够清楚、参数校验够严格、返回结果够简洁整个调用链路会顺滑得超出预期反过来工具描述含糊参数宽松模型就会在调用阶段反复横跳体验立刻崩掉。所以动手开发之前先把“这个服务到底想给 AI 暴露哪些能力、每个能力的边界是什么”想清楚这一步省下来的时间远比写代码本身多得多。如果你按这篇把订单服务跑通了下一步可以试试把自己经常用的小脚本往 MCP 里套一层很快你就能体会到“让 AI 直接操控你手头工具”的感觉了。
返回列表