首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

写个MCP服务让Cursor帮我去找SVG图标(iconfont)【入门】

  • 25-04-18 18:01
  • 3487
  • 6118
juejin.cn

功能简介

本文基于ModelContextProtocol的服务端工具,实现了两个功能。

  1. 实现一个工具get-svg: 根据用户提供的SVG名称svgName, 从 IconFont 网站获取 SVG 图标。
  2. 实现一个工具change-svg: 当使用get-svg获取的SVG图标不合适的时候,根据svgName重新获取一个新的SVG图标

本文基于和参考了官方文档获取天气的示例, 建议先学习和了解 对于服务器开发人员 - 模型上下文协议 --- For Server Developers - Model Context Protocol

代码仓库

mcp_svg_search: MCP服务,提供两个工具让Cursor可以去iconfont找SVG图标,不满意时可以再更换。

想法来源

最近 MCP 挺火,那天在官网看完快速入门, 提供的样例是一个查询天气的工具。 写完之后觉得挺有意思,但是好像有点不实用。

v2-ceffeea02ea38faed7b4df79559ea456_b.jpg

突然想到我们做前端的, 经常需要去iconfont找SVG图标, 我能不能让 Cursor 支持这个功能。 而且在实现上来说和查询天气没有什么本质上的区别。 说干就干,开写。

模拟请求

首先,和获取天气一样,我们要有一个查询获取SVG图标的接口, 扒了一下 iconfont 搜索图标的接口, 把请求头和请求参数给灵码和 Cursor, 让他们帮我用 node-fetch 模拟一下搜索的请求。( 灵码也是阿里系的好像有点手足相残了。)

注意: 技术用于服务自身,分享用于学习交流, 请不要用于非法用途。

测试一下获取一个名为 apple 的SVG图标。

js
代码解读
复制代码
import fetch from 'node-fetch'; // icon font 网站的cookie const cookie = `` // icon font 网站的token const ctoken = `` // 根据svgName获取svg async function getSVGData(svgName){ try { // 请求头 const headers = { accept: 'application/json, text/javascript, */*; q=0.01', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'bx-v': '2.5.28', 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', cookie: cookie, origin: 'https://www.iconfont.cn', pragma: 'no-cache', referer: `https://www.iconfont.cn/search/index?searchType=icon&q=${svgName}&page=1&fromCollection=-1`, 'sec-ch-ua': '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0', 'x-csrf-token': ctoken, 'x-requested-with': 'XMLHttpRequest' }; // 表单数据 const formData = new URLSearchParams({ q: `${svgName}`, sortType: 'updated_at', page: '1', pageSize: '54', sType: '', fromCollection: '-1', fills: '', t: `${(new Date()).getTime()}`, ctoken: ctoken }).toString(); // 发送请求 const response = await fetch('https://www.iconfont.cn/api/icon/search.json', { method: 'POST', headers: headers, body: formData }) const resJSON = await response.json() ; console.log(resJSON); if(resJSON?.code === 200) { return resJSON.icons?.[0] } else { throw new Error(`Failed to fetch svg data: ${response.statusText}`); } } catch (error) { console.error(error); return null; } } getSVGData('apple')

此处的cookies和ctoken, 请使用你自己的,不知道的话F12看一下就知道了。

image.png

看了一下控制台, 输出成功。模拟请求已成功。

image.png

我们再改造一下,符合实际的业务需求, 以下是最终版本。

js
代码解读
复制代码
/** * @description 根据svg图标名称获取svg * @param svgName 获取的svgName * @param ids 已经获取过的id组合, 以 - 分隔 * @returns 返回svg的信息 */ async function getSvgIcon(svgName, ids = undefined) { try { // 模拟请求,如果请求不行,请自行修改 const headers = { accept: 'application/json, text/javascript, */*; q=0.01', 'accept-encoding': 'gzip, deflate, br, zstd', 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6', 'bx-v': '2.5.28', 'cache-control': 'no-cache', 'content-type': 'application/x-www-form-urlencoded; charset=UTF-8', cookie: cookie, origin: 'https://www.iconfont.cn', pragma: 'no-cache', referer: `https://www.iconfont.cn/search/index?searchType=icon&q=${svgName}&page=1&fromCollection=-1`, 'sec-ch-ua': '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"', 'sec-ch-ua-mobile': '?0', 'sec-ch-ua-platform': '"Windows"', 'sec-fetch-dest': 'empty', 'sec-fetch-mode': 'cors', 'sec-fetch-site': 'same-origin', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0', 'x-csrf-token': ctoken, 'x-requested-with': 'XMLHttpRequest' }; // 表单数据 const formData = new URLSearchParams({ q: `${svgName}`, sortType: 'updated_at', page: '1', pageSize: '54', sType: '', fromCollection: '-1', fills: '', t: `${(new Date()).getTime()}`, ctoken: ctoken }).toString(); // 发送请求 const response = await fetch('https://www.iconfont.cn/api/icon/search.json', { method: 'POST', headers: headers, body: formData }); // 获取json const resJSON = await response.json(); if (resJSON?.code === 200) { // 如果ids不存在,就是纯粹获取 if (!ids) { return resJSON?.data.icons?.[0]; } // 当ids存在,就是换一个图标 else { // 分割ids获取id数组 const _ids = ids.split('-').map(id => { return parseInt(id); }); // 返回第一个id不包含在_ids中的icon return resJSON?.data?.icons?.filter((icon) => { return !_ids.includes(icon.id); })?.[0]; } } else { throw new Error(`Failed to fetch svg data: ${response.statusText}`); } } catch (error) { console.error(error); return null; } }

这里默认在获取的时候,返回列表的第一个页面。 增加了一个ids的参数用于对获取的SVG图标不满意的时候,调取更换svg的工具。 这个后面再讲解。

注册一个 get-svg 工具

这个工具实现了根据用户提供的SVG名称svgName, 从 IconFont 网站获取 SVG 图标。

js
代码解读
复制代码
// 根据名称获取svg图标的tool server.tool( 'get-svg', '根据svg图标名称获取svg', { svgName: z.string().describe('svg图标名称'), }, async ({ svgName }) => { const svg = await getSvgIcon<{ id: number; name: string; status: number; is_private: number; category_id: string; slug: string; unicode: string; width: number; height: number; defs: null | string; // 假设defs可以为null或字符串 path_attributes: string; fills: number; font_class: string; user_id: number; repositorie_id: number; created_at: string; updated_at: string; svg_hash: string; svg_fill_hash: string; fork_from: null | number; // 假设fork_from可以为null或数字 deleted_at: null | string; // 假设deleted_at可以为null或字符串 show_svg: string; }>(svgName); if (!svg) { return { content: [{ type: 'text', text: '获取svg失败,请检查svg名称是否正确' }] } } const formattedSvg = ` svg图标名称: ${svg.name}, show_svg: ${svg.show_svg}, path_attributes: ${svg.path_attributes}, id: ${svg.id}, ids: ${svg.id} `; /* // 目前cursor不支持type: image的消息, 后续支持可以传预览图 const buffer = await sharp(Buffer.from(svg.show_svg)).resize(128, 128) // 设置宽度和高度为128px .png().toBuffer(); const base64 = buffer.toString('base64') */ return { content: [{ type: 'text', text: formattedSvg }, /* { "type": "image", "data": base64, "mimeType": "image/png" } */ ] } } )

看了一下查询接口返回的数据格式

image.png

可以看到show_svg就是我们要的svg内容, 复制一下请求返回值,然后让灵码帮我写了一下TS类型。 返回给Cursor的内容只取了svg图标名称、 show_svg(svg元素)、 path_attributes(路径参数)、 id、 ids(已获取过的svg图标id集合字符串,以 “-” 拼接,用于后续重新获取功能)。需要其他参数的可以自行增加。

改造完后就可以试一下了,运行一下打包命令pnpm build, 再配置一下Cursor的MCP (此处在官方文档有详解,大概就是编译成JS文件,然后配置MCP指向该文件)

image.png

此处由于我环境安装了fnm, 所以命令比较不一样。 正常情况下, 直接以下命令应该即可:

js
代码解读
复制代码
node D:\\MyProject\\svgSearch\\build\\index.js // 或者 npx D:\\MyProject\\svgSearch\\build\\index.js

此处的路径置换为你系统内的打包路径。

配置保存好后,看到 MCP 服务显示正常,tools 显示正常(此处显示两个, 有一个稍后实现)。

我们试一下获取一个 名为banana 的 SVG图标:

image.png

看到已经触发了MCP的 tool, 我们点击 Run tool

image.png

调用成功! 目前已经实现了第一个功能: 根据提供的SVG名称到iconfont找, 然后返回第一个图标。

第二个工具 change-svg: 重新获取一个新的SVG图标

当使用get-svg获取的SVG图标不合适的时候怎么办?重新调用get-svg? 但是逻辑上已经写死返回列表的第一个, 重新调用也是获取同一个。 因此我们再写一个工具去重新获取一个不同的SVG图标: change-svg

js
代码解读
复制代码
// 根据ids和svg图标名称去重新获取一个新的svg server.tool('change-svg', '根据id和svg图标名称去重新获取一个新的svg', { svgName: z.string().describe('svg图标名称'), id: z.string().describe('之前获取的相同名称的svg图标的id'), ids: z.string().describe('之前获取的相同名称的svg图标的ids') }, async ({ svgName, id, ids }) => { const svg = await getSvgIcon(svgName, ids) as any; if (!svg) { return { content: [{ type: 'text', text: '获取svg失败,请检查svg名称是否正确' }] }; } const formattedSvg = ` svg图标名称: ${svg.name}, show_svg: ${svg.show_svg}, path_attributes: ${svg.path_attributes}, id: ${svg.id}, ids: ${ids}-${svg.id} `; return { content: [{ type: 'text', text: formattedSvg }] }; })

其实和get-svg没有太大的区别,关键在于使用了一个ids变量记录了所有获取过的SVG图标的id, 在返回给Cursor的时候进行了一个拼接更新。

再在调用getSVGData获取到iconfont的返回数据时候, 如果存在ids 则去做一个去重判断, 返回第一个不重复的SVG图标

js
代码解读
复制代码
// 如果ids不存在,就是纯粹获取 if (!ids) { return resJSON?.data.icons?.[0]; } // 当ids存在,就是换一个图标 else { // 分割ids获取id数组 const _ids = ids.split('-').map(id => { return parseInt(id); }); // 返回第一个id不包含在_ids中的icon return resJSON?.data?.icons?.filter((icon) => { return !_ids.includes(icon.id); })?.[0]; }

我们增加完这个工具后,再编译一下, 在Cursor刷新一下MCP工具。然后试一下,让他帮我更换一个图标。 image.png

可以看到已经触发了change-svg。我们再跑一下。

image.png

可以看到调用成功,我们可以保存一下SVG的代码看一下, 正对的应该就是下面第一第二个元素。

image.png

总结拓展

本文只是对MCP应用,官方示例的一个拓展和探索。把搜索天气更改成了搜索SVG, 体验一下工具的魅力。可能有人会说:哎呀,我叫Cursor 帮我写一个SVG图标就好了。 你也有你的道理的,我们做人不要那么执着。

5375a3d6c08015f84175db44e75e136.jpg

拓展方面,我试过能不能在工具返回的时候顺便返回SVG图标的预览图, 因为看MCP服务的文档是支持图片消息的

Tools - Model Context Protocol

061bab8ab1b86f4149bc0e9bec40ab3.png

但是我经过尝试后发现Cursor好像不支持, 那我们就不要这么执着了,静待未来吧。

image.png

另外一个拓展可能是把列表返回Cursor, 再结合用户提示去筛选可能会更好,有兴趣的可以自行实现一下。我也是刚学习,只是个想法,欢迎各位指导学习。

创作不易,点赞收藏

欢迎各位指导学习

注:本文转载自juejin.cn的赛博丁真Damon的文章"https://juejin.cn/post/7493909446185025573"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

109
人工智能
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2024 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top