import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { sendCommandToFigma } from "../communication";
import { logger } from "../logger";
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 text-related MCP tools
*/
export function registerTextTools(server: McpServer): void {
// Create Text
server.tool(
"create_text",
"Create a new text element in Figma",
{
x: z.number().describe("X position"),
y: z.number().describe("Y position"),
text: z.string().describe("Text content"),
fontSize: z.number().optional().describe("Font size (default: 14)"),
fontWeight: z
.number()
.optional()
.describe("Font weight (e.g., 400 for Regular, 700 for Bold)"),
fontColor: rgbaSchema.optional().describe("Font color in RGBA format"),
name: z
.string()
.optional()
.describe("Semantic layer name for the text node"),
parentId: z
.string()
.optional()
.describe("Optional parent node ID to append the text to"),
},
async ({ x, y, text, fontSize, fontWeight, fontColor, name, parentId }) => {
try {
const result = await sendCommandToFigma<{ name: string; id: string }>(
"create_text",
{
x,
y,
text,
fontSize: fontSize ?? 14,
fontWeight: fontWeight ?? 400,
fontColor: fontColor ?? { r: 0, g: 0, b: 0, a: 1 },
name: name ?? "Text",
parentId,
},
);
return {
content: [
{
type: "text",
text: `Created text "${result.name}" with ID: ${result.id}`,
},
],
};
} catch (error) {
logger.error("Error creating text", error);
return {
content: [
{
type: "text",
text: `Error creating text: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// Set Text Content
server.tool(
"set_text_content",
"Set the text content of an existing text node in Figma",
{
nodeId: z.string().describe("The ID of the text node to modify"),
text: z.string().describe("New text content"),
},
async ({ nodeId, text }) => {
try {
const result = await sendCommandToFigma<{ name: string }>(
"set_text_content",
{ nodeId, text },
);
return {
content: [
{
type: "text",
text: `Set text content of node "${result.name}" to "${text}"`,
},
],
};
} catch (error) {
logger.error("Error setting text content", error);
return {
content: [
{
type: "text",
text: `Error setting text content: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// Set Multiple Text Contents
server.tool(
"set_multiple_text_contents",
"Set text content for multiple text nodes at once (use scan_text_nodes to get node IDs first)",
{
nodeId: z.string().describe("The root node ID containing the text nodes"),
text: z
.array(
z.object({
nodeId: z.string().describe("The ID of the text node"),
text: z.string().describe("New text content"),
}),
)
.describe("Array of node ID and text pairs"),
},
async ({ nodeId, text }) => {
try {
const result = await sendCommandToFigma<{
success: boolean;
updatedCount: number;
failedCount: number;
}>("set_multiple_text_contents", { nodeId, text });
return {
content: [
{
type: "text",
text: `Updated ${result.updatedCount} text nodes, ${result.failedCount} failed`,
},
],
};
} catch (error) {
logger.error("Error setting multiple text contents", error);
return {
content: [
{
type: "text",
text: `Error setting multiple text contents: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
}