当前位置: 首页 > news >正文

【MCP】从0到1实现一个MCP Client

如果连人间真爱都不知,千万年的修行还有什么用?

《青蛇》

01 MCP回顾

MCP全称Model Context Protocol,模型上下文协议。

上一篇文章【MCP】从0到1实现一个MCP Server 我们介绍了如何从0~1实现一个自定义的 MCP Server,感兴趣的朋友可以点击文章链接回顾下或者查看下。

02 MCP Client本质

为什么我们需要 MCP Client

因为我们在拿到一个 MCP Server 的时候没办法直接使用,必须结合 LLM 才能发挥这个 Server 的能力,所以我们需要一个基于 LLM 的 Client 去承接这个 Server。此时,MCP Client就出现了。

MCP Client 的本质

纵观市面上所有的,基于 LLM 的应用或者说客户端,无不是聊天对话机器人形式。再到具体的应用,比如说Cline,比如说Cursor等等,也是对话形式,coze、dify、n8n工具的本质也是对话。再往底层说,LLM 的本质就是输入输出。所以,如果我们要自定义一个 MCP Client,那必然也是对话形式。

03 快速实现简单的MCP Client

开始构建可与所有 MCP 服务器集成的,专属我们自己的客户端。在本教程中,我们将学习如何构建连接到 MCP 服务器的 LLM 支持的聊天机器人客户端。

这里我们使用 Python 实现MCP Client的编码与调用,Python版本为3.10或更高,MCP SDK 版本为 1.2.0 或更高。

官方文档建议使用 uv 作为包管理工作,那这次我就用一下吧。

这里默认各位朋友是掌握了 Python 语言的,所以我省略了环境的配置,也省略了项目的创建。我的项目长这样:

在这里插入图片描述

跟 MCP Server 的开发一样,就一个 main.py 文件,所有逻辑都在这一个文件里面。

我的代码基本逻辑来源于官方Demo:https://gist.github.com/zckly/f3f28ea731e096e53b39b47bf0a2d4b1 ,官方使用的 LLM 是Anthropic,我的代码里用的是OpenAI的SDK,做了一些微小的改动。完整代码如下:

import json
import os
import asyncio
from typing import Optional
from contextlib import AsyncExitStackfrom mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_clientfrom openai import OpenAI
from dotenv import load_dotenv# 加载.env文件中的环境变量
load_dotenv()class MCPClient:def __init__(self):# 初始化会话和客户端对象self.session: Optional[ClientSession] = None # 用于保存 MCP 客户端会话self.exit_stack = AsyncExitStack()  # 用于管理异步资源的生命周期# 初始化 OpenAI 客户端self.client = OpenAI(base_url=os.getenv("BASE_URL"),api_key=os.getenv("API_KEY"),  # 从环境变量中获取 API 密钥)async def connect_to_server(self, server_script_path: str):"""连接到 MCP 服务器参数:server_script_path: 服务器脚本路径 (.py 或 .js)"""is_python = server_script_path.endswith('.py')  # 判断是否为 Python 脚本is_js = server_script_path.endswith('.js') # 判断是否为 JavaScript 脚本if not (is_python or is_js):raise ValueError("Server script must be a .py or .js file")command = "python" if is_python else "node" # 根据文件类型选择执行命令server_params = StdioServerParameters(command=command,args=[server_script_path],env=None)# 启动标准输入输出客户端stdio_transport = await self.exit_stack.enter_async_context(stdio_client(server_params))self.stdio, self.write = stdio_transport# 创建并初始化 MCP 客户端会话self.session = await self.exit_stack.enter_async_context(ClientSession(self.stdio, self.write))await self.session.initialize() # 初始化会话# 列出可用工具response = await self.session.list_tools()tools = response.toolsprint("\nConnected to server with tools:", [tool.name for tool in tools]) # 打印可用工具名称async def process_query(self, query: str) -> str:"""处理用户查询,使用 LLM 和可用工具进行响应"""messages = [{"role": "user","content": query # 用户输入的查询内容}]# 获取可用工具列表并构造工具调用格式response = await self.session.list_tools()available_tools =[{"type": "function","function": {"name":tool.name, # 工具名称"description": tool.description,  # 工具描述"parameters": tool.inputSchema # 输入参数格式}}for tool in response.tools]# 调用 LLM 模型生成回复response = self.client.chat.completions.create(model=os.getenv("MODEL"),  # 使用的模型名称messages=messages, # 对话历史tools=available_tools # 可用工具列表)# 处理模型回复并执行工具调用(如有)tool_results = []final_text = []for choice in response.choices:message = choice.messageis_function_call = message.tool_callsif is_function_call:tool_call = message.tool_calls[0]tool_name = tool_call.function.nametool_args = json.loads(tool_call.function.arguments)# 执行工具调用result = await self.session.call_tool(tool_name, tool_args)tool_results.append({"call": tool_name, "result": result})final_text.append(f"[Calling tool {tool_name} with args {tool_args}]")  # 记录调用信息# 将工具调用结果加入对话历史if message.content and hasattr(message.content, 'text'):messages.append({"role": "assistant","content": message.content})messages.append({"role": "user","content": result.content[0].text})# 再次调用 LLM 获取最终回复response = self.client.chat.completions.create(model=os.getenv("MODEL"),messages=messages,)final_text.append(response.choices[0].message.content) # 添加模型最终回复else:final_text.append(message.content) # 直接添加模型回复return "\n".join(final_text)  # 返回最终回复内容async def chat_loop(self):"""运行交互式聊天循环"""print("\nMCP Client Started!")print("Type your queries or 'quit' to exit.")while True:try:query = input("\nQuery: ").strip() # 获取用户输入if query.lower() == 'quit': # 如果输入 quit 则退出循环breakresponse = await self.process_query(query)  # 处理用户查询print("\n" + response) # 打印回复except Exception as e:print(f"\nError: {str(e)}")async def cleanup(self):"""Clean up resources"""await self.exit_stack.aclose()  # 关闭所有异步资源async def main():if len(sys.argv) < 2:print("Usage: python main.py <path_to_server_script>")sys.exit(1)# 创建 MCP 客户端client = MCPClient()try:# 将客户端链接到 MCP Serverawait client.connect_to_server(sys.argv[1])# 开启聊天循环await client.chat_loop()finally:# 程序终止,清理资源await client.cleanup()if __name__ == "__main__":import sys# 异步运行主函数asyncio.run(main())

关键组件解释

  1. 客户端初始化
  • MCPClient 类使用会话管理和 API 客户端进行初始化
  • 使用 AsyncExitStack 进行适当的资源管理
  • 使用OpenAI sdk编写 LLM 使用逻辑
  1. 服务器连接
  • 支持 Python 和 Node.js 服务器
  • 验证服务器脚本类型
  • 建立适当的沟通渠道(控制台的输入输出)
  • 初始化会话并列出可用工具
  1. 查询处理
  • 维护对话上下文
  • 处理 OpenAI 的响应和工具调用
  • 管理 OpenAI 和 tools 之间的消息流
  • 将结果合并为连贯的响应
  1. 交互式界面
  • 提供简单的命令行界面
  • 处理用户输入并显示响应
  • 包括基本错误处理
  • 允许正常退出
  1. 资源管理
  • 正确清理资源
  • 连接问题的错误处理
  • 正常关闭过程

这里我们通过uv run main.py <path/to/server.py>运行我们的python代码。

在这里插入图片描述

当我们输入“获取alice的用户信息”,按回车键发送后,模型将调用工具,并做如下回复

04 总结

至此,我们就从 0~1 完成了一个简单的,属于我们自己的 MCP Client。

整个 client 执行逻辑如下,当我们提交查询时:

  • 客户端从服务器获取可用工具的列表
  • 您的查询将与工具描述一起发送给 LLM
  • LLM 决定使用哪些工具(如果有)
  • 客户端通过服务器执行请求的工具调用
  • 结果将发送回 LLM
  • LLM 提供自然语言响应
  • 此时将向用户显示响应

这生活已经很难了,难能可贵的是您能在百忙之中抽空阅读这些文章。如果能给到您一点小小的帮助,也是我非常喜闻乐见的😁

以上内容依据官方文档编写,官方地址:https://modelcontextprotocol.io

往期回顾

【MCP】从0到1实现一个MCP Server

新手友好!!!一图看懂Agentic Workflows(代理工作流)

DeepSeek+Cline使用教程(告别手动编码,全面拥抱AI)

【LangChain进阶教程】十五、LangChain进阶之Callbacks(完结篇)

http://www.xdnf.cn/news/317323.html

相关文章:

  • 【Python类(Class)完全指南】面向对象编程入门
  • 阿里云服务器-centos部署定时同步数据库数据-dbswitch
  • 【Django】中间件
  • 软件工程(三):模块的内聚模型
  • 如何在大型项目中解决 VsCode 语言服务器崩溃的问题
  • 政务浏览器 一站式首页功能配置说明
  • 极狐GitLab 命名空间的类型有哪些?
  • css animation 动画属性
  • 华为昇腾910B通过vllm部署InternVL3-8B教程
  • 大模型系列(五)--- GPT3: Language Models are Few-Shot Learners
  • IPFS集群部署
  • Linux/AndroidOS中进程间的通信线程间的同步 - 信号量
  • Java游戏服务器开发流水账(1)游戏服务器的架构浅析
  • Wireshark抓账号密码
  • 一文走进GpuGeek | conda常用命令
  • Prompt(提示词)工程师,“跟AI聊天”
  • Java版ERP管理系统源码(springboot+VUE+Uniapp)
  • FID和IS的区别
  • STM32裸机开发问题汇总
  • (1-1)Java的JDK、JRE、JVM三者间的关系
  • 淘宝按图搜索商品(拍立淘)爬虫实战指南
  • 技术视界|青龙机器人训练地形详解(二):添加地形到训练环境
  • 光伏“531”政策倒逼下,光储充一体化系统如何破解分布式光伏收益困局?
  • sql错题(3)
  • 学习记录:DAY23
  • 发那科机器人3(机器人编程基础)
  • Python小酷库系列:5个常用的dict属性化访问扩展库
  • Kubernetes调度技术:污点与容忍生产级应用指南
  • Selenium使用指南
  • 7.2.安全防御