MCP 全称Model Context Protocol(模型上下文协议),是由 Anthropic 发起、现由 Linux 基金会托管的开放行业标准,专门解决AI Agent 与外部工具、数据源之间的标准化接入问题。它定义了一套统一的通信规则、能力发现机制和交互格式,让任意符合规范的 AI 应用都能无缝调用任意符合规范的外部能力,被称为「AI 时代的通用工具接口标准」。
Host-Client-Server 的三级架构
| 角色 | 定位 | 核心职责 | 典型实例 |
|---|---|---|---|
| Host(宿主) | AI 应用本体 | 管理大模型、执行业务逻辑、统筹所有工具会话 | Claude Desktop、VS Code、Cursor、自研 Agent 平台 |
| Client(客户端) | 连接器 / 协议适配器 | 每个服务端对应一个独立 Client;负责协议握手、消息路由、会话管理、安全隔离 | Host 内置的 MCP 客户端模块 |
| Server(服务端) | 能力提供方 | 把底层的 API、数据库、文件系统等包装成标准 MCP 能力对外暴露 | 文件系统服务、数据库服务、第三方业务工具 |
一个Host可以同时管理多个 Client,每个 Client 对应一个独立的 Server 连接,不同的工具之间天然隔离,互不影响
两层协议分层
MCP 把协议拆分为独立的两层,底层通信变化不影响上层业务逻辑
数据层(内层):基于 JSON-RPC 2.0 标准,定义所有业务消息的格式,语义和交互流程,包括生命周期管理,核心原语,通知机制。这一层和传输方式无关,是协议的核心
传输层(外层):定义客户端和服务端之间的通信通道,主流支持两种传输方式,1.stdio(标准输入输出),用于本地进程间通信,延迟极低,适合本地工具服务,2.HTTP:用于远程服务通信,支持跨网络,跨部署,适合第三方云服务
核心原语
原语(Primitives)是 MCP 协议的核心,规定了服务端可以对外暴露的能力类型,以及客户端可以提供的回调能力,所有交互都基于这些标准原语展开。
服务端原语:
服务端通过这三类原语对外暴露能力,也是 Agent 开发最常用的部分:
1.Tools(工具):可执行的函数,是最核心的原语。Agent 可以调用工具执行操作,比如查询数据库、调用业务 API、操作文件、执行命令。对应标准方法tools/list(发现工具)、tools/call(调用工具)。
2.Resource(资源):给模型提供上下文的数据,比如文件内容、表结构、文档片段。模型只能读取,不能修改,适合给模型注入静态背景信息。
3.Prompts(提示模板):可复用的提示词模板,比如少样本示例、工具使用引导、系统提示,用来统一模型和工具的交互方式。
客户端原语:
服务端也可以反向向客户端请求能力,这是 MCP 和普通 API 最大的区别之一:
1.Sampling(采样):服务端可以请求客户端的大模型生成文本,比如工具执行到一半需要模型做决策,不用自己集成大模型 SDK。
2.ELicitation(引导):服务端可以请求用户补充信息或确认操作,比如高危操作前向用户二次确认。
3.Logging(日志):服务端可以向客户端发送日志,用于统一调试和监控
标准扩展位 _meta字段:
协议专门在消息结构中预留了_meta字段,用于传输带外元数据,比如用户身份、会话 ID、链路追踪信息等。这类数据不属于业务参数,不应该塞进工具的 arguments 里,_meta就是标准的存放位置 —— 这也是你之前笔记里「身份走 _meta」的规范依据。
完整的工具链与工具调用生命周期
一次标准的 MCP 工具调用,从连接建立到结果返回,遵循严格的标准化流程:
1.初始化与能力协商:
Host 创建 Client,向目标 Server 发起 initialize 请求,携带客户端支持的能力、协议版本
Server 返回自身支持的能力,协议版本,可用的原语范围
Client 发送 notifications/initialized 通知,确认握手完成,会话进入就绪状态
2.工具发现
Client 调用 tools/list 方法,向 Server 查询当前可用的所有工具
Server 返回工具列表,包含每个工具的名称、描述、参数 Schema(JSON Schema 格式)
Client 把工具列表同步给 Host,Host 将其注入大模型的上下文,供模型决策调用
3.工具调用与结果返回
模型决定调用某个工具,输出工具调用意图
Client 封装成标准的 tools/call 请求,发送给 Server
Server 执行工具,返回结构化结果(成功/错误都遵循统一格式)
Client 把结果回传给 Host,注入对话历史,模型基于结果继续生成回答
MCP vs Function Calling
| 维度 | Function Calling | MCP |
|---|---|---|
| 层级 | 模型能力层 | 通信协议层 |
| 核心作用 | 让模型输出结构化的工具调用意图 | 让工具以标准方式接入、被发现、被调用 |
| 工具发现 | 静态,工具 Schema 写死在 prompt 或代码里 | 动态,运行时通过tools/list实时获取 |
| 跨厂商兼容 | 不兼容,每个厂商格式有差异 | 统一标准,任意模型、任意工具都能对接 |
| 部署形态 | 和应用同进程 | 工具服务独立部署,天然隔离 |
Function Calling 是「模型怎么说要调工具」,MCP 是「工具怎么接进来、怎么执行」。生产环境中通常两者配合使用:模型用 Function Calling 生成调用意图,通过 MCP 协议真正执行调用。
MCP vs 自定义 REST API
| 维度 | 自定义 REST API | MCP |
|---|---|---|
| 面向对象 | 面向人类开发者、服务间调用 | 面向 AI 模型、Agent 自动调用 |
| 发现方式 | 静态文档(Swagger/OpenAPI),设计时确定 | 运行时动态发现,工具可实时增减 |
| 集成成本 | M×N 复杂度:每接一个新工具,每个 Agent 都要写适配 | M+N 复杂度:工具和 Agent 各接一次标准协议,即可互通 |
| 状态管理 | 默认无状态 | 有状态会话,上下文跨调用保留 |
| 安全边界 | 接口级鉴权,逻辑分散在每个接口 | 协议层统一鉴权、隔离、审计 |
REST 是通用的服务间通信协议,MCP 是专门为 AI Agent 优化的工具接入协议
用官方 Python SDK 写的最小化服务端+客户端,完整走通「初始化→列工具→调工具」全流程
1.服务端
import asyncio from mcp.server.fastmcp import FastMCP #创建 MCP 服务实例 mcp=FastMCP("DemoCalculator") #注册一个工具 - 自动生成 inputSchema,自动暴露给 tools/list @mcp.tool() def add(a: int, b: int) -> int: """Add two integers together""" return a + b if __name__=="__main__": #stdio 传输模式(本地进程通信) mcp.run(transport="stdio")2.客户端
通过 stdio 连接服务端,完成完整调用流程
import asyncio from mcp.client.stdio import stdio_client from mcp.client.session import ClientSession async def main(): #建立 stdio 传输通道,启动子进程运行服务端 async with stdio_client(["python", "server.py"]) as (read, write): #创建会话,执行 initialize 握手 async with ClientSession(read, write) as session: await session.initialize() # 调用 tools/list 获取工具列表 tools=await session.list_tools() print("可用工具: ") for tool in tools.tools: print(f" - {tool.name}: {tool.description}") #调用 tools/call 执行 add 工具 result=await session.call_tool("add", {"a":5, "b":3}) print(f"\n调用结果:{result.content[0].text}") if __name__=="__main__": asyncio.run(main())核心方法的源码级实现
tool/list 的底层实现
tools/list 请求的处理逻辑本质就是遍历注册的工具,返回标准化结构
async def handle_list_tools(self, request:ListToolRequest) -> ListToolsResult: tools=[] for name, tool in self._tool_registry.items(): tools.append({ "name": name, "description": tool.description, "inputSchema": tool.input_schema # JSON Schema 格式 }) return ListToolsResult(tools=tools)客户端收到后,会把这个列表转换成模型能理解的 Function Calling 格式,注入 prompt(这一步又是怎么实现的)
tools/call 的底层实现
async def handle_call_tool(self, request:CallToolRequest) -> CallToolresult: tool_name=request.params.name arguments=request.params.arguments #从注册表查找工具 if tool_name not in self._tool_registry: return CallToolResult( content=[{"type": "text", "text": f"Unknown tool: {tool_name}"}], isError=True ) try: #执行工具函数 result=await self._tool_registry[tool_name].handler(**arguments) #包装成标准返回格式 return CallToolResult( content=[{"type":"text", "text":str(result)}] ) except Exception as e: #结构化错误返回 return CallToolResult( content=[{"type": "text", "text": str(e)}], isError=True )