乐于分享
好东西不私藏

AI知识科普(8):在实战中掌握MCP——AI的触手,它并不神秘!

AI知识科普(8):在实战中掌握MCP——AI的触手,它并不神秘!
前面的教程中我们已经简单提及到:MCP(Model Context Protocol,中文:模型上下文协议,它为大语言模型(LLM)提供了一个标准的“万能插座”,让AI能够“动手”去操作外部世界,而不仅仅是“动嘴”聊天。今天,我们先不聊繁琐的MCP协议标准和规范,而是从应用角度出发,用实战的方式快速说清楚:MCP为何物?为什么要用MCP?如何使用它?
对应地,本文会从:“AI如何调用工具”到“AI如何使用现有的MCP Server”,为你逐步递进地剖析MCP的关键落地细节,帮你跳过协议标准等繁文缛节,直击要害,先人一步掌握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工具的一个简单的工作流程。
使用现有MCP服务
有了理论基础,接下来我们就要谈落地了。避免有人抬杠,先声明:理论上,使用Claude Desktop或者Cursor或任何基于Spring AI或者LandChain等来做开发是最好的选择,因为它们几乎不需要你写代码,只需要做配置就能让你的Agent实现MCP相关功能。但是!为了把更底层的原理展示给大家,我们选择在我们最初实现的聊天AI的代码基础上进行继续开发(没看过原先教程和代码的读者可以先去看一下):

AI知识科普(4):借鸡下蛋,教你打造...

在继续开发之前,先简单介绍一下目标软件的核心工作原理:即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, dictand "content" in result:                texts = []                for item in result["content"]:                    if isinstance(item, dictand "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()
运行聊天AI,询问天气情况测试如下:
可见,当你询问当天天气情况时,大模型判断需要调用weatherTool工具进行查询;当你询问未来的天气情况时,大模型则判断需要调用weatherPlanningTool工具进行查询。
上面选择了一个纯粹的天气查询MCP服务器来进行测试,它只能提供天气信息,若你想查询其他信息,需要配置其他MCP服务器。当然,你有很多种方法去配置MCP服务器,甚至可以同时配置多个MCP服务器。值得提醒的是:虽然MCP是通用的标准协议,整体的使用方法大致相同,但并不是所有的MCP服务器都能直接用同一套代码去访问,因为不同的MCP服务器之间的使用方式可能会有些微小的差别,需要自行去阅读相关的服务器使用文档。请读者自行去测试其他MCP服务器。
下一篇文章我们将会继续为大家解读如何在本地部署自己的MCP服务器,并将本地化的应用封装为MCP工具(或API调用),并挂接到我们自己的Agent之上使用,以实现Agent通过MCP去控制本地设备的目的,敬请关注。

全篇完

声明
本文由智源科普原创,转载请注明来源;文中部分图片源于网络,如有侵权可联系我们删除;本文内容仅代表个人观点,无任何政治立场、无任何商业目的,且个人见识有限,不当之处请各位看官权当笑话,不喜勿喷。