本文介绍了MCP大模型上下文协议的的概念,并对比了MCP协议和function call的区别。
1. 什么是MCP?
官网:https://modelcontextprotocol.io/introduction
2025年,Anthropic提出了MCP协议。MCP全称为Model Context Protocol,翻译过来是大模型上下文协议。这个协议的主要为AI大模型和外部工具 (比如让AI去查询信息,或者让AI操作本地文件)之间的交互提供了一个统一的处理协议。我们常用的USB TypeC接口(USB-C)统一了USB接口的样式,MCP协议就好比AI大模型中的USB-C,统一了大模型与工具的对接方式。
MCP协议采用了C/S架构,也就是服务端、客户端架构,能支持在客户端设备上调用远程Server提供的服务,同时也支持stdio流式传输模式,也就是在客户端本地启动mcp服务端 。只需要在配置文件中新增MCP服务端,就能用上这个MCP服务器提供的各种工具,大大提高了大模型使用外部工具的便捷性。
MCP是开源协议,能让所有AI厂商、AI工具都将MCP集成到自己的客户端中,从而扩大MCP的可用面。毕竟只有用的人越多,协议才能不断发展,不断变得更好。
2. 了解function call 在MCP没有出来之前,我们的AI Agent开发如果想调用外部工具需要针对不同的AI大模型SDK编写不同的代码,其中最为常用的是openai提供的function call的处理逻辑。
本小节参考博客:
2.1. function call demo 2.1.1. 配置工具,AI提供参数 当我们调用 OpenAI Chat Completions 接口时,可以通过tools参数传入可供使用的外部工具。这个工具的调用中就包含了工具的作用,工具需要传入的参数,以及参数的释义。其中tool_choice
字段设置为auto代表让大模型自动选择tools,设置为none时不会调用外部工具。
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 { "tool_choice" : "auto" , "messages" : [ { "role" : "system" , "content" : "你是一个天气查询助手" } , { "role" : "user" , "content" : "帮我查询上海的天气" } ] , "tools" : [ { "type" : "function" , "function" : { "name" : "get_weather" , "description" : "获取指定城市的天气" , "parameters" : { "type" : "object" , "properties" : { "city" : { "type" : "string" , "description" : "城市名" } } , "required" : [ "city" ] , } } } ] }
对应的python openai代码如下,我们将tools部分放入一个包含dict的list,作为create函数的tools参数即可。同时tool_choice传入auto代表自动选择工具。这里我用了硅基流动提供的Qwen2.5模型作为演示,运行下面这个代码需要修改api_key为正确值。
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 import openai import json def main (): client = openai.OpenAI( api_key="xxxxx" , base_url="https://api.siliconflow.cn/v1" ) tools = [{ "type" : "function" , "function" : { "name" : "get_weather" , "description" : "获取指定城市的天气" , "parameters" : { "type" : "object" , "properties" : { "city" : { "type" : "string" , "description" : "城市名" } }, "required" : ["city" ], } } }] res = client.chat.completions.create(model="Qwen/Qwen2.5-32B-Instruct" , messages=[{ "role" : "system" , "content" : "你是一个天气查询助手" }, { "role" : "user" , "content" : "帮我查询上海的天气" }], tools=tools, tool_choice="auto" ) print ("content:" ,res.choices[0 ].message.content) print ("tools:" ,res.choices[0 ].message.tool_calls) print ("message:" , res.choices[0 ].message.to_dict())
运行程序,发出请求后,大模型就会根据用户提出的问题和提供的tools,来为这个tools编写需要提供的参数。此时content会是空,不会输出内容,tool_calls中会包含调用的工具和参数。
1 2 3 4 ❯ uv run main.py content: tools: [ChatCompletionMessageToolCall(id='01964be6e485603d6a2a0acbbc7eba91', function=Function(arguments='{"city": "上海"}', name='get_weather'), type='function')] message: {'content': '', 'role': 'assistant', 'tool_calls': [{'id': '01964be6e485603d6a2a0acbbc7eba91', 'function': {'arguments': '{"city": "上海"}', 'name': 'get_weather'}, 'type': 'function'}]}
对应如下json格式响应,包含了我们的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 "message" : { "role" : "assistant" , "content" : "" , "tool_calls" : [ { "id" : "01964be6e485603d6a2a0acbbc7eba91" , "type" : "function" , "function" : { "name" : "get_weather" , "arguments" : "{\n \"city\": \"上海\"\n}" } } ] }
2.1.2. 调用工具并让AI二次处理 随后,我们就可以根据这个大模型返回的参数来调用我们的函数,并得到函数的返回结果,再次与大模型进行对话。此时需要按下面的方式维护对话上下文,首先需要将第一次请求AI返回的结果插入到上下文中("role": "assistant"
的json字符串),然后再插入工具调用的数据,格式如下:
1 2 3 4 5 { "role" : "tool" , "content" : "工具调用结果" , "tool_call_id" : "ai调用工具时返回的id" }
其中content代表工具调用的结果(字符串形式,内容可以是json),并且需要用tool_call_id
来标识这是哪一个工具调用的请求,必须要和"role": "assistant"
响应中的id对应。
二次AI交互对应python代码如下,在上文提供的python代码之后追加
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 messages.append(res.choices[0 ].message.to_dict()) tool_call = res.choices[0 ].message.tool_calls[0 ] arguments = json.loads(tool_call.function.arguments) messages.append({ "role" : "tool" , "content" : get_weather(arguments['city' ]), "tool_call_id" : tool_call.id }) res = client.chat.completions.create(model=model, messages=messages, tools=tools, tool_choice="auto" ) print ("content:" , res.choices[0 ].message.content)print ("tools:" , res.choices[0 ].message.tool_calls)print ("message:" , res.choices[0 ].message.to_dict())
其中get_weather函数如下,为了测试是写死的值,返回一个json字符串
1 2 def get_weather (location ): return '{"Celsius": 27, "type": "sunny"}'
最终运行结果,AI成功根据我们工具调用的返回值来输出了对话方式的天气情况,包括温度和晴天。这样我们就完成了一个完整的tools调用和AI再处理的过程了
1 2 3 4 5 6 7 8 ❯ uv run main.py content: tools: [ChatCompletionMessageToolCall(id='01964be6e485603d6a2a0acbbc7eba91', function=Function(arguments='{"city": "上海"}', name='get_weather'), type='function')] message: {'content': '', 'role': 'assistant', 'tool_calls': [{'id': '01964be6e485603d6a2a0acbbc7eba91', 'function': {'arguments': '{"city": "上海"}', 'name': 'get_weather'}, 'type': 'function'}]} ------------------- content: 上海当前的天气是晴天,温度是27摄氏度。 tools: None message: {'content': '上海当前的天气是晴天,温度是27摄氏度。', 'role': 'assistant'}
本次function call的完整上下文和代码 完整json上下文信息
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 [ { "role" : "system" , "content" : "你是一个天气查询助手" } , { "role" : "user" , "content" : "帮我查询上海的天气" } , { "content" : "" , "role" : "assistant" , "tool_calls" : [ { "id" : "01964beeb9ee27098b74149d86560b35" , "function" : { "arguments" : "{\"city\": \"上海\"}" , "name" : "get_weather" } , "type" : "function" } ] } , { "role" : "tool" , "content" : "{\"Celsius\": 27, \"type\": \"sunny\"}" , "tool_call_id" : "01964beeb9ee27098b74149d86560b35" } , { "role" : "assistant" , "content" : "上海当前的天气是晴天,温度是27摄氏度。" } ]
完整代码如下
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 import openai import jsondef get_weather (location ): return '{"Celsius": 27, "type": "sunny"}' def main (model="Qwen/Qwen2.5-32B-Instruct" ): client = openai.OpenAI( api_key="xxxx" , base_url="https://api.siliconflow.cn/v1" ) tools = [{ "type" : "function" , "function" : { "name" : "get_weather" , "description" : "获取指定城市的天气" , "parameters" : { "type" : "object" , "properties" : { "city" : { "type" : "string" , "description" : "城市名" } }, "required" : ["city" ], } } }] messages = [{ "role" : "system" , "content" : "你是一个天气查询助手" }, { "role" : "user" , "content" : "帮我查询上海的天气" }] res = client.chat.completions.create(model=model, messages=messages, tools=tools, tool_choice="auto" ) print ("content:" , res.choices[0 ].message.content) print ("tools:" , res.choices[0 ].message.tool_calls) print ("message:" , res.choices[0 ].message.to_dict()) print ("-------------------" ) messages.append(res.choices[0 ].message.to_dict()) tool_call = res.choices[0 ].message.tool_calls[0 ] arguments = json.loads(tool_call.function.arguments) messages.append({ "role" : "tool" , "content" : get_weather(arguments['city' ]), "tool_call_id" : tool_call.id }) res = client.chat.completions.create(model=model, messages=messages, tools=tools, tool_choice="auto" ) print ("content:" , res.choices[0 ].message.content) print ("tools:" , res.choices[0 ].message.tool_calls) print ("message:" , res.choices[0 ].message.to_dict()) if __name__ == "__main__" : main()
这里也测试一下把tool_choice
设置为none 的情况,此时即便传入了tools,AI也不会认为有外部工具,会直接返回文字说明。tool_choice
还有另外一个可选值是required,也就是必须要调用外部工具。
1 2 3 4 ❯ uv run main.py content: 我无法提供实时数据或即时查询服务,因为我当前的功能不包括访问互联网获取最新信息。你可以通过查询各类天气应用查看上海最新的天气情况,或者提供具体日期,我可以教你如何根据这些信息来判断和理解天气状况。如果你有任何关于天气的一般性问题,或需要了解某些天气条件的影响,也欢迎向我询问。 tools: None message: {'content': '我无法提供实时数据或即时查询服务,因为我当前的功能不包括访问互联网获取最新信息。你可以通过查询各类天气应用查看上海最新的天气情况,或者提供具体日期,我可以教你如何根据这些信息来判断和理解天气状况。如果你有任何关于天气的一般性问题,或需要了解某些天气条件的影响,也欢迎向我询问。', 'role': 'assistant'}
2.2. function call的问题 如下是一次function call的流程图
graph TD
A[编写tools,并提供tools参数配置] --> |调用OpenAI Chat Completions接口,提供tools|C[AI理解tools的作用和参数,并返回调用参数]
C --> |脚本处理AI返回的参数,调用tools,获取结果|E[AI获取tools结果,解析并生成自然语言回答] 调用OpenAI Chat Completions接口,提供tools
经过这个流程会发现一个问题,即便是简单的调用一个只有单参数的获取天气的函数,在使用openai这个第三方库的情况下都需要费很大劲,主要是tools的调用操作需要我们自己编写脚本实现 ,如果我们整个AI处理流程涉及到更多tools函数的时候,就很难处理了。
为了解决这个问题,openai在2025年新开源的OpenAI Agent SDK 中提供了更加便捷的tools工具调用的处理,只需要编写一个工具类,在Agent初始化的时候传入,Agent就能自动识别这个工具类中的方法并调用工具(自动给本地的函数传入参数)。示例代码可以参考开源仓库中的example 。
不过这还是没有解决一个最根本的问题,那就是外部工具调用方式的不统一 。光是我现在知道的Agent开发SDK就有三个(OpenAI、QwenAgent、谷歌ADK),这三个SDK都会有一套自己的tools调用逻辑,而且openai和qwen的SDK更是只支持自家的模型,这样就会导致即便是同一个tools,在使用不同的SDK的时候,也需要针对这个SDK去重新编写一套tools的调用逻辑,很是麻烦。
了解了这个背景之后,想必你能理解MCP协议的重要性了,它规范了tools的调用方式,同一个tools我们只需要编写一次mcp server,就能够在众多支持mcp的AI Agent里面被调用,解决了针对不同AI模型或SDK对tools进行单独适配的痛点!
3. MCP协议详解 接下来让我们简单了解一下MCP协议是怎么提供统一的tools调用方式的。在这之前,需要先注明几个名词
MCP Hosts:如 Claude Desktop、IDE 或 AI 工具,希望通过 MCP 访问数据的程序(也就是AI Agent程序)
MCP Clients:用于维护和服务器链接的一对一工具
MCP Servers:通过MCP协议提供特定能力
本地数据源:MCP服务器可以安装访问本地的文件、数据库和服务
远程服务:MCP服务器可通过各类API链接互联网上的外部系统
其中要注意MCP Hosts和Clients的区别,为了更好的区分,后文会用AI来指代MCP Hosts,毕竟MCP工具的输出结果都是会让AI来处理的。
3.1. MCP Server 以官方的MCP Server Demo 为例,在tools模式下,Server主要提供的是两个能力,一个是获取当前服务器支持的tools,另外一个就是call tool调用工具的能力。
其中,获取当前支持的tools会返回tools列表、每个tools的inputSchema参数和参数的type/description释义。这一点和function call是类似的,只不过mcp的sdk将其包装成了一个types.Tool类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @app.list_tools() async def list_tools () -> list [types.Tool]: return [ types.Tool( name="fetch" , description="Fetches a website and returns its content" , inputSchema={ "type" : "object" , "required" : ["url" ], "properties" : { "url" : { "type" : "string" , "description" : "URL to fetch" , } }, }, ) ]
除了人工编写这部分参数列表,我们还可以参考mcp_server_git 的实现,借助pydantic来定义每一个方法的参数列表,并使用schema()
自动获取参数释义。
1 2 3 4 5 Tool( name=GitTools.DIFF_STAGED, description="Shows changes that are staged for commit" , inputSchema=GitDiffStaged.schema(), )
举个例子,运行下面的代码,可以得到GitStatus这个类定义的两个入参的名称、类型和说明
1 2 3 4 5 6 7 8 9 10 from pydantic import BaseModelclass GitStatus (BaseModel ): repo_path: str hello_text: str print (GitStatus.model_json_schema())
需要注意的是,这里获取到的title释义是直接根据参数名称 来的,并没有人工编写的description那么准确。所以使用这种方式传入inputSchema的时候,需要我们尽可能地标准命名参数名称,让AI能通过参数名称直接推断出这个参数要传入什么内容 。
调用函数的操作就和function call类似了,MCP协议传入的同样也是arguments列表,需要我们将其解析并调用我们实际编写的函数
1 2 3 4 5 6 7 8 9 @app.call_tool() async def fetch_tool ( name: str , arguments: dict ) -> list [types.TextContent | types.ImageContent | types.EmbeddedResource]: if name != "fetch" : raise ValueError(f"Unknown tool: {name} " ) if "url" not in arguments: raise ValueError("Missing required argument 'url'" ) return await fetch_website(arguments["url" ])
除了这种复杂的方式,mcp sdk还提供了一个FastMcp ,只需要我们在编写的函数上加一个@mcp.tool()
装饰器,就能立马把我们的普通函数变成mcp tools,非常方便。
1 2 3 4 5 6 7 8 9 from mcp.server.fastmcp import FastMCPmcp = FastMCP("Demo" ) @mcp.tool() def add (a: int , b: int ) -> int : """Add two numbers""" return a + b mcp.run()
使用这种方式对时候,装饰器会自动去获取我们函数的参数以及参数的类型,并生成types.Tool
返回给客户端。这个装饰器有两个参数,name在不传入的时候默认为函数名称,description在不传入的时候默认为函数的docstring(也就是函数下的"""
注释)
1 2 name: Optional name for the tool (defaults to function name) description: Optional description of what the tool does
从SDK的源码中可以找到,装饰器会调用Tool.from_function
来生成types.Tool
类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def add_tool ( self, fn: Callable [..., Any ], name: str | None = None , description: str | None = None , ) -> Tool: """Add a tool to the server.""" tool = Tool.from_function(fn, name=name, description=description) existing = self._tools.get(tool.name) if existing: if self.warn_on_duplicate_tools: logger.warning(f"Tool already exists: {tool.name} " ) return existing self._tools[tool.name] = tool return tool
在from_function
函数中会注册name和description
1 2 3 4 func_name = name or fn.__name__ if func_name == "<lambda>" : raise ValueError("You must provide a name for lambda functions" ) func_doc = description or fn.__doc__ or ""
3.1.2. prompts和resources 除了最常用的tools,mcp还提供了prompts和resources两种服务方式,其中prompt是用于定义一些常用操作的提示词,此时客户端可以直接去获取这些提示词和ai交互,避免我们针对某一个流程重复编写提示词;resources是定义一个url格式,当我们的交流中出现这个url格式的时候,ai就可以调用这个工具去做一些特定操作,比如请求某个api或者操作数据库。
但很不幸的是,以上都是慕雪的个人简单理解,由于prompt和resources实在没有找到可以参考的博客或如何使用的demo,我并不是很理解它们在AI工具中是怎么被使用以及是在什么时候被使用的。网上针对MCP的教程也主要集中于tools层面。
后续如果对这俩有更多了解了,再回来补充本文。
3.2. 客户端配置本地和远程mcp server 在MCP SDK中主要提供了两种server启动的方式,一个是stdio流式传输(本地)的方式,另外一个是sse远程API的方式。
1 TRANSPORTS = Literal ["stdio" , "sse" ]
这两种方式分别对应了两种服务器的配置方式。如果是本地的mcp服务器,需要使用命令来指定mcp服务器代码文件所在路径,并启动它。这个代码可以是github上克隆的仓库,也可以是通过npm或其他方式安装到本地的可执行文件。
3.2.1. 本地(stdio) 以python编写的mcp server为例,需要通过如下方式启动某一个mcp server,其中--directory
指定的工作路径,必须指定这个工作路径才能找到mcp-simple-tool
的代码
1 2 uv run --directory /Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool mcp-simple-tool
此时是采用stdio方式启动的server,对应配置文件如下(可供Agent SDK调用)
1 2 3 4 5 6 7 8 9 10 11 12 13 { "mcpServers" : { "mcp-simple-tool" : { "command" : "uv" , "args" : [ "run" , "--directory" , "/Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool" , "mcp-simple-tool" ] } } }
当客户端需要使用这个mcp server的时候,会自动根据我们配置的命令去尝试在本地启动这个mcp服务端,然后和它交互。所以,如果使用stdio来配置mcp server但本地却没有uv环境的时候,程序是无法启动的。
以CherryStudio为例,在mcp配置中,以stdio格式添加我们这个配置,点击右上角保存,保存成功则代表配置正常。保存失败则需要检查配置的命令和路径是否出错
此时勾选底部的MCP服务器,和AI对话,给出一个URL,他会自动调用工具去下载这个URL的html文件,并解析和输出他对这个HTML文件的理解。
需要注意的是CherryStudio的mcpServers json配置文件并不是标准mcpServers的格式,多了一些字段,估计是方便前端设计。在AI的初次输出中也会把GdTGt4qMFpnyYqBxaqTrM输出出来,因为在标准mcpServers配置中GdTGt4qMFpnyYqBxaqTrM字段就是mcp服务端的名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 { "mcpServers" : { "GdTGt4qMFpnyYqBxaqTrM" : { "isActive" : true , "name" : "网页获取" , "type" : "stdio" , "description" : "通过url获取网页内容" , "registryUrl" : "" , "command" : "uv" , "args" : [ "run" , "--directory" , "/Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool" , "mcp-simple-tool" ] } } }
标准的mcpServers的格式并不需要那些额外字段,只需要我在前文给出的基础mspServers配置就可以了。以Qwen-Agent为例,只需要把json填入tools中就可以调用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 from qwen_agent.agents import Assistantdef init_agent_service (): llm_cfg = { 'model' : 'Qwen/Qwen2.5-32B-Instruct' , 'model_server' : 'https://api.siliconflow.cn/v1' , 'api_key' : 'xxxx' } system = ('你是一个强大的助手,可以帮用户处理问题。' ) tools = [{ "mcpServers" : { "mcp-simple-tool" : { "command" : "uv" , "args" : [ "run" , "--directory" , "/Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool" , "mcp-simple-tool" ] } } }] bot = Assistant( llm=llm_cfg, name='网页查看助手' , description='网页查看' , system_message=system, function_list=tools, ) return bot def main (text='这个网站是什么?https://blog.musnow.top/' ): bot = init_agent_service() messages = [{'role' : 'user' , 'content' : text}] for response in bot.run(messages): print ('bot response:' , response) if __name__ == "__main__" : main()
Qwen最终的输出如下
1 {'role': 'assistant', 'content': '这个网址是一个个人博客站点,名字叫做“慕雪的寒舍”。站点描述自己为“爱折腾的代码初学者”。\n\n博客主要内容包含了编程学习(如Python、C、ROS等)、博客建站的相关知识以及一些编程相关的项目开发记录。\n\n网站首页还展示了近期发表的文章、公告、分类和标签等信息方便用户查找和浏览。\n\n总之,这是一个包含了编程学习和项目开发记录内容的个人技术博客。', 'reasoning_content': '', 'name': '网页查看助手'}
3.2.2. 远程(sse) 远程调用的配置就需要服务器的url了。首先通过如下方式启动demo,提供命令行参数sse以远程方式启动,port指定端口8000
1 uv run --directory /Users/mothra/data/code/python/openai/mcp-python-sdk/examples/servers/simple-tool mcp-simple-tool --transport sse --port 8000
此时终端会输出当前进程PID以及服务端的url
1 2 3 4 INFO: Started server process [17058] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
对应mcpServers配置文件,其中disabled 是当前server是否被禁用,设置false为启用这个server,timeout 是链接服务端的超时时间。
1 2 3 4 5 6 7 8 9 { "mcpServers" : { "exampleServer" : { "url" : "http://127.0.0.1:8000/sse" , "disabled" : false , "timeout" : 30 } } }
在cherrystudio中填写 http://0.0.0.0:8000/sse
作为sse服务地址
同样可以正常调用,输出结果
注意,在QwenAgent SDK中必须使用http://127.0.0.1:8000/sse
才能正常连接这个远程服务器,不能使用http://0.0.0.0:8000/sse
,否则会出现502 Bad Gateway错误,详见issue 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 INFO:mcp.client.sse:Connecting to SSE endpoint: http://0.0.0.0:8000/sse/ DEBUG:httpcore.connection:connect_tcp.started host='127.0.0.1' port=7897 local_address=None timeout=5 socket_options=None DEBUG:httpcore.connection:connect_tcp.complete return_value=<httpcore._backends.anyio.AnyIOStream object at 0x1075a3c70> DEBUG:httpcore.http11:send_request_headers.started request=<Request [b'GET']> DEBUG:httpcore.http11:send_request_headers.complete DEBUG:httpcore.http11:send_request_body.started request=<Request [b'GET']> DEBUG:httpcore.http11:send_request_body.complete DEBUG:httpcore.http11:receive_response_headers.started request=<Request [b'GET']> DEBUG:httpcore.http11:receive_response_headers.complete return_value=(b'HTTP/1.1', 502, b'Bad Gateway', [(b'Connection', b'close'), (b'Content-Length', b'0')]) INFO:httpx:HTTP Request: GET http://0.0.0.0:8000/sse/ "HTTP/1.1 502 Bad Gateway" DEBUG:httpcore.http11:response_closed.started DEBUG:httpcore.http11:response_closed.complete 2025-04-19 15:51:29,240 - mcp_manager.py - 206 - INFO - Failed to connect to server: unhandled errors in a TaskGroup (1 sub-exception) INFO:qwen_agent_logger:Failed to connect to server: unhandled errors in a TaskGroup (1 sub-exception) 2025-04-19 15:51:29,247 - mcp_manager.py - 91 - INFO - Error executing function: 'NoneType' object is not iterable INFO:qwen_agent_logger:Error executing function: 'NoneType' object is not iterable
修改了tools的配置为远程url,其他代码保持不变
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 from qwen_agent.agents import Assistantimport logginglogging.basicConfig(level=logging.DEBUG) def init_agent_service (): llm_cfg = { 'model' : 'Qwen/Qwen2.5-32B-Instruct' , 'model_server' : 'https://api.siliconflow.cn/v1' , 'api_key' : 'xxxx' } system = ('你是一个强大的助手,可以帮用户处理问题。' ) tools = [{ "mcpServers" : { "exmaple-server" : { "url" : "http://127.0.0.1:8000/sse" , "disabled" : False , "timeout" : 30 } } }] bot = Assistant( llm=llm_cfg, name='网页查看助手' , description='网页查看' , system_message=system, function_list=tools, ) return bot def main (text='这个网站是什么?https://blog.musnow.top/' ): bot = init_agent_service() messages = [{'role' : 'user' , 'content' : text}] for response in bot.run(messages): print ('bot response:' , response) if __name__ == "__main__" : main()
QwenAgent正常调用工具并返回网页的结果
1 {'role': 'assistant', 'content': '这是一个名为"慕雪的寒舍"的博客网站。博客的作者是慕雪年华。网站上有很多关于编程学习、技术分享的文章,同时,博客还提供了分类、标签、归档等功能来帮助读者查找信息。它看起来像是一个个人的技术博客。', 'reasoning_content': '', 'name': '网页查看助手'}
3.3. 使用mcp协议后的流程图 使用MCP协议后,流程图就变成了下面这样,此时ai工具就从tools中解放了出来,我们可以随心所欲地添加任何我们需要的mcp server配置,最终ai能自动调用这些外部工具并处理他们的结果,不再需要像function call一样人工编写脚本处理大模型生成的参数,也不需要在不同Agent SDK中独立为tools编写适配代码了。
graph TD;
A[ai工具加载mcp配置] --> |启动mcp服务器|B
B[mcp client] --> |链接服务器,获取已有工具,调用工具|C[mcp server]
C -->|返回已有工具,返回工具调用结果|B
在让AI调用外部工具的方面,MCP协议还是非常重要的。
4. The end 关于MCP协议的介绍到这里就结束啦,主要介绍的还是MCP TOOLS方面的内容,有任何问题欢迎评论区讨论。