Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions src/cursor_mcp_plugin/code.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,10 @@ async function handleCommand(command, params) {
return await setTextContent(params);
case "clone_node":
return await cloneNode(params);
case "set_node_interactions":
return await setNodeInteractions(params);
case "set_overflow_direction":
return await setOverflowDirection(params);
default:
throw new Error(`Unknown command: ${command}`);
}
Expand Down Expand Up @@ -1167,3 +1171,108 @@ async function cloneNode(params) {
height: "height" in clone ? clone.height : undefined,
};
}

// Add the setNodeInteractions function implementation
async function setNodeInteractions(params) {
const { nodeId, interactions } = params || {};

if (!nodeId) {
throw new Error("Missing nodeId parameter");
}

if (!interactions || !Array.isArray(interactions)) {
throw new Error("Missing or invalid interactions parameter");
}

const node = await figma.getNodeByIdAsync(nodeId);
if (!node) {
throw new Error(`Node not found with ID: ${nodeId}`);
}

try {
// Use Figma API's setReactionsAsync to set interactions
await node.setReactionsAsync(interactions.map(interaction => {
let actionObj = {
type: interaction.actionType
};

// Add different parameters based on action type
if (interaction.actionType === "NAVIGATE") {
actionObj.destination = interaction.destination;
actionObj.transition = interaction.transition || { type: "NONE" };
} else if (interaction.actionType === "URL") {
actionObj.url = interaction.url;
} else if (interaction.actionType === "OPEN_NODE") {
actionObj.nodeId = interaction.targetNodeId;
} else if (interaction.actionType === "OVERLAY") {
actionObj.nodeId = interaction.targetNodeId;
actionObj.preserveScrollPosition = interaction.preserveScrollPosition || false;
} else if (interaction.actionType === "SWAP") {
actionObj.componentId = interaction.componentId;
}

return {
actions: [actionObj],
trigger: {
type: interaction.triggerType || "ON_CLICK",
}
};
}));

return {
id: node.id,
name: node.name,
interactionsCount: interactions.length,
};
} catch (error) {
console.error("Complete error object:", error);
throw new Error(`Error setting interactions: ${error.message || "Unknown error"}`);
}
}

// Add the setOverflowDirection function implementation
async function setOverflowDirection(params) {
const { nodeId, direction } = params || {};

if (!nodeId) {
throw new Error("Missing nodeId parameter");
}

if (!direction) {
throw new Error("Missing direction parameter");
}

const node = await figma.getNodeByIdAsync(nodeId);
if (!node) {
throw new Error(`Node not found with ID: ${nodeId}`);
}

// 检查节点是否支持overflowDirection属性
if (!("overflowDirection" in node)) {
throw new Error(`Node does not support overflow direction: ${nodeId}`);
}

// 设置overflowDirection
switch (direction.toUpperCase()) {
case "NONE":
node.overflowDirection = "NONE";
break;
case "HORIZONTAL":
node.overflowDirection = "HORIZONTAL";
break;
case "VERTICAL":
node.overflowDirection = "VERTICAL";
break;
case "BOTH":
node.overflowDirection = "BOTH";
break;
default:
throw new Error(`Invalid overflow direction: ${direction}`);
}

return {
id: node.id,
name: node.name,
overflowDirection: node.overflowDirection
};
}
108 changes: 107 additions & 1 deletion src/talk_to_figma_mcp/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,110 @@ server.tool(
}
);

// Add Interactions Tool
server.tool(
"set_node_interactions",
"Set interactive behaviors to a node in Figma",
{
nodeId: z.string().describe("The ID of the node to add interactions to"),
interactions: z.array(
z.object({
actionType: z.enum([
"NAVIGATE",
"URL",
"OPEN_NODE",
"OVERLAY",
"SWAP",
"BACK",
"CLOSE"
]).describe("Type of interaction action"),
triggerType: z.enum([
"ON_CLICK",
"ON_HOVER",
"ON_PRESS",
"ON_DRAG"
]).default("ON_CLICK").describe("Type of trigger (default: ON_CLICK)"),
// Optional parameters based on action type
destination: z.string().optional().describe("For NAVIGATE: Target page ID"),
transition: z.object({
type: z.enum(["NONE", "DISSOLVE", "SMART_ANIMATE", "MOVE_IN", "MOVE_OUT", "PUSH", "SLIDE_IN", "SLIDE_OUT"])
}).optional().describe("For NAVIGATE: Transition effect"),
url: z.string().optional().describe("For URL: Web URL to open"),
targetNodeId: z.string().optional().describe("For OPEN_NODE/OVERLAY: Target node ID"),
preserveScrollPosition: z.boolean().optional().describe("For OVERLAY: Preserve scroll position"),
componentId: z.string().optional().describe("For SWAP: Component ID to swap to")
})
).describe("Array of interactions to add to the node")
},
async ({ nodeId, interactions }) => {
try {
const result = await sendCommandToFigma('set_node_interactions', {
nodeId,
interactions
});
const typedResult = result as { name: string, interactionsCount: number };
return {
content: [
{
type: "text",
text: `Successfully added ${typedResult.interactionsCount} interactions to node "${typedResult.name}"`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `Error adding interactions: ${error instanceof Error ? error.message : String(error)}`
}
]
};
}
}
);

// Add Set Overflow Direction Tool
server.tool(
"set_overflow_direction",
"Set the overflow direction (scroll behavior) of a node in Figma",
{
nodeId: z.string().describe("The ID of the node to modify"),
direction: z.enum([
"NONE",
"HORIZONTAL",
"VERTICAL",
"BOTH"
]).describe("Overflow direction: NONE (no scrolling), HORIZONTAL, VERTICAL, or BOTH")
},
async ({ nodeId, direction }) => {
try {
const result = await sendCommandToFigma('set_overflow_direction', {
nodeId,
direction
});
const typedResult = result as { name: string, overflowDirection: string };
return {
content: [
{
type: "text",
text: `设置节点 "${typedResult.name}" 的滚动方向为 ${typedResult.overflowDirection}`
}
]
};
} catch (error) {
return {
content: [
{
type: "text",
text: `设置滚动方向出错: ${error instanceof Error ? error.message : String(error)}`
}
]
};
}
}
);

// Define design strategy prompt
server.prompt(
"design_strategy",
Expand Down Expand Up @@ -900,7 +1004,9 @@ type FigmaCommand =
| 'join'
| 'set_corner_radius'
| 'set_text_content'
| 'clone_node';
| 'clone_node'
| 'set_node_interactions'
| 'set_overflow_direction';

// Helper function to process Figma node responses
function processFigmaNodeResponse(result: unknown): any {
Expand Down