MCP是什么
MCP即模型上下文协议(Model Context Protocol),是一种开放协议,旨在为大型语言模型(LLM)提供标准化的上下文访问方式。官方举例说,MCP犹如AI应用的 “USB-C端口”,使LLM能够通过统一的接口无缝连接至多种数据源和工具(如文件系统、数据库或外部API)。它是基于JSON-RPC格式构建的,提供了一种面向客户端和服务器之间上下文交换和采样协调的有状态会话协议。正逐渐成为事实标准。
与FunctionCall的区别与联系
FunctionCall工作流程
FunctionCall指的是AI模型根据上下文自动执行函数的机制,用来规定模型是如何调用函数的。
FunctionCall一般的过程如下:
- 将用户的自然语言输入与已有函数的描述作为输入参数传给LLM。
- LLM结合输入参数,决定调用哪些函数,并指明必要参数(如函数的入参),进行格式化(如JSON、XML格式)的输出。
- 用户端接收到LLM格式化的函数调用后,对本地的函数进行调用,得到结果。
- 将得到的函数结果传给LLM,使得LLM有了所需的上下文信息。
FunctionCall实际上强调的是LLM本身的能力,一些经过特殊训练或调优的LLM能够根据用户的自然语言输入决定使用哪些函数,并按约定的格式表达出函数的调用。这里所描述的 “格式”,不同LLM提供商之间是可能有差异的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // OpenAI GPT的function call 格式 { "type": "function_call", "id": "fc_12345xyz", "call_id": "call_12345xyz", "name": "get_weather", "arguments": "{\"location\": \"Shanghai\"}" }
// Google Gemini的function call 格式 { "functionCall": { "name": "get_weather", "args": { "location": "Shanghai" } } }
|
FunctionCall的缺点
FunctionCall的缺点在于处理不好多轮对话和复杂需求,适合边界清晰、描述明确的任务。如果需要处理很多的任务,那么FunctionCall的代码比较难维护。
需要自行维护可用函数列表、外部系统的接入需要进行针对适配,不具有通用性。
MCP与FunctionCall的联系与区别
两者具有一定联系,但侧重点不同。
FunctionCall侧重于描述LLM本身具有的结构化函数调用能力(即调用哪些函数),而MCP则侧重于描述函数的规范化执行(即怎么执行被调用的函数),大概是分配与执行的关系。具体区别如下表所示:
| 比较维度 |
FunctionCall |
MCP |
| 性质 |
功能 |
协议 |
| 主要职责 |
解析用户意图并选择合适的函数调用,并进行格式化输出 |
规范化函数的具体执行过程,即规范LLM应用与外部系统的交互 |
| 责任方 |
各LLM提供商,平台依赖性强 |
LLM应用(client端)、外部系统(server端),LLM兼容 |
| 数据结构 |
因LLM提供商而有所不同,缺乏统一标准 |
规范的JSON-RPC |
| 灵活性 |
调用预定义函数,功能扩展需要额外开发 |
允许运行时发现新工具,动态适配和扩展 |
| 使用场景 |
轻量级、单任务、封闭环境应用 |
复杂场景、多数据源、跨平台协作 |
两者不是替代关系。在实际应用中,MCP和FunctionCall相互配合,发挥各自的优势。
例如,某教育智能体采用 “MCP + FunctionCall” 组合:使用MCP调用题库API生成题目,通过FunctionCall调用本地Markdown渲染工具格式化答案。
目前MCP的缺点
- 生态尚不足,支持MCP的应用数量较少
- 很难判断MCP服务器的安全性
- 部署和管理有一定门槛、产品化程度不高
MCP架构、工作流程
MCP架构

MCP采用客户端 - 服务器模型,其核心组件包括MCP Hosts、MCP Clients和MCP Servers:
- MCP Hosts:运行MCP的主应用程序,例如Claude Desktop、Cursor或AI Agent,负责通过MCP访问外部数据。
- MCP Clients:协议客户端,包含于Host中,由其管理。客户端与服务器建立一对一连接,负责发送请求并接收响应。
- MCP Servers:轻量级程序,通过MCP暴露特定功能(如文件读取或API调用)。
MCP工作流程

MCP的基本工作流程如下:
- 客户端启动,读取MCP配置:客户端在启动时会读取相关的MCP配置信息,这些信息可能包括MCP Server的地址、可用工具的相关配置等,为后续的连接和操作做准备。
- 连接MCP Server:客户端根据读取的配置信息,尝试与MCP Server建立连接。在连接过程中,客户端可能会向Server发送连接请求,并等待Server的响应,以确认连接是否成功。
- 获取工具列表:连接成功后,客户端向MCP Server请求获取可用的工具列表。Server会将支持的工具信息返回给客户端,客户端可以根据这些信息了解有哪些工具可供使用。
- 发送用户问题时,同时提供可用工具列表:当用户提出问题时,客户端会将用户的问题以及可用的工具列表一起发送给LLM。这样LLM在处理用户问题时,可以根据工具列表来判断是否需要调用工具。
- LLM判断是否需要调用工具:LLM接收到用户问题和工具列表后,会根据问题的内容和自身的逻辑判断是否需要调用工具来解决问题。如果需要调用工具,LLM会指定需要调用的工具和相应的参数。
- 客户端按照LLM指示调用Server上的工具:客户端接收到LLM的指示后,会根据指示调用MCP Server上相应的工具。客户端将调用请求发送给Server,Server执行工具并将结果返回给客户端,客户端再将结果反馈给LLM,最终由LLM生成用户问题的最终回复。
结合mcpserver.py和mcpclient.py代码片段可以进一步了解MCP的工作流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| import json import httpx from typing import Any from mcp.server.fastmcp import FastMCP
mcp = FastMCP("WeatherServer")
OPENWEATHER_API_BASE = "https://api.seniverse.com/v3/weather/now.json" API_KEY = "****" USER_AGENT = "weather-app/1.0"
async def fetch_weather(city: str) -> dict[str, Any] | None: """ 从 天气 API 获取天气信息。 :param city: 城市名称(需使用英文,如 beijing) :return: 天气数据字典;若出错返回包含 error 信息的字典 """ params = { "location": city, "key": API_KEY, "language": "zh-Hans", "unit": "c" } async with httpx.AsyncClient() as client: try: response = await client.get(OPENWEATHER_API_BASE, params=params, timeout=30.0) print(response) response.raise_for_status() return response.json() except httpx.HTTPStatusError as e: return {"error": f"HTTP 错误: {e.response.status_code}"} except Exception as e: return {"error": f"请求失败: {str(e)}"}
def format_weather(data: dict[str, Any] | str) -> str: """ 将天气数据格式化为易读文本。 :param data: 天气数据(可以是字典或 JSON 字符串) :return: 格式化后的天气信息字符串 """ if isinstance(data, str): try: data = json.loads(data) except Exception as e: return f"无法解析天气数据: {e}" if "status_code" in data: return f"⚠️ {data['error']}" result = data.get("results", [{}])[0] location = result.get("location", {}) city = location.get("name", "未知") country = location.get("country", "未知") now = result.get("now", {}) temp = now.get("temperature", "N/A") description = now.get("text", "未知") humidity = "N/A" wind_speed = "N/A" return ( f"🌍 {city}, {country}\n" f"🌡 温度: {temp}°C\n" f"💧 湿度: {humidity}%\n" f"🌬 风速: {wind_speed} m/s\n" f"🌤 天气: {description}\n" )
@mcp.tool() async def query_weather(city: str) -> str: """ 输入指定城市的英文名称,返回今日天气查询结果。 :param city: 城市名称(需使用英文) :return: 格式化后的天气信息 """ data = await fetch_weather(city) return format_weather(data) if __name__ == "__main__": mcp.run(transport='stdio')
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
| import asyncio import os import json from typing import Optional from contextlib import AsyncExitStack from openai import OpenAI from dotenv import load_dotenv from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client
load_dotenv() class MCPClient: def __init__(self): """初始化 MCP 客户端""" self.exit_stack = AsyncExitStack() self.openai_api_key = os.getenv("OPENAI_API_KEY") self.base_url = os.getenv("BASE_URL") self.model = os.getenv("MODEL") if not self.openai_api_key: raise ValueError("❌ 未找到 OpenAI API Key,请在 .env 文件中设置 OPENAI_API_KEY") self.client = OpenAI(api_key=self.openai_api_key, base_url=self.base_url) self.session: Optional[ClientSession] = None self.exit_stack = AsyncExitStack() async def connect_to_server(self, server_script_path: str): """连接到 MCP 服务器并列出可用工具""" is_python = server_script_path.endswith('.py') is_js = server_script_path.endswith('.js') if not (is_python or is_js): raise ValueError("服务器脚本必须是 .py 或 .js 文件") command = "python" if is_python else "node" server_params = StdioServerParameters( command=command, args=[server_script_path], env=None ) stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params)) self.stdio, self.write = stdio_transport self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write)) await self.session.initialize() response = await self.session.list_tools() tools = response.tools print("\n已连接到服务器,支持以下工具:", [tool.name for tool in tools]) async def process_query(self, query: str) -> str: """ 使用大模型处理查询并调用可用的 MCP 工具 (Function Calling) """ messages = [{"role": "user", "content": query}] response = await self.session.list_tools() available_tools = [{ "type": "function", "function": { "name": tool.name, "description": tool.description, "input_schema": tool.inputSchema } } for tool in response.tools] response = self.client.chat.completions.create( model=self.model, messages=messages, tools=available_tools ) content = response.choices[0] if content.finish_reason == "tool_calls": tool_call = content.message.tool_calls[0] tool_name = tool_call.function.name tool_args = json.loads(tool_call.function.arguments) result = await self.session.call_tool(tool_name, tool_args) print(result) print(f"\n\n[Calling tool {tool_name} with args {tool_args}]\n\n") print("#########################") print(result.content[0].text) messages.append(content.message.model_dump()) messages.append({ "role": "tool", "content": result.content[0].text, "tool_call_id": tool_call.id, }) response = self.client.chat.completions.create( model=self.model, messages=messages, ) return response.choices[0].message.content return content.message.content async def chat_loop(self): """运行交互式聊天循环""" print("\n🤖 MCP 客户端已启动!输入 'quit' 退出") while True: try: query = input("\nuser: ").strip() if query.lower() == 'quit': break response = await self.process_query(query) print(f"\n🤖 OpenAI: {response}") except Exception as e: print(f"\n⚠️ 发生错误: {str(e)}") async def cleanup(self): """清理资源""" await self.exit_stack.aclose() async def main(): if len(sys.argv) < 2: print("Usage: python client.py <path_to_server_script>") sys.exit(1) client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.cleanup() if __name__ == "__main__": import sys asyncio.run(main())
|
写在最后
MCP协议主要规定了两个部分的内容:
- 动态地提供对可用函数的标准化的描述,每个Server有哪些函数可以用;
- 标准化对外部系统的调用与结果的处理,如何调用这些函数。
这两点可以总结为函数的注册和使用,让大模型感知外部环境的一个协议。
MCP本身并没有规定与大模型的交互方式,不同的MCP Host与大模型交互会有差异,例如提示词方式与FunctionCall方式。
开发生态

文章作者:米兰
原始链接:https://blog.milanchen.site/posts/mcp.html
版权声明:转载请声明出处