聊到 2025 年 AI 基础设施里最绕不开的三个字母,MCP 绝对排得上号。不管你是写代码的、做运维的,还是在搞 AI 应用的,最近大概率被 MCP、MCP 服务、Tool 这几个词刷过屏。我第一次接触 MCP 是 Claude 刚宣布支持那阵,第一反应是:这不就是给大模型装外接硬盘吗?后来自己部署、调试、写 server,才发现协议、服务、Tool 这三者的关系并没有字面上那么好理解,很多人不是不会用,而是第一步就被名词绕晕了。下面这篇是我按自己趟坑顺序整理出来的,把 MCP 协议是什么、MCP 服务怎么搭、Tool 在其中扮演什么角色一次讲透,再给出一套能直接复现的本地部署方案。适合刚开始接触 MCP、想在 Claude Desktop、Cursor 或自己业务系统里接 MCP 的开发者参考。
1. 为什么会有 MCP:一个“AI 时代的通用插头”
1.1 过去接入 AI 的能力有多痛苦
在大模型刚火起来的时候,你想让 AI 帮你查数据库、发邮件、操作浏览器,基本每个需求都要专门写一段胶水代码。拿调用外部工具这件事来说,OpenAI 有 function calling,Anthropic 有 tool use,各家还都有自己的参数格式和调用规范。今天接飞书要写一套,明天接 Jira 又要重新写一套,后天换个模型提供商,之前的适配代码又得改一遍。
这感觉很像早期打印机没有统一驱动标准:每个软件厂商都要专门适配每一台硬件,每次新设备出来都是一次重复劳动。当时的 AI 应用开发也是这样,大量的时间不是花在业务逻辑上,而是花在“怎么把大模型和工具连接起来”这种低水平重复上。大家在社区里吐槽:给大模型接一个数据库,无非是写 Python 函数、转 JSON Schema、拼 prompt,这套流程能复制上百次。
MCP 就是为了终结这种碎片化而出现的。它把“AI 应用想调用外部能力”这件事标准化了:只要你的服务实现了 MCP 协议,任何支持 MCP 的 AI 客户端都能直接发现并调用它,不需要为每家厂商单独做适配。社区里有个很形象的比喻:MCP 之于 AI 工具链,就像 USB-C 之于充电接口。你不需要为每台设备准备专属充电线,只需要一个标准接口,大家都按这个规格来就行。
1.2 软件协议 vs 硬件协议:MCP 属于哪一类
网上有个高频问题:MCP 到底是软件协议还是硬件协议,和 USB、PCIe 这些概念是什么关系。答案很明确:MCP 是纯软件协议,而且属于应用层协议,跟 HTTP、WebSocket、JSON-RPC 是同一个层级的角色。它不涉及电压、电平、引脚、时钟同步这些硬件概念,它定义的是一套“AI 应用与服务之间如何交换消息”的规则。
具体来说,MCP 消息采用 JSON-RPC 2.0 格式,所有能力都通过请求、响应、通知三类消息来表达。传输层可以选择本地 stdio,也就是标准输入输出通道,也可以选择基于 HTTP 的 Streamable HTTP。这种分层设计很方便:协议逻辑和传输方式解耦,同一个服务既能在本地被 Claude Desktop 拉起,也能部署到远程服务器供网页端或者手机端 App 调用。
理解这一点之后,很多困惑就能解开。比如“手机怎么获取 MCP 服务”这个问题,本质上不是让手机系统装一个 MCP 协议栈,而是你去找一个支持 MCP 的 AI 客户端 App,然后在 App 里填写远程 MCP server 的地址和鉴权信息。因为远程服务走的是 HTTP,手机上照样能用,只是背后多了一层网络连接罢了。
2. MCP 协议核心构成:Client、Server、原语
2.1 三个角色:Host、Client、Server 别搞混
刚开始看 MCP 文档时很容易被角色绕晕,因为一个完整调用链里至少有三个东西:Host、Client、Server。我用餐厅来类比,一下就清楚了。
Host 是用户直接接触的宿主应用,比如 Claude Desktop、Cursor、Trae,或者你集成到业务系统里的 AI 助手,它负责渲染对话、调度大模型、展示结果。Client 是宿主应用内部负责跟 MCP Server 通信的模块,它执行协议握手、发现工具、发起调用,相当于餐厅里的服务员。Server 是实际干活的一方,它暴露出一批能力,相当于后厨,客人不会直接走进厨房点菜,得由服务员拿着菜单过去沟通。
很多人在排查问题时搞不清该改哪里。如果 Cursor 里看不到某个工具,问题可能在 Client 的配置,也就是 Cursor 里 MCP 配置项填得不对;如果工具看到了但调用报错,问题多半在 Server 端。先把角色边界划清楚,排错范围立刻缩小一半。
下面这个表可以帮你快速对齐:
| 角色 | 职责 | 常见例子 |
|---|---|---|
| Host | 面向用户的 AI 应用,负责对话与交互 | Claude Desktop、Cursor、Trae |
| Client | 宿主的协议代理,负责发现和调用能力 | Cursor 内置 MCP Client、Claude Desktop 客户端模块 |
| Server | 独立进程或服务,暴露工具、资源、提示词 | 自建 Python 服务、Playwright MCP、GitHub MCP Server |
2.2 三类原语:Tool、Resource、Prompt 各管什么
MCP 协议里定义了三种原语,也就是 Server 可以向 Client 暴露的三类能力,这经常被人忽略。很多人以为“MCP 就是 Tool”,其实 Tool 只是其中一种,另外两种同样重要。
Tool 是一个可执行函数,模型根据你的描述判断“要不要调用”,调用后会执行操作并返回结果,典型的 Tool 包括查询天气、创建工单、执行 SQL、发 HTTP 请求等等。Resource 是只读数据,用来给模型提供上下文,比如一份配置文件、一篇文档、一条数据库记录,它不是一个动作,而是一份内容。Prompt 是预制的提示词模板,帮你把高频使用的指令封装起来,类似于“写好框架、填入参数”的套路。
三者的关系可以理解为:Resource 是“给模型看的资料”,Tool 是“让模型能动手的按钮”,Prompt 是“教模型怎么干活的话术”。实际项目里 Tool 最常见,但如果你想把某些稳定数据传给模型,用 Resource 往往比塞进 prompt 更清爽。我自己在做 MCP server 时,会把产品配置做成 Resource,把需要读写的操作做成 Tool,职责分开,后续维护也轻松。
2.3 一次完整调用是怎么发生的
MCP 协议底层的消息交互并不神秘,本质上是一串 JSON-RPC 消息。第一次建立连接时,Client 和 Server 先做 initialize 握手,交换协议版本和双方支持的能力。握手成功之后,Client 会发 tools/list 请求,Server 返回工具清单,每个工具都包含名称、描述、输入参数 Schema。大模型在对话过程中根据用户请求和工具描述决定要不要调用某个 Tool,决定后由 Client 发送 tools/call 请求,Server 执行并返回结果。
举个例子,一次 tools/list 请求可能长这样:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }对应的响应里会包含你定义过的工具名、描述和参数约束。看到这个格式,你会更清楚一点:MCP 协议本身不定义任何具体业务能力,它只定义“如何发现能力、如何请求能力、如何返回结果”这套元规则。真正的能力由每个 Server 自己实现,协议负责把它们标准化地暴露出去。
2.4 传输方式选择:stdio 还是 HTTP
MCP 的传输方式直接决定了你这个服务怎么被访问。目前主流的两种是 stdio 和 Streamable HTTP。stdio 是客户端启动一个本地子进程,通过标准输入输出跟 MCP Server 通信,适合在桌面客户端、IDE、本地开发环境里用。它的优点是没有网络开销、启动迅速、安全边界清晰,缺点是不能跨机器调用。
Streamable HTTP 则把 Server 暴露成一个远程 HTTP 端点,客户端通过 URL 访问,支持鉴权、支持远程部署。手机端 MCP App、网页端 AI 工具、服务器上的共享服务,基本都是走这种模式。旧版协议里的 HTTP + SSE 正在被 Streamable HTTP 取代,WebSocket 传输也在逐步收敛。
选型时不需要太纠结。本地个人开发优先用 stdio,配置文件最简单;要给团队用或者接入手机 App,就部署成 Streamable HTTP。有一点要注意:本地 stdio 模式下,Server 的所有日志都不能写到标准输出,因为 stdout 是协议通道,一旦混入日志,客户端解析 JSON-RPC 就会失败。这个坑我在后面排查章节会详细展开。
3. MCP 服务与 Tool 的实际形态:一个 Tool 怎么跑到大模型手里的
3.1 MCP Server 就是一个“能力集装箱”
从形态上看,一个 MCP Server 就是一个独立的程序,它可以是 Python 脚本、Node.js 服务,甚至是 Java 进程。它监听在某个传输通道上,用协议向外暴露能力。官方社区已经有大量现成 Server,比如文件系统访问、Git 操作、数据库查询、Playwright 浏览器自动化、Chrome DevTools 调试等。
这些现成 Server 的使用方式通常非常简单,很多都是一条命令的事。比如想给 AI 接入浏览器操作能力,官方 Playwright MCP 就能直接拉起来,然后在客户端配置里把这个进程交给 MCP Client 管理。你不需要关心它内部怎么实现的,只要它跑起来、暴露了 Tool,AI 客户端就能发现并调用。
这也是 MCP 和传统插件体系最大的区别。传统插件是“客户端主动调用插件提供的接口”,MCP 则是“客户端通过标准协议发现并调用工具”,Server 端只是实现协议,不需要为每个客户端定制接口。对开发者来说,写一次 Server,就能同时服务 Cursor、Claude Desktop、Trae,以及任何兼容 MCP 的客户端。
3.2 Tool 的定义不是代码,而是“描述 + 参数 Schema”
很多人手写 MCP Server 时,以为只要把 Python 函数写出来就算定义了一个 Tool。实际上在协议层面,Tool 是一个元数据对象,它由三部分组成:name、description、inputSchema。模型能看到的主要是 description 和 inputSchema,它通过这些信息判断这个工具是干什么的、需要传什么参数。
比如说你在 Python SDK 里写这个函数:
@mcp.tool() def get_weather(city: str) -> str: """查询指定城市的当前天气""" ...在协议握手阶段,它会变成类似这样的元数据:
{ "name": "get_weather", "description": "查询指定城市的当前天气", "inputSchema": { "type": "object", "properties": { "city": {"type": "string"} }, "required": ["city"] } }理解这一点非常重要,因为它直接影响“模型到底会不会主动调用这个 Tool”。模型不是读过你的源码再决定调用,它只看到元数据。如果你的 description 写得含糊,参数名和含义不清晰,模型就该猜了,猜错的结果就是不调用、传错参数,或者连续报错。
3.3 描述怎么写,工具才容易被模型选中
我踩过的最大一个坑就是:工具写好了,但模型怎么都不主动调用。后来发现不是代码问题,是 description 太敷衍。工具描述要做到三个点:动词开头说明动作,写清楚输入参数的约束和单位,最好带上一个典型调用场景。
同样是“查询价格”,一个写法是“查价格”,另一个写法是“根据商品 ID 查询实时售价,单位为人民币元,返回含税和不含税两种价格”。后者显然更容易让模型在合适时机选中它。我开始意识到,给 Tool 写描述,本质上是在给模型写 API 文档,不是在给同事写注释。这个思维转变之后,工具被调用的成功率直线上升。
另外要注意 inputSchema 的类型约束。MCP SDK 通常会自动从函数签名生成 Schema,但如果你用了复杂对象或者可选参数,最好显式检查一下生成的 Schema,确认 required 字段和 type 是符合预期的。模型调用工具时如果参数校验失败,体验会非常差,而且这类报错不会太友好。
4. 实操:从零部署一个自建 MCP 服务
4.1 环境准备与依赖安装
下面我用 Python 来演示,因为 Python SDK 封装得比较完善,新手也能快速上手。建议 Python 3.10 以上版本,有虚拟环境习惯的可以先建一个独立环境,避免污染全局依赖。安装官方 SDK 只需要一条命令:
pip install mcp如果你在用 uv,也可以执行uv add mcp。安装完成后,可以用python -c "import mcp; print(mcp.__version__)"验证 SDK 是否正常。这里多说一句:MCP SDK 更新速度比较快,不同大版本之间 API 会有调整,网上很多教程用的是 0.x 版本代码,如果你的环境是新版本,遇到 API 不存在的问题时,优先看官方示例和本地 SDK 文档。
4.2 完整代码:一个带 Tool 和 Resource 的最小服务
这个示例服务我起名叫 price-demo,提供一个计算含税价的 Tool,再提供一个返回配置信息的 Resource。代码非常短,但足够把 MCP 的核心概念串起来。
import sys import logging from mcp.server.fastmcp import FastMCP logging.basicConfig(stream=sys.stderr, level=logging.INFO) mcp = FastMCP("price-demo") @mcp.tool() def calc_price(base: float, tax_rate: float = 0.06) -> float: """计算含税价格,接受不含税金额 base 和税率 tax_rate(默认 0.06),返回含税金额""" if base < 0: raise ValueError("base 不能为负数") return round(base * (1 + tax_rate), 2) @mcp.resource("config://app") def get_config() -> str: """返回示例配置信息,演示 Resource 的用法""" return "version=1.0\ncurrency=CNY" if __name__ == "__main__": mcp.run(transport="stdio")看到这个代码,你已经完成了最核心的部分:定义了一个 Tool、一个 Resource、把它们挂载到 MCP Server 上。注意 logging 的 stream 参数我故意指定成 stderr,原因前面说过,stdout 要留给协议通信。
4.3 用官方 Inspector 做本地测试
写完之后别急着接客户端,先用 MCP Inspector 验证一遍。官方审查工具可以一行命令启动:
npx @modelcontextprotocol/inspector它默认会启动一个本地调试页面。界面上可以配置要连接的服务,选择 command 模式,填上启动命令,比如:
python /path/to/your/server.py启动后,你可以手动发 tools/list、tools/call 等请求。我通常先看 tools/list 返回里有没有 calc_price 和 get_config,再用 tools/call 传一组参数验证返回值。这样能把问题锁定在 Server 端还是客户端配置端,比直接接 Claude Desktop 再排查快得多。
Inspector 是调试 MCP Server 的必备工具,强烈建议每一位想深入 MCP 的开发者养成熟练使用的习惯。后面遇到“客户端不显示工具”“工具调用报错”这类问题,第一反应应该是开 Inspector 复现,而不是反复改客户端配置。
4.4 把服务接到 Claude Desktop
本地验证通过之后,就可以接到 Claude Desktop 了。配置文件路径在 macOS 上是:
~/Library/Application Support/Claude/claude_desktop_config.jsonWindows 上是:
%APPDATA%\Claude\claude_desktop_config.json内容这样写:
{ "mcpServers": { "price-demo": { "command": "/usr/local/bin/python", "args": ["/Users/yourname/mcp-demo/server.py"] } } }有几个细节容易踩坑:command 必须用绝对路径,尤其是 Python 解释器路径,别随手写python,因为 Claude Desktop 启动子进程时的环境变量和你的终端不一定一样。args 里脚本路径也建议写绝对路径。改完配置后,重启 Claude Desktop,然后在对话里问它“计算一件不含税 100 元的商品,按 6% 税率算含税价是多少”,如果配置没问题,它就会自动调用你的 calc_price 派生工具。
4.5 接到 Cursor、Trae 和 IDEA 里的操作
桌面 IDE 的接入方式大同小异。以 Cursor 为例,打开 Settings 里的 MCP 面板,选择添加 MCP server,模式选 command,填上跟 Claude Desktop 配置里一样的命令和参数。添加成功且服务正常启动后,工具列表会出现在对话框下方。Trae 的 MCP 管理界面也类似,找到相关入口把同样的命令粘进去就行。
IDEA 用户可以通过通义灵码等插件接入 MCP。这类插件通常支持远程 MCP 连接的配置,新建连接时填写 HTTP endpoint 和鉴权信息即可。如果你是本地调试,也可以填本地命令,具体入口在插件的 AI 设置里搜索 MCP。这里提醒一下,IDE 类插件的 MCP 支持更新频率很高,界面入口经常微调,遇到找不到入口的情况,直接查插件官方文档比看旧教程靠谱。
4.6 从本地 stdio 到远程 HTTP
如果想把服务部署到服务器,或者让手机端 App 也能访问,就要把传输方式改成 HTTP。FastMCP 支持直接指定 transport 为 http,示例代码只要改一行:
if __name__ == "__main__": mcp.run(transport="http", host="0.0.0.0", port=8000)不同 SDK 版本的参数写法可能略有差异,但思路是一致的。远程模式下,客户端不再用 command 启动本地进程,而是配置一个 URL。比如 Claude Desktop 的配置文件可以写成:
{ "mcpServers": { "price-demo-remote": { "url": "http://your-server-ip:8000/mcp" } } }远程部署要特别关注鉴权。MCP 协议本身不强约束鉴权方式,但如果你把服务暴露到公网而不加任何防护,等于把一个可以执行代码的接口公开挂在网上,这是非常危险的做法。建议至少加一层 Token 鉴权,客户端配置里通过 header 传递密钥。不要图省事把 Token 拼在 URL 里,因为 URL 会出现在日志、历史记录和代理服务器的缓存里,泄露风险很大。
5. 常见问题与排查技巧实录
5.1 三层排查法
接 MCP 服务遇到问题,我习惯按三层来拆:客户端配置层、服务启动层、协议调用层。客户端配置层主要看 mcpServers 配置项是不是写对了,路径是不是绝对路径,URL 地址是否可达。服务启动层看 Server 进程有没有起来,启动时有没有报错,依赖是否齐全。协议调用层用 Inspector 发请求,看 tools/list 是否有响应,调一个工具看返回是否符合预期。
大多数问题都出在前两层。特别是本地 stdio 模式,最常见的原因是客户端启动 Server 时的工作目录或 Python 路径不对。你可以在终端手动执行配置里的命令,如果能正常运行,再考虑客户端环境差异。
5.2 问题速查表
| 现象 | 常见原因 | 处理方法 |
|---|---|---|
| 客户端找不到 MCP 服务 | mcpServers 配置格式错误或路径错误 | 检查配置 JSON,command 和 args 改为绝对路径 |
| 工具列表是空的 | Server 启动失败或协议版本不兼容 | 终端手动启动看报错,用 Inspector 连接测试 |
| 工具调用报参数校验错误 | inputSchema 和实际参数类型不一致 | 检查函数签名,确认 required 字段符合预期 |
| 模型始终不调用工具 | description 描述不清晰,或参数名容易误解 | 重写 description,加入单位、边界、典型场景 |
| 调用超时 | 任务耗时过长或网络不稳定 | 设置更长超时,或将长任务拆分计算 |
| Codex 找不到 MCP 服务 | 配置未生效或 URL 缺少 /mcp 后缀 | 检查启动日志,确认 endpoint 地址正确,重启客户端 |
| 服务端输出日志导致协议中断 | 日志写到了 stdout | 日志全部写 stderr 或文件 |
| 远程服务返回 401 | 鉴权 Token 缺失或过期 | 检查客户端 header 配置,重新生成 Token |
5.3 Server 端日志如何做自定义管理
很多人在本地 stdio 模式下调试时,发现 AI 客户端连不上服务,或者工具调用永远没响应,打开终端一看,代码里到处是 print。这在我这踩过不止一次。stdout 是 MCP 协议通道,你每打印一行普通文本,客户端解析 JSON-RPC 时就多一行非法输入,轻则工具不显示,重则整个连接失败。
正确做法是让所有业务日志走 stderr,或者干脆落到文件中。Python 里用 logging 模块,指定stream=sys.stderr,或者增加一个 FileHandler 写日志文件。日志内容也建议带时间戳和函数名,方便排查线上问题。如果你正在开发一个长期维护的 MCP server,日志管理这件事值得一开始就规划好,不然后期排障会非常痛苦。
5.4 回写打通与权限控制
“MCP 回写打通”是很多业务系统的真实需求。所谓回写,就是让 AI 不仅能读数据,还能把操作结果写回系统里,比如更新工单状态、创建记录、修改配置。这在协议层面没有任何特殊之处,本质上就是定义一个具有写权限的 Tool,Server 端执行更新逻辑。
但回写能力意味着风险成倍增加。模型有可能在错误时机调用写工具,或者因为参数理解偏差产生错误操作。我的建议是:写工具一定要加确认机制或权限校验,参数尽量收敛,避免暴露“执行任意 SQL”“执行任意命令”这类危险工具。协议不会替你负责安全问题,MCP 服务至少要按内部不可信接口来对待。
6. 从 IDE 到浏览器:MCP 生态还能怎么玩
6.1 浏览器自动化:Playwright MCP、Chrome DevTools MCP、browser-use 的区别
浏览器类 MCP 是近期热度最高的一类应用,好多人问 Playwright MCP 和 browser-use MCP 有什么区别,这里我给出一个直观判断:两者定位不同。Playwright MCP 是一个 MCP Server,它把浏览器操作能力暴露成 Tool,让 AI 客户端决定什么时候打开网页、点击哪里、输入什么内容,你是在和 Claude Desktop、Cursor 这类宿主配合使用。Chrome DevTools MCP 针对的是网页调试场景,基于 Chrome DevTools Protocol,偏性能分析、网络请求、DOM 调试。browser-use 则是一个独立的浏览器代理框架,它自己承担 Agent 决策,自己去操作浏览器,不完全依赖 MCP 客户端调度。
选型时你要先问自己场景是什么。如果只是想让聊天助手能帮你看网页内容,Playwright MCP 就够了。如果你想做自动化测试、性能调优,Chrome DevTools MCP 更对口。如果你要的是一个能自己完成多步任务的浏览器 Agent,那研究 browser-use 本身更直接。另外像 Dify 这类平台也支持浏览器 MCP 的接入,本质是把它当成一个 Tool Provider 配置进去,思路与桌面客户端一致。
6.2 设计协作类 MCP:以蓝湖为例
开发圈最近聊得多的还有蓝湖 MCP 服务。这类服务把设计稿标注、切图信息、组件属性暴露给 AI,让 AI 能直接读取设计资源来生成代码或回答问题。部署方式和通用远程 MCP 没区别:拿到服务地址和鉴权 Token,在支持 URL 模式 MCP 的客户端里加一条配置,就能在对话中调用相关工具。
这类服务的价值在于打通设计到开发的链路。以前开发要自己打开设计稿,切图、量尺寸、看标注,现在这些信息可以变成 Resource 给模型当上下文。但要注意,设计数据往往涉及未上线方案,接入的时候要做好访问控制,别把内部设计资源无防护地暴露给公网 MCP 端点。
6.3 企业系统集成:当 MCP 遇上中后台项目
像 RuoYi-Vue-Pro 这类后台管理系统集成 MCP,是另一个有意思的方向。把企业内部已有的 API 包成 Tool,再让 AI 助手通过 MCP 调用,就能实现“用自然语言操作后台系统”的效果。本质上不是有什么特殊的 MCP 黑魔法,而是把系统服务暴露成 MCP Server,然后在某个 AI 客户端里配置好连接。
真正麻烦的不是协议,而是权限映射。企业内部 API 各有各的角色权限体系,MCP Server 在调用这些 API 时必须继承用户身份,不能让它拥有一个“超级管理员”的通用凭证。我见过不少团队把服务端密钥写死在配置里,结果任何能访问 MCP 端点的用户都等于获得了后台操作权限。解决思路是让 MCP Server 感知当前对话的用户身份,做一层权限转换,或者至少在 Tool 内做参数级白名单校验。
6.4 安全上必须养成的四个习惯
关于 MCP 服务安全,我想额外强调几点。第一,不要把私密 Token 暴露在客户端配置里分享给他人。哪怕只是本地工具,也要当成敏感配置管理。第二,远程 MCP 服务需要用 HTTPS 和额外鉴权,不要裸奔在公网。第三,工具权限要最小化,能只读不开放写,能限参数不放开任意输入。第四,定期更新 MCP SDK 和官方 Server,协议还在快速演进,老版本可能存在安全隐患。
我自己跑过一段时间 MCP 服务之后,最大的感受是:技术门槛并不高,真正拉开差距的是对“协议边界”的理解。MCP 协议只负责连接和消息格式,不负责业务、不负责安全、不负责模型决策,这些都得靠开发者自己设计。把 description 当 API 文档写,把 Server 当开放接口保护,把 Tool 参数当用户输入校验,做到这三点,MCP 就能稳定地成为你 AI 应用里的一个坚实组件。最后再补一句实用建议:刚开始不要追求把很多工具塞进一个 Server,先从一个最小服务跑通全链路,再去扩展能力,这个节奏最不容易劝退。