MCP协议与Python SDK实战:从MCP Server到Client开发全指南
发布时间:2026/9/29 15:43:57来源:尧图网络
1. 说实话MCP到底是个啥Model Context ProtocolMCP这个名词最近在AI圈出现频率暴涨。如果你关注过Claude、Cursor这类AI工具的插件生态大概率看到过“MCP Server”“MCP Client”之类的字眼。简单理解MCP是一个开放协议专门用来让AI大模型与外部工具、数据源、应用系统进行标准化通信。打个比方如果说AI模型是电脑主机MCP就是那个USB-C接口——鼠标键盘显示器标准统一之后插上就能用。在没有MCP之前想让AI调用某个API或者读某个数据库开发方式非常混乱。有人用函数调用Function Calling有人靠提示词让模型自己推断格式有人直接写死轮询逻辑。每一种集成都要单独写适配器工具一多项目就变成蜘蛛网。MCP的诞生就是为了解决这种碎片化问题它定义了一套统一的对话模型、消息格式、传输机制和生命周期管理工具方只要实现一次MCP协议任何支持MCP的客户端都能直接调用。这套Python SDK就是你用Python语言接入MCP生态的官方工具包。它提供了一整套类和方法让你既能快速搭建一个MCP Server把工具/数据暴露给AI也能实现MCP Client让AI能访问外部服务。这篇文章我会从环境搭建讲到Server/Client完整开发再讲到stdio和HTTP传输模式的区别最后是社区里最容易踩的坑。如果你正打算让AI接入自己的业务系统或者想在个人项目里做一套Agent工具链这篇内容可以直接照着做。2. 环境准备与Python SDK安装2.1 版本要求和Python环境MCP Python SDK目前对Python版本的要求是3.10及以上。这不是随便定的SDK内部用了大量类型注解特性、异步编程模型以及较新的标准库能力3.9及以下版本在解析类型表达式和方法签名时会直接报错。建议直接装Python 3.11或3.12不仅兼容性最稳运行性能也比3.10好一些。如果本机已经装了Python但不确定版本终端跑一下python --version如果是Linux或macOS可能需要用python3命令。另外强烈建议用虚拟环境隔离项目依赖不要一股脑全局安装。虚拟环境能避免不同项目之间的包版本冲突尤其是后面你可能会同时用到mcp、fastmcp、httpx这些库版本之间互相打架的情况并不罕见。2.2 安装mcp库最常规的安装方式就是pippip install mcp如果你的网络环境不稳定可以指定国内镜像源实测下来速度快很多pip install mcp -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成之后验证一下pip show mcp能看到版本信息、依赖项列表就说明装好了。SDK目前的主要依赖包括httpx、pydantic、anyio、sse-starlette等。注意这些依赖是自动拉取的不要手动去装旧版本否则可能出现pydantic版本冲突导致的类型校验错误。提示如果你想跟踪开发版本可以用pip install mcp --pre安装预发布版但生产环境不建议这么做。预发布版接口变动频繁社区文档和示例未必同步更新。2.3 SDK整体架构速览安装完成之后先快速浏览一下包结构这能帮你建立整体认知。mcp包下面主要有几个核心模块mcp.server: 服务端实现包含FastMCP类、底层Server类以及stdio和Streamable HTTP两种传输方式。mcp.client: 客户端实现包含同步/异步两种接口支持stdio和HTTP连接。mcp.shared: 共享的会话管理、请求上下文、消息构造等基础设施。mcp.types: 定义了协议中所有核心类型比如工具定义、资源模板、调用结果等。mcp.tools、mcp.resources、mcp.prompts: 辅助装饰器和工具集。看这段架构你会发现一件事SDK把协议层、会话层、传输层做了清晰解耦。这意味着你在开发业务逻辑时基本不碰底层协议细节但它又给你留了底层接口遇到特殊需求时依然可以自己扩展。装好SDK之后用python -c import mcp; print(mcp.__version__)确认一切就绪。3. 从零搭建第一个MCP Server3.1 FastMCP上手最快的服务器类MCP SDK最友好的地方在于它提供了一个叫FastMCP的高层接口。叫“Fast”是有原因的——三行代码就能把一个工具暴露给AI客户端。它的设计思路跟FastAPI一脉相承用装饰器注册工具用类型注解声明参数一切约定大于配置。先看一个最精简的示例from mcp.server.fastmcp import FastMCP mcp FastMCP(demo-server) mcp.tool() def add(a: int, b: int) - int: 两数相加 return a b if __name__ __main__: mcp.run()就这么一段代码你已经有了一个可以运行的MCP Server。它的默认传输方式是stdio也就是通过标准输入输出跟客户端通信。为什么要默认stdio因为这是最通用、最轻量的进程间通信方式任何语言、任何环境都能支持不需要额外开端口、配防火墙适合本地场景的首选。3.2 工具函数的核心写法与命名规则用mcp.tool()注册的函数有几个细节值得注意。函数名会直接作为工具名暴露给AI所以命名必须见名知意比如get_user_info比gui好一万倍。函数的docstring会被作为工具描述发送给模型这个描述质量直接决定AI能不能正确调用工具——你要在docstring里写清楚函数能做什么、参数含义、返回什么。举个例子mcp.tool() def get_stock_price(symbol: str) - dict: 获取A股实时行情。 Args: symbol: 股票代码如600519表示贵州茅台000001表示平安银行。 Returns: 包含当前价格、涨跌幅、成交量的字典。 # 这里写真实的行情API调用逻辑 ...再看参数定义。Pydantic会负责参数的类型校验函数签名里的类型注解会转成JSON Schema传给客户端。所以类型一定要写准确AI会根据这个Schema去构造参数。比如你预料到用户可能会传中文股票名那参数设计上就应该考虑是加一个辅助字段还是用查询逻辑去兼容而不是把类型随便写成str了事。3.3 Server资源的注册与生命周期FastMCP除了工具还支持注册Resource和Prompt后面我会单独讲。但Server本身的启动过程值得提前说清楚。mcp.run()看似简单实际上内部做了几件事初始化事件循环、建立消息处理管线、启动传输监听。如果你用的是默认stdio模式客户端进程会启动你的Server脚本然后通过stdin发送JSON-RPC消息Server处理完把结果写到stdout。这个模式非常适合本地开发调试因为一切信息都能在终端看到。注意在stdio模式下你的Server脚本不要随意往stdout里打印无关信息。一旦print输出混入标准输出流客户端解析JSON时会直接崩掉。如果非要打印日志请使用logging模块让它输出到stderr。3.4 自定义日志配置与调试技巧使用logging输出日志的正确做法import logging logging.basicConfig(levellogging.INFO, streamsys.stderr) logger logging.getLogger(mcp-demo)这样日志会走标准错误流和stdout上的协议消息完全隔离。这个细节官方文档里写得很隐晦但实际开发中极其重要——我见过太多人栽在这个坑里明明工具逻辑没问题结果因为一个print导致整个会话崩掉。调试阶段推荐把日志级别设为DEBUG这样SDK内部收发的完整消息结构都会打印到stderr。你能清楚地看到AI发给你的JSON是什么样子你返回的结果是什么结构开发效率会高很多。4. MCP Client端开发4.1 客户端基本原理有Server自然就有Client。Client的作用是连接一个或多个MCP Server发现它们提供的工具、资源和提示然后向它们发起调用。MCP协议中Client是整个链路的上游AI模型如Claude其实是通过Agent层变成Client的或者你自己用SDK写一个客户端程序。MCP Python SDK的Client部分设计得稍微底层一些但力量很强。你先要用stdio_client建立到本地Server的连接from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client server_params StdioServerParameters( commandpython, args[server.py], envNone ) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() result await session.call_tool(add, {a: 1, b: 2})这个模式很清晰stdio_client负责拉起子进程并建立双向管道ClientSession负责在管道之上建立MCP会话你通过session对象直接跟远端服务交互。envNone表示客户端式子进程的环境变量直接继承当前进程的如果你需要给子进程设置特殊环境变量比如API密钥可以传一个字典。4.2 异步Client与现代Python写法整个SDK全面基于异步编程这意味着你要用async/await语法。对写惯同步代码的人来说刚开始可能有点不习惯但你很快会发现异步的好处尤其在批量调用多个工具的场景下并发效率提升非常明显。异步并发调用多个工具的经典写法import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server_params StdioServerParameters(commandpython, args[server.py]) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() names [tool.name for tool in tools] print(可用工具:, names) tasks [ session.call_tool(add, {a: i, b: i * 10}) for i in range(5) ] results await asyncio.gather(*tasks) for r in results: print(r) asyncio.run(main())看到没一旦用上异步批量调用并行执行速度是同步方式的数倍。而且gather里任何一个协程抛异常其他任务都会收到取消信号——这种异常传播机制在多工具调度时非常重要。4.3 Client与同进程Server的通信还有一种特殊但很实用的场景你的Server和Client在同一个Python进程内。比如你在做一个FastAPI服务它既对外提供HTTP接口内部又要通过MCP调用工具函数。这种场景可以用mcp.server.fastmcp.FastMCP配合内存传输来做不需要走任何外部进程。FastMCP内部提供了run_client_async方法专门用来在同进程内测试和调用from mcp.server.fastmcp import FastMCP mcp FastMCP(demo) mcp.tool() def multiply(a: int, b: int) - int: 简单乘法 return a * b async def test(): async with mcp.run_client_async() as client: result await client.call_tool(multiply, {a: 3, b: 5}) print(result.content) import asyncio asyncio.run(test())这种方法在单元测试和集成测试里尤其好用——你不需要启动一个外部Client进程去连Server代码短、启动快、逻辑直观。实测下来用run_client_async做CI里的功能自测非常顺手省去了进程管理的各种麻烦。5. 传输模式与部署场景5.1 stdio模式解析stdio模式的核心思想是“进程即服务”。Client启动Server子进程用stdin和stdout来传消息。这种模式的优势非常突出无需开放网络端口安全性天然更高进程生命周期跟随Client不会留下僵尸服务本地开发和调试友好任何错误信息直接打在stderr里但它也有适用边界。stdio模式要求Client和Server必须在同一台机器上无法远程连接。如果Server部署在云端服务器上本地Client想直接连就不行了。这时候你得用网络传输。5.2 Streamable HTTP模式新版SDK里主推的网络传输方式是Streamable HTTP这是MCP协议最新的规范之一取代了早期实验阶段的HTTPSSE模式。它通过单个HTTP端点同时处理客户端请求和服务器推送消息用POST发送JSON-RPC请求服务端用流式响应返回结果。FastMCP切换到HTTP模式极其简单只要改run方法if __name__ __main__: mcp.run(transportstreamable-http)但真正的生产部署需要你用ASGI服务器来挂载这个服务。FastMCP直接返回了一个ASGI应用实例你可以用mcp.streamable_http_app()拿到来配合uvicorn启动from mcp.server.fastmcp import FastMCP mcp FastMCP(http-server) mcp.tool() def hello(name: str) - str: 打个招呼 return f你好{name} app mcp.streamable_http_app()然后命令行启动uvicorn main:app --host 0.0.0.0 --port 8080这样一个MCP服务就在HTTP端口上提供能力了。任意MCP客户端只要知道这个URL就能通过标准HTTP协议来发现和调用工具。我在用这个方案时发现几个值得注意的点streamable_http_app()每次被调用会创建新的App实例最好在模块顶层定义一次不要每次请求都新建。生产环境必须用uvicorn这类ASGI服务器直接运行不要在if __name__里再包一层run()。如果服务需要鉴权可以在ASGI应用外面套一层中间件读取请求头里的Authorization字段做校验。5.3 旧版SSE模式与新老接口差异早期版本的SDK只有SSEServer-Sent Events模式通过/sse和/messages两个端点实现双向通信。看过很多老教程的人可能会被两个端点绕晕。新版SDK把这两个老接口合并集中到了一个流式端点接口也从原来的mcp.sse_app()变成了mcp.streamable_http_app()。如果你是在2025年之后安装的SDK请直接使用新接口。网上很多教程还是老写法照抄会出现路由错误或者会话异常。我测试过老接口和新客户端的兼容性发现新SDK的客户端仍然可以用streamable_http_client去连接旧版SSE端点但消息格式变动较多不推荐混用。6. 资源与提示不止工具调用的完整Agent体验6.1 Resources让AI能“读”上下文MCP协议不止有工具调用这一种能力。它还定义了Resources资源和Prompts提示词模板。Resources可以理解成暴露给AI的只读“上下文文档”比如项目的README、数据库的表结构说明、某个配置文件的JSON内容。模型在需要的时候会主动去读取这些资源就像你写代码时翻查阅手册一样。定义Resource的方式跟工具非常像from mcp.server.fastmcp import FastMCP mcp FastMCP(context-server) mcp.resource(config://app/settings) def get_settings() - str: 应用配置信息 return {feature_x: true, feature_y: false, rate_limit: 100}注意这里的URI是有业务含义的。MCP协议规定资源URI用scheme://path的格式你可以自定义scheme比如config://、doc://、db://只要是合法URI即可。AI客户端看到这个URI之后会根据描述决定什么时候读取、读来干什么用。6.2 Resource模板用参数生成动态资源静态资源很简单但很多时候上下文内容跟某个具体对象绑定的。比如给你每个用户返回一份独立的配置这时候就要用ResourceTemplatemcp.resource_template(user://{user_id}/profile) def get_user_profile(user_id: str) - str: 获取指定用户的主页资料 return f用户{user_id}的画像数据活跃度7.2偏好[科技,数码]模板中用花括号{user_id}声明参数。客户端调用时传入具体的user_idSDK在解析到URI后匹配模板把参数提取出来传给函数体。这个能力在做个性化Agent时极其有用——AI不用一次性把所有用户数据都拉过来等到谈话涉及某个用户时再精确读取。6.3 Prompts给AI提供交互范式Prompts提供的是可复用的提示词模板。它不是让你直接给AI“洗脑”而是定义某个场景下的“交互流程入口”。举个典型场景——你想让AI扮演一个代码审查工具后端逻辑需要特定的Prompt模板。mcp.prompt(code-review) def code_review_prompt(code: str) - list[dict]: 生成代码审查提示词 return [ { role: user, content: f请对以下代码进行审查重点关注安全性、性能和可维护性\n\n{code} } ]FastMCP中Prompt函数返回的是一个消息序列list of dict每个消息包含role和content。客户端拿到这个序列之后会直接把消息灌给模型上下文。这种机制最大的价值在于把高质量提示词封装成标准接口团队内部分享、复用都很方便。6.4 三者如何配合使用Tools、Resources、Prompts在MCP里不是互斥的三个独立功能而是一个完整的Agent能力集合。Tools负责“动”——执行外部操作Resources负责“读”——提供知识背景Prompts负责“定调”——定义交互模式。我做一个财务分析Agent时就用到了全套能力Resources提供最新的财报数据和指标说明Tools负责查询实时股价、生成图表、发送邮件Prompts定义“财务分析师”角色和对话节奏这样一来AI模型在回答问题时不是凭空臆测而是先读资源获取背景再调工具验证数据再按预设的Prompt风格组织回答。三个一起用整个系统的可靠性、专业感和用户体验提升了不止一个档次。7. 认证与鉴权生产环境绕不开的话题7.1 为什么MCP服务需要独立鉴权很多人会忽略一个关键问题MCP服务一旦跑在网络上就变成了一个公开的“AI可调用接口”。如果你内部有一个查询工资数据的工具没做鉴权任何能连到该端点的客户端都能让AI调出所有员工的薪资——这就是数据安全事故。鉴权方案有两种思路。第一层是传输层鉴权HTTP模式下用API Key、Bearer Token或者OAuth2。第二层是工具层授权即使客户端拿到了会话权限也要确保它能调用的工具范围是被允许的。7.2 在SDK层面做一个简单的请求拦截SDK没有内置完整的用户维度的权限管理但你可以灵活地通过装饰器或者中间件去实现。比如给FastMCP传一个middleware参数或者在HTTP App外层用Starlette的中间件机制做统一拦截。一个非常实用的做法是在HTTP模式下给每个请求加上Bearer Token校验from starlette.applications import Starlette from starlette.middleware.base import BaseHTTPMiddleware from starlette.responses import JSONResponse class AuthMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): auth request.headers.get(Authorization, ) if not auth.startswith(Bearer ) or auth.split( )[1] ! your-secret-key: return JSONResponse({error: unauthorized}, status_code401) return await call_next(request) app Starlette(routes[...]) # 把你的MCP路由挂进来 app.add_middleware(AuthMiddleware)如果团队内部已经接入了OAuth2体系同样可以在这个中间件里做回调验证。7.3 客户端如何携带认证信息客户端连接Server时如何把Token传过去HTTP模式下很简单直接设置请求头from mcp.client.streamable_http import streamable_http_client async with streamable_http_client( urlhttp://localhost:8080/mcp, headers{Authorization: Bearer your-secret-key} ) as (read, write): async with ClientSession(read, write) as session: ...这里注意streamable_http_client的headers参数不仅传Bearer Token还可以传其他自定义头比如X-User-ID用来做用户级审计。MCP协议本身不强制定任何Header格式这部分完全由你定。8. 常见问题与坑位实录8.1 典型问题速查表问题现象原因解决方案Pydantic版本冲突ImportError: cannot import name BaseModel环境里存在多个pydantic版本用虚拟环境统一降级pydantic2.*工具不显示客户端list_tools()返回空列表装饰器没生效或模块没被导入确认用了mcp.tool()且server已加载该模块print导致崩溃会话中途断开或消息解析失败stdout被非协议数据污染所有日志改用logging设置streamsys.stderr找不到异步循环RuntimeError: no running event loop在同步代码里直接调用异步Session用asyncio.run()或者把整个调用链放进async函数连接不上HTTP ServerConnection refuseduvicorn没启动或地址端口错误先curl http://ip:port/确认可达工具名带中文或特殊字符报Schema校验错误MCP协议要求工具名必须合法标识符工具名统一用英文小写加下划线8.2 工具定义与Pydantic的相爱相杀MCP SDK底层基于Pydantic做数据模型定义和参数校验这很强大但也容易让不熟悉Pydantic的人踩坑。最典型的问题是在工具函数里定义复杂类型时不知道怎么写类型注解。如果你要返回结构化数据直接用普通的dict或list是可以的SDK会序列化成JSON。但如果你的数据结构很复杂带上嵌套的类定义会涉及Pydantic Model的复用from pydantic import BaseModel, Field class MarketData(BaseModel): symbol: str price: float Field(description当前价格) mcp.tool() def get_market_data(symbol: str) - MarketData: 获取市场数据 return MarketData(symbolsymbol, price123.45)返回值声明为Pydantic ModelSDK能自动将其序列化为符合Schema的JSON结构客户端的AI模型也就能更准确地理解返回数据的结构。8.3 Jupyter Notebook用户的特殊障碍很多人在Jupyter里跑MCP代码被坑得怀疑人生最典型的问题是asyncio.run()在Jupyter环境里跑不了。因为Jupyter自带一个运行中的事件循环你不能再开一个。这时候有两类解法第一种是改用await语法直接执行因为Jupyter的cell天然支持top-level awaitasync with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() print(await session.list_tools())第二种是使用nest_asyncio包pip install nest_asyncioimport nest_asyncio nest_asyncio.apply()然后就能正常用asyncio.run()了。这个方法在处理一些复杂并发场景时尤其好用。实测下来nest_asyncio最省心只在Jupyter环境里有必要正常Python脚本别加。8.4 Windows环境下的特殊注意事项Windows下跑stdio模式需要额外小心。MCP Server子进程的启动依赖于命令行参数Windows的路径分隔符和处理方式与Linux不同经常出现路径解析错误。避坑要点Server脚本路径用绝对路径避免相对路径带来的歧义command字段不要写python用sys.executable获取当前解释器的完整路径确保子进程用的是同一个环境不要在StdioServerParameters的args里带shellTrue风格的重定向符号SDK内部直接用subprocess.Popen不需要shellimport sys from mcp import StdioServerParameters server_params StdioServerParameters( commandsys.executable, args[server.py], )这个看起来简单但帮很多人省了一大把排查时间。9. 经验之谈我把MCP用在自己项目里的几点体会第一个体会是先用FastMCP把业务逻辑跑通再考虑底层定制。很多人一开始就想自己实现完整协议栈研究半天初始化握手和消息构造结果还没写到业务代码就放弃。先做加法再做减法这句话在MCP开发里最实用。第二个体会是工具颗粒度设计决定Agent智商。真正好用的MCP Server工具数量不需要多但每个工具的外延要清晰。记住“一个工具只做一件事”的原则比如get_user_info只负责查用户信息和偏好不要顺便把库存也查了。哪天你想做一次完整数据聚合可以再写一个get_overview_report来组合调用多个基础工具。功能聚合放上层原子能力放下层。第三个体会是MCP的社区生态已经极其丰富。你去GitHub搜一圈会发现已经有各种现成Server数据库、浏览器自动化、待办事项、邮件、GitHub、金融数据、设计工具应有尽有。想要快速上手某个场景直接装一个现成的MCP Server试试水比从零开发靠谱得多。我自己折腾MCP最大的感受是这套协议把AI应用开发的IoT化程度提升了一大截。以前写Agent要关心的集成混乱问题现在全都收敛到标准协议层剩下的只剩业务本身。这篇内容里的代码和方案都是我实际在本地跑过、在项目里用过的直接把环境配好、逻辑带入进去就能出一套能用的东西。如果你还在观望MCP值不值得学我的建议是值得而且越早学越好。
网站建设高端定制企业官网