让 Cursor 教我写 MCP Client
文章目录
- 1. 写在最前面
- 2. 动手实现一个 MCP Client
- 2.1 How 天气查询 Client
- 2.1.1 向 Cursor 提问的艺术
- 2.1.2 最终成功展示
- 2.1.3 client 的代码
- 3. MCP 协议核心之一总结
- 3.1 SSE vs WebSocket
- 4. 碎碎念
- 5. 参考资料
1. 写在最前面
学习了 MCP Server 的实现后,刚好趁着今天又是提测的间隙,抽点时间学习一下 MCP Client 的实现。
注:千万不能让自己成为,你试一下……,你再试一下的开发……。
2. 动手实现一个 MCP Client
之前的文章中有介绍过如何实现一个 MCP Server。那这个 MCP Client 的就简单的实现为调用之前的天气查询的 MCP Server 吧。
2.1 How 天气查询 Client
考虑到 cursor 没有办法查询非当前工作区的内容,笔者只能采用最原始的方式将 MCP Server 的源码拷贝过来,直接在 MCP Client 的项目中进行使用。
2.1.1 向 Cursor 提问的艺术
经过这阶段的 Cursor 使用下来,笔者最深刻的一个感受就是提问的内容一定要具体,具体成伪代码为最佳。
错误示例:
问:你能帮我实现一个 mcp 的 client 吗?
答:其他省略,请看总结
注:明显这个解法是没有办法调用之前的天气查询的 MCP Server 的。
正确示例:
问:localhost:8080 地址的 mcp server ,调用 server 代码如下 package main ……
答:
注:这个回答就很不错了,基本符合笔者最初的构想,但是美中不足的是,还是再修复一次错误。
错误修复过程:
不得不感叹于 Cursor 强大的能力,三个问题,就实现了对天气查询 MCP Server 的调用。
2.1.2 最终成功展示
以下是 MCP 的 Client 在调用 Server 的效果展示:
2.1.3 client 的代码
mcp client 实现的代码结构:
-> mcp_client tree
.
├── README.md
├── go.mod
├── go.sum
├── main.go
├── mcp_client
└── pkg└── client└── client.go2 directories, 6 files
client.go 的实现:
package clientimport ("bytes""context""encoding/json""fmt""io""net/http""sync"
)// MCPResponse 定义 MCP 响应格式
type MCPResponse struct {Type string `json:"type"`Content interface{} `json:"content"`
}// MCPToolResponse 定义工具响应格式
type MCPToolResponse struct {Name string `json:"name"`Parameters interface{} `json:"parameters,omitempty"`Response interface{} `json:"response,omitempty"`Error string `json:"error,omitempty"`
}// Client 代表 MCP 客户端
type Client struct {serverAddr stringhttpClient *http.Clientconnected boolmu sync.RWMutex
}// NewClient 创建一个新的 MCP 客户端实例
func NewClient(serverAddr string) *Client {return &Client{serverAddr: serverAddr,httpClient: &http.Client{},}
}// Connect 连接到 MCP 服务器
func (c *Client) Connect(ctx context.Context) error {c.mu.Lock()defer c.mu.Unlock()// 检查健康状态resp, err := c.httpClient.Get(fmt.Sprintf("http://%s/health", c.serverAddr))if err != nil {return fmt.Errorf("服务器连接失败: %v", err)}defer resp.Body.Close()if resp.StatusCode != http.StatusOK {return fmt.Errorf("服务器状态异常: %s", resp.Status)}c.connected = truereturn nil
}// GetWeather 获取指定城市的天气信息
func (c *Client) GetWeather(ctx context.Context, city string) (*MCPToolResponse, error) {payload := map[string]interface{}{"tool": "get_weather","parameters": map[string]string{"city": city,},}jsonData, err := json.Marshal(payload)if err != nil {return nil, fmt.Errorf("序列化请求失败: %v", err)}req, err := http.NewRequestWithContext(ctx, "POST", fmt.Sprintf("http://%s/sse/invoke", c.serverAddr), bytes.NewBuffer(jsonData))if err != nil {return nil, fmt.Errorf("创建请求失败: %v", err)}req.Header.Set("Content-Type", "application/json")resp, err := c.httpClient.Do(req)if err != nil {return nil, fmt.Errorf("发送请求失败: %v", err)}defer resp.Body.Close()body, err := io.ReadAll(resp.Body)if err != nil {return nil, fmt.Errorf("读取响应失败: %v", err)}var response MCPToolResponseif err := json.Unmarshal(body, &response); err != nil {return nil, fmt.Errorf("解析响应失败: %v", err)}return &response, nil
}// Status 获取服务器状态
func (c *Client) Status(ctx context.Context) (string, error) {c.mu.RLock()defer c.mu.RUnlock()if !c.connected {return "未连接", nil}resp, err := c.httpClient.Get(fmt.Sprintf("http://%s/health", c.serverAddr))if err != nil {return "", fmt.Errorf("检查服务器状态失败: %v", err)}defer resp.Body.Close()if resp.StatusCode == http.StatusOK {return fmt.Sprintf("已连接到 %s,服务器状态正常", c.serverAddr), nil}return fmt.Sprintf("已连接到 %s,但服务器状态异常: %s", c.serverAddr, resp.Status), nil
}// Close 关闭客户端连接
func (c *Client) Close() error {c.mu.Lock()defer c.mu.Unlock()c.connected = falsereturn nil
}
main.go 的实现:
package mainimport ("context""fmt""os""os/signal""syscall""example/mcp_client/pkg/client""github.com/spf13/cobra"
)var (serverAddr stringmcpClient *client.Client
)var rootCmd = &cobra.Command{Use: "mcp-client",Short: "MCP Client - A command line tool for interacting with MCP server",Long: `MCP Client is a command line tool that allows you to interact with the MCP (Master Control Program) server.
It provides various commands for managing and monitoring your MCP resources.`,PersistentPreRun: func(cmd *cobra.Command, args []string) {mcpClient = client.NewClient(serverAddr)ctx := context.Background()if err := mcpClient.Connect(ctx); err != nil {fmt.Printf("连接服务器失败: %v\n", err)os.Exit(1)}},
}var statusCmd = &cobra.Command{Use: "status",Short: "Check the status of MCP server",RunE: func(cmd *cobra.Command, args []string) error {status, err := mcpClient.Status(context.Background())if err != nil {return err}fmt.Println(status)return nil},
}var weatherCmd = &cobra.Command{Use: "weather [city]",Short: "Get weather information for a city",Args: cobra.MaximumNArgs(1),RunE: func(cmd *cobra.Command, args []string) error {city := "北京" // 默认城市if len(args) > 0 {city = args[0]}response, err := mcpClient.GetWeather(context.Background(), city)if err != nil {return err}if response.Error != "" {return fmt.Errorf("获取天气信息失败: %s", response.Error)}// 格式化输出天气信息fmt.Printf("%s的天气信息:\n", city)fmt.Printf("%+v\n", response.Response)return nil},
}func init() {rootCmd.PersistentFlags().StringVarP(&serverAddr, "server", "s", "localhost:8080", "MCP server address")rootCmd.AddCommand(statusCmd)rootCmd.AddCommand(weatherCmd)
}func main() {// 设置信号处理sigChan := make(chan os.Signal, 1)signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)// 在后台监听信号go func() {<-sigChanfmt.Println("\n正在关闭客户端...")if err := mcpClient.Close(); err != nil {fmt.Printf("关闭客户端时发生错误: %v\n", err)}os.Exit(0)}()if err := rootCmd.Execute(); err != nil {fmt.Println(err)os.Exit(1)}
}
3. MCP 协议核心之一总结
笔者通过动手实现一对极简的 MCP Server 和 MCP Client 发现,SSE(Server-Sent Events)协议应该是 MCP 协议实现的基石之一。
于是,笔者翻阅了 MCP 的官方文档说明,得出 — MCP includes two standard transport implementations:
-
Standard Input/Output (stdio)
-
Server-Sent Events (SSE)
3.1 SSE vs WebSocket
笔者看到 SSE 协议是基于 http 实现的,不仅好奇 SSE 跟 WebSocket 协议的区别:
Curosr 总结的 SSE 和 WebSocket 的对比:
-
通信方式:
-
SSE:单向通信,服务器向客户端推送数据
-
WebSocket:双向通信,客户端和服务器都可以主动发送数据
-
-
协议:
-
SSE:基于 HTTP,使用标准的 HTTP 连接
-
WebSocket:独立的协议,使用 ws:// 或 wss:// 协议前缀
-
-
连接特性:
-
SSE:自动重连,浏览器实现了断线重连机制
-
WebSocket:需要手动实现重连逻辑
-
-
数据格式:
-
SSE:仅支持文本数据(UTF-8)
-
WebSocket:支持文本和二进制数据
-
-
使用场景:
-
SSE:适用于服务器到客户端的实时通知、事件流
-
WebSocket:适用于需要低延迟、高频率双向通信的场景
-
-
兼容性:
-
SSE:较好的浏览器兼容性,但IE不支持
-
WebSocket:现代浏览器都支持
-
4. 碎碎念
做人不能太贪心,就像一口没办法吃成一个胖子,知识也不是一天就能学完的,那就慢慢积累着吧!
-
不妨大胆点,反正没人能活着离开这个世界。
-
突然觉得自己真的很好,有点小漂亮,善解人意,懂得换位思考,分享欲比较旺盛,三观正,待人真诚,自愈能力也强。即使自己情绪不好陷入内耗也还是会倾听他人的烦恼开导别人。能在无数次的崩溃中慢慢自愈,能在滥情的世界里始终保持清醒,但别否定和怀疑自己啦,你真的超棒的。
5. 参考资料
- Transports - Model Context Protocol
相关文章:

让 Cursor 教我写 MCP Client
文章目录 1. 写在最前面2. 动手实现一个 MCP Client2.1 How 天气查询 Client2.1.1 向 Cursor 提问的艺术2.1.2 最终成功展示2.1.3 client 的代码 3. MCP 协议核心之一总结3.1 SSE vs WebSocket 4. 碎碎念5. 参考资料 1. 写在最前面 学习了 MCP Server 的实现后,刚好…...

反射, 注解, 动态代理
文章目录 单元测试什么是单元测试咱们之前是如何进行单元测试的? 有啥问题 ?现在使用方法进行测试优点Junit单元测试的使用步骤删除不需要的jar包总结 反射认识反射、获取类什么是反射反射具体学什么?反射第一步:或者Class对象 获…...

vue vite 无法热更新问题
一、在vue页面引入组件CustomEmployeesDialog,修改组件CustomEmployeesDialog无法热更新 引入方式: import CustomEmployeesDialog from ../dialog/customEmployeesDialog.vue 目录结构: 最后发现是引入import时,路径大小写与目…...
【CustomPagination:基于Vue 3与Element Plus的高效二次封装分页器】
CustomPagination:基于Vue 3与Element Plus的高效二次封装分页器 在现代Web应用开发中,分页是处理大量数据列表时不可或缺的功能。Element Plus等UI库提供了基础的分页组件,但在大型项目中,为了追求极致的用户体验和视觉统一&…...
kafka connect 大概了解
kafka connect Introduction Kafka Connect is the component of Kafka that provides data integration between databases, key-value stores, search indexes, file systems, and Kafka brokers. kafka connect 是一个框架,用来帮助集成其他系统的数据到kafka…...
UniApp 在华为三折叠屏中的适配问题与最佳解决方案(rpx 实战指南)
随着折叠屏设备的普及,如华为 Mate Xs、Mate X3 等多形态设备越来越常见,开发者在 UniApp 项目中遇到的适配问题也变得复杂。本文将聚焦一个关键问题:在三折叠屏设备上,使用 px 单位造成页面显示异常,并给出最推荐的解…...

深度学习中的查全率与查准率:如何实现有效权衡
📌 友情提示: 本文内容由银河易创AI(https://ai.eaigx.com)创作平台的gpt-4-turbo模型辅助生成,旨在提供技术参考与灵感启发。文中观点或代码示例需结合实际情况验证,建议读者通过官方文档或实践进一步确认…...
Docker从0到1:入门指南
目录 什么是DockerDocker的核心概念 容器(Container)镜像(Image)镜像层(Image Layers)Dockerfile仓库(Repository)数据卷(Volume)网络(Network) Docker架构Docker安装Docker基本命令实际应用场景Docker生态系统最佳实践常见问题 什么是Docker Docker是一个开源的应用容器引擎…...

Windows玩游戏的时候,一按字符键就显示桌面
最近打赛伯朋克 2077 的时候,不小心按错键了,导致一按字符键就显示桌面。如下: 一开始我以为是输入法的问题(相信打游戏的人都知道输入法和奔跑键冲突的时候有多烦),但是后来解决半天发现并不是。在网上搜…...

Gemini 2.5 Flash和Pro预览版价格以及上下文缓存的理解
Gemini 2.5 Flash和Pro预览版价格 Gemini 2.5 Flash 预览版就是 Google 的最新 AI 大模型,能处理巨量内容。可以免费体验,但有次数和功能上的限制;付费层级才开放全部高级功能。价格也比传统 API 略有不同,尤其在“思考预算”“上…...

vue2 头像上传+裁剪组件封装
背景:最近在进行公司业务开发时,遇到了头像上传限制尺寸的需求,即限制为一寸证件照(宽295像素,高413像素)。 用到的第三方库: "vue-cropper": "^0.5.5" 完整组件代码&…...
unity 鼠标更换指定图标
1.准备两张图 要求图片导入设置如下 将 Texture Type 改为 Cursor 确保 Read/Write Enabled 已勾选 取消勾选 Generate Mip Maps 将 Filter Mode 设为 Point (保持清晰边缘) 将 Compression 设为 None (无压缩) 2.创建脚本,把脚本挂到场景中 ,该…...

AI-02a5a5.神经网络-与学习相关的技巧-权重初始值
权重的初始值 在神经网络的学习中,权重的初始值特别重要。实际上,设定什么样的权重初始值,经常关系到神经网络的学习能否成功。 不要将权重初始值设为 0 权值衰减(weight decay):抑制过拟合、提高泛化能…...

【springcloud学习(dalston.sr1)】Eureka单个服务端的搭建(含源代码)(三)
该系列项目整体介绍及源代码请参照前面写的一篇文章【springcloud学习(dalston.sr1)】项目整体介绍(含源代码)(一) 这篇文章主要介绍单个eureka服务端的集群环境是如何搭建的。 通过前面的文章【springcloud学习(dalston.sr1)】…...

Node.js数据抓取技术实战示例
Node.js常用的库有哪些呢?比如axios或者node-fetch用来发送HTTP请求,cheerio用来解析HTML,如果是动态网页的话可能需要puppeteer这样的无头浏览器。这些工具的组合应该能满足大部分需求。 然后,可能遇到的难点在哪里?…...
[架构之美]Spring Boot集成MyBatis-Plus高效开发(十七)
[架构之美]Spring Boot集成MyBatis-Plus高效开发(十七) 摘要:本文通过图文代码实战,详细讲解Spring Boot整合MyBatis-Plus全流程,涵盖代码生成器、条件构造器、分页插件等核心功能,助你减少90%的SQL编写量…...

windows10 安装 QT
本地环境有个qt文件,这里是5.14.2 打开一个cmd窗口并指定到该文件根目录下 .\qt-opensource-windows-x86-5.14.2.exe --mirror https://mirrors.ustc.edu.cn/qtproject 执行上面命令 记住是文件名,记住不要傻 X的直接复制,是你的文件名 点击…...

WordPress 和 GPL – 您需要了解的一切
如果您使用 WordPress,GPL 对您来说应该很重要,您也应该了解它。查看有关 WordPress 和 GPL 的最全面指南。 您可能听说过 GPL(通常被称为 WordPress 的权利法案),但很可能并不完全了解它。这是有道理的–这是一个复杂…...
计算机网络:什么是计算机网络?它的定义和组成是什么?
计算机网络是指通过通信设备和传输介质,将分布在不同地理位置的计算机、终端设备及其他网络设备连接起来,实现资源共享、数据传输和协同工作的系统。其核心目标是使设备之间能够高效、可靠地交换信息。 关键组成部分 硬件设备 终端设备:如计算…...

C++书本摆放 2024年信息素养大赛复赛 C++小学/初中组 算法创意实践挑战赛 真题详细解析
目录 C++书本摆放 一、题目要求 1、编程实现 2、输入输出 二、算法分析 三、程序编写 四、运行结果 五、考点分析 六、 推荐资料 1、C++资料 2、Scratch资料 3、Python资料 C++书本摆放 2024年信息素养大赛 C++复赛真题 一、题目要求 1、编程实现 中科智慧科技…...
在scala中使用sparkSQL读入csv文件
以下是使用 Spark SQL(Scala)读取 CSV 文件的完整代码示例: scala import org.apache.spark.sql.SparkSession import org.apache.spark.sql.types._object CSVReadExample {def main(args: Array[String]): Unit {// 创建SparkSessionval…...

RabbitMQ 核心概念与消息模型深度解析(一)
一、RabbitMQ 是什么 在当今分布式系统盛行的时代,消息队列作为一种至关重要的中间件技术,扮演着实现系统之间异步通信、解耦和削峰填谷等关键角色 。RabbitMQ 便是消息队列领域中的佼佼者,是一个开源的消息代理和队列服务器,基于…...

论文阅读笔记——双流网络
双流网络论文 视频相比图像包含更多信息:运动信息、时序信息、背景信息等等。 原先处理视频的方法: CNN LSTM:CNN 抽取关键特征,LSTM 做时序逻辑;抽取视频中关键 K 帧输入 CNN 得到图片特征,再输入 LSTM&…...
思路解析:第一性原理解 SQL:连接(JOIN)
目录 题目描述 🎯 应用第一性原理来思考这个 SQL 题目 ✅ 第一步:还原每个事件的本质单位 ✅ 第二步:如果一个表只有事件,如何构造事件对? ✅ 第三步:加过滤条件,只保留“同一机器、同一进…...
Java面向对象三大特性深度解析
Java面向对象三大特性封装继承多态深度解析 前言一、封装:数据隐藏与访问控制的艺术1.1 封装的本质与作用1.2 封装的实现方式1.2.1 属性私有化与方法公开化1.2.2 封装的访问修饰符 二、继承:代码复用与类型扩展的核心机制2.1 继承的定义与语法2.2 继承的…...

LabVIEW在电子电工教学中的应用
在电子电工教学领域,传统教学模式面临诸多挑战,如实验设备数量有限、实验过程存在安全隐患、教学内容更新滞后等。LabVIEW 作为一款功能强大的图形化编程软件,为解决这些问题提供了创新思路,在电子电工教学的多个关键环节发挥着重…...

Vue3 怎么在ElMessage消息提示组件中添加自定义icon图标
1、定义icon组件代码: <template><svg :class"svgClass" aria-hidden"true"><use :xlink:href"iconName" :fill"color"/></svg> </template><script> export default defineComponen…...

生活破破烂烂,AI 缝缝补补(附提示词)
写在前面:【Fire 计算器】已上线,快算算财富自由要多少 现实不总温柔,愿你始终自渡。 请永远拯救自己于水火之中。 毛绒风格提示词(供参考): 1. 逼真毛绒风 Transform this image into a hyperrealist…...

张 。。 通过Token实现Loss调优prompt
词编码模型和 API LLM不匹配,采用本地模型 理性中性案例(针对中性调整比较合理) 代码解释:Qwen2模型的文本编码与生成过程 这段代码展示了如何使用Qwen2模型进行文本的编码和解码操作。 模型加载与初始化 from transformers import AutoModelForCausalLM, AutoTokenizer...
Ubuntu 22.04.5 LTS上部署Docker及相关优化
以下是在Ubuntu 22.04.5 LTS上部署Docker及相关优化的步骤: 安装Docker 更新系统:在安装Docker之前,先确保系统是最新的,执行以下命令:sudo apt update sudo apt upgrade -y安装依赖包:安装一些必要的依赖…...