Designer Style Tool
style_toolCreate, retrieve, update, delete, and query styles in Webflow sites. Manage CSS styles, combo classes, and breakpoints programmatically.
Instructions
Designer Tool - Style tool to perform actions like create style, get all styles, update styles, remove styles
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| siteId | Yes | The ID of the site. DO NOT ASSUME site id. ALWAYS ask user for site id if not already provided or known. use sites_list tool to fetch all sites and then ask user to select one of them. | |
| actions | Yes |
Implementation Reference
- src/tools/deStyle.ts:203-209 (handler)The handler/executor function for the style_tool. It calls the RPC layer with the siteId and actions array, formats the response or error.
async ({ siteId, actions }) => { try { return formatResponse(await styleToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } }, - src/tools/deStyle.ts:17-201 (schema)The input schema for the tool, defining Zod validations for create_style, get_styles, update_style, remove_style, and query_styles actions, including nested property definitions and constraints.
{ title: "Designer Style Tool", annotations: { readOnlyHint: false, openWorldHint: true, }, description: "Designer Tool - Style tool to perform actions like create style, get all styles, update styles, remove styles", inputSchema: { ...SiteIdSchema, actions: z.array( z .object({ create_style: z .object({ name: z.string().describe("The name of the style"), properties: z .array( z.object({ property_name: z .string() .describe("The name of the property"), property_value: z .string() .optional() .describe("The value of the property"), variable_as_value: z .string() .optional() .describe("The variable id to use as the value"), }), ) .describe( "The properties of the style. if you are looking to link a variable as the value, then use the variable_as_value field. but do not use both property_value and variable_as_value", ), parent_style_names: z .array(z.string()) .optional() .describe( "The name of the parent style to create the new style in. this will use to create combo class", ), }) .optional() .describe("Create a new style"), get_styles: z .object({ skip_properties: z .boolean() .optional() .describe( "Whether to skip the properties of the style. to get minimal data.", ), include_all_breakpoints: z .boolean() .optional() .describe( "Whether to include all breakpoints styles or not. very data intensive.", ), query: z .enum(["all", "filtered"]) .describe("The query to get all styles or filtered styles"), filter_ids: z .array(z.string()) .optional() .describe( "The ids of the styles to filter by. should be used with query filtered", ), }) .optional() .describe("Get all styles"), update_style: z .object({ style_name: z .string() .describe("The name of the style to update"), breakpoint_id: z .enum([ "xxl", "xl", "large", "main", "medium", "small", "tiny", ]) .optional() .describe("The breakpoint to update the style for"), pseudo: z .enum([ "noPseudo", "nth-child(odd)", "nth-child(even)", "first-child", "last-child", "hover", "active", "pressed", "visited", "focus", "focus-visible", "focus-within", "placeholder", "empty", "before", "after", ]) .optional() .describe("The pseudo class to update the style for"), properties: z .array( z.object({ property_name: z .string() .describe("The name of the property"), property_value: z .string() .optional() .describe("The value of the property"), variable_as_value: z .string() .optional() .describe("The variable id to use as the value"), }), ) .optional() .describe( "The properties to update or add to the style for", ), remove_properties: z .array(z.string()) .optional() .describe("The properties to remove from the style"), parent_style_names: z .array(z.string()) .optional() .describe( "The parent style names to update the style for (for combo class)", ), }) .optional() .describe("Update a style"), remove_style: z .object({ style_name: z.string().describe("The name of the style to remove"), parent_style_names: z .array(z.string()) .optional() .describe("The parent style names (for combo class)"), }) .optional() .describe("Remove a style"), query_styles: z .object({ queries: z.array( z.object({ label: z.string().optional().describe("A label to identify this query in the results."), style_id: z.string().optional().describe("Filter by style ID. Exact match. Bypasses other filters."), name_path: z.array(z.string()).optional().describe("Filter by style name path. Supports combo classes (e.g. ['base', 'combo']). Each segment is a case-insensitive substring match."), properties: z.array( z.object({ name: z.string().describe("CSS property name. Case-insensitive substring match."), value: z.string().optional().describe("CSS property value. Case-insensitive substring match."), }) ).optional().describe("Filter styles that have ALL listed CSS properties matching. Each entry checks name (and optionally value)."), include_properties: z.boolean().optional().describe("Include style properties in results. Default: false."), include_all_breakpoints: z.boolean().optional().describe("Include all breakpoint styles in results. Default: false."), limit: z.number().min(1).max(200).optional().describe("Max results for this query. Default: 50, Max: 200."), }) ), }) .optional() .describe("Query styles by name path, ID, or CSS properties [BETA]"), }) .strict() .refine( (d) => [d.create_style, d.get_styles, d.update_style, d.remove_style, d.query_styles].filter(Boolean) .length >= 1, { message: "Provide at least one of create_style, get_styles, update_style, remove_style, query_styles.", }, ), ), }, - src/tools/deStyle.ts:7-230 (registration)The registration function `registerDEStyleTools` that registers the tool named 'style_tool' with the MCP server, including metadata (title, description, annotations, inputSchema) and the handler callback.
export function registerDEStyleTools(server: McpServer, rpc: RPCType) { const styleToolRPCCall = async (siteId: string, actions: any) => { return rpc.callTool("style_tool", { siteId, actions: actions || [], }); }; server.registerTool( "style_tool", { title: "Designer Style Tool", annotations: { readOnlyHint: false, openWorldHint: true, }, description: "Designer Tool - Style tool to perform actions like create style, get all styles, update styles, remove styles", inputSchema: { ...SiteIdSchema, actions: z.array( z .object({ create_style: z .object({ name: z.string().describe("The name of the style"), properties: z .array( z.object({ property_name: z .string() .describe("The name of the property"), property_value: z .string() .optional() .describe("The value of the property"), variable_as_value: z .string() .optional() .describe("The variable id to use as the value"), }), ) .describe( "The properties of the style. if you are looking to link a variable as the value, then use the variable_as_value field. but do not use both property_value and variable_as_value", ), parent_style_names: z .array(z.string()) .optional() .describe( "The name of the parent style to create the new style in. this will use to create combo class", ), }) .optional() .describe("Create a new style"), get_styles: z .object({ skip_properties: z .boolean() .optional() .describe( "Whether to skip the properties of the style. to get minimal data.", ), include_all_breakpoints: z .boolean() .optional() .describe( "Whether to include all breakpoints styles or not. very data intensive.", ), query: z .enum(["all", "filtered"]) .describe("The query to get all styles or filtered styles"), filter_ids: z .array(z.string()) .optional() .describe( "The ids of the styles to filter by. should be used with query filtered", ), }) .optional() .describe("Get all styles"), update_style: z .object({ style_name: z .string() .describe("The name of the style to update"), breakpoint_id: z .enum([ "xxl", "xl", "large", "main", "medium", "small", "tiny", ]) .optional() .describe("The breakpoint to update the style for"), pseudo: z .enum([ "noPseudo", "nth-child(odd)", "nth-child(even)", "first-child", "last-child", "hover", "active", "pressed", "visited", "focus", "focus-visible", "focus-within", "placeholder", "empty", "before", "after", ]) .optional() .describe("The pseudo class to update the style for"), properties: z .array( z.object({ property_name: z .string() .describe("The name of the property"), property_value: z .string() .optional() .describe("The value of the property"), variable_as_value: z .string() .optional() .describe("The variable id to use as the value"), }), ) .optional() .describe( "The properties to update or add to the style for", ), remove_properties: z .array(z.string()) .optional() .describe("The properties to remove from the style"), parent_style_names: z .array(z.string()) .optional() .describe( "The parent style names to update the style for (for combo class)", ), }) .optional() .describe("Update a style"), remove_style: z .object({ style_name: z.string().describe("The name of the style to remove"), parent_style_names: z .array(z.string()) .optional() .describe("The parent style names (for combo class)"), }) .optional() .describe("Remove a style"), query_styles: z .object({ queries: z.array( z.object({ label: z.string().optional().describe("A label to identify this query in the results."), style_id: z.string().optional().describe("Filter by style ID. Exact match. Bypasses other filters."), name_path: z.array(z.string()).optional().describe("Filter by style name path. Supports combo classes (e.g. ['base', 'combo']). Each segment is a case-insensitive substring match."), properties: z.array( z.object({ name: z.string().describe("CSS property name. Case-insensitive substring match."), value: z.string().optional().describe("CSS property value. Case-insensitive substring match."), }) ).optional().describe("Filter styles that have ALL listed CSS properties matching. Each entry checks name (and optionally value)."), include_properties: z.boolean().optional().describe("Include style properties in results. Default: false."), include_all_breakpoints: z.boolean().optional().describe("Include all breakpoint styles in results. Default: false."), limit: z.number().min(1).max(200).optional().describe("Max results for this query. Default: 50, Max: 200."), }) ), }) .optional() .describe("Query styles by name path, ID, or CSS properties [BETA]"), }) .strict() .refine( (d) => [d.create_style, d.get_styles, d.update_style, d.remove_style, d.query_styles].filter(Boolean) .length >= 1, { message: "Provide at least one of create_style, get_styles, update_style, remove_style, query_styles.", }, ), ), }, }, async ({ siteId, actions }) => { try { return formatResponse(await styleToolRPCCall(siteId, actions)); } catch (error) { return formatErrorResponse(error); } }, ); /** * Since now we support 500+ styles, we don't need to learn more about styles. */ // server.registerTool( // "de_learn_more_about_styles", // { // title: "Designer Learn More About Webflow Styles", // annotations: { // readOnlyHint: true, // openWorldHint: true, // }, // description: // "Designer tool - Learn more about styles supported by Webflow Designer." + // "Please do not use any other styles which is not supported by Webflow Designer." + // "Please use the long-form alias of a CSS property when managing styles. For example, the property row-gap has a long-form alias of grid-row-gap, margin has long-form alias of margin-top, margin-right, margin-bottom, margin-left, etc.", // inputSchema: {}, // }, // async ({}) => formatResponse(supportDEStyles) // ); } - src/mcp.ts:71-78 (registration)The `registerDesignerTools` function that calls `registerDEStyleTools(server, rpc)` to wire up the style_tool into the MCP server.
export function registerDesignerTools(server: McpServer, rpc: RPCType) { registerDEAssetTools(server, rpc); registerDEComponentsTools(server, rpc); registerDEElementTools(server, rpc); registerDEPagesTools(server, rpc); registerDEStyleTools(server, rpc); registerDEVariableTools(server, rpc); } - src/utils/formatResponse.ts:18-22 (helper)The `formatResponse` helper that serializes the tool's result into a TextContent response for the MCP protocol.
export function formatResponse(response: any): ToolResponse { return { content: [{ type: "text" as "text", text: JSON.stringify(response) }], }; }