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

功能简介

本文基于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 挺火,那天在官网看完快速入门 , 提供的样例是一个查询天气工具。 写完之后觉得挺有意思,但是好像有点不实用。

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

模拟请求

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

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

测试一下获取一个名为 appleSVG图标

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')

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

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

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

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"
      } */
    ]
    }
  }
)

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

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

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

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

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

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

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

我们试一下获取一个 名为bananaSVG图标

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

调用成功! 目前已经实现了第一个功能: 根据提供的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工具。然后试一下,让他帮我更换一个图标。

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

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

总结拓展

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

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

Tools - Model Context Protocol

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

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

创作不易,点赞收藏

欢迎各位指导学习

相关推荐
yi念zhi间2 天前
如何把ASP.NET Core WebApi打造成Mcp Server
后端·ai·mcp
杨浦老苏3 天前
MCPHub:一站式MCP服务器聚合平台
人工智能·docker·ai·群晖·mcp
伊织code3 天前
AWS MCP Servers
服务器·python·ai·云计算·aws·mcp
Generalzy3 天前
Model Context Protocol (MCP)笔记
笔记·ai·mcp
极小狐3 天前
如何创建并使用极狐GitLab 项目访问令牌?
数据库·ci/cd·gitlab·devops·mcp
gs801406 天前
MCP智能体多Agent协作系统设计(Multi-Agent Cooperation)
人工智能·mcp
liaowenxiong7 天前
修改或禁用Cursor的全局搜索默认快捷键
cursor
斯普信专业组8 天前
AI Agent新范式:FastGPT+MCP协议实现工具增强型智能体构建
人工智能·智能体·mcp
老马啸西风8 天前
敏感词 v0.25.0 新特性之 wordCheck 策略支持用户自定义
人工智能·ai·nlp·中文分词·openai·deepseek·mcp
Bruce_Liuxiaowei8 天前
Cherry Studio的MCP协议集成与应用实践:从本地工具到云端服务的智能交互
服务器·ai·mcp