
如果你最近在关注 AI 编程助手、Agent 或者各类大模型应用项目大概率已经频繁看到 MCP 这个缩写。网上把它称作“AI 应用界的 USB-C”也被不少人预测为 Agent 时代的基础设施协议。但坦白说很多文章讲完概念就停了真正动手写过 Server、跑通过一次工具调用的并不多。这篇文章的目标很直接先讲清楚 MCP 到底解决什么问题再拆解它的架构和通信流程最后用 Python 从零写一个可运行的 MCP Server 和 Client。你阅读完可以直接照着在本地跑通后续再推广到自己的项目里。如果你正在做 Agent 类应用、准备把大模型接入内部系统或者只是想让 Claude、Cursor 这类工具读取和操作你的数据这篇文章会帮你省掉不少弯路。我需要先给你一个明确判断MCP 真正值得关注的地方不是它又发明了一套新协议而是它把“大模型怎么连接外部工具和数据”这件事从每家各自实现的私有接口变成了一个统一、开放、可插拔的公共标准。这一层变化带来的工程价值远大于协议本身的技术难度。1. 这篇文章真正要解决的问题先放下概念回到一个实际场景。假设你正在做一个 Agent希望让大模型能够查询公司内部订单数据库。传统做法是什么你需要先写一个 HTTP 接口把数据库查询封装成 API然后给大模型写出这个工具的 JSON Schema帮模型学会什么时候调用、传什么参数接着还要处理模型返回的 function call 结果再拼回复。这套流程你做过一次第二个工具也差不多但当你接到第三个、第四个工具或者要把这套能力给另一个 Agent 平台使用时你会发现几乎所有工作都在重复。更麻烦的是每家模型厂商的 function calling 格式不一样每个数据源的接入方式也不一样。你每接入一个工具就要写一套胶水代码每换一个模型平台又要调整一遍。这个“N 个模型 × M 个工具”的集成矩阵才是 Agent 落地过程中最消耗人力的部分。MCP 的思路是一次反转它不让模型厂商去适配每个工具也不让工具开发者去适配每个模型而是定义了一个“中间层协议”。工具方实现一个 MCP Server模型应用方作为 MCP Host 去连接大家按同一套协议对话。工具接一次就能被任何支持 MCP 的客户端复用客户端接一次就能访问所有符合标准的 Server。回到读者收益这篇文章适合以下三类人正在做 Agent 或 AI 应用苦于工具集成代码不断重复的开发者想让 Claude Desktop、Cursor、VS Code 等客户端直接操作本地文件、数据库或自定义服务的用户想理解 MCP 协议背后机制准备在团队内推动工具接入标准化的人。读完你至少能动手完成一次 MCP 服务通信写出 Server写出 Client以及用官方 Inspector 完成调试。2. MCP 基础概念与核心原理2.1 MCP 是什么为什么是现在出现MCP 全称 Model Context Protocol即模型上下文协议最早由 Anthropic 在 2024 年底提出并开源。它的目的用一句话概括让 AI 模型安全、标准化地访问外部数据和工具。为什么“现在”出现因为大模型本身是一个“只有脑子、没有手脚”的系统。模型能生成文字但它不知道你磁盘上的文件内容也无法直接调用你公司的业务接口。为了让模型“动手”业界此前走的是 function calling 路线由应用层把工具定义传给模型模型在回答中生成调用意图应用层再真正执行。function calling 解决了“模型能不能调用”的问题但没有解决“工具怎么接入、怎么复用”的问题。每个平台封装方式不同每个工具都要单独适配。随着 Agent 应用变多、工具变多、平台变多社区自然需要一个公共协议。MCP 就是这个阶段的产物它标准化了模型应用与工具/数据源之间的通信方式。2.2 MCP 的架构Host、Client、ServerMCP 采用客户端-服务端架构整个链路里有三个角色角色作用类比Host模型应用主程序比如 Claude Desktop、Cursor、IDE 插件你电脑上的应用程序ClientHost 内部与 Server 建立连接、维护会话的组件应用程序里的 USB 驱动Server暴露工具、资源、提示词的服务进程外接设备比如 U 盘、打印机实际项目里Host 可以同时连接多个 Server。每个 Server 提供一组相关能力比如一个“数据库工具 Server”、一个“文件系统 Server”。端侧应用不会把工具逻辑塞进模型上下文里而是通过协议向 Server 发起请求。2.3 三个核心原语Tools、Resources、PromptsMCP Server 对外提供的不是“接口”这一个概念而是三种能力Tools模型可以主动调用的函数对应传统 API 中的“动作”。比如读取文件、计算表达式、发送请求。Tools 有名称、描述和输入 Schema类似 function calling 里的工具定义。Resources提供给模型作为上下文参考的数据对应文件、数据库记录、文档片段。Resources 有 URI可以被客户端读取并在对话中引用。Prompts预定义的提示词模板帮助用户或模型快速触发一套标准操作流程比如“生成周报”“写代码评审意见”。初学者最容易混淆 Tools 和 Resources。简单区分Tools 是动词模型调用来“做事情”Resources 是名词模型读取来“获得信息”。一个 Server 可以只提供 Tools也可以同时提供两类。2.4 通信方式JSON-RPC 与传输层MCP 的消息格式基于 JSON-RPC 2.0。这意味着无论是请求还是响应都是结构化 JSON。消息类型包括 request、response、notification 三类Client 与 Server 之间通过消息交换完成能力协商和功能调用。传输层目前主流有两种stdioServer 作为子进程启动通过标准输入和标准输出进行 JSON-RPC 通信。开发调试最方便Claude Desktop 本地配置默认使用这种方式。Streamable HTTP / SSEServer 作为 HTTP 服务运行适合远程部署和跨机器调用。协议生命周期大致可以分成四个阶段连接与初始化、能力协商、功能调用list 工具、call 工具 / 读取资源、关闭。初始化阶段会有握手双方声明自身支持的能力比如 Server 声明支持 toolsClient 声明支持某类提示。握手完成之前双方不会发送业务调用。3. 环境准备与前置条件本文的代码示例基于 Python 官方 MCP SDK你需要准备以下环境操作系统Windows / macOS / Linux 均可Windows 下注意命令路径差异Python 版本建议 3.10 及以上Python 包管理工具pip 或 uv可选Node.js 环境用于启动 MCP Inspector 调试工具推荐VS Code 或任意编辑器。不要纠结“必须用哪个版本”。MCP SDK 迭代较快具体版本以你安装时的最新稳定版为准本文演示的是通用实现思路。你可以先创建目录并准备虚拟环境mkdir mcp-demo cd mcp-demo python -m venv .venv # Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate4. 核心流程拆解一次 MCP 通信是怎么发生的在写代码之前先理解通信时序。下面是一次典型的 MCP 工具调用过程。第一步Client 启动 Server 进程建立传输通道。如果使用 stdioClient 通过 subprocess 创建子进程并接管其标准输入输出。第二步Client 发送 initialize 请求Server 返回协议版本、Server 能力列表和实现信息。这一步相当于握手。第三步双方发送 initialized 通知并可能继续交换能力相关信息。之后 Client 才会认为连接可用。第四步Client 发送 tools/list 请求Server 返回可用工具列表包括名称、描述、输入 Schema。第五步模型根据对话内容选择工具并决定参数。这里注意真正产生工具调用意图的是模型应用层的推理结果MCP 只负责把这些意图传达到 Server。第六步Client 发送 tools/call 请求参数包含工具名和调用参数。Server 执行业务逻辑并返回结果。第七步Client 把工具结果回传给模型模型结合结果生成最终回答。这里真正容易踩坑的地方是很多人以为 MCP 是“模型直接调用服务器接口”实际上模型并不直接参与协议通信。模型、Client、Server 三者是分层的协议只存在于 Client 与 Server 之间。理解这一点后续排查问题会快很多。5. 完整示例代码实现用 Python 实现 MCP 服务通信下面我们做一个最小但完整的示例。Server 提供两个能力读取文本文件内容、计算两数之和。Client 连接 Server列出工具并实际调用。5.1 安装依赖在虚拟环境中安装官方 SDKpip install mcp如果安装较慢可以切换国内镜像pip install mcp -i https://pypi.tuna.tsinghua.edu.cn/simple安装完成后可以用pip show mcp查看版本。5.2 编写 MCP Server这里使用 FastMCP 这一高层封装它在官方 SDK 中提供可以大幅简化 Server 编写。完整代码如下# 文件路径mcp-demo/server.py from fastmcp import FastMCP # 创建 MCP 服务名称会显示在客户端列表中 mcp FastMCP(demo-server) # 注册一个工具读取文本文件 mcp.tool() def read_file(path: str) - str: 读取指定路径的文本文件内容并返回。 参数 path: 要读取的文件的绝对路径或相对路径。 try: with open(path, r, encodingutf-8) as f: return f.read() except Exception as e: return f读取文件失败: {str(e)} # 注册一个工具两数相加 mcp.tool() def add(a: float, b: float) - float: 计算两个数字的和。 参数 a: 第一个加数。 b: 第二个加数。 return a b if __name__ __main__: # 默认走 stdio 传输 mcp.run()这一段代码中需要说明几点mcp.tool()装饰器把普通 Python 函数注册成 MCP 工具函数的 docstring 会被转换为工具的 description供模型理解调用意图因此描述要写清楚函数的类型注解会转换为输入 Schema这决定了模型能传入什么类型参数mcp.run()默认启动 stdio 传输Server 通过标准输入输出与 Client 通信。如果你希望注册资源或提示词FastMCP 也提供了mcp.resource()和mcp.prompt()装饰器后续可以自行扩展。5.3 编写 MCP Client下面写一个客户端负责启动 Server 子进程、初始化会话、列出工具并调用add# 文件路径mcp-demo/client.py import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): # 声明如何启动 Server 子进程 server_params StdioServerParameters( commandpython, args[server.py], ) # 建立 stdio 通道并创建会话 async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: # 初始化握手 await session.initialize() # 获取 Server 提供的工具列表 tools await session.list_tools() print(Server 提供的工具) for tool in tools.tools: print(f - {tool.name}: {tool.description}) # 调用 add 工具 result await session.call_tool(add, {a: 3, b: 5}) print(调用 add(3, 5) 的结果) print(result.content) if __name__ __main__: asyncio.run(main())这段代码的逻辑是stdio_client负责启动server.py子进程并把其标准输入输出包装为异步读写流ClientSession在流之上维护 JSON-RPC 会话session.initialize()完成握手list_tools()获取能力call_tool()执行调用。有一点需要注意如果你在 Windows 上运行command可以改为python若环境里有多个 Python 版本建议写完整路径比如C:\\Python311\\python.exe。5.4 使用 MCP Inspector 快速调试不写 Client 代码也可以直接用一个官方调试工具验证 Server。MCP Inspector 是一个可视化调试器通过 npx 启动npx modelcontextprotocol/inspector python server.py启动后它会在本地开启一个 Web 调试页面你可以在页面上连接 Server、查看工具列表、手动传参调用并看到返回结果。这个工具非常适合在开发早期验证工具逻辑是否正确建议收藏。5.5 配置到 Claude Desktop如果你用的是 Claude Desktop可以在它的配置文件里注册这个 Server这样就能直接在对话中使用。配置文件通常是claude_desktop_config.jsonMac 位于~/Library/Application Support/Claude/Windows 位于%APPDATA%\Claude\。配置写法如下{ mcpServers: { demo-server: { command: python, args: [/绝对路径/mcp-demo/server.py] } } }配置后重启 Claude Desktop在对话中询问“读取某个文件试试”模型就会通过 MCP 调用我们的 Server 工具。注意这里的触发条件是工具描述必须让模型能理解何时使用。如果描述含糊模型可能不会主动调用。6. 运行结果与效果验证先启动 Server 单独测试看它是否能作为 stdio 进程正常运行python server.py此时程序不会打印任何内容而是等待标准输入上的协议消息。这是正常现象不是卡死。如果终端出现多余打印反而说明代码不合适因为额外输出会污染 JSON-RPC 数据流。再运行客户端python client.py预期输出类似Server 提供的工具 - read_file: 读取指定路径的文本文件内容并返回。参数... - add: 计算两个数字的和。参数... 调用 add(3, 5) 的结果 [TextContent(typetext, text8.0, ...)]判断成功的核心标准是客户端能通过initialize()握手没有报协议错误list_tools()返回了 Server 中注册的两个工具call_tool()返回结果为 8.0。如果失败先不要急着改代码按照下一节的排查顺序走一遍大多数问题都出在传输层或者环境路径上。7. 常见问题与排查思路这几个问题在第一次跑 MCP 时出现频率最高整理成表格方便对照。问题现象可能原因排查方式解决方案启动后无任何输出像卡住stdio 模式本来就不打印也可能通信真的未建立用 MCP Inspector 连接确认进程是否启动正常现象继续用客户端测试若 Inspector 也无响应检查 command 路径客户端报 initialize 超时Server 启动失败或启动后立刻退出手动执行python server.py看是否报错检查 Python 环境是否一致确认依赖已装进当前虚拟环境而非全局环境通信数据被破坏解析 JSON 失败Server 代码里往 stdout 打印了调试日志查看代码中是否有print()日志必须写到 stderr用import sys; print(..., filesys.stderr)不要污染 stdoutWindows 下无法启动子进程command写成了python3或路径含空格未处理在终端执行where python查看实际命令使用python或完整绝对路径路径带空格时注意参数数组写法工具列表为空函数没有加装饰器或注册逻辑未执行检查mcp.tool()和函数缩进确认装饰器应用在函数定义上并处于模块顶层作用域模型不调用工具工具描述不够清晰缺少触发条件说明查看客户端里工具描述是否完整展示重写 docstring讲清工具用途和典型调用场景这里想特别强调 stdout 污染问题。MCP stdio 传输是“一行一个 JSON-RPC 消息”的管道任何额外的输出都会让协议解析失败。调试日志、print输出、甚至第三方库的普通日志都有可能出现在 stdout 里导致极其诡异的通信超时。生产代码中务必显式把日志配置到 stderr或者单独输出到日志文件。8. 最佳实践与工程建议从“能跑通”到“可维护、可上线”中间还有不少距离。结合 MCP 协议特点给几条工程建议。第一工具描述和 Schema 要写得“像给小白的操作手册”。模型不是靠读源码理解工具的它只能看到描述和参数 Schema。描述里应写清楚什么场景下用、参数含义、返回值结构最好带上一个典型调用示例。描述写得越具体模型正确调用率越高。第二工具粒度要适中。不要把一个超大业务逻辑塞进一个工具也不要把一次原子操作拆成五个互相依赖的工具。经验做法是一个工具只做一件事输入输出保持简单尽量用基础数据类型。如果确实要返回复杂结构考虑转换为 JSON 字符串并在描述中说明字段结构。第三异常必须兜底并转化为可读信息。工具内部要用 try-except 捕获异常把错误转成一句人能看懂的话返回。模型拿到错误信息后通常会调整参数重试或向用户解释而不是直接把崩溃栈抛给用户。第四日志隔离。凡是 stdio 模式的 Server生产日志一律走 stderr 或文件。可以把logging.StreamHandler指定的 stream 设置为sys.stderr。这个习惯能帮你避免大量诡异的协议错误。第五权限与安全边界。MCP Server 暴露给模型的能力本质上等于暴露给用户的对话。如果你注册了一个“删除文件”或“执行 SQL”的工具用户只要在对话框中提出请求模型就可能真的执行。因此在设计工具时要按最小权限原则开放能力对高危操作增加二次确认参数或只读模式。涉及生产环境的操作务必先在测试环境验证。第六传输层选择要看使用场景。本地个人工具、IDE 集成用 stdio 最简单直接团队共享服务、远程部署用 Streamable HTTP 更合适。HTTP 模式下要注意鉴权和端口管理不要让任何人都能访问工具服务。第七版本锁定。MCP SDK 还在快速迭代不同版本的 API 可能有变化。项目里应使用 requirements.txt 或 pyproject.toml 锁定 SDK 版本避免同事拉代码后因为版本不一致导致莫名报错。9. 总结与后续学习方向本文的核心内容可以浓缩成三句话MCP 是连接大模型与外部工具/数据的统一协议它用 JSON-RPC 定义消息、用 stdio 或 HTTP 完成传输通过 Tools、Resources、Prompts 三个原语表达能力一次完整通信要经过初始化握手、能力协商、工具列表获取和工具调用几个阶段你可以用 Python 官方 SDK 在半小时内写出一个可用的 Server 和 Client。建议你下一步按这个顺序继续深入先用 MCP Inspector 把本文的 Server 调试一遍把工具描述改差再改好感受描述对模型调度的影响试着给 Server 增加一个 Resource让客户端能读取某个固定文档把 stdio 换成 Streamable HTTP部署到本地网络用远程 Client 测试研究官方协议文档中的采样、根目录、图片等高级能力这些在生产项目中迟早会用上。最后提醒一句MCP 的确火爆但它不是银弹。如果你的项目只有一个工具、一个模型平台写一个 function calling 适配可能更简单。当工具数量、平台数量开始膨胀时MCP 的标准化价值才会真正体现出来。判断要不要引入的标准不是“它火不火”而是“你的集成成本是否已经高到需要一份公共协议”。把这句判断带到你的项目里比记住任何 API 都有用。