前言
2023年6月,ChatGPT 更新了Function call 功能(现在也叫做Tool Call),本质上是通过指令微调让模型可以理解函数并进行调用,从而能够让模型根据自然语言的描述来调用执行的函数或工具来完成任务;在初期应用的情况下,工具的实现主要在调用客户端中进行出现,同时也会利用远程的API 接口,从而也促使了API接口平台之类的SAAS服务火热起来;
而2024 年11月Anthropic 发布的MCP协议,MCP(模型上下文协议)是用于将 AI 应用程序连接到外部系统的开源标准。 使用 MCP,Claude 或 ChatGPT 等 AI 应用程序可以连接到数据源(例如本地文件、数据库)、工具(例如搜索引擎、计算器)和工作流(例如专用提示),从而使它们能够访问关键信息并执行任务。
理解MCP协议的话,其更像是一种远程的工具即服务(Tool As A Service)的实现,通过框架来是将工具作为服务接口的形式提供,从而让不同客户端都可以进行调用;所以下文会深入分析MCP内部通信和工具调用的实现机制。
工具调用
虽然真正意义上的“Function Calling”出现在 2023 年 6 月的 OpenAI API 更新,但其实在更早期的AutoGPT项目中,AutoGPT 提出了一种让大模型执行的动作的方法,通过让大语言模型理解函数定义并自动进行函数调用,其实就是Function call 的基本雏形了;
然后回到openai 的函数调用功能,其核心思想是模型可以根据上下文自动生成结构化 JSON,用来调用外部函数;其主要流程如下:
- Define functions:定义可供LLM调用的实际执行函数,eg:get_current_weather
def get_current_weather(location, unit="fahrenheit"): """Get the current weather in a given location"""
- 将上面的函数转换为Json schema,通过自然语言来对函数的参数和作用进行描述,让LLM能够理解该函数,并选择该函数进行调用
{ "type": "function", "function": { "name": "get_current_weather", "description": "Get the current weather in a given location", "parameters": { "type": "object", "properties": { "location": { "type": "string", "description": "The city and state, e.g. San Francisco, CA", }, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}, }, "required": ["location"], }, } }
- 在LLM API接口调用的时候添加该工具调用,当LLM自身在理解任务的时候就会返回指定的函数调用的名称和参数,如:
- User:东京的天气怎么样?
- Assistant:get_current_weather(location=”东京”)
- User:result:东京,天气良好,温度10度
- Assistant:ok,根据返回的结果知道东京当前天气良好,温度10度
通信流程
根据上文中关于函数调用流程介绍,可以了解到函数调用的主要设计思想是:
- 开发者定义函数和函数 schema(类似 JSON Schema);
- 模型根据用户请求自动填充参数;
- 程序执行后将结果再传回模型。
对应的MCP框架中的流程如下:
- 开发者在MCP Server中定义工具函数和函数Schema
- 客户端连接MCP服务端获取工具函数列表和函数Schema
- 客户端将用户消息和函数列表发送给模型,模型根据用户请求自动填充参数;
- 客户端将通过MCP将函数调用的参数发送给MCP服务端,服务端返回执行的结果
- 获取到服务端执行结果之后再将结果传回给模型
该流程中,MCP的基本通信流程分为初始阶段,协议通信阶段和结束通信三个阶段,具体使用的远程通信协议为SSE。
SSE(Server-Sent Events)是一种基于 HTTP 协议的服务器推送技术,允许服务端主动向客户端发送数据流,其所具备的单向数据传输的能力也随着AI的应用而被广泛运用于客户端于大模型的通信过程中,有效解决了短轮训等方式存在的多次无效请求的问题,同时兼容了大模型流式输出Token 的方式,在MCP 中具体SSE通信数据流如下:
消息传输
可以通过抓包的方式简单分析下MCP协议具体通信消息的格式,在MCP 中,通信的消息格式采用都是JSON-RPC 形式,消息初始化过程中,客户端将分别发送以下消息,分别用于告知MCP需要进行初始化和通知服务端初始化已完成。
初始化阶段,客户端和服务器主要完成以下工作:
- 建立协议版本兼容性
- 交换和协商功能
- 共享实现细节
POST /messages/?session_id=1925698595864c2f95c0f80676884aa8 HTTP/1.1 Host: 127.0.0.1:8080 Accept: */* Accept-Encoding: gzip, deflate Connection: keep-alive User-Agent: python-httpx/0.28.1 Content-Length: 180 Content-Type: application/json { "method": "initialize", "params": { "protocolVersion": "2024-11-05", "capabilities": { "roots": { "listChanged": true } }, "clientInfo": { "name": "mcp", "version": "0.1.0" } }, "jsonrpc": "2.0", "id": 0 }
而后服务端返回初始化的内容,客户端同时需要再通知服务端初始完成;
POST /messages/?session_id=1925698595864c2f95c0f80676884aa8 HTTP/1.1 {"method":"notifications/initialized","jsonrpc":"2.0"}
初始化完成之后,进入协议操作阶段,客户端需要获取服务端支持的相关工具函数,并在必要的时候进行调用,如获取工具列表
POST /messages/?session_id=1925698595864c2f95c0f80676884aa8 HTTP/1.1 { "method": "tools/list", "jsonrpc": "2.0", "id": 2 }
服务端接收到获取列表的请求之后,将会返回工具列表和函数Schema
event: message data: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"name":"get_alerts","description":"Get weather alerts for a US state.\n\nArgs:\n state: Two-letter US state code (e.g. CA, NY)\n","inputSchema":{"properties":{"state":{"title":"State","type":"string"}},"required":["state"],"title":"get_alertsArguments","type":"object"}},{"name":"get_forecast","description":"Get weather forecast for a location.\n\nArgs:\n latitude: Latitude of the location\n longitude: Longitude of the location\n","inputSchema":{"properties":{"latitude":{"title":"Latitude","type":"number"},"longitude":{"title":"Longitude","type":"number"}},"required":["latitude","longitude"],"title":"get_forecastArguments","type":"object"}}]}}
这一块具体实现的话,可以参考mcp 中将函数转换为工具的实现,实现方式其实就是获取函数的参数,类型注解,函数注释,函数名等信息,然后组装为大模型适配的schema信息,实现细节可以阅读仓库的代码 ,这里就不对细节过多赘述了。
具体的代码使用方式如下:
from mcp.server.fastmcp.tools.base import Tool from mcp.types import Tool as MCPTool async def get_forecast(latitude: float, longitude: float) -> str: """Get weather forecast for a location. Args: latitude: Latitude of the location longitude: Longitude of the location """ ... tool_info = Tool.from_function( get_forecast, name=None, title=None, description=None, annotations=None, icons=None, structured_output=None, ) mcp_tool = MCPTool( name=tool_info.name, title=tool_info.title, description=tool_info.description, inputSchema=tool_info.parameters, outputSchema=tool_info.output_schema, annotations=tool_info.annotations, icons=tool_info.icons, )
以上代码执行的结果如下如下,这里就返回了工具的名词,工具的Schema 等信息了。
{ "name": "get_forecast", "title": null, "description": "Get weather forecast for a location.\n\nArgs:\n latitude: Latitude of the location\n longitude: Longitude of the location\n", "inputSchema": { "properties": { "latitude": { "title": "Latitude", "type": "number" }, "longitude": { "title": "Longitude", "type": "number" } }, "required": [ "latitude", "longitude" ], "title": "get_forecastArguments", "type": "object" }, "outputSchema": { "properties": { "result": { "title": "Result", "type": "string" } }, "required": [ "result" ], "title": "get_forecastOutput", "type": "object" }, "icons": null, "annotations": null, "meta": null }
客户端将工具Schema和用户请求同时发送大模型之后,大模型就能够理解工具并根据用户的请求选择需要执行的工具,同时返回工具的输入参数,客户端获取到参数之后将向服务端发送工具调用请求,请求格式如下:
POST /messages/?session_id=1925698595864c2f95c0f80676884aa8 HTTP/1.1 {"method":"tools/call","params":{"name":"get_forecast","arguments":{"latitude":40.7128,"longitude":-74.006}},"jsonrpc":"2.0","id":3}
MCP服务端执行完之后,通过SSE将执行的结果发送给客户端,协议数据如下;
event: message data: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"\nTonight:\nTemperature: 64..F\nWind: 3 mph S\nForecast: Mostly clear. Low around 64, with temperatures rising to around 66 overnight. South wind around 3 mph.\n\n---\n\nMonday:\nTemperature: 78..F\nWind: 2 to 12 mph S\nForecast: Sunny. High near 78, with temperatures falling to around 76 in the afternoon. South wind 2 to 12 mph.\n\n---\n\nMonday Night:\nTemperature: 64..F\nWind: 7 to 10 mph S\nForecast: Mostly clear, with a low around 64. South wind 7 to 10 mph.\n\n---\n\nTuesday:\nTemperature: 78..F\nWind: 7 to 14 mph SW\nForecast: Mostly sunny, with a high near 78. Southwest wind 7 to 14 mph.\n\n---\n\nTuesday Night:\nTemperature: 64..F\nWind: 14 mph SW\nForecast: A chance of rain showers between 8pm and 2am, then showers and thunderstorms. Mostly cloudy. Low around 64, with temperatures rising to around 68 overnight. Southwest wind around 14 mph. Chance of precipitation is 90%. New rainfall amounts between a quarter and half of an inch possible.\n"}],"isError":false}}
具体的函数执行的代码细节可以参考MCP 服务单代码中Tool类的run 函数 , 实际流程为输入参数并调用函数执行,同时对结果进行转换,转换为字符等大模型能够使用的类型。
Streamable HTTP
而在2025年6月,MCP 使用Streamable HTTP 来替代之前的SSE+HTTP的协议格式,传输数据格式还是沿用早期SSE的JSON-RPC形式的传输格式,该协议的具体原理这里就不延伸展开了。
只说一下特别的点,相比于SSE+HTTP的形式,Streamable HTTP 支持了MCP将工具作为无状态服务的使用形式,从而让其支持了非MCP客户端的调用使用,也更符合我们开头所说的工具即服务的理念,
# 请求 POST /mcp HTTP/1.1 Host: 127.0.0.1:8001 Accept-Encoding: gzip, deflate User-Agent: python-httpx/0.28.1 accept: application/json, text/event-stream content-type: application/json mcp-protocol-version: 2025-06-18 Content-Length: 108 {"method":"tools/call","params":{"name":"web_search","arguments":{"query":"xiaomi"}},"jsonrpc":"2.0","id":3} # 响应 event: message data: {"jsonrpc":"2.0","id":3,"result":{"content":[{"type":"text","text":"小米集團成立於2010年4月6日,2018年7月9日於香港交易所主機板掛牌上市,是一家以智慧型手機、智慧型硬體和IoT平台為核心的消費電子及智慧型製造公司。"}],"structuredContent":{"result":"小米集團成立於2010年4月6日,2018年7月9日於香港交易所主機板掛牌上市,是一家以智慧型手機、智慧型硬體和IoT平台為核心的消費電子及智慧型製造公司。"},"isError":false}}