import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { sendCommandToFigma } from "../communication";
import { logger } from "../logger";
// Reusable Zod schemas
const rgbaSchema = z.object({
r: z.number().min(0).max(1).describe("Red component (0-1)"),
g: z.number().min(0).max(1).describe("Green component (0-1)"),
b: z.number().min(0).max(1).describe("Blue component (0-1)"),
a: z.number().min(0).max(1).optional().describe("Alpha component (0-1)"),
});
/**
* Register shape creation MCP tools
*/
export function registerShapeTools(server: McpServer): void {
// Create Rectangle
server.tool(
"create_rectangle",
"Create a new rectangle in Figma",
{
x: z.number().describe("X position"),
y: z.number().describe("Y position"),
width: z.number().describe("Width of the rectangle"),
height: z.number().describe("Height of the rectangle"),
name: z.string().optional().describe("Optional name for the rectangle"),
parentId: z
.string()
.optional()
.describe("Optional parent node ID to append the rectangle to"),
},
async ({ x, y, width, height, name, parentId }) => {
try {
const result = await sendCommandToFigma("create_rectangle", {
x,
y,
width,
height,
name: name ?? "Rectangle",
parentId,
});
return {
content: [
{
type: "text",
text: `Created rectangle: ${JSON.stringify(result)}`,
},
],
};
} catch (error) {
logger.error("Error creating rectangle", error);
return {
content: [
{
type: "text",
text: `Error creating rectangle: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// Create Frame
server.tool(
"create_frame",
"Create a new frame in Figma with optional auto-layout settings",
{
x: z.number().describe("X position"),
y: z.number().describe("Y position"),
width: z.number().describe("Width of the frame"),
height: z.number().describe("Height of the frame"),
name: z.string().optional().describe("Optional name for the frame"),
parentId: z
.string()
.optional()
.describe("Optional parent node ID to append the frame to"),
fillColor: rgbaSchema.optional().describe("Fill color in RGBA format"),
strokeColor: rgbaSchema
.optional()
.describe("Stroke color in RGBA format"),
strokeWeight: z.number().positive().optional().describe("Stroke weight"),
layoutMode: z
.enum(["NONE", "HORIZONTAL", "VERTICAL"])
.optional()
.describe("Auto-layout mode for the frame"),
layoutWrap: z
.enum(["NO_WRAP", "WRAP"])
.optional()
.describe("Whether the auto-layout frame wraps its children"),
paddingTop: z
.number()
.optional()
.describe("Top padding for auto-layout frame"),
paddingRight: z
.number()
.optional()
.describe("Right padding for auto-layout frame"),
paddingBottom: z
.number()
.optional()
.describe("Bottom padding for auto-layout frame"),
paddingLeft: z
.number()
.optional()
.describe("Left padding for auto-layout frame"),
primaryAxisAlignItems: z
.enum(["MIN", "MAX", "CENTER", "SPACE_BETWEEN"])
.optional()
.describe("Primary axis alignment. SPACE_BETWEEN ignores itemSpacing."),
counterAxisAlignItems: z
.enum(["MIN", "MAX", "CENTER", "BASELINE"])
.optional()
.describe("Counter axis alignment for auto-layout frame"),
layoutSizingHorizontal: z
.enum(["FIXED", "HUG", "FILL"])
.optional()
.describe("Horizontal sizing mode for auto-layout frame"),
layoutSizingVertical: z
.enum(["FIXED", "HUG", "FILL"])
.optional()
.describe("Vertical sizing mode for auto-layout frame"),
itemSpacing: z
.number()
.optional()
.describe("Distance between children in auto-layout frame"),
},
async (params) => {
try {
const result = await sendCommandToFigma<{ name: string; id: string }>(
"create_frame",
{
...params,
name: params.name ?? "Frame",
fillColor: params.fillColor ?? { r: 1, g: 1, b: 1, a: 1 },
},
);
return {
content: [
{
type: "text",
text: `Created frame "${result.name}" with ID: ${result.id}. Use the ID as parentId to add children inside this frame.`,
},
],
};
} catch (error) {
logger.error("Error creating frame", error);
return {
content: [
{
type: "text",
text: `Error creating frame: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
}