最近系统学习MCP(Model Context Protocol,模型上下文协议),起因是手头几个AI应用项目又双叒陷进集成泥潭:每个应用都要单独对接本地文件、数据库、外部API,一套能力N个应用各写一遍适配代码,接口一变动全线跟着改。MCP说白了,就是在AI应用和外部工具之间统一了一层标准接口,让工具提供方把能力封装成一个独立进程,应用方按协议去调用,从此告别一对多的混乱局面。这篇笔记是我从零学MCP的完整过程,从概念拆解、环境准备、写最小服务器,到接入客户端、踩坑排查,一次讲透。适合被工具集成问题困扰、想搞懂MCP并能手写一个服务器接到自己AI客户端里的开发者。1. 先搞清楚MCP到底解决什么问题1.1 从一对多到一对一:集成矩阵为什么必须拆先说场景。早先做一个AI编程助手,要接代码搜索、命令行、知识库三块能力,每个数据源都要单独写适配层。后来换了另一个AI应用,又要重新对接一遍。如果M个AI应用要对接N个外部工具,传统做法就是M×N个集成点,每个集成点都得有人维护。更麻烦的是,任何一个工具的API变动,所有依赖它的应用都得跟着改。MCP的解法是把关系改成“应用→协议→工具”。每个工具方只需要提供一个MCP Server,把自己能力按协议暴露出去;每个AI应用只需要实现一次MCP客户端,剩下的工具接入全走同一套标准。集成次数从M×N降成M+N。改动工具内部实现时,只要协议层没变,应用侧无感知。对比一下两种方式的差异:对比维度传统适配模式MCP模式集成数量M×N个连接M+N个连接维护成本每个连接都要单独管只看协议层和各自一侧复用性低,换应用就得重写高,一次性封装到处复用改动影响面工具API一变全局崩工具内部改动不影响应用调试方式各写各的日志统一看协议消息这也是为什么MCP一出来就被很多开发者当成基础设施来看待——它解决的不是某一个小问题,而是整个工具集成范式的底层问题。1.2 Host、Server、Client:三个角色一张图MCP的架构里有三个角色,很容易搞混,我一开始就绕晕过。Host(宿主):用户直接面对的AI应用,负责处理对话、调度模型、展示结果。你可以理解成“发号施令的大脑”。Server(MCP服务器):被集成方的能力提供者,是一个独立进程,向外暴露三类能力:工具(Tools)、资源(Resources)、提示词(Prompts)。Client(协议客户端):藏在Host和Server之间,负责把前者的请求翻译成协议消息、把后者的响应翻译回去,完成握手、消息序列化、会话管理之类的事。实际用户开发时,通常只需要写Server,客户端和Host由AI应用实现。但这三个角色必须分清,否则看文档时很容易懵。我常用一个餐厅类比帮助记忆:Host是顾客,Server是后厨做好的菜品,Client是服务员。顾客不直接进后厨拿菜,而是通过服务员下单、传菜、反馈,服务员只负责传消息,不决定菜品怎么做。这样一拆,顾客换另一家餐厅(换Host)、后厨换厨师(换Server实现),服务员的工作流程都不用变。1.3 为什么选JSON-RPC 2.0作为消息协议MCP底层传输用的是JSON-RPC 2.0,就是一个很轻量的请求/响应协议。第一次看到这个选择我也有点意外,毕竟现在REST、gRPC都挺流行,为什么选它?核心原因是简单、无状态、方便调试。REST的语义依赖HTTP方法和URL设计,对不同动作的约束是松散的,表达“调用某个远程函数”这种语义要绕一层;gRPC虽然强类型、性能好,但引入IDL定义、编译生成代码、二进制传输,对动态灵活的AI应用交互来说偏重了。JSON-RPC 2.0用method、params、id三个基础字段,天然匹配远程调用场景,直接看懂、直接调试。放一条真实的协议报文感受一下:{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "reverse_text", "arguments": { "text": "hello" } } }一眼能看懂是“调用reverse_text工具”,返回也是一样的结构,带result或error。这个设计对协议层排错太友好了,打开日志就能还原完整交互链路。2. 学习路线与准备:别一上来就写代码2.1 建议先读完的三份材料学新东西最容易犯的错就是直接搜教程、抄代码。MCP还在快速演进,网络教程大量滞后,照抄很容易卡在版本差异上。我的建议是按这个顺序读:协议规范文档,重点读工具、资源、提示词、传输四个章节,搞清楚“协议支持什么、边界在哪”。官方SDK自带的示例代码,看一遍“怎么跑起来”,此时不追究每行代码含义。社区里成熟的完整示例仓库,关注“真实场景中怎么组织代码、怎么选能力”。读这三份材料正好对应三种问题:是什么、怎么用、怎么选。我当时花了一个周末把这些过完,再去看别人踩坑帖,一眼就知道问题出在哪,后续排查效率高很多。2.2 环境准备怎么做最省心MCP官方有不少语言的SDK,我选的是Python版本,因为生态最成熟、API最简洁。环境上强烈建议用虚拟环境,原因后面细说。操作步骤:python -m venv .venv source .venv/bin/activate # Windows下是 .venv\Scripts\activate pip install mcp装完后验证一下版本,确保安装成功:python -c "import mcp; print(mcp.__version__)"这里必须用虚拟环境的原因很实在:MCP的Python SDK依赖pydantic等一堆包,这些包如果装到系统Python全局环境里,很容易跟其他项目冲突,导致各种“服务器秒退”或“连不上”的怪问题。我实际排查过的案例里,相当一部分都是依赖污染引起的。包版本同样重要,如果看到文档推荐的是特定版本,就按版本装,别贪最新。2.3 我踩过的第一个坑:教程版本与SDK接口对不上这个坑强烈建议所有新手引以为戒。我刚开始学习时,照着网上某篇文章写Server,复制粘贴运行直接报错。一查发现,那篇文章用的旧API,比如创建Server对象的方式、注册工具的方法,在当前SDK里全都变了。原因很简单:MCP还很年轻,官方SDK在快速迭代,接口调整频繁,连官方示例都在跟着改。网络教程写作时有自己的版本快照,一到现实世界里就过时了。解决方式只有一个:代码报错时先别急着搜解决方案,优先查看本地SDK的签名和官方文档。用help()或直接翻安装包源码,都比大海捞针式的搜索高效。python -c "from mcp.server.fastmcp import FastMCP; help(FastMCP)"这个习惯适用于所有快速演进的新技术:以官方文档和本地代码为唯一基准,社区文章只提供思路,不提供答案。3. 手写一个最小可运行的MCP Server3.1 最小框架:三行代码先跑起来先不追求功能,目标是把最小框架跑通。用官方Python SDK自带的FastMCP类,这是个便捷封装,把连接管理、协议握手、消息分发这些琐事都包了,初期写代码只用关注业务能力。from mcp.server.fastmcp import FastMCP mcp = FastMCP("learn-mcp-demo") if __name__ == "__main__": mcp.run()拆解一下:第一行引入FastMCP,第二行创建一个服务实例,传入的名字会显示在客户端的MCP服务器列表里,相当于给它起个标识。最后run()启动进程,默认走stdio传输(标准输入输出),这个选择后面细说。跑起来没有输出是正常的,因为它等着被Host拉起,而不是自己去响应。想验证它活着,得配合调试工具或客户端。3.2 添加两个工具:注释和类型注解就是说明书接下来往里面塞两个工具函数。这里有个思维转换:传统API开发者把函数签名当契约,MCP里函数签名是给AI模型看的说明书。from mcp.server.fastmcp import FastMCP mcp = FastMCP("learn-mcp-demo") @mcp.tool() def reverse_text(text: str) -> str: """将输入文本反转后返回""" return text[::-1] @mcp.tool() def celsius_to_fahrenheit(celsius: float) -> float: """摄氏度转华氏度,参数为摄氏温度值""" return celsius * 9 / 5 + 32 if __name__ == "__main__": mcp.run()MCP通过协议把工具名、描述、参数JSON Schema发给Host,模型读到的时候是这样:{ "name": "reverse_text", "description": "将输入文本反转后返回", "inputSchema": { "type": "object", "properties": { "text": {"type": "string"} }, "required": ["text"] } }模型看到这个结构,用户说“帮我把MCP学习笔记反转一下”,它就能正确填入参数、发起调用。实操心得:工具描述一定要用通俗简短的自然语言写清楚“干什么”,别加多余修饰。参数数量控制得越少越好,超过5个参数的函数模型很容易传错,宁可拆成多个工具,也不要堆一个大而全的工具。3.3 本地验证:拿到工具列表和调用面板先别急着接客户端,用官方调试面板跟Server聊一聊。打开调试面板,选stdio传输,填启动命令和参数,连接成功后能看到三样东西。服务器注册的工具列表自动生成的参数校验表单实时的JSON-RPC通信日志对我帮助最大的是通信日志。输入一条调用请求,日志里能看到握手过程、工具列表拉取、调用请求、结果返回、错误码全链路,原始报文一清二楚。这一步把“理解协议”从纸面变成了亲眼所见,调试面板是学习MCP的必经之路。3.4 关于传输方式的一个建议MCP支持stdio和HTTP两种传输,我建议初学阶段只用stdio。stdio的进程生命周期由Host管理,启动、通信、退出都走标准输入输出,进程边界天然隔离,安全性好。没有端口暴露,没有网络监听,调试时打开终端就能看到所有输出。HTTP适合远程部署、多客户端共享服务,但代价是要处理网络鉴权、跨域、端口占用一整套问题,这些对学习协议没帮助。我之前见过有人一开始就用HTTP部署自己的Server,结果一直在调网络问题,完全偏离了学习主线。等把stdio模式跑通,再迁移到HTTP只需要换一段启动代码,半个小时内能完成。4. 接入客户端:让AI助手真正“用”起来4.1 客户端配置入口怎么找Server写好了,下一步就是把它接到一个AI客户端里。现在主流AI客户端基本都内置了MCP支持,桌面端的配置面板、编程编辑器的设置界面里总能找到MCP相关入口。以我常用的某AI客户端为例,设置里可以新增MCP Server,点进去后会生成一个配置文件。不同产品入口位置可能不同,但底层结构是统一的:一个JSON对象,里面每个键对应一个MCP服务器条目。如果找不到入口,直接在程序设置里搜索“MCP”三个字母,基本都能定位。4.2 一条典型配置的逐字段拆解客户端的MCP配置长这样:{ "mcpServers": { "learn-demo": { "command": "/Users/me/projects/mcp-learn/.venv/bin/python", "args": ["/Users/me/projects/mcp-learn/server.py"], "env": {} } } }逐字段拆一下。command是启动Server的可执行程序;args是传给它的参数,一般就是Python脚本的绝对路径;env可以给子进程注入额外环境变量。这里有两条实用经验。第一条,args里务必写绝对路径。客户端启动子进程时的工作目录不一定是项目目录,写相对路径很容易翻车。第二条,command直接指向虚拟环境里的python二进制,而不是系统python,比如.venv/bin/python。这样依赖隔离最彻底,不会出现“系统Python没装mcp包导致连不上”的问题。注意:JSON配置里不允许有注释和尾逗号,很多配置解析失败都是这个原因。填完后如果提示解析错误,用在线JSON校验器检查一遍再重启客户端。4.3 从“已连接”到“真的调用了工具”配置完成后重启客户端,列表里应该能看到learn-demo状态为已连接(或者叫Active)。但这只代表握手成功,不代表真的能用。完整验证分三步。第一步,看列表状态。第二步,在对话窗口发一句自然语言触发它:“请用learn-demo把‘MCP学习笔记’反转一下”。第三步,观察消息流。客户端会显示模型发起了工具调用,指向reverse_text,参数text为“MCP学习笔记”,然后返回反转结果。这个过程的原理值得展开:Host启动时拿到了Server的工具列表,会把自己的描述拼进模型的上下文里;模型根据用户请求的语义,判断是否应该调用、调用哪个工具、填入什么参数。这就是“模型自动选择工具”的本质——不是客户端硬编码映射,而是模型在上下文中看到了工具的“说明书”后自主决策。不同客户端对工具调用的展示形式不一样,有的会先弹确认框,有的直接自动执行。建议选一个能显示工具调用明细的客户端,学习阶段能清楚看到模型实际填了什么参数,排查问题省很多事。5. 从“能跑”到“好用”:资源、提示词与权限设计5.1 资源与工具的分工取舍MCP暴露的三类能力里,工具和资源最容易搞混。工具是“动作”,有副作用,比如创建文件、发消息、执行脚本。资源是“数据”,只读提供,比如配置文件内容、数据库表结构、日志片段。这个区别直接决定模型怎么选用:如果模型需要读取数据但不需要改变状态,优先定义资源,比如把./config/app.yaml暴露成一个资源,模型直接读取解析;如果需要执行操作、产生变化,必须定义工具,比如删除日志文件的接口。我一开始把什么都定义成工具,结果模型老选错。后来把“只读数据”全部改成资源,“有副作用的动作”保留为工具,模型的选择准确率立刻上来了。语义分界清晰,比什么都往里塞实用得多。5.2 提示词模板:把高频任务固化成流程提示词(Prompts)是MCP里最容易被忽略、但在真实场景里很实用的一块。它的作用是:把“用户每次都要重复描述的任务流程”提前固化在服务端,调用时自动引导模型执行。举一个“生成周报”的示例:{ "prompt": "generate-report", "arguments": [ { "name": "scope", "required": true } ] }用户在客户端发起该提示词后,模型会按预设步骤收集信息、组织格式、输出报告。价值在于标准化——团队多人使用时效果一致,不用靠口头约定“每次都要按这个格式来”。有个坑需要提前说:不是所有客户端都支持Prompts,工具和资源是底线能力,Prompts属于加分项。写Server时可以把Prompts放在最后,优先级低于前两者。5.3 安全边界:想清楚再暴露能力MCP Server一旦被客户端加载,模型就可以调用它暴露的工具。权限设计的分寸必须拿捏,我把几条实用经验写在这里。第一条,按最小权限暴露工具。某个内部工具只读文件时就别顺手暴露删除功能,边界越窄,出问题的可能性越小。第二条,敏感信息走环境变量。数据库口令、API密钥这类配置不要硬编码在脚本里,通过env字段注入,代码仓库里不留凭证。第三条,提防“提示注入”。Server返回的内容如果不可信,未经处理直接塞进模型上下文,可能被恶意文本引导做出异常动作。对不可信数据源做白名单校验或内容过滤,是MCP Server上线前的必要检查。注意:MCP的工具调用是模型基于上下文内容自主决策的,任何暴露给Server的数据都可能影响模型行为。把“不可信数据直接进上下文”当成一个需要刻意防御的问题,而不是“应该不会出事”的小概率事件。6. 常见问题与排查技巧实录6.1 高频问题速查表整理一下我学习和实操中遇到的高频问题,做成一张速查表,方便之后查阅。症状高频原因解决思路客户端列表显示未连接command路径错误、虚拟环境未生效先在终端手动跑server.py,确认进程能正常启动并保持运行工具调用超时工具内部阻塞,比如网络请求、大文件IO在工具函数内设置超时,必要时用异步方式避免阻塞消息循环模型说找不到这个工具配置未生效、模型没理解工具描述重启客户端,检查工具描述是否过分抽象,减少语义相近的工具数量Server进程秒退语法错误、依赖缺失、端口被占用在终端手动执行server.py,直接看stderr完整输出,先定位退出码配置文件解析失败写了注释或尾逗号用JSON校验器格式化,删掉所有注释再保存连上了但调用结果为空工具函数没有return值MCP走的是请求-响应模型,工具必须显式return,打印语句不会作为结果回传每个问题背后都有值得展开的点。比如“工具调用超时”,常见原因就是把第三方API同步请求直接写在工具函数里,一个远程接口慢了几秒,整个Server就像卡死一样。解决办法是设置合理的超时时间,或者对耗时操作做异步化,保证工具能及时返回。6.2 我的高效排查套路踩过几次坑后,我总结出一套排查顺序,比到处乱搜高效得多。第一步,手动启动Server。在终端直接python server.py,看进程能不能保持运行,如果秒退,错误信息会直接打在终端里,这是最快的定位方式。第二步,检查依赖环境。确认当前用的是虚拟环境,pip list看一下mcp和pydantic版本对不对。很多“Client连不上Server”最终都查到这里。第三步,检查客户端配置。重点看command、args是不是绝对路径,JSON里有没有非法内容。这一步最多花两分钟,但能过滤掉一大半配置问题。第四步,打开调试面板看通信日志。把传输类型、启动命令填好,连接后翻一翻消息日志——握手是否成功、工具列表是否正常下发、调用请求是否正确返回。原始报文能直接告诉你协议哪一层出问题。第五步,带着完整报错信息去权威渠道检索,优先搜官方issue和文档更新记录。注意附上当前SDK版本,因为很多老报错其实早就修复了。这套流程的核心是“先复现、再隔离、后定位”。不做前四步直接搜报错,往往看到的信息不完整,搜到的东西也不对版。7. 写在笔记最后学完这一轮,最深的体会是MCP的重点不在写代码,而在理解边界——哪些东西应该由模型自主决定,哪些必须由服务端硬约束。工具描述给模型的自由度,参数设计框住模型的行动范围,权限配置守住系统的安全底线,这几条线划清楚了,MCP Server就算真正摸到门道了。现在我写内部小工具时,第一反应已经是“要不要把它做成一个MCP Server”,因为统一调用方式真的省心。下一步我打算把资源、提示词和团队内部的数据库表结构结合起来,做一个知识库服务,跑通了再回来更新笔记。希望这篇记录能帮你绕开我走过的弯路。
最近系统学习MCP(Model Context Protocol,模型上下文协议),起因是手头几个AI应用项目又双叒陷进集成泥潭:每个应用都要单独对接本地文件、数据库、外部API,一套能力N个应用各写一遍适配代码,接口一变动全线跟着改。MCP说白了,就是在AI应用和外部工具之间统一了一层标准接口,让工具提供方把能力封装成一个独立进程,应用方按协议去调用,从此告别一对多的混乱局面。这篇笔记是我从零学MCP的完整过程,从概念拆解、环境准备、写最小服务器,到接入客户端、踩坑排查,一次讲透。适合被工具集成问题困扰、想搞懂MCP并能手写一个服务器接到自己AI客户端里的开发者。1. 先搞清楚MCP到底解决什么问题1.1 从一对多到一对一:集成矩阵为什么必须拆先说场景。早先做一个AI编程助手,要接代码搜索、命令行、知识库三块能力,每个数据源都要单独写适配层。后来换了另一个AI应用,又要重新对接一遍。如果M个AI应用要对接N个外部工具,传统做法就是M×N个集成点,每个集成点都得有人维护。更麻烦的是,任何一个工具的API变动,所有依赖它的应用都得跟着改。MCP的解法是把关系改成“应用→协议→工具”。每个工具方只需要提供一个MCP Server,把自己能力按协议暴露出去;每个AI应用只需要实现一次MCP客户端,剩下的工具接入全走同一套标准。集成次数从M×N降成M+N。改动工具内部实现时,只要协议层没变,应用侧无感知。对比一下两种方式的差异:对比维度传统适配模式MCP模式集成数量M×N个连接M+N个连接维护成本每个连接都要单独管只看协议层和各自一侧复用性低,换应用就得重写高,一次性封装到处复用改动影响面工具API一变全局崩工具内部改动不影响应用调试方式各写各的日志统一看协议消息这也是为什么MCP一出来就被很多开发者当成基础设施来看待——它解决的不是某一个小问题,而是整个工具集成范式的底层问题。1.2 Host、Server、Client:三个角色一张图MCP的架构里有三个角色,很容易搞混,我一开始就绕晕过。Host(宿主):用户直接面对的AI应用,负责处理对话、调度模型、展示结果。你可以理解成“发号施令的大脑”。Server(MCP服务器):被集成方的能力提供者,是一个独立进程,向外暴露三类能力:工具(Tools)、资源(Resources)、提示词(Prompts)。Client(协议客户端):藏在Host和Server之间,负责把前者的请求翻译成协议消息、把后者的响应翻译回去,完成握手、消息序列化、会话管理之类的事。实际用户开发时,通常只需要写Server,客户端和Host由AI应用实现。但这三个角色必须分清,否则看文档时很容易懵。我常用一个餐厅类比帮助记忆:Host是顾客,Server是后厨做好的菜品,Client是服务员。顾客不直接进后厨拿菜,而是通过服务员下单、传菜、反馈,服务员只负责传消息,不决定菜品怎么做。这样一拆,顾客换另一家餐厅(换Host)、后厨换厨师(换Server实现),服务员的工作流程都不用变。1.3 为什么选JSON-RPC 2.0作为消息协议MCP底层传输用的是JSON-RPC 2.0,就是一个很轻量的请求/响应协议。第一次看到这个选择我也有点意外,毕竟现在REST、gRPC都挺流行,为什么选它?核心原因是简单、无状态、方便调试。REST的语义依赖HTTP方法和URL设计,对不同动作的约束是松散的,表达“调用某个远程函数”这种语义要绕一层;gRPC虽然强类型、性能好,但引入IDL定义、编译生成代码、二进制传输,对动态灵活的AI应用交互来说偏重了。JSON-RPC 2.0用method、params、id三个基础字段,天然匹配远程调用场景,直接看懂、直接调试。放一条真实的协议报文感受一下:{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "reverse_text", "arguments": { "text": "hello" } } }一眼能看懂是“调用reverse_text工具”,返回也是一样的结构,带result或error。这个设计对协议层排错太友好了,打开日志就能还原完整交互链路。2. 学习路线与准备:别一上来就写代码2.1 建议先读完的三份材料学新东西最容易犯的错就是直接搜教程、抄代码。MCP还在快速演进,网络教程大量滞后,照抄很容易卡在版本差异上。我的建议是按这个顺序读:协议规范文档,重点读工具、资源、提示词、传输四个章节,搞清楚“协议支持什么、边界在哪”。官方SDK自带的示例代码,看一遍“怎么跑起来”,此时不追究每行代码含义。社区里成熟的完整示例仓库,关注“真实场景中怎么组织代码、怎么选能力”。读这三份材料正好对应三种问题:是什么、怎么用、怎么选。我当时花了一个周末把这些过完,再去看别人踩坑帖,一眼就知道问题出在哪,后续排查效率高很多。2.2 环境准备怎么做最省心MCP官方有不少语言的SDK,我选的是Python版本,因为生态最成熟、API最简洁。环境上强烈建议用虚拟环境,原因后面细说。操作步骤:python -m venv .venv source .venv/bin/activate # Windows下是 .venv\Scripts\activate pip install mcp装完后验证一下版本,确保安装成功:python -c "import mcp; print(mcp.__version__)"这里必须用虚拟环境的原因很实在:MCP的Python SDK依赖pydantic等一堆包,这些包如果装到系统Python全局环境里,很容易跟其他项目冲突,导致各种“服务器秒退”或“连不上”的怪问题。我实际排查过的案例里,相当一部分都是依赖污染引起的。包版本同样重要,如果看到文档推荐的是特定版本,就按版本装,别贪最新。2.3 我踩过的第一个坑:教程版本与SDK接口对不上这个坑强烈建议所有新手引以为戒。我刚开始学习时,照着网上某篇文章写Server,复制粘贴运行直接报错。一查发现,那篇文章用的旧API,比如创建Server对象的方式、注册工具的方法,在当前SDK里全都变了。原因很简单:MCP还很年轻,官方SDK在快速迭代,接口调整频繁,连官方示例都在跟着改。网络教程写作时有自己的版本快照,一到现实世界里就过时了。解决方式只有一个:代码报错时先别急着搜解决方案,优先查看本地SDK的签名和官方文档。用help()或直接翻安装包源码,都比大海捞针式的搜索高效。python -c "from mcp.server.fastmcp import FastMCP; help(FastMCP)"这个习惯适用于所有快速演进的新技术:以官方文档和本地代码为唯一基准,社区文章只提供思路,不提供答案。3. 手写一个最小可运行的MCP Server3.1 最小框架:三行代码先跑起来先不追求功能,目标是把最小框架跑通。用官方Python SDK自带的FastMCP类,这是个便捷封装,把连接管理、协议握手、消息分发这些琐事都包了,初期写代码只用关注业务能力。from mcp.server.fastmcp import FastMCP mcp = FastMCP("learn-mcp-demo") if __name__ == "__main__": mcp.run()拆解一下:第一行引入FastMCP,第二行创建一个服务实例,传入的名字会显示在客户端的MCP服务器列表里,相当于给它起个标识。最后run()启动进程,默认走stdio传输(标准输入输出),这个选择后面细说。跑起来没有输出是正常的,因为它等着被Host拉起,而不是自己去响应。想验证它活着,得配合调试工具或客户端。3.2 添加两个工具:注释和类型注解就是说明书接下来往里面塞两个工具函数。这里有个思维转换:传统API开发者把函数签名当契约,MCP里函数签名是给AI模型看的说明书。from mcp.server.fastmcp import FastMCP mcp = FastMCP("learn-mcp-demo") @mcp.tool() def reverse_text(text: str) -> str: """将输入文本反转后返回""" return text[::-1] @mcp.tool() def celsius_to_fahrenheit(celsius: float) -> float: """摄氏度转华氏度,参数为摄氏温度值""" return celsius * 9 / 5 + 32 if __name__ == "__main__": mcp.run()MCP通过协议把工具名、描述、参数JSON Schema发给Host,模型读到的时候是这样:{ "name": "reverse_text", "description": "将输入文本反转后返回", "inputSchema": { "type": "object", "properties": { "text": {"type": "string"} }, "required": ["text"] } }模型看到这个结构,用户说“帮我把MCP学习笔记反转一下”,它就能正确填入参数、发起调用。实操心得:工具描述一定要用通俗简短的自然语言写清楚“干什么”,别加多余修饰。参数数量控制得越少越好,超过5个参数的函数模型很容易传错,宁可拆成多个工具,也不要堆一个大而全的工具。3.3 本地验证:拿到工具列表和调用面板先别急着接客户端,用官方调试面板跟Server聊一聊。打开调试面板,选stdio传输,填启动命令和参数,连接成功后能看到三样东西。服务器注册的工具列表自动生成的参数校验表单实时的JSON-RPC通信日志对我帮助最大的是通信日志。输入一条调用请求,日志里能看到握手过程、工具列表拉取、调用请求、结果返回、错误码全链路,原始报文一清二楚。这一步把“理解协议”从纸面变成了亲眼所见,调试面板是学习MCP的必经之路。3.4 关于传输方式的一个建议MCP支持stdio和HTTP两种传输,我建议初学阶段只用stdio。stdio的进程生命周期由Host管理,启动、通信、退出都走标准输入输出,进程边界天然隔离,安全性好。没有端口暴露,没有网络监听,调试时打开终端就能看到所有输出。HTTP适合远程部署、多客户端共享服务,但代价是要处理网络鉴权、跨域、端口占用一整套问题,这些对学习协议没帮助。我之前见过有人一开始就用HTTP部署自己的Server,结果一直在调网络问题,完全偏离了学习主线。等把stdio模式跑通,再迁移到HTTP只需要换一段启动代码,半个小时内能完成。4. 接入客户端:让AI助手真正“用”起来4.1 客户端配置入口怎么找Server写好了,下一步就是把它接到一个AI客户端里。现在主流AI客户端基本都内置了MCP支持,桌面端的配置面板、编程编辑器的设置界面里总能找到MCP相关入口。以我常用的某AI客户端为例,设置里可以新增MCP Server,点进去后会生成一个配置文件。不同产品入口位置可能不同,但底层结构是统一的:一个JSON对象,里面每个键对应一个MCP服务器条目。如果找不到入口,直接在程序设置里搜索“MCP”三个字母,基本都能定位。4.2 一条典型配置的逐字段拆解客户端的MCP配置长这样:{ "mcpServers": { "learn-demo": { "command": "/Users/me/projects/mcp-learn/.venv/bin/python", "args": ["/Users/me/projects/mcp-learn/server.py"], "env": {} } } }逐字段拆一下。command是启动Server的可执行程序;args是传给它的参数,一般就是Python脚本的绝对路径;env可以给子进程注入额外环境变量。这里有两条实用经验。第一条,args里务必写绝对路径。客户端启动子进程时的工作目录不一定是项目目录,写相对路径很容易翻车。第二条,command直接指向虚拟环境里的python二进制,而不是系统python,比如.venv/bin/python。这样依赖隔离最彻底,不会出现“系统Python没装mcp包导致连不上”的问题。注意:JSON配置里不允许有注释和尾逗号,很多配置解析失败都是这个原因。填完后如果提示解析错误,用在线JSON校验器检查一遍再重启客户端。4.3 从“已连接”到“真的调用了工具”配置完成后重启客户端,列表里应该能看到learn-demo状态为已连接(或者叫Active)。但这只代表握手成功,不代表真的能用。完整验证分三步。第一步,看列表状态。第二步,在对话窗口发一句自然语言触发它:“请用learn-demo把‘MCP学习笔记’反转一下”。第三步,观察消息流。客户端会显示模型发起了工具调用,指向reverse_text,参数text为“MCP学习笔记”,然后返回反转结果。这个过程的原理值得展开:Host启动时拿到了Server的工具列表,会把自己的描述拼进模型的上下文里;模型根据用户请求的语义,判断是否应该调用、调用哪个工具、填入什么参数。这就是“模型自动选择工具”的本质——不是客户端硬编码映射,而是模型在上下文中看到了工具的“说明书”后自主决策。不同客户端对工具调用的展示形式不一样,有的会先弹确认框,有的直接自动执行。建议选一个能显示工具调用明细的客户端,学习阶段能清楚看到模型实际填了什么参数,排查问题省很多事。5. 从“能跑”到“好用”:资源、提示词与权限设计5.1 资源与工具的分工取舍MCP暴露的三类能力里,工具和资源最容易搞混。工具是“动作”,有副作用,比如创建文件、发消息、执行脚本。资源是“数据”,只读提供,比如配置文件内容、数据库表结构、日志片段。这个区别直接决定模型怎么选用:如果模型需要读取数据但不需要改变状态,优先定义资源,比如把./config/app.yaml暴露成一个资源,模型直接读取解析;如果需要执行操作、产生变化,必须定义工具,比如删除日志文件的接口。我一开始把什么都定义成工具,结果模型老选错。后来把“只读数据”全部改成资源,“有副作用的动作”保留为工具,模型的选择准确率立刻上来了。语义分界清晰,比什么都往里塞实用得多。5.2 提示词模板:把高频任务固化成流程提示词(Prompts)是MCP里最容易被忽略、但在真实场景里很实用的一块。它的作用是:把“用户每次都要重复描述的任务流程”提前固化在服务端,调用时自动引导模型执行。举一个“生成周报”的示例:{ "prompt": "generate-report", "arguments": [ { "name": "scope", "required": true } ] }用户在客户端发起该提示词后,模型会按预设步骤收集信息、组织格式、输出报告。价值在于标准化——团队多人使用时效果一致,不用靠口头约定“每次都要按这个格式来”。有个坑需要提前说:不是所有客户端都支持Prompts,工具和资源是底线能力,Prompts属于加分项。写Server时可以把Prompts放在最后,优先级低于前两者。5.3 安全边界:想清楚再暴露能力MCP Server一旦被客户端加载,模型就可以调用它暴露的工具。权限设计的分寸必须拿捏,我把几条实用经验写在这里。第一条,按最小权限暴露工具。某个内部工具只读文件时就别顺手暴露删除功能,边界越窄,出问题的可能性越小。第二条,敏感信息走环境变量。数据库口令、API密钥这类配置不要硬编码在脚本里,通过env字段注入,代码仓库里不留凭证。第三条,提防“提示注入”。Server返回的内容如果不可信,未经处理直接塞进模型上下文,可能被恶意文本引导做出异常动作。对不可信数据源做白名单校验或内容过滤,是MCP Server上线前的必要检查。注意:MCP的工具调用是模型基于上下文内容自主决策的,任何暴露给Server的数据都可能影响模型行为。把“不可信数据直接进上下文”当成一个需要刻意防御的问题,而不是“应该不会出事”的小概率事件。6. 常见问题与排查技巧实录6.1 高频问题速查表整理一下我学习和实操中遇到的高频问题,做成一张速查表,方便之后查阅。症状高频原因解决思路客户端列表显示未连接command路径错误、虚拟环境未生效先在终端手动跑server.py,确认进程能正常启动并保持运行工具调用超时工具内部阻塞,比如网络请求、大文件IO在工具函数内设置超时,必要时用异步方式避免阻塞消息循环模型说找不到这个工具配置未生效、模型没理解工具描述重启客户端,检查工具描述是否过分抽象,减少语义相近的工具数量Server进程秒退语法错误、依赖缺失、端口被占用在终端手动执行server.py,直接看stderr完整输出,先定位退出码配置文件解析失败写了注释或尾逗号用JSON校验器格式化,删掉所有注释再保存连上了但调用结果为空工具函数没有return值MCP走的是请求-响应模型,工具必须显式return,打印语句不会作为结果回传每个问题背后都有值得展开的点。比如“工具调用超时”,常见原因就是把第三方API同步请求直接写在工具函数里,一个远程接口慢了几秒,整个Server就像卡死一样。解决办法是设置合理的超时时间,或者对耗时操作做异步化,保证工具能及时返回。6.2 我的高效排查套路踩过几次坑后,我总结出一套排查顺序,比到处乱搜高效得多。第一步,手动启动Server。在终端直接python server.py,看进程能不能保持运行,如果秒退,错误信息会直接打在终端里,这是最快的定位方式。第二步,检查依赖环境。确认当前用的是虚拟环境,pip list看一下mcp和pydantic版本对不对。很多“Client连不上Server”最终都查到这里。第三步,检查客户端配置。重点看command、args是不是绝对路径,JSON里有没有非法内容。这一步最多花两分钟,但能过滤掉一大半配置问题。第四步,打开调试面板看通信日志。把传输类型、启动命令填好,连接后翻一翻消息日志——握手是否成功、工具列表是否正常下发、调用请求是否正确返回。原始报文能直接告诉你协议哪一层出问题。第五步,带着完整报错信息去权威渠道检索,优先搜官方issue和文档更新记录。注意附上当前SDK版本,因为很多老报错其实早就修复了。这套流程的核心是“先复现、再隔离、后定位”。不做前四步直接搜报错,往往看到的信息不完整,搜到的东西也不对版。7. 写在笔记最后学完这一轮,最深的体会是MCP的重点不在写代码,而在理解边界——哪些东西应该由模型自主决定,哪些必须由服务端硬约束。工具描述给模型的自由度,参数设计框住模型的行动范围,权限配置守住系统的安全底线,这几条线划清楚了,MCP Server就算真正摸到门道了。现在我写内部小工具时,第一反应已经是“要不要把它做成一个MCP Server”,因为统一调用方式真的省心。下一步我打算把资源、提示词和团队内部的数据库表结构结合起来,做一个知识库服务,跑通了再回来更新笔记。希望这篇记录能帮你绕开我走过的弯路。