import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { sendCommandToFigma } from "../communication";
import { logger } from "../logger";
// Reusable schemas
const rgbaSchema = z.object({
r: z.number().min(0).max(1),
g: z.number().min(0).max(1),
b: z.number().min(0).max(1),
a: z.number().min(0).max(1).optional(),
});
const variableAliasSchema = z.object({
type: z.literal("VARIABLE_ALIAS"),
id: z.string(),
});
const variableValueSchema = z.union([
z.boolean(),
z.number(),
z.string(),
rgbaSchema,
variableAliasSchema,
]);
const variableScopeSchema = z.enum([
"ALL_SCOPES",
"ALL_FILLS",
"FRAME_FILL",
"SHAPE_FILL",
"TEXT_FILL",
"STROKE_COLOR",
"EFFECT_COLOR",
"CORNER_RADIUS",
"WIDTH_HEIGHT",
"GAP",
"OPACITY",
"FONT_FAMILY",
"FONT_SIZE",
"FONT_WEIGHT",
"LINE_HEIGHT",
"LETTER_SPACING",
"PARAGRAPH_SPACING",
"PARAGRAPH_INDENT",
]);
/**
* Register Figma Variables CRUD MCP tools
*/
export function registerVariableTools(server: McpServer): void {
// ============ Collection CRUD ============
server.tool(
"get_local_variable_collections",
"Get all local variable collections in the current Figma file",
{},
async () => {
try {
const result = await sendCommandToFigma(
"get_local_variable_collections",
{},
);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error getting variable collections", error);
return {
content: [
{
type: "text",
text: `Error getting variable collections: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"get_variable_collection",
"Get a specific variable collection by ID",
{
collectionId: z.string().describe("The ID of the variable collection"),
},
async ({ collectionId }) => {
try {
const result = await sendCommandToFigma("get_variable_collection", {
collectionId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error getting variable collection", error);
return {
content: [
{
type: "text",
text: `Error getting variable collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"create_variable_collection",
"Create a new variable collection",
{
name: z.string().describe("Name of the collection"),
initialModes: z
.array(z.string())
.optional()
.describe("Initial mode names (default creates one mode)"),
},
async ({ name, initialModes }) => {
try {
const result = await sendCommandToFigma("create_variable_collection", {
name,
initialModes,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error creating variable collection", error);
return {
content: [
{
type: "text",
text: `Error creating variable collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"update_variable_collection",
"Update a variable collection (rename)",
{
collectionId: z.string().describe("The ID of the collection to update"),
name: z.string().optional().describe("New name for the collection"),
},
async ({ collectionId, name }) => {
try {
const result = await sendCommandToFigma("update_variable_collection", {
collectionId,
name,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error updating variable collection", error);
return {
content: [
{
type: "text",
text: `Error updating variable collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"delete_variable_collection",
"Delete a variable collection",
{
collectionId: z.string().describe("The ID of the collection to delete"),
},
async ({ collectionId }) => {
try {
const result = await sendCommandToFigma("delete_variable_collection", {
collectionId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error deleting variable collection", error);
return {
content: [
{
type: "text",
text: `Error deleting variable collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// ============ Variable CRUD ============
server.tool(
"get_variables_in_collection",
"Get all variables in a specific collection",
{
collectionId: z.string().describe("The ID of the variable collection"),
},
async ({ collectionId }) => {
try {
const result = await sendCommandToFigma("get_variables_in_collection", {
collectionId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error getting variables in collection", error);
return {
content: [
{
type: "text",
text: `Error getting variables in collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"create_variable",
"Create a new variable in a collection",
{
collectionId: z
.string()
.describe("The ID of the collection to add the variable to"),
name: z.string().describe("Name of the variable"),
resolvedType: z
.enum(["BOOLEAN", "COLOR", "FLOAT", "STRING"])
.describe("The data type of the variable"),
scopes: z
.array(variableScopeSchema)
.optional()
.describe("Variable scopes (e.g., ALL_FILLS, CORNER_RADIUS)"),
description: z
.string()
.optional()
.describe("Description of the variable"),
},
async ({ collectionId, name, resolvedType, scopes, description }) => {
try {
const result = await sendCommandToFigma("create_variable", {
collectionId,
name,
resolvedType,
scopes,
description,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error creating variable", error);
return {
content: [
{
type: "text",
text: `Error creating variable: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"update_variable",
"Update a variable's properties",
{
variableId: z.string().describe("The ID of the variable to update"),
name: z.string().optional().describe("New name for the variable"),
scopes: z
.array(variableScopeSchema)
.optional()
.describe("New scopes for the variable"),
description: z.string().optional().describe("New description"),
},
async ({ variableId, name, scopes, description }) => {
try {
const result = await sendCommandToFigma("update_variable", {
variableId,
name,
scopes,
description,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error updating variable", error);
return {
content: [
{
type: "text",
text: `Error updating variable: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"delete_variable",
"Delete a variable",
{
variableId: z.string().describe("The ID of the variable to delete"),
},
async ({ variableId }) => {
try {
const result = await sendCommandToFigma("delete_variable", {
variableId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error deleting variable", error);
return {
content: [
{
type: "text",
text: `Error deleting variable: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// ============ Mode Management ============
server.tool(
"get_variable_modes",
"Get all modes in a variable collection",
{
collectionId: z.string().describe("The ID of the variable collection"),
},
async ({ collectionId }) => {
try {
const result = await sendCommandToFigma("get_variable_modes", {
collectionId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error getting variable modes", error);
return {
content: [
{
type: "text",
text: `Error getting variable modes: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"create_variable_mode",
"Add a new mode to a variable collection",
{
collectionId: z.string().describe("The ID of the variable collection"),
name: z.string().describe("Name of the new mode"),
},
async ({ collectionId, name }) => {
try {
const result = await sendCommandToFigma("create_variable_mode", {
collectionId,
name,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error creating variable mode", error);
return {
content: [
{
type: "text",
text: `Error creating variable mode: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"rename_variable_mode",
"Rename a mode in a variable collection",
{
collectionId: z.string().describe("The ID of the variable collection"),
modeId: z.string().describe("The ID of the mode to rename"),
name: z.string().describe("New name for the mode"),
},
async ({ collectionId, modeId, name }) => {
try {
const result = await sendCommandToFigma("rename_variable_mode", {
collectionId,
modeId,
name,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error renaming variable mode", error);
return {
content: [
{
type: "text",
text: `Error renaming variable mode: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"delete_variable_mode",
"Delete a mode from a variable collection",
{
collectionId: z.string().describe("The ID of the variable collection"),
modeId: z.string().describe("The ID of the mode to delete"),
},
async ({ collectionId, modeId }) => {
try {
const result = await sendCommandToFigma("delete_variable_mode", {
collectionId,
modeId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error deleting variable mode", error);
return {
content: [
{
type: "text",
text: `Error deleting variable mode: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// ============ Value Management ============
server.tool(
"set_variable_value_for_mode",
"Set the value of a variable for a specific mode",
{
variableId: z.string().describe("The ID of the variable"),
modeId: z.string().describe("The ID of the mode"),
value: variableValueSchema.describe(
"The value (boolean, number, string, RGBA color, or variable alias)",
),
},
async ({ variableId, modeId, value }) => {
try {
const result = await sendCommandToFigma("set_variable_value_for_mode", {
variableId,
modeId,
value,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error setting variable value", error);
return {
content: [
{
type: "text",
text: `Error setting variable value: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// ============ Binding ============
server.tool(
"bind_variable_to_node",
"Bind a variable to a node property",
{
nodeId: z.string().describe("The ID of the node"),
variableId: z.string().describe("The ID of the variable to bind"),
field: z
.string()
.describe(
"The field to bind (e.g., 'fills', 'strokes', 'cornerRadius', 'width', 'height', 'itemSpacing')",
),
},
async ({ nodeId, variableId, field }) => {
try {
const result = await sendCommandToFigma("bind_variable_to_node", {
nodeId,
variableId,
field,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error binding variable to node", error);
return {
content: [
{
type: "text",
text: `Error binding variable to node: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"unbind_variable_from_node",
"Remove a variable binding from a node property",
{
nodeId: z.string().describe("The ID of the node"),
field: z.string().describe("The field to unbind"),
},
async ({ nodeId, field }) => {
try {
const result = await sendCommandToFigma("unbind_variable_from_node", {
nodeId,
field,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error unbinding variable from node", error);
return {
content: [
{
type: "text",
text: `Error unbinding variable from node: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
// ============ Extended Collections ============
server.tool(
"get_extended_variable_collections",
"Get all extended variable collections in the current Figma file. Extended collections enable theming by inheriting from a parent collection and allowing value overrides.",
{},
async () => {
try {
const result = await sendCommandToFigma(
"get_extended_variable_collections",
{},
);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error getting extended variable collections", error);
return {
content: [
{
type: "text",
text: `Error getting extended variable collections: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"create_extended_collection",
"Create an extended variable collection from a library or local collection. Extended collections enable theming by inheriting variables and modes from a parent collection. Requires Enterprise plan.",
{
collectionKey: z
.string()
.describe("The key of the parent variable collection to extend"),
name: z.string().describe("Name for the new extended collection"),
},
async ({ collectionKey, name }) => {
try {
const result = await sendCommandToFigma("create_extended_collection", {
collectionKey,
name,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error creating extended collection", error);
return {
content: [
{
type: "text",
text: `Error creating extended collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"get_extended_collection",
"Get detailed information about an extended collection including its variable overrides",
{
collectionId: z
.string()
.describe("The ID of the extended variable collection"),
},
async ({ collectionId }) => {
try {
const result = await sendCommandToFigma("get_extended_collection", {
collectionId,
});
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error getting extended collection", error);
return {
content: [
{
type: "text",
text: `Error getting extended collection: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"set_extended_collection_override",
"Set a variable override value in an extended collection. This allows theming by overriding inherited variable values.",
{
collectionId: z
.string()
.describe("The ID of the extended variable collection"),
variableId: z.string().describe("The ID of the variable to override"),
modeId: z.string().describe("The ID of the mode to set the override for"),
value: variableValueSchema.describe(
"The override value (boolean, number, string, RGBA color, or variable alias)",
),
},
async ({ collectionId, variableId, modeId, value }) => {
try {
const result = await sendCommandToFigma(
"set_extended_collection_override",
{
collectionId,
variableId,
modeId,
value,
},
);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error setting extended collection override", error);
return {
content: [
{
type: "text",
text: `Error setting extended collection override: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
server.tool(
"remove_extended_collection_override",
"Remove all overrides for a variable in an extended collection, reverting to the parent collection's values",
{
collectionId: z
.string()
.describe("The ID of the extended variable collection"),
variableId: z
.string()
.describe("The ID of the variable to remove overrides for"),
},
async ({ collectionId, variableId }) => {
try {
const result = await sendCommandToFigma(
"remove_extended_collection_override",
{
collectionId,
variableId,
},
);
return {
content: [{ type: "text", text: JSON.stringify(result) }],
};
} catch (error) {
logger.error("Error removing extended collection override", error);
return {
content: [
{
type: "text",
text: `Error removing extended collection override: ${
error instanceof Error ? error.message : String(error)
}`,
},
],
};
}
},
);
}