1.引言
上一篇文章,我们知道function calling实现了大模型通往外部世界的桥梁,于大模型而言,function calling实现了一种插件化的机制,完成能力扩展。
你还记得编程实现function calling有哪些关键点吗?首先要定义一个工具函数,像这样:
其次,要通过dict数据结构形式描述工具,这很关键(大模型根据描述来选择工具),像这样:
我们看到,要实现function calling是一件相对繁琐的事情,而且这里还存在巴比塔困境,即各家大模型各玩各的,简单直白说,就是没有规矩,不成方圆。
于是,2024年11月Anthropic(发布Claude大模型)公司,提出了mcp(Model Context Protocol)模型上下文协议,试图通过标准化协议方式,实现书同文,车同轨。
这是一种进步,任何事物的发展,当从百花齐放到一统江湖的时候,说明黎明的曙光到来。接下来我将通过两篇文章,手把手给你分享mcp两种通信模式的实现。
2.案例
2.1.什么是mcp
mcp是一个标准化开放协议,规范了应用程序向LLM提供上下文的方式。类似于计算机世界USB接口,mcp提供了一种标准化的方式让AI模型能够连接到不同的外部数据源和工具。简单说,就是让大模型function calling有统一的标准可循。
mcp架构图:c/s 机构模式
从架构图中,mcp有三个核心角色:
- 客户端服务端资源
实践应用流程图:
外部应用和数据源,通过mcp协议开放接口能力,AI应用通过mcp协议接入外部工具和数据源,实现LLM大模型于外部世界的交互。
访问:mcp.so/,看到很多大的玩家已经通过mcp开放了相关接口服务能力
2.2.环境准备
搞清楚了mcp相关的概念和流程,我们自己从代码层面实现一套mcp完整流程。当然,要先安装相关的库准备环境
pip install uv mcp
备注一下,只安装mcp库其实就够用了,网上很多文章都绑定了uv库,uv等价于python中pip+虚拟环境,不用也是可以的。
2.3.server端代码
server端实现工具接口,数据开放。等价于function calling中函数定义
from mcp.server.fastmcp import FastMCP# 创建MCP 服务mcp = FastMCP('Demo')@mcp.tool()def add(a:int, b:int) ->int: """" 计算两个整数的和并返回 """ return a+bif __name__ == "__main__": # 以标准 I/O 方式运行 MCP 服务器 mcp.run(transport='stdio')
2.4.客户端代码
import asyncioimport jsonfrom typing import Optionalfrom contextlib import AsyncExitStackfrom openai import OpenAIfrom mcp import ClientSession, StdioServerParametersfrom mcp.client.stdio import stdio_clientclass MCPClient: def __init__(self): """初始化MCP客户端""" self.exit_stack = AsyncExitStack() self.opanai_api_key = "" # 调用模型的api_key self.base_url = "https://api.deepseek.com" # 调用模型url, 这里以deepseek作演示 self.model = "deepseek-chat" # 调用deepseek-v3模型 self.client = OpenAI(api_key=self.opanai_api_key, base_url=self.base_url) self.session: Optional[ClientSession] = None # Optional提醒用户该属性是可选的,可能为None self.exit_stack = AsyncExitStack() # 用来存储和清除对话中上下文的,提高异步资源利用率 async def connect_to_server(self, server_script_path): """连接到MCP服务器并列出MCP服务器的可用工具函数""" server_params = StdioServerParameters( command="python", args=[server_script_path], env=None ) # 设置启动服务器的参数, 这里是要用python执行server.py文件 # 启动MCP服务器并建立通信 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() # 与服务器建立stdio连接 # 列出MCP服务器上的工具 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 Server可用的MCP工具""" 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(f"\n\n[Calling tool {tool_name} with args {tool_args}]\n\n") # 将模型返回的调用工具的对话记录保存在messages中 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("\n用户:").strip() if query.lower() == 'quit': break response = await self.process_query(query) print(f"\nDeepSeek-V3: {response}") except Exception as e: print(f"发生错误: {str(e)}") async def clean(self): """清理资源""" await self.exit_stack.aclose()async def main(): client = MCPClient() try: await client.connect_to_server(sys.argv[1]) await client.chat_loop() finally: await client.clean()if __name__ == "__main__": import sys asyncio.run(main())
客户端实现连接,协议转换,重点要关注connect_to_server方法
在与大模型交互中,有一段很熟悉的代码
这不就是我们function calling吗?本质没有变化,mcp只是给我们提供了一套标准,让大家在一个统一的标准下玩,便于开放互联互通。
2.5.执行结果
代码都开发完了,启动执行看看结果,需要注意,我们这篇文章演示标准IO模式,即mcp的stdio本地通信模式,服务端以子进程方式运行,命令如下:
python mymcp/hello_mcp_client.py mymcp/hello_mcp_server.py
启动完成,我们看到成功列出服务端开放的工具列表,这里是add工具
输入信息:111求和222等于多少,与大模型愉快交流
大模型理解我们的问题,选择了add工具,最终完成任务。