01.前言
AgentScope 作为一款功能强大的开源多智能体开发框架,为开发者提供了智能体构建、工具使用、多智能体编排等全方位支持。随着 MCP 协议在开源社区掀起热潮,我们以 AgentScope 框架为基础,从实践出发,深入思考 MCP 协议的优势和存在的问题,探索 MCP 对智能体应用开发的深远影响。
02.实践:基于 AgentScope 的 MCP 初体验
作为多智能体开发领域的先行者,AgentScope 迅速响应社区需求,率先实现了对 MCP 的全面支持。通过其内置的 ServiceToolkit 模块,开发者可以轻松实现对 sse 和 stdio 类型 MCP Server 的支持。
下面,让我们通过一个具体的示例,展示如何利用 MCP Server 构建一个智能体应用。这个示例由一个名为 Friday 的智能体和用户组成,通过调用 ServiceToolkit 的 add_mcp_servers 方法,智能体将能够连接到 MCP Server 并获取其中包含的工具。
from agentscope.agents import ReActAgentV2, UserAgent from agentscope.service import ServiceToolkit import agentscope # 加载模型配置 agentscope.init( model_configs={ "model_config": "my_config", "model_type": "dashscope_chat", "model_name": "qwen-max", } ) # 初始化 toolkit,并连接 MCP 服务器 toolkit = ServiceToolkit() toolkit.add_mcp_servers( { "mcpServers": { "amap-amap-sse": { "url": "https://mcp.amap.com/sse?key=xxx" } }, } ) # 创建 ReAct 智能体 agent = ReActAgentV2( name="Friday", model_config_name="my_config", service_toolkit=toolkit, max_iters=20, sys_prompt="""You're a helpful assistant named {{name}}. # Target Your target is to finish the given task with the provided tools.""" ) user_agent = UserAgent(name="user") # 显式地构建工作流程/对话 x = None while True: x = agent(x) x = user_agent(x) if x.content == "exit": break
得益于 ServiceToolkit 模块的 add_mcp_servers 方法,开发者可以轻松添加任意的 MCP Server。然而为了构建一个好的智能体应用,添加 MCP 工具仅仅是一个开始,下面我们继续探索这个样例,并尝试给出一些优化方案。
03.思考:MCP 实践中的问题
问题1:MCP 的隐性要求
下面的表格里,我们列举了高德 MCP Server 支持的主要函数,以及它们的输入和输出。
函数 |
介绍 |
输入 |
输出 |
maps_text_search |
根据用户传入关键词,搜索相关的POI地点信息 |
keywords, city |
POIs |
maps_geo |
将详细的结构化地址转换为经纬度坐标 |
address, city |
location |
maps_around_search |
根据用户传入关键词以及坐标,搜索出radius半径范围的POI地点信息 |
keywords, location, radius |
POIs |
... |
在智能体使用这些工具时,我们发现了这样的现象:当用户输入“ 搜索云谷园区附近的咖啡厅”时,智能体会直接调用 maps_around_search 函数,并基于自己的先验知识,给出“云谷园区”的经纬度坐标。
编辑
因此,我们需要做出第一步的优化,在智能体的系统提示(System prompt)中,添加“不要做任何假设”的约束条件,并提醒智能体对任何地点,都需要先调用 maps_geo 函数,获取其经纬度坐标。具体的系统提示如下:
"""You're a helpful assistant named {{name}}. # Target Your target is to finish the given task with the provided tools. # Note ## When Using Gaode API Tools 1. The input locations maybe not specific, so you MUST first use the `maps_text_search` function to get the complete and accurate address. After that, you can use the `maps_geo` function to get the latitude and longitude of the location 2. DON'T make any assumptions! All the locations (include latitude and longitude) you use should be obtained from the tools 3. Sometimes, there maybe multiple locations for the same name, once you feel the search result is not accurate, you should research with different keywords or use `generate_response` function to ask for more information 4. You can use Gaode API tools to obtain POI related pictures"""
接着我们再次运行这个智能体。面对同样的用户输入,大模型进行了如下的两次工具调用,即先调用 maps_geo 函数,获取“云谷园区”的经纬度坐标,然后调用 maps_around_search 函数,搜索出radius半径范围的POI地点信息。
编辑
经过第一次优化后,看起来我们的智能体已经能够正确的使用地图工具了。
然而在这个过程中,我们还会发现一个问题,那就是 maps_geo 函数返回的经纬度坐标并不准确,这是由于 maps_geo 函数需要的输入是 详细的结构化地址,而智能体输入的是 模糊的地点描述。
此时,我们发现了一个问题,即 MCP Server 工具中蕴含着隐藏的 SOP(Standard Operating Procedure)。在我们的例子当中,高德 MCP Server 的正确使用方式是:
① 先调用 maps_text_search 函数,将模糊的地点描述转化为详细的结构化地址 ② 再调用 maps_geo 函数,获取该地点的经纬度坐标 ③ 最后调用 maps_around_search 函数,搜索出 radius 半径范围的 POI 地点信息
上面的 SOP 过程是不是很眼熟?没错,这个过程和 RAG(Retrieval-Augmented Generation)的流程非常相似。调用 maps_text_search 函数的过程对应于 RAG 中的用户“查询改写”(例如“今天天气怎么样”,需要改写为“2025年5月12日,杭州天气怎么样”)。只有遵循这个 SOP 流程,才能正确地使用地图工具,而期待大模型能够自动发现并遵循这个 SOP 流程,目前仍存在较大的难度。
问题2:MCP 的精度问题
由于 MCP Server 的开发与智能体的开发是分离的,因此 MCP Server 中的工具大多数时候对开发者和智能体来说都是黑盒,我们无法得知工具的内部实现,也无法得知工具的精度。
举个例子,我们需要查询阿里巴巴云谷园区附近 500 米内的咖啡厅,但是如果 maps_geo 返回的坐标误差在 500 米以上,那么即使 maps_around_search 返回的 POI 地点信息再准确,也可能是毫无意义的。
这一点要求开发者需要对 MCP Server 的精度有一定的了解,才能更好地使用 MCP Server。
实践:多 MCP Server 组合使用
当一个智能体潜在可能需要多种不同的 MCP Server 时,一口气将所有的 MCP Server 添加到 ServiceToolkit 中可能并不是一个很好的选择,例如在前面的尝试中,我们专门为高德 MCP Server 添加了系统提示,如果添加的 MCP Server 越来越多,那么系统提示中的注意事项也会越变越长,这显然不是一种优雅的解决方案。
那么将每个 MCP Server 分配给一个智能体,每个智能体固定负责一个功能模块呢?是一个不错的思路,但是我们还有更简单,灵活和优雅的解决方案,那就是允许智能体自己选择并装备 MCP Server。
在我们的方案中,MCP Server 会更像一个工具的集合,而不是一个独立的工具。因此,我们为ReActAgentV2添加了一个下面的 reset_equipped_tools 函数,它也将作为一个工具函数给到智能体,允许智能体根据当前的任务选择装备不同的 MCP Server。
# ... class ReActAgentV2(AgentBase): # ... def reset_equipped_tools( self, gaode_map: bool = False, # 高德 MCP Server webpage_fetch: bool = False, # 网页内容抓取 MCP Server webpage_deploy: bool = False, # 网页部署 MCP Server multimodal: bool = False, # 多模态 MCP Server ) -> ServiceResponse: """Reset your equipped tool functions. Use this function when your current tools are not enough to solve the task. Note this function is not incremental, it will first disable all the current tools, and then enable the new tools. Args: gaode_map (`bool`, defaults to `False`): The gaode map related tools for geography related tasks webpage_fetch (`bool`, defaults to `False`): The webpage fetching tools webpage_deploy (`bool`, defaults to `False`): The webpage deploying tools, which can be used to deploy the given webpage multimodal (`bool`, defaults to `False`): The multimodal tools, including text to image/audio tools """ # Clear the current tools self.service_toolkit.service_funcs = {} self.service_toolkit.add(self.reset_equipped_tools) self.service_toolkit.add(self.generate_response) selected_mcps = {} notes = [] if gaode_map: selected_mcps = { **selected_mcps, **MCP_CONFIGS[MCP_NAMES.GAODE_MAP] } notes.append(MCP_NOTES[MCP_NAMES.GAODE_MAP]) if webpage_fetch: selected_mcps = { **selected_mcps, **MCP_CONFIGS[MCP_NAMES.WEBPAGE_FETCH] } notes.append(MCP_NOTES[MCP_NAMES.WEBPAGE_FETCH]) if webpage_deploy: selected_mcps = { **selected_mcps, **MCP_CONFIGS[MCP_NAMES.WEBPAGE_DEPLOY] } notes.append(MCP_NOTES[MCP_NAMES.WEBPAGE_DEPLOY]) if multimodal: selected_mcps = { **selected_mcps, **MCP_CONFIGS[MCP_NAMES.MULTIMODAL] } notes.append(MCP_NOTES[MCP_NAMES.MULTIMODAL]) self.service_toolkit.add_mcp_servers({"mcpServers": selected_mcps}) # Delete useless tools if "fetch_html" in self.service_toolkit.service_funcs: self.service_toolkit.service_funcs.pop("fetch_html") if "fetch_txt" in self.service_toolkit.service_funcs: self.service_toolkit.service_funcs.pop("fetch_txt") # Reset the system prompt self.sys_prompt = "\n\n".join([SYSTEM_PROMPT_BASE] + notes).format( name=self.name) print("重置之后", self.sys_prompt) return ServiceResponse( status=ServiceExecStatus.SUCCESS, content="Success: Tools reset successfully", )
这个函数中主要完成两件事情:
① 根据参数改变当前 ServiceToolkit 对象中的工具 ② 根据参数动态改变智能体的系统提示,从而指导智能体使用不同的 MCP Server
经过上述的改动后,智能体就可以根据当前用户的需求,选择并装备不同的 MCP Server 了。并且这个智能体也可以作为一个独立的单元,集成到多智能体的解决方案中。
04.展望
前面的尝试中,我们尝试构建了基于 MCP 的样例,思考并尝试解决了构建应用过程中遇到的一些问题,并将其拓展成了一个更加灵活的智能体解决方案。你可能已经注意到,文中遇到的问题并非 MCP 独有,而是所有工具类 API 在与大模型结合时普遍存在的挑战。但是伴随着 MCP 的兴起,越来越多复杂的工具 API 会被集成到 MCP 中,类似的问题将越来越凸显。另一方面,人们对大模型,或者说智能体总是抱有很高的期望:我们希望它们更加智能,能够自动化地完成复杂任务,能够进一步减少人工调试和干预。要实现这一目标,仅靠工具层面的提升还远远不够。未来,模型训练、自我学习与进化、长期记忆等多项技术将协同作用,推动智能体能力的全面提升。工具的使用也将不再局限于简单的封装或选择,而是演变为涵盖模型训练、自主学习和知识积累在内的系统化解决方案。