【值得收藏】MCP工具调用实战:Python实现大模型Agent交互,支持所有MCP Server类型

该文章已生成可运行项目,

背景

MCP(Model Context Protocol,模型上下文协议)的出现,统一了Agent工具使用的方式,虽然市面上绝大多数MCP存在问题,但不可否认依然还是有非常多优秀好用的MCP工具,关于MCP协议更多的内容,后续会有专门的文章介绍,本文先聚焦在如何使用MCP工具上。

本文会涉及如何使用Python开发MCP Client,并使用LLM完成一次工具调用示例,支持MCP目前支持的所有传输类型,包括stdio、sse、streamable-http,也就是说,市面上所有类型的MCP Server,都是支持的。

总的调用方式,其实与我们之前介绍的常规工具大同小异,只是需要一些准备工作,总体流程如下:

准备工作

  • API申请:申请高德API Key、秘塔搜API,这两个都不是必须的,是为了演示sse类型和streamable-http类型的传输类型需要的,可以根据需要申请
  • 模型准备:LM Studio中下载qwen3 8b,也可以使用Ollama或者在线服务
  • 安装依赖

核心代码

MCP官方库给出的样例代码,都是异步函数,为了兼顾到不同的使用场景,本文进行了扩展,参考langchain的命名方式,alist_toolsacall_tool都是异步方法,需要使用await alist_tools(...)这样的方式进行调用,如果你的调用方是非异步函数,则可以使用list_toolscall_tools

由于async方法是事件循环驱动的,Jupyter Notebook启动后,本身就已经有一个事件循环在运行了,asyncio默认不允许事件循环出现嵌套,因此在Jupyter Notebook中调用async方法,需要在调用前执行如下代码:

import nest_asyncionest_asyncio.apply()

MCP Client

完整的MCP Client实现如下:

from contextlib import asynccontextmanagerfrom typing import Listfrom mcp import ClientSession, Toolfrom mcp.client.streamable_http import streamablehttp_clientfrom mcp.client.sse import sse_clientfrom mcp.client.stdio import stdio_client, StdioServerParametersimport asyncioclass MCPClient:    def __init__(self, mcp_servers: dict, timeout: int=60):        """        Args:            mcp_servers: MCP Server配置字典,格式参考Claude MCP Server定义格式(https://docs.claude.com/en/docs/claude-code/mcp?utm_source=chatgpt.com),格式如下:            {                "mcpServers": {                    "mcp-server-chart": {                        "command": "npx",                        "args": [                            "-y",                            "@antv/mcp-server-chart"                        ]                    },                    "mySseServer": {                        "url": "https://api.example.com/sse",                        "type": "sse",                        "headers": {                            "Authorization": "Bearer your_api_key"                        }                    },                    "myHttpServer": {                        "type": "http",                        "url": "https://api.example.com/mcp",                        "headers": {                            "Authorization": "Bearer your_api_key"                        }                    }                }            }        """        self.mcp_servers = mcp_servers['mcpServers']        self.timeout = timeout    def _get_mcp_client(self, mcp_server_name):        server_config = self.mcp_servers[mcp_server_name]        if'url'in server_config:            mcp_timeout = float(self.timeout)            logger.info(f"mcp_timeout: {mcp_timeout}")            url = server_config['url']            headers = server_config.get('headers', {})            if server_config['type'] == 'sse':                client = sse_client(url=url, headers=headers, timeout=mcp_timeout)            else:                client = streamablehttp_client(url=url, headers=headers, timeout=mcp_timeout)        else:            server_params = StdioServerParameters(                command=server_config['command'],                args=server_config['args'],                env=server_config.get('env')            )            client = stdio_client(server_params)        return client    @asynccontextmanager    asyncdef _get_transport(self, mcp_server_name):        server_config = self.mcp_servers[mcp_server_name]        # logger.info(f"mcp_server_name: {mcp_server_name}, server_config: {server_config}")        if'url'notin server_config and'command'notin server_config:            raise ValueError(f"Invalid MCP server config: {server_config}")        if'url'in server_config:            mcp_timeout = float(self.timeout)            logger.info(f"mcp_timeout: {mcp_timeout}")            url = server_config['url']            headers = server_config.get('headers', {})            if server_config['type'] == 'sse':                client = sse_client(url=url, headers=headers, timeout=mcp_timeout)            else:                client = streamablehttp_client(url=url, headers=headers, timeout=mcp_timeout)        else:            server_params = StdioServerParameters(                command=server_config['command'],                args=server_config['args'],                env=server_config.get('env')            )            client = stdio_client(server_params)        asyncwith client as session_input:            # streamablehttp_client返回的session_input包含get_session_id函数            if server_config['type'] == 'http':                read_stream, write_stream, get_session_id = session_input            else:                read_stream, write_stream = session_input            yield read_stream, write_stream    asyncdef alist_tools(self, mcp_server_name):        """        列出指定MCP Server上可用的工具(异步)        Returns:            可用的工具列表        """        logger.info(f"mcp_server_name: {mcp_server_name}")        tools = []        asyncwith self._get_transport(mcp_server_name) as (read_stream, write_stream):            asyncwith ClientSession(read_stream, write_stream) as session:                await session.initialize()                response = await session.list_tools()                tools = response.tools        return tools    asyncdef acall_tool(self, mcp_server_name, tool_name: str, tool_args: dict={}):        """        调用指定MCP Server上的工具(异步)        Args:            tool_name: 工具名称            tool_args: 工具参数        Returns:            工具调用结果        """        logger.info(f"call_tool, server_name: {mcp_server_name}, tool_name: {tool_name}, tool_args: {tool_args}")                asyncwith self._get_transport(mcp_server_name) as (read_stream, write_stream):            # 使用客户端流创建会话            asyncwith ClientSession(read_stream, write_stream) as session:                # 初始化连接                await session.initialize()                result = await session.call_tool(tool_name, tool_args)        return result        def list_tools(self, mcp_server_name):        return asyncio.run(self.alist_tools(mcp_server_name))        def call_tool(self, mcp_server_name, tool_name: str, tool_args: dict={}):        return asyncio.run(self.acall_tool(mcp_server_name, tool_name, tool_args))

采用与Claude相同的配置格式,我们使用三种类型的MCP Server进行实验:

  • mcp-server-chart:这是蚂蚁集团开发的可视化MCP Server,stdio类型
  • 高德地图MCP Server:sse类型
  • 秘塔搜MCP Server:streamable-http类型

获取工具

config_data = {    "mcpServers": {        "mcp-server-chart": {            "command": "npx",            "args": [                "-y",                "@antv/mcp-server-chart"            ]        },        "amap": {            "url": f"https://mcp.amap.com/sse?key={os.getenv('AMAP_API_KEY')}",            "type": "sse"        },        "metaso": {            "type": "http",            "url": "https://metaso.cn/api/mcp",            "headers": {                "Authorization": f'Bearer {os.getenv("METASO_API_KEY")}'            }        }    }}mcp_client = MCPClient(config_data)# 使用异步方法获取工具tools = await mcp_client.alist_tools('metaso')# 使用非异步方法获取工具tools = mcp_client.list_tools('metaso')# 查看工具定义的格式[item.model_dump() for item in tools]

MCP工具定义格式如下:

[{'name': 'metaso_web_search',  'title': None,  'description': '根据关键词搜索网页、文档、论文、图片、视频、播客等内容',  'inputSchema': {'type': 'object',   'properties': {'q': {'description': '搜索查询关键词', 'type': 'string'},    'size': {'description': '返回结果数量,默认10', 'type': 'integer'},    'scope': {'description': '搜索范围:webpage, document, paper, image, video, podcast',     'type': 'string'},    'includeSummary': {'description': '是否包含摘要', 'type': 'boolean'},    'includeRawContent': {'description': '是否包含原始内容', 'type': 'boolean'}},   'required': ['q']},  'outputSchema': None,  'annotations': None,  'meta': None}, {'name': 'metaso_web_reader',  'title': None,  'description': '读取指定URL的网页内容',  'inputSchema': {'type': 'object',   'properties': {'format': {'description': '输出类型:json, markdown',     'type': 'string'},    'url': {'description': '要读取的URL地址', 'type': 'string'}},   'required': ['format', 'url']},  'outputSchema': None,  'annotations': None,  'meta': None}, {'name': 'metaso_chat',  'title': None,  'description': '基于RAG的智能问答服务',  'inputSchema': {'type': 'object',   'properties': {'model': {'description': '使用的模型,默认fast', 'type': 'string'},    'message': {'description': '用户问题', 'type': 'string'}},   'required': ['message']},  'outputSchema': None,  'annotations': None,  'meta': None}]

工具格式转换

此处需要一个转换函数,将MCP工具定义,转换成OpenAI API所能够接受的格式:

def convert_tools(tools: List[Tool]):    """    将MCP Server的list tools获取到的工具列表,转换为OpenAI API的可用工具列表    """    ret = []    for tool in tools:        parameters = {            "type": "object",            "properties": {},            "required": (                tool.inputSchema["required"] if"required"in tool.inputSchema else []            ),        }        properties = tool.inputSchema["properties"]        for param_name in properties:            if"type"in properties[param_name]:                param_type = properties[param_name]["type"]            elif (                "anyOf"in properties[param_name]                and len(properties[param_name]["anyOf"]) > 0            ):                param_type = properties[param_name]["anyOf"][0]["type"]            else:                param_type = "string"            parameters["properties"][param_name] = {                "type": param_type,            }            parameters["properties"][param_name].update(properties[param_name])        ret.append(            {                "type": "function",                "function": {                    "name": tool.name,                    "description": tool.description,                    "parameters": parameters,                },            }        )    return ret

转换后的工具格式如下:

[{'type': 'function','function': {'name': 'metaso_web_search',   'description': '根据关键词搜索网页、文档、论文、图片、视频、播客等内容',   'parameters': {'type': 'object',    'properties': {'q': {'type': 'string', 'description': '搜索查询关键词'},     'size': {'type': 'integer', 'description': '返回结果数量,默认10'},     'scope': {'type': 'string',      'description': '搜索范围:webpage, document, paper, image, video, podcast'},     'includeSummary': {'type': 'boolean', 'description': '是否包含摘要'},     'includeRawContent': {'type': 'boolean', 'description': '是否包含原始内容'}},    'required': ['q']}}}, {'type': 'function','function': {'name': 'metaso_web_reader',   'description': '读取指定URL的网页内容',   'parameters': {'type': 'object',    'properties': {'format': {'type': 'string',      'description': '输出类型:json, markdown'},     'url': {'type': 'string', 'description': '要读取的URL地址'}},    'required': ['format', 'url']}}}, {'type': 'function','function': {'name': 'metaso_chat',   'description': '基于RAG的智能问答服务',   'parameters': {'type': 'object',    'properties': {'model': {'type': 'string', 'description': '使用的模型,默认fast'},     'message': {'type': 'string', 'description': '用户问题'}},    'required': ['message']}}}]

调用工具

await mcp_client.acall_tool('metaso', 'metaso_web_search', {'q': '介绍一下DeepSeek-3.2-Exp', 'size': 4})

结果如下:

CallToolResult(meta=None, content=[TextContent(type='text', text='{"credits":3,"searchParameters":{"conciseSnippet":false,"format":"chat_completions","includeRawContent":false,"includeSummary":false,"q":"介绍一下DeepSeek-3.2-Exp","scope":"webpage","size":4},"total":27,"webpages":[{"authors":["黄心怡"],"date":"2025年09月29日","docId":"bfdf167f-bb4f-46e9-806d-af2daf9323f7","link":"https://finance.sina.com.cn/roll/2025-09-29/doc-infseiwf8257449.shtml","position":1,"score":"high","snippet":"!\\n[](https://n.sinaimg.cn/sinakd20250929s/125/w640h285/20250929/e229-6c4371c90d165930ef391cb1e279019a.png)\\n为了评估引入稀疏注意力机制的影响,DeepSeek方面特意将DeepSeek-V3.2-Exp的训练配置与V3.1-Terminus进行了对齐。\\n在各个领域的公开基准测试中,DeepSeek-V3.2-Exp的表现与V3.1-Terminus相当。\\n!\\n[](https://n.sinaimg.cn/sinakd20250929s/516/w640h676/20250929/cce0-0a6934e05c8471dedb3cffa2e3e3129b.png)\\nDeepSeek方面称,在新模型的研究过程中,需要设计和实现很多新的GPU算子。","title":"DeepSeek-V3.2-Exp 正式发布并开源"},{"date":"2025年09月29日","docId":"e17377bc-21d6-405a-8afb-9293671894b7","link":"https://www.sohu.com/a/939975717_121956424","position":2,"score":"high","snippet":"在人工智能和自然语言处理领域,技术的不断进步为我们带来了新的可能性。\\n今日(9月29日),DeepSeek正式发布了其全新的实验性版本——DeepSeek-V3.2-Exp。\\n这一版本不仅是对前一代V3.1-Terminus的更新,更是一次技术革新,标志着稀疏注意力机制在长文本处理上的新探索。\\n1. 深入了解DeepSeek-V3.2-Exp\\nDeepSeek-V3.2-Exp模型的推出,意味着在长文本的训练和推理效率方面,DeepSeek团队迈出了重要的一步。","title":"DeepSeek-V3.2-Exp"},{"date":"2025年09月29日","docId":"2aea422e-fe74-44bc-964c-80589fcff72e","link":"https://www.163.com/dy/article/KALDDBVM05566Y1D.html","position":3,"score":"high","snippet":"DeepSeek 果然又在过节搞事情了!\\n今天, DeepSeek-V3.2-Exp 版本正式发布。\\n该版本是基于公司此前发布的 DeepSeek-V3.1-Terminus 模型,升级而来。\\n版本命名中,Exp 是“实验版”的意思。\\n作为其下一代架构探索的关键中间步骤,新模型的核心亮点在于,引入了自主研发的 DeepSeek Sparse Attention (DSA) 稀疏注意力机制,以大幅优化长文本处理的训练和推理效率。","title":"DeepSeek-V3.2-Exp 版本正式发布"},{"authors":["陈骏达"],"date":"2023年09月30日","docId":"9a12ca13-1eb5-4c16-bfb7-8e08c4c413f3","link":"https://news.qq.com/rain/a/20250930A033C800","position":4,"score":"high","snippet":"结语:DeepSeek迈向新一代架构\\n正如其名字内的Exp(实验版)所言,DeepSeek-V3.2-Exp的推出,本身并不是一次性能爆表的升级,而更像是一场架构实验,展示了一种在长文本处理中兼顾性能和效率的新路径。\\n作为技术原型,DeepSeek-V3.2-Exp背后的DSA机制或许很快就会得到进一步完善。\\n随着相关技术的持续优化和更多企业、研究者参与验证,DeepSeek有望在不久的未来交出更令人惊喜的成果。","title":"DeepSeek离下一代架构,又近了一步!"}]}', annotations=None, meta=None, description=None)], structuredContent=None, isError=False, error=None)

最后

为什么要学AI大模型

当下,⼈⼯智能市场迎来了爆发期,并逐渐进⼊以⼈⼯通⽤智能(AGI)为主导的新时代。企业纷纷官宣“ AI+ ”战略,为新兴技术⼈才创造丰富的就业机会,⼈才缺⼝将达 400 万!

DeepSeek问世以来,生成式AI和大模型技术爆发式增长,让很多岗位重新成了炙手可热的新星,岗位薪资远超很多后端岗位,在程序员中稳居前列。

在这里插入图片描述

与此同时AI与各行各业深度融合,飞速发展,成为炙手可热的新风口,企业非常需要了解AI、懂AI、会用AI的员工,纷纷开出高薪招聘AI大模型相关岗位。
在这里插入图片描述
最近很多程序员朋友都已经学习或者准备学习 AI 大模型,后台也经常会有小伙伴咨询学习路线和学习资料,我特别拜托北京清华大学学士和美国加州理工学院博士学位的鲁为民老师给大家这里给大家准备了一份涵盖了AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频 全系列的学习资料,这些学习资料不仅深入浅出,而且非常实用,让大家系统而高效地掌握AI大模型的各个知识点。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

AI大模型系统学习路线

在面对AI大模型开发领域的复杂与深入,精准学习显得尤为重要。一份系统的技术路线图,不仅能够帮助开发者清晰地了解从入门到精通所需掌握的知识点,还能提供一条高效、有序的学习路径。

img

但知道是一回事,做又是另一回事,初学者最常遇到的问题主要是理论知识缺乏、资源和工具的限制、模型理解和调试的复杂性,在这基础上,找到高质量的学习资源,不浪费时间、不走弯路,又是重中之重。

AI大模型入门到实战的视频教程+项目包

看视频学习是一种高效、直观、灵活且富有吸引力的学习方式,可以更直观地展示过程,能有效提升学习兴趣和理解力,是现在获取知识的重要途径

在这里插入图片描述
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
在这里插入图片描述

海量AI大模型必读的经典书籍(PDF)

阅读AI大模型经典书籍可以帮助读者提高技术水平,开拓视野,掌握核心技术,提高解决问题的能力,同时也可以借鉴他人的经验。对于想要深入学习AI大模型开发的读者来说,阅读经典书籍是非常有必要的。
在这里插入图片描述

600+AI大模型报告(实时更新)

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。
在这里插入图片描述

AI大模型面试真题+答案解析

我们学习AI大模型必然是想找到高薪的工作,下面这些面试题都是总结当前最新、最热、最高频的面试题,并且每道题都有详细的答案,面试前刷完这套面试题资料,小小offer,不在话下
在这里插入图片描述

在这里插入图片描述

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值