前面的教程中我们已经简单提及到:MCP(Model Context Protocol,中文:模型上下文协议),它为大语言模型(LLM)提供了一个标准的“万能插座”,让AI能够“动手”去操作外部世界,而不仅仅是“动嘴”聊天。今天,我们先不聊繁琐的MCP协议标准和规范,而是从应用角度出发,用实战的方式快速说清楚:MCP为何物?为什么要用MCP?如何使用它?对应地,本文会从:“AI如何调用工具”到“AI如何使用现有的MCP Server”,为你逐步递进地剖析MCP的关键落地细节,帮你跳过协议标准等繁文缛节,直击要害,先人一步掌握MCP应用的终极奥义,后续便可徐徐图之
。首先,我们回到最初的结论:大模型的知识是截止于它的训练日期之前的,之后发生的任何事情它是不知道的。比如,你提问:“今天的日期是什么?”,看看大模型是怎么回答的:可见,在大模型眼里,它是没有“现在”或者“今天”的概念的,你多问它几次,它可能会给你“忽悠”一个“假日期”。很多人认为它回答的日期就是它训练的日期,这是错误的!在大模型眼里,它大概率知道自己的知识库截止日期,但是当你询问它当前日期信息时,它回答的日期并不一定就是训练日期,可能是一个训练数据中的“锚点日期”或“常见范例”。例如:“2023年4月13日”,原因是在很多公开数据集中,2023年的数据量非常大,4月13日可能在各种文本(新闻、博客、论坛帖子等)中被反复提及。当然,该日期也很可能是训练数据中的“热门日期”,如某个重大事件发生的日期,这个日期可能在序列语料中出现了无数次,权重极高。同理,大模型不可能知道它训练结束之后发生的任何事情。比如,你问它最新的国际时事,它的回答很可能是已经过去的历史事件。例如,当你问它最近发生的美以空袭伊朗事件时:从测试看出,案例中大模型回答的是2023年1月17日发生的空袭事件,而不是最近发生在2026年2月28日的空袭事件。此外,像天气、航班、股市等实时信息也是在大模型知识库范畴之外的,它也是无法直接回答该类问题的:也许你会说:但是我问deepseek和千问、豆包等AI的时候他们明明是可以正确回答我此类问题的呀!那是当然,因为他们已经不是纯粹的大模型了,而是已经适配了相关的MCP服务,通过MCP来“连接”外部世界,以获取更多的信息(或者通过MCP来“操控”物理世界)。它是怎么做到的呢?你不妨把这个过程想象成一个标准化的“点菜 - 做菜 - 上菜”流程:
- “菜单”注册(Tool Discovery):你(或你的聊天AI客户端)启动一个或多个MCP Server,这些Server启动时,会将自己能提供的所有功能(如“查询天气”、“获取CPU状态”等),按照MCP协议的标准格式生成一份“菜单”,并注册到MCP Client(也就是你的Agent)。
- “点菜”决策(Toll Calling):当你在聊天界面输入“请帮我查询天气”时,你的聊天AI(Agent)会分析这句话的意图。识别出这是一个需要查询实时天气信息的问题,于是它会做出“点菜”的决策,于是它决定调用“菜单”上的“get_weather”这个“菜品”。
- “做菜”执行(Tool Execution):AI会生成一个符合MCP格式的指令(一个JSON-RPC调用),通过Client发送给对应的MCP Server。这个MCP Server就像一个“厨师”一样,当它收到指令后,就去执行真正的代码逻辑(比如查询广州市今天的天气信息)。
- “上菜”响应(Tool Response):MCP Server拿到查询结果(例如{"weather": "27°~34°,多云"})后,把它封装好,再通过MCP Client回传给大模型使用。
- 大模型最终作答:拿到真实数据后,大模型会组织语言,最终生成并输出答复,例如:“你好,今天广州多云,气温为27°~34°”。
在此过程中,所有的通讯都遵循MCP协议(通常基于JSON-RPC),可以通过标准输入输出或者HTTP + SSE两种方式进行传输。这就是AI调用MCP工具的一个简单的工作流程。有了理论基础,接下来我们就要谈落地了。避免有人抬杠,先声明:理论上,使用Claude Desktop或者Cursor或任何基于Spring AI或者LandChain等来做开发是最好的选择,因为它们几乎不需要你写代码,只需要做配置就能让你的Agent实现MCP相关功能。但是!为了把更底层的原理展示给大家,我们选择在我们最初实现的聊天AI的代码基础上进行继续开发(没看过原先教程和代码的读者可以先去看一下):
在继续开发之前,先简单介绍一下目标软件的核心工作原理:即MCP协议基于JSON-RPC2.0标准,通过HTTP + SSE(Server-Sent Events)方式进行通信,整个交互流程如下:
- MCP Client发起初始化请求:告诉MCP Server自己支持的协议版本和能力。
- MCP Client请求工具列表:询问MCP Server提供了哪些可用的工具(如weatherTool)。
- MCP Client配置大模型:告知可调用的MCP工具列表(含名字、描述及详细参数)。
- 大模型自主决策:大模型根据具体问题,判断是否需要调用MCP工具以及调用哪个工具。
- 大模型通过MCP Client调用工具:发送工具的名称和参数,MCP Server执行后返回结果。
- MCP Client将结果交给大模型:大模型根据返回的结果或数据生成自然语言并作答。
如果说原有的聊天AI的代码实现了“用户->大模型->用户”的简单对话,那现在则变成了“用户->大模型->Mcp Server->大模型->用户”的增强模式。大模型需要先判断是否需要调用MCP工具,若需要,则通过MCP Client调用指定工具并获得调用结果,然后作答。关于MCP Server,网络上的MCP服务器已形成丰富的生态系统:既有官方维护的生产级工具,也有开源的社区项目。虽然它们的底层协议一致,但是服务类型、功能定位以及使用方式都会有所不同。在MCP服务器的运行方式上,存在本地进程和远程服务两种方式。其中本地进程方式是指服务器作为AI客户端的一个本地子进程来运行,适合用于访问本地文件、数据库或执行本地命令,比如用于浏览器自动化的Playwright以及用于数据库管理的本地PostgreSQL等;而远程服务方式则是指服务器运行在远端,AI客户端通过HTTP请求进行调用,如阿里云、高德地图等,他们无需在本地部署。此外,值得注意的是:不是所有的MCP服务器都能自由免费的使用,特别是一些垂直领域的MCP服务器,他们是明确需要收费访问的。且大多数免费访问的MCP服务器都需要注册账号并申请API Key才能受限地访问。当然,也有完全免费且自由使用的MCP服务器。下面我们选一个免费且不需要任何注册和API key的MCP服务器来进行测试,服务器地址如下:
https://mcp-weathertrax.jaredco.com/mcp
这是一个用于提供天气信息的MCP服务器。理论上,我们可以有很多种方法去访问MCP服务器,比如使用原始的requests,或者用MCP的官方SDK库,又或者是第三方py-mcp-client库等。我们这里选择使用httpx库,其他方式请读者自行尝试。下面先简单试一下访问MCP服务器,并获取它支持的工具列表:import httpxdef get_tool_list(): url = "https://mcp-weathertrax.jaredco.com/mcp" payload = { "jsonrpc": "2.0", "method": "tools/list", "id": 1 } with httpx.Client() as client: resp = client.post(url, json=payload) data = resp.json() if "error" in data: print(f"调用失败: {data['error']}") return tools = data.get("result", {}).get("tools", []) for tool in tools: print(f"工具名称: {tool.get('name')}") print(f"工具描述: {tool.get('description', '')}") print(f"工具参数: {tool.get('inputSchema', {})}") print()if __name__ == "__main__": get_tool_list()
从测试结果看出,该MCP服务器只支持两个工具:一个名为weatherTool,另一个名为weatherPlanningTool。通过工具描述知道,weatherTool用来获取已经发生的天气信息,而weatherPlanningTool则是获取未来的天气预报信息。理论上,我们只需要调用这两个工具,就能实现相应的功能,例如:payload = { "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "weatherTool", "arguments": { "location": "北京", "query_type": "current" } }, "id": 2}
但是,如果每一个工具我们都要去封装操作函数,那显然非常不合理。我们更倾向于定义一个通用型的MCPClient类,用来负责处理与MCP Server之间的通信,然后只需要把获取到的工具列表告诉大模型,让大模型自行判断是否需要调用MCP工具以及调用哪个MCP工具即可。MCPClient类的参考实现代码如下:import jsonimport httpxclass MCPClient: """ 通用MCP-CLIENT类 - HTTPx版本 """ def __init__(self, s_url: str): self.s_url = s_url self.tools = [] self._client = None def connect(self): """ 建立连接并获取工具列表 """ # 建立连接 self._client = httpx.Client() # 获取工具列表 payload = { "jsonrpc": "2.0", "method": "tools/list", "id": 1 } resp = self._client.post(self.s_url, json=payload) data = resp.json() if "result" not in data or "tools" not in data["result"]: raise RuntimeError("get tool list fail!") # 记录工具列表 for tool in data["result"]["tools"]: self.tools.append({ "type": "function", "function": { "name": tool["name"], "description": tool.get("description", ""), "parameters": tool.get("inputSchema", {}) } }) return True def call(self, name: str, arguments: dict): """ 调用工具 """ if self._client is None: raise RuntimeError("no MCP connect!") # 调用工具 payload = { "jsonrpc": "2.0", "method": "tools/call", "params": {"name": name, "arguments": arguments}, "id": 1 } resp = self._client.post(self.s_url, json=payload) data = resp.json() # 解析结果 if "error" in data: return f"错误: {data['error']}" if "result" in data: result = data["result"] # 尝试提取文本内容 if isinstance(result, dict) and "content" in result: texts = [] for item in result["content"]: if isinstance(item, dict) and "text" in item: texts.append(item["text"]) if texts: return '\n'.join(texts) return json.dumps(result, ensure_ascii=False) return json.dumps(data, ensure_ascii=False) def close(self): """ 关闭连接 """ if self._client: self._client.close() self._client = None
它只需提供连接、调用和关闭三个方法。接下来,便可将它适配到聊天AI中,主要涉及两处修改:一处是在调用大模型时传入MCP工具列表并设置为自动判断模式,另一处是根据大模型回复判断是否需要MCP调用,需要的话则执行相应的MCP工具,然后将执行结果二次发给大模型,由大模型做出最终答复,代码如下:# 初始化MCP客户端,并建立连接mcp_client = MCPClient(MCP_SERVER)mcp_client.connect()# 显示MCP工具列表print(f"🤖 MCP Server: \n {MCP_SERVER}\n")print(f"🤖 MCP Tool-list:")for tool in mcp_client.tools: print(f" {tool['function']['name']}")print("\n")# 初始化客户端llm_client = ZhipuAI(api_key="这里填你的API KEY")print("🤖 对话开始(输入 'quit' 或 'exit' 退出)")print("-" * 50)dialog = []while True: # 获取用户输入 user_input = input("\n👤 你: ").strip() # 判断退出条件 if user_input.lower() in ['quit', 'exit']: print("\n👋 再见!\n") break; # 跳过空输入 if not user_input: continue # 将新对话内容追加到dialog尾部 dialog.append({"role": "user", "content": user_input}) try: start_time = time.time() # 调用大模型 response = llm_client.chat.completions.create( model="glm-4-flash", messages=dialog, tools=mcp_client.tools, tool_choice="auto", stream=True ) # 计算响应时延 end_time = time.time() print(f"\n⚡ 响应延迟:【{(end_time - start_time)*1000:.0f}ms】\n") print("🤖 AI: ", end="", flush=True) full_reply = "" tool_lists = [] for chunk in response: delta = chunk.choices[0].delta # 收集完整回复 if delta.content: print(delta.content, end="", flush=True) full_reply += delta.content # 收集tool_calls if delta.tool_calls: tool_lists = delta.tool_calls # 调用MCP工具 if tool_lists: tool_calls = [] for tool in tool_lists: tool_calls.append({ "id": tool.id, "type": "function", "function": { "name": tool.function.name, "arguments": tool.function.arguments } }) tool_call = tool_lists[0] tool_name = tool_call.function.name tool_args = json.loads(tool_call.function.arguments) tool_rets = mcp_client.call(tool_name, tool_args) print(f"🔧 [调用 MCP 工具] {tool_name}({tool_args})") print("🤖 AI: ", end="", flush=True) # 调用MCP工具后,发起第二次大模型调用,让大模型做出最终答复 response = llm_client.chat.completions.create( model="glm-4-flash", messages=[ {"role": "user", "content": user_input}, {"role": "assistant", "content": full_reply, "tool_calls": tool_calls}, {"role": "tool", "tool_call_id": tool_call.id, "content": tool_rets} ], stream=True ) full_reply = "" for chunk in response: if chunk.choices and chunk.choices[0].delta.content: content = chunk.choices[0].delta.content print(content, end="", flush=True) full_reply += content print("\n") # 把AI回复的内容添加到会话列表里 dialog.append({"role": "assistant", "content": full_reply}) except Exception as e: print(f"❌ 错误: {e}")# 关闭MCP连接mcp_client.close()
可见,当你询问当天天气情况时,大模型判断需要调用weatherTool工具进行查询;当你询问未来的天气情况时,大模型则判断需要调用weatherPlanningTool工具进行查询。上面选择了一个纯粹的天气查询MCP服务器来进行测试,它只能提供天气信息,若你想查询其他信息,需要配置其他MCP服务器。当然,你有很多种方法去配置MCP服务器,甚至可以同时配置多个MCP服务器。值得提醒的是:虽然MCP是通用的标准协议,整体的使用方法大致相同,但并不是所有的MCP服务器都能直接用同一套代码去访问,因为不同的MCP服务器之间的使用方式可能会有些微小的差别,需要自行去阅读相关的服务器使用文档。请读者自行去测试其他MCP服务器。下一篇文章我们将会继续为大家解读如何在本地部署自己的MCP服务器,并将本地化的应用封装为MCP工具(或API调用),并挂接到我们自己的Agent之上使用,以实现Agent通过MCP去控制本地设备的目的,敬请关注。
本文由智源科普原创,转载请注明来源;文中部分图片源于网络,如有侵权可联系我们删除;本文内容仅代表个人观点,无任何政治立场、无任何商业目的,且个人见识有限,不当之处请各位看官权当笑话,不喜勿喷。