
做Agent开发这几年我越来越觉得真正拉开项目差距的不是模型选型而是模型和外部世界之间的那一层胶水。MCP-Server开发就是把这种胶水做成标准化件的过程。MCPModel Context Protocol现在基本成了智能体连接工具、数据和上下文的标准协议能够把原先散落在各个Agent项目里的工具调用、资源访问、Prompt管理统一成一个可复用的Server让两三个人的小团队也能搭出多Agent协作的基础设施。这篇文章不聊PPT框架也不抄文档就讲我最近一次从零搭建MCP Server踩过的坑、做过的取舍和可以直接抄走的代码。1. 项目概述MCP Server在Agent项目里的位置1.1 当Agent遇到MCP一次通信协议的标准化很多人刚开始接触Agent开发时都会被一堆概念绕晕Agent、Skill、Harness、工具调用、记忆框架……我自己的体会是不管上层怎么抽象真正干活的时候就是一件事让模型能安全地调用外部能力。早期每个Agent项目都自己定义一套工具调用格式A项目的function_call和B项目的tools长得完全不一样做集成时只能各写各的适配层非常痛苦。MCP-Server开发的核心价值就是把“模型调用外部工具”这件事统一成一个稳定的协议。MCP Server的作用相当于一个中间层它把文件读取、数据库查询、HTTP请求、内部API等能力包装成协议统一的结构Agent端只需要理解MCP协议不需要关心每个工具背后的实现细节。这个思路很像USB-C接口设备内部可以千差万别但对外接口统一插上就能用。我做这个项目时的直接动因是团队里两个Agent产品要共用同一套内部数据查询能力。如果各自开发等于同一套逻辑维护两遍如果做成MCP Server两个Agent通过同样的协议访问后续再加第三个Agent时基本零成本接入。这个收益在单Agent项目里不明显一旦进入多Agent协作阶段MCP Server几乎就是基础设施。1.2 这个项目能解决什么问题实际开发前我建议先想清楚MCP Server在你的系统里承担什么职责。按我的经验它主要解决三类问题工具复用问题同一套工具接口可以被不同Agent框架、不同模型调用不用重复开发。权限边界问题Agent只能访问MCP Server暴露出来的工具敏感操作可以集中在Server层做鉴权和审计。上下文管理问题MCP Server可以通过Resources给Agent提供额外上下文避免把所有信息都塞进Prompt。适合看这篇文章的人主要是正在做Agent项目的开发者、想要从传统API开发转过来的后端工程师以及团队里负责Agent基础设施的技术负责人。如果你只是用现成Agent产品不需要关心MCP Server开发但只要你想让Agent自己调数据库、操作文件、访问内部系统这篇文章里的思路可以直接用。2. 开发前必须想清楚的设计协议、SDK与工具边界2.1 选Python还是TypeScriptMCP的官方SDK目前主流是Python和TypeScript我在这个项目里用了Python。原因有几个团队原有AI服务都是Python写的模型推理、数据预处理这部分生态成熟而且FastMCP这个库封装的体验很好写起来比原生SDK顺手很多。如果你做的Agent端是Node.js生态选TypeScript更合理省去跨语言的麻烦。选型时我比较过两种框架的差别可以看这个表维度Python (FastMCP)TypeScript (官方SDK)上手速度快装饰器写工具很直观中等类型定义清晰但异步逻辑稍复杂生态支持PyTorch等AI生态无缝衔接和Node.js服务集成方便部署体积需要Python运行时可直接打包成Node服务适合场景数据处理、AI推理类工具前端工具链、JS全栈团队我的建议是别在这个选型上纠结太久。MCP协议是语言无关的先跑通再考虑优化。对大多数团队来说哪一种更顺手就选哪一种因为后续真正的工作量在工具设计和权限控制不在SDK本身。2.2 理解Tool、Resource、Prompt三种原语MCP Server对外暴露三类能力新手最容易搞混Tool可执行的函数比如查询订单、发送消息、画图。模型会根据对话内容决定是否调用Tool并传参数。Resource可读取的数据比如文件内容、数据库记录、配置文件。它像是Agent的知识补充可以在对话前自动加载也可以让模型根据需要读取。Prompt预定义的提示词模板可以理解成常用任务的“快捷指令”比如“总结这篇文章”“生成周报”等。在MCP-Server开发中最常见的错误是“什么能力都做Tool”。我建议反过来有副作用、需要参数计算的操作用Tool纯数据查询、内容读取用Resource流程固定、模板化的任务用Prompt。这样分类能让Agent更准确地区分该用哪种方式获取信息。2.3 项目目录结构与模块划分MCP Server看起来小但结构不规划好后面加工具时很痛苦。我这次用的目录结构是mcp-server/ ├── pyproject.toml ├── src/ │ └── my_mcp_server/ │ ├── __init__.py │ ├── server.py # FastMCP实例与入口 │ ├── tools/ # 各类工具模块 │ │ ├── __init__.py │ │ ├── order_tool.py │ │ └── weather_tool.py │ ├── resources/ # 资源定义 │ │ ├── __init__.py │ │ └── data_resource.py │ ├── prompts/ # 提示词模板 │ │ ├── __init__.py │ │ └── report_prompt.py │ └── auth/ # 鉴权和权限校验 │ ├── __init__.py │ └── access_control.py └── tests/模块划分的原则是工具之间尽量独立共用的鉴权逻辑抽到底层。千万不要把几百个工具堆在一个文件里后面调试和权限审计全会变成灾难。3. 实操从零搭建一个MCP Server3.1 初始化工程与配置文件我用的Python环境是Python 3.11依赖管理用uv包发布工具用hatchling。先创建虚拟环境并装依赖uv venv uv pip install mcp[cli] fastmcpfastmcp提供了更简洁的开发接口底层还是走MCP协议。如果你不习惯额外封装直接用官方mcp包也可以只是代码会啰嗦一些。我在这个项目里选择FastMCP还有一个原因它的热重载能力让长对话调试舒服很多。核心入口server.py长这样from fastmcp import FastMCP mcp FastMCP( my-agent-server, instructions服务于Agent项目的MCP Server提供订单查询、天气查询和周报生成能力。, host127.0.0.1, port8000, )这里有个容易被忽略的点instructions字段不是给人看的而是给Agent模型看的。模型在决定是否调用工具时会参考这段说明来理解Server的整体用途。我一开始写了“MCP Server”等于没说后来改成“提供订单查询、天气查询和周报生成能力”模型的调用准确率立刻上去了。3.2 编写第一个Tool天气查询工具是MCP Server里出镜率最高的部分。用FastMCP写一个天气查询工具只需要装饰器from fastmcp import FastMCP import httpx mcp FastMCP(my-agent-server) mcp.tool() async def get_weather(city: str, unit: str celsius) - str: 查询指定城市当前天气。 Args: city: 城市中文名比如“北京、上海”。 unit: 温度单位支持celsius和fahrenheit默认celsius。 # 这里仅示意实际项目我会调用内部天气服务API async with httpx.AsyncClient() as client: resp await client.get( https://api.example.com/weather, params{city: city, unit: unit}, timeout5.0, ) resp.raise_for_status() data resp.json() return f{city}当前温度{data[temp]}度天气{data[condition]}。这段代码有两点很关键。第一函数注释必须详细写清楚参数含义和取值范围因为模型就是靠这些注释生成调用参数的注释越明确参数越准确。第二返回结果一定要是完整的“可直接读给用户听”的话不要返回{temp: 30}这种JSON模型虽然能理解但会多一次推理而且容易在复述时丢失细节。我踩过的一个坑某次函数注释里没写城市取值是“中文名”结果模型每次调用都传拼音比如beijing导致查询一直返回空。后来把注释改成“城市中文名比如北京、上海”问题立即消失。3.3 实现Resource与PromptResource在MCP Server里的实现方式和Tool类似但它们表达的是“只读数据源”。以读取团队规范文档为例mcp.resource(docs://team-rule) async def get_team_rule() - str: 团队开发规范Agent在生成代码风格建议时需要读取。 with open(./docs/team_rule.md, r, encodingutf-8) as f: return f.read()这里URI可以自定义docs://team-rule就是资源的地址。Agent端可以通过MCP协议把资源内容合并进上下文。Prompt模板的实现也很直接mcp.prompt() def weekly_report(project: str) - str: 生成项目周报的提示词模板。 return f请根据项目{project}的进展生成一份本周工作总结和下周计划。模板的价值是让Agent在需要固定格式输出时不用从零开始构造Prompt也能保证不同会话之间的输出风格一致。3.4 本地调试用MCP Inspector验证写完后需要本地调试。MCP官方提供了Inspector通过SSE或stdio连接Server后可以直接在网页上手动调用工具。启动方式mcp dev src/my_mcp_server/server.pyInspector启动后它能展示Server暴露了哪些Tool、Resource和Prompt还能手动传参调用查看返回结果。这一步非常建议做因为Agent框架里的调用链复杂如果工具本身返回格式有问题排查成本会呈指数级上升。我调试时习惯先测三种情况正常参数、边界参数比如空字符串、错误参数比如不存在的城市。只有这三种情况都返回稳定结果我才会把Server接入真实的Agent对话流程。4. 与Agent框架集成和多Agent协作4.1 把MCP Server挂到Agent应用上MCP Server本身跑起来只是第一步关键是怎么让Agent应用调用它。主流Agent框架如LangChain、LlamaIndex以及各种自研框架基本都有MCP适配器。以我的项目为例Agent端通过MCP客户端连接本地跑的Serverfrom mcp.client.stdio import stdio_client from mcp.client.sse import sse_client # stdio方式通过子进程通信适合单机部署 async with stdio_client(python -m my_mcp_server) as (read, write): # 将client接入Agent框架 pass # SSE方式通过网络通信适合跨服务集成 async with sse_client(http://127.0.0.1:8000/mcp) as (read, write): pass在这个阶段你可能会遇到情绪上的巨大落差明明Inspector里调用没问题接上Agent后模型却不调用工具或者调用一次就停住了。原因多半是Agent框架对工具结果的格式有额外要求或者超时设置太短。建议先把超时调到30秒以上观察日志里工具调用的raw input和raw output。4.2 多Agent协作时的服务编排多Agent场景下MCP Server更像一个“能力注册中心”。比如我有三个Agent一个负责查天气一个负责查订单一个负责生成周报。我不需要把它们做成三个巨大的单体服务而是拆成三个独立MCP Server然后在编排层按需连接。这种做法的好处是故障隔离订单Server挂掉天气Agent不受影响权限也好控制每个Server可以配置独立的访问白名单。坏处是部署运维成本增加所以小项目还是建议先单Server等规模上来了再拆。如果你做过传统微服务这套思路其实是相通的。Agent只是从“用户直接访问API”变成了“模型决定访问哪个API”而MCP就是那个服务注册和调用协议。4.3 和Agent记忆、Skill的区别聊到这里必须把概念理清因为“Agent系列”文章很容易被名词误导。MCP Server里面的Tool和Resource其实可以理解为Agent的“Skill”实现形式之一。Skill是Agent能力的抽象描述MCP Tool是具体的通信实现。你可以说一个MCP Tool就是一个Skill但不能说Skill一定要用MCP实现。而Agent记忆体系是另一回事。记忆处理的是“这个用户是谁、上次聊到哪、长期偏好是什么”它通常独立于MCP Server由记忆框架单独维护。在我的项目里MCP Server负责访问外部系统记忆框架负责维护对话上下文两者在调用链上不冲突。如果你正在做Agent开发学习路线规划我建议按这个顺序先理解LLM基础再做Prompt再学Tool调用然后接触MCP-Server开发最后再深入多Agent协作和记忆框架。跳过Tool直接学多Agent会非常飘。5. 上线前必须处理的工程化问题安全、eval与监控5.1 工具调用安全边界Agent的一个风险是模型可能在上下文引导下调用危险工具比如删除文件、发消息、转账。MCP Server开发时必须把安全校验放在工具内部而不是依赖Agent的自觉。我做的三个基本校验参数白名单比如城市名只允许预设列表里的值字符串长度、数值范围都做硬限制。操作权限按用户身份控制可调用工具集合管理员的Agent和普通人员的Agent看到的Tool列表不同。审计日志每次工具调用记录调用者、参数、返回状态、耗时方便事后追溯。这些安全逻辑不是“AI安全”的泛泛而谈而是实打实的代码防线。即使模型被诱导只要Server层校验通过不了也造成不了实际危害。5.2 Agent Eval如何验证MCP工具效果做完MCP Server不能只测“工具能跑”还要验证“Agent能正确使用”。这时候就要做Agent Eval。我给这个项目做了三组评测集评测类别测试样例通过标准工具选择10个“查天气”请求10个“查订单”请求模型正确调用对应工具参数抽取20个不同表达的用户查询工具参数完全正确错误处理5个空城市、5个无权限操作模型给出友好提示不崩溃跑完eval后我发现一个规律工具描述里带具体例子的参数抽取准确率能提升15%左右。所以我在Tool说明里多加了一句“例如get_weather(city北京)”。5.3 日志与追踪MCP Server要监控光有print不够。我在Server里统一挂上结构化日志输出JSON格式包含tool_name、input_args、output、duration_ms、user_id、session_id。这样当Agent行为异常时可以按session_id拉出完整调用链。如果你用的是FastMCP可以在工具装饰器外面再包一层日志装饰器。不要把这个工作拖到上线后MCP Server没日志几乎等于裸奔。6. 常见问题与避坑实录6.1 工具执行超时我在真实环境里遇到最多的问题是Agent端默认超时只有5秒而MCP Server里有些工具比如查询报表要跑十几秒。症状是Agent提示“工具调用失败”但Server日志显示已经执行成功。解决办法有两个一个是在Agent端调大超时时间比如设置为30秒或60秒另一个是MCP Server里对长任务用异步模式先把任务提交进去再通过Resource查询执行状态。对于初学者我建议先用第一种简单直接。6.2 Schema与参数不匹配MCP SDK会自动从函数注释生成输入Schema但这个Schema不一定被所有模型正确理解。常见问题是模型传了函数定义里没有的参数或者漏传必填参数。FastMCP的做法是严格校验但校验失败的提示经常很简陋。我的处理是在Tools函数里加一层手动参数校正。比如把city参数统一做一次strip和别名映射unit参数如果不是合法值就默认成celsius。这种防御式编程反而能让Agent一次调用成功率更高。6.3 Session与状态隔离MCP Server如果同时服务多个Agent会话需要注意状态隔离。我在一次测试里发现A用户的查询结果被B用户看到了原因是Server内部用了一个模块级变量缓存数据。排查了很久才发现是变量作用域写错了。建议所有会话相关数据都放在由session_id标识的上下文对象里不要用全局变量。测试时至少模拟两个并发会话交叉调用不能只测单个会话。6.4 Resources加载导致上下文膨胀有些Agent项目启动时会把所有MCP Resource一股脑加载进上下文结果上下文窗口被塞满模型反而忽略关键信息。这是我在做“文档类”MCP Server时的教训。后来我调整了设计默认只加载摘要类Resource完整内容让Agent在需要时再主动读取。这样既能控制上下文长度又不会完全切断信息通道。写在最后MCP-Server开发这件事做到后面你会发现在协议层面并不复杂复杂的是对工具边界的思考、对权限的掌控、对Agent调用行为的评估。我个人的体会是不要追求一次把所有工具都接进来先接一个高频工具跑通Agent、Eval、监控的全链路再逐步扩展。最后再分享一个小技巧MCP Server开发时给每个工具写测试用例时别只测“传参正确”的情形多测一测“模型传错参”的情形。因为Agent场景里脏数据才是常态你的Server扛得住脏数据Agent项目的稳定性就赢了一半。