Cursor Talk To Figma MCP源码漫游:从入口到插件通信
项目架构全景图
Cursor Talk To Figma MCP实现了Cursor AI与Figma之间的模型上下文协议(Model Context Protocol, MCP)集成,通过三级架构实现AI与设计工具的双向通信:
核心技术栈
| 组件 | 技术选型 | 作用 |
|---|---|---|
| MCP服务器 | TypeScript + @modelcontextprotocol/sdk | 实现AI工具通信协议 |
| WebSocket服务器 | Bun + ws | 建立实时通信通道 |
| Figma插件 | JavaScript + Figma Plugin API | 操作设计文档 |
| 构建工具 | Bun + tsup | 包管理与代码构建 |
| 类型系统 | Zod | 数据验证与接口定义 |
代码入口解析
1. 命令行入口:server.ts
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import WebSocket from "ws";
// 初始化MCP服务器
const server = new McpServer({
name: "TalkToFigmaMCP",
version: "1.0.0",
});
// 注册工具函数(后文详述)
server.tool("get_document_info", ...);
server.tool("create_rectangle", ...);
// ...更多工具注册
// 启动服务器
const transport = new StdioServerTransport();
server.serve(transport);
关键设计: 通过#!/usr/bin/env node声明为可执行文件,package.json中配置为bin入口,实现bunx cursor-talk-to-figma-mcp命令启动。
2. 通信枢纽:socket.ts
WebSocket服务器作为通信核心,采用信道(channel)机制管理多客户端连接:
// 信道管理核心数据结构
const channels = new Map<string, Set<ServerWebSocket<any>>>();
// 连接处理流程
function handleConnection(ws: ServerWebSocket<any>) {
// 1. 发送欢迎消息
ws.send(JSON.stringify({
type: "system",
message: "Please join a channel to start chatting",
}));
// 2. 处理消息路由
ws.on("message", (message) => {
const data = JSON.parse(message);
if (data.type === "join") {
joinChannel(ws, data.channel); // 加入信道
} else if (data.type === "message") {
broadcastMessage(ws, data); // 广播消息
}
});
// 3. 断开清理
ws.on("close", () => {
channels.forEach(clients => clients.delete(ws));
});
}
服务器启动流程:
const server = Bun.serve({
port: 3055,
// Windows WSL兼容性配置
// hostname: "0.0.0.0",
fetch(req, server) {
// 处理CORS预检请求
if (req.method === "OPTIONS") {
return new Response(null, {
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
},
});
}
// 升级WebSocket连接
if (server.upgrade(req)) return;
// 常规HTTP响应
return new Response("WebSocket server running");
},
websocket: {
open: handleConnection,
message: handleMessage,
close: handleClose
}
});
通信协议深度剖析
请求响应生命周期
核心数据结构
命令消息格式:
interface CommandMessage {
id: string; // UUID标识
type: string; // 命令类型
payload: object; // 命令参数
channel: string; // 目标信道
timestamp: number; // 时间戳
}
响应消息格式:
interface ResponseMessage {
id: string; // 对应命令ID
result?: any; // 成功结果
error?: { // 错误信息
message: string;
code: number;
};
timestamp: number;
}
进度更新格式:
interface CommandProgressUpdate {
type: 'command_progress';
commandId: string;
status: 'started' | 'in_progress' | 'completed';
progress: number; // 0-100
processedItems: number; // 已处理项数
totalItems: number; // 总项数
message: string; // 状态描述
}
核心功能实现
1. 组件覆盖传播系统
该功能允许将一个组件实例的覆盖属性传播到多个目标实例,大幅减少重复设计工作:
// 提取源实例覆盖属性
server.tool(
"get_instance_overrides",
"提取组件实例的覆盖属性",
{
sourceInstanceId: z.string().describe("源实例ID")
},
async ({ sourceInstanceId }) => {
const result = await sendCommandToFigma("get_instance_overrides", {
sourceInstanceId
});
return {
content: [{ type: "text", text: JSON.stringify(result) }]
};
}
);
// 应用覆盖属性到目标实例
server.tool(
"set_instance_overrides",
"将覆盖属性应用到目标实例",
{
targetInstanceIds: z.array(z.string()).describe("目标实例ID列表"),
overrides: z.object({}).describe("从get_instance_overrides获取的覆盖属性")
},
async ({ targetInstanceIds, overrides }) => {
// 分块处理大量目标实例
const CHUNK_SIZE = 10;
const results = [];
for (let i = 0; i < targetInstanceIds.length; i += CHUNK_SIZE) {
const chunk = targetInstanceIds.slice(i, i + CHUNK_SIZE);
const result = await sendCommandToFigma("set_instance_overrides", {
targetInstanceIds: chunk,
overrides
});
results.push(result);
// 发送进度更新
sendProgressUpdate({
commandId: currentCommandId,
status: 'in_progress',
progress: (i / targetInstanceIds.length) * 100,
processedItems: i,
totalItems: targetInstanceIds.length,
currentChunk: i/CHUNK_SIZE + 1,
totalChunks: Math.ceil(targetInstanceIds.length/CHUNK_SIZE),
message: `已处理 ${i} 个实例`
});
}
return {
content: [{ type: "text", text: JSON.stringify(mergeResults(results)) }]
};
}
);
2. 批量文本替换引擎
针对大型设计文件的文本更新需求,实现智能分块扫描与批量替换:
// 文本节点扫描实现
async function scanTextNodesRecursive(node: SceneNode, textNodes: TextNode[] = [], chunkSize = 50) {
// 如果是文本节点且包含内容,添加到结果
if (node.type === "TEXT" && node.characters.trim().length > 0) {
textNodes.push(node);
// 每达到chunkSize就发送进度更新
if (textNodes.length % chunkSize === 0) {
sendProgressUpdate({
commandType: "scan_text_nodes",
status: "in_progress",
processedItems: textNodes.length,
message: `已扫描 ${textNodes.length} 个文本节点`
});
}
}
// 递归处理子节点
if ("children" in node) {
for (const child of node.children) {
// 递归扫描子节点
await scanTextNodesRecursive(child, textNodes, chunkSize);
}
}
return textNodes;
}
// 批量更新实现
async function batchUpdateTextNodes(textUpdates: Array<{id: string, text: string}>, batchSize = 10) {
const results = [];
// 分批处理更新
for (let i = 0; i < textUpdates.length; i += batchSize) {
const batch = textUpdates.slice(i, i + batchSize);
const result = await sendCommandToFigma("set_multiple_text_contents", {
updates: batch
});
results.push(result);
// 发送进度更新
sendProgressUpdate({
commandType: "set_multiple_text_contents",
status: "in_progress",
progress: (i / textUpdates.length) * 100,
processedItems: i,
totalItems: textUpdates.length,
currentChunk: i/batchSize + 1,
totalChunks: Math.ceil(textUpdates.length/batchSize),
message: `已更新 ${i} 个文本节点`
});
}
return results;
}
2. 原型连接器生成
将Figma原型交互转换为可视化连接器线条,提升流程图可读性:
// 获取原型交互
server.tool(
"get_reactions",
"获取原型交互信息",
{},
async () => {
const result = await sendCommandToFigma("get_reactions");
return {
content: [{ type: "text", text: JSON.stringify(result) }]
};
}
);
// 创建连接器
server.tool(
"create_connections",
"基于原型交互创建连接器",
{
reactions: z.array(z.object({
sourceId: z.string(),
destinationId: z.string(),
description: z.string().optional()
})).describe("原型交互信息数组"),
connectorStyleId: z.string().optional().describe("连接器样式ID")
},
async ({ reactions, connectorStyleId }) => {
// 分块处理大量连接器创建
const BATCH_SIZE = 5;
const results = [];
for (let i = 0; i < reactions.length; i += BATCH_SIZE) {
const batch = reactions.slice(i, i + BATCH_SIZE);
const result = await sendCommandToFigma("create_connections", {
reactions: batch,
connectorStyleId
});
results.push(result);
// 发送进度更新
sendProgressUpdate({
commandType: "create_connections",
status: "in_progress",
progress: (i / reactions.length) * 100,
processedItems: i,
totalItems: reactions.length,
message: `已创建 ${i} 个连接器`
});
}
return {
content: [{ type: "text", text: JSON.stringify(results) }]
};
}
);
插件系统解析
Figma插件作为操作设计文档的实际执行者,通过manifest.json声明其能力:
{
"name": "Cursor MCP Plugin",
"id": "cursor-mcp-plugin",
"api": "1.0.0",
"main": "code.js",
"ui": "ui.html",
"editorType": ["figma", "figjam"],
"networkAccess": {
"allowedDomains": ["https://google.com"],
"devAllowedDomains": ["http://localhost:3055", "ws://localhost:3055"]
},
"documentAccess": "dynamic-page",
"enableProposedApi": true
}
插件核心通信逻辑:
// code.js - Figma插件入口
let ws;
let currentChannel = null;
// 建立WebSocket连接
function connectWebSocket() {
// 开发环境配置
const wsUrl = "ws://localhost:3055";
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log("WebSocket连接已建立");
showUI(); // 连接成功后显示UI
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
handleCommand(message); // 处理命令
};
ws.onerror = (error) => {
console.error("WebSocket错误:", error);
};
ws.onclose = () => {
console.log("WebSocket连接已关闭");
// 自动重连机制
setTimeout(connectWebSocket, 3000);
};
}
// 命令处理分发
function handleCommand(message) {
const { id, type, payload } = message;
try {
switch (type) {
case "get_document_info":
return sendResponse(id, figma.root.toJSON());
case "get_selection":
return sendResponse(id, figma.currentPage.selection.map(n => n.toJSON()));
case "create_rectangle":
return createRectangle(payload, id);
// ...其他命令处理
default:
return sendError(id, `未知命令: ${type}`);
}
} catch (error) {
sendError(id, error.message);
}
}
// 发送响应
function sendResponse(id, result) {
ws.send(JSON.stringify({
id,
result,
timestamp: Date.now()
}));
}
// 发送错误
function sendError(id, message) {
ws.send(JSON.stringify({
id,
error: { message },
timestamp: Date.now()
}));
}
// 启动连接
connectWebSocket();
项目构建与部署
构建流程
关键脚本解析
package.json中定义的核心脚本:
{
"scripts": {
"start": "bun run dist/server.js", // 启动MCP服务器
"socket": "bun run src/socket.ts", // 启动WebSocket服务器
"setup": "./scripts/setup.sh", // 项目初始化
"build": "tsup", // 构建项目
"build:watch": "tsup --watch", // 监视模式构建
"dev": "bun run build:watch", // 开发模式
"pub:release": "bun run build && npm publish" // 发布到npm
}
}
多环境配置
针对不同开发环境的配置策略:
| 环境 | WebSocket地址 | 构建命令 | 调试方式 |
|---|---|---|---|
| 开发环境 | ws://localhost:3055 | bun run dev | Figma开发插件 |
| 生产环境 | wss://mcp-server.example.com | bun run build | 已发布插件 |
| Windows WSL | ws://0.0.0.0:3055 | 需修改socket.ts | 本地网络配置 |
高级功能与最佳实践
1. 大型设计文件处理策略
为避免Figma插件超时和性能问题,系统实现了多层次分块处理:
2. 错误处理与重试机制
// 带重试机制的命令发送函数
async function sendCommandWithRetry(commandType: string, payload: any, retries = 3, delayMs = 1000) {
const commandId = uuidv4();
// 记录命令开始
logger.info(`发送命令: ${commandType} (ID: ${commandId})`);
return new Promise((resolve, reject) => {
// 设置超时定时器
const timeout = setTimeout(() => {
pendingRequests.delete(commandId);
reject(new Error(`命令超时: ${commandType}`));
}, 30000);
// 存储请求
pendingRequests.set(commandId, {
resolve,
reject,
timeout,
lastActivity: Date.now()
});
// 发送命令
try {
ws.send(JSON.stringify({
id: commandId,
type: commandType,
payload,
channel: currentChannel,
timestamp: Date.now()
}));
} catch (error) {
clearTimeout(timeout);
pendingRequests.delete(commandId);
// 重试逻辑
if (retries > 0) {
logger.warn(`命令发送失败,重试(${retries}): ${error.message}`);
setTimeout(() => {
sendCommandWithRetry(commandType, payload, retries - 1, delayMs * 2)
.then(resolve)
.catch(reject);
}, delayMs);
} else {
reject(error);
}
}
});
}
3. 性能优化技巧
- 选择性数据传输:过滤不必要的节点属性,减少数据传输量
function filterFigmaNode(node: any) {
// 跳过矢量图节点
if (node.type === "VECTOR") {
return null;
}
const filtered: any = {
id: node.id,
name: node.name,
type: node.type,
};
// 仅包含关键样式属性
if (node.fills && node.fills.length > 0) {
filtered.fills = node.fills.map((fill: any) => ({
type: fill.type,
color: rgbaToHex(fill.color),
opacity: fill.opacity
}));
}
// 处理文本内容
if (node.characters) {
filtered.characters = node.characters;
}
// 递归处理子节点
if (node.children) {
filtered.children = node.children
.map((child: any) => filterFigmaNode(child))
.filter((child: any) => child !== null);
}
return filtered;
}
- 颜色格式转换:统一颜色表示格式
function rgbaToHex(color: any): string {
// 跳过已为十六进制的颜色
if (color.startsWith('#')) {
return color;
}
const r = Math.round(color.r * 255);
const g = Math.round(color.g * 255);
const b = Math.round(color.b * 255);
const a = Math.round(color.a * 255);
// 转换为十六进制格式
return `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}${a === 255 ? '' : a.toString(16).padStart(2, '0')}`;
}
扩展与贡献指南
开发新工具的步骤
- 定义工具接口:使用Zod定义输入参数
server.tool(
"tool_name",
"工具描述",
{
param1: z.string().describe("参数1描述"),
param2: z.number().optional().describe("参数2描述")
},
async ({ param1, param2 }) => {
// 实现工具逻辑
}
);
- 实现Figma插件命令:在插件中添加对应处理函数
// 在插件的handleCommand中添加
case "tool_name":
return handleToolName(payload, id);
// 实现命令处理函数
function handleToolName(payload, id) {
// Figma API操作
const result = figma.currentPage.createRectangle();
sendResponse(id, result.toJSON());
}
- 添加进度更新:对于耗时操作实现进度反馈
// 发送进度更新
function sendProgressUpdate(update: CommandProgressUpdate) {
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
type: 'command_progress',
...update,
timestamp: Date.now()
}));
}
}
性能优化检查表
开发新功能时应遵循的性能优化指南:
- 对超过20个节点的操作使用分块处理
- 实现进度更新机制,每处理10%发送一次更新
- 使用filterFigmaNode过滤不必要的属性
- 批量操作使用try/catch包裹单个操作,允许部分失败
- 长时间操作添加取消机制
- 缓存频繁访问的资源(如样式、组件)
未来演进路线
- 双向数据流增强:实现Figma操作实时反馈到Cursor
-
离线操作队列:支持网络中断后操作自动重试
-
高级设计分析:集成设计系统合规性检查
-
多AI模型集成:支持同时连接多个AI服务
-
自定义命令系统:允许用户定义组合操作
总结
Cursor Talk To Figma MCP通过创新的MCP协议实现了AI与设计工具的深度集成,其三层架构设计确保了系统的可扩展性和稳定性。核心亮点包括:
- 实时双向通信:基于WebSocket的低延迟消息系统
- 分块处理机制:针对大型设计文件的性能优化
- 丰富的设计工具集:覆盖从简单编辑到复杂设计自动化
- 完善的错误处理:重试机制与详细状态反馈
- 跨平台兼容性:支持Windows、macOS和Linux环境
该项目不仅解决了AI与设计工具通信的技术难题,更为开发者提供了构建类似集成的完整框架。通过本文的源码解析,开发者可以快速掌握MCP协议实现、WebSocket通信和Figma插件开发的核心技术,为扩展系统功能或构建新的集成工具奠定基础。
要开始使用或贡献代码,请访问项目仓库:https://gitcode.com/gh_mirrors/cu/cursor-talk-to-figma-mcp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



