#
tokens: 38592/50000 54/55 files (page 1/2)
lines: on (toggle) GitHub
raw markdown copy reset
This is page 1 of 2. Use http://codebase.md/webflow/mcp-server?lines=true&page={x} to view the full context.

# Directory Structure

```
├── .github
│   └── workflows
│       ├── ci.yml
│       └── publish-oss.yml
├── .gitignore
├── LICENSE.md
├── package-lock.json
├── package.json
├── README.md
├── server.json
├── src
│   ├── index.ts
│   ├── mcp.ts
│   ├── modules
│   │   └── designerAppBridge.ts
│   ├── schemas
│   │   ├── CollectionItemPostSingleSchema.ts
│   │   ├── CollectionItemWithIdInputSchema.ts
│   │   ├── ComponentDomWriteNodesItemSchema.ts
│   │   ├── ComponentPropertyUpdateSchema.ts
│   │   ├── DEElementIDSchema.ts
│   │   ├── DEElementSchema.ts
│   │   ├── index.ts
│   │   ├── OptionFieldSchema.ts
│   │   ├── ReferenceFieldSchema.ts
│   │   ├── RegisterInlineSiteScriptSchema.ts
│   │   ├── SiteIdSchema.ts
│   │   ├── StaticFieldSchema.ts
│   │   ├── WebflowCollectionsCreateRequestSchema.ts
│   │   ├── WebflowCollectionsFieldUpdateSchema.ts
│   │   ├── WebflowCollectionsItemsCreateItemLiveRequestSchema.ts
│   │   ├── WebflowCollectionsItemsCreateItemRequestSchema.ts
│   │   ├── WebflowCollectionsItemsListItemsRequestSortBySchema.ts
│   │   ├── WebflowCollectionsItemsListItemsRequestSortOrderSchema.ts
│   │   ├── WebflowCollectionsItemsUpdateItemsLiveRequestSchema.ts
│   │   ├── WebflowCollectionsItemsUpdateItemsRequestSchema.ts
│   │   ├── WebflowPageDomWriteNodesItemSchema.ts
│   │   └── WebflowPageSchema.ts
│   ├── tools
│   │   ├── aiChat.ts
│   │   ├── cms.ts
│   │   ├── components.ts
│   │   ├── deAsset.ts
│   │   ├── deComponents.ts
│   │   ├── deElement.ts
│   │   ├── dePages.ts
│   │   ├── deStyle.ts
│   │   ├── deVariable.ts
│   │   ├── index.ts
│   │   ├── localDeMCPConnection.ts
│   │   ├── pages.ts
│   │   ├── rules.ts
│   │   ├── scripts.ts
│   │   └── sites.ts
│   ├── types
│   │   └── RPCType.d.ts
│   └── utils
│       ├── appPort.ts
│       ├── formatResponse.ts
│       ├── generateUUIDv4.ts
│       ├── index.ts
│       ├── supportStyles.ts
│       └── typeGuards.ts
├── tsconfig.json
└── webflow_logo.png
```

# Files

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | .env
 2 | .prettierrc
 3 | .wrangler/
 4 | dist/
 5 | node_modules/
 6 | 
 7 | # System
 8 | .DS_Store
 9 | .idea/
10 | .vscode/
11 | .cursor
12 | pnpm-lock.yaml
13 | 
14 | # Private keys
15 | key.pem
16 | key.pem.pub
17 | *.pem
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Webflow's MCP server
  2 | 
  3 | A Node.js server implementing Model Context Protocol (MCP) for Webflow using the [Webflow JavaScript SDK](https://github.com/webflow/js-webflow-api). Enable AI agents to interact with Webflow APIs. Learn more about Webflow's Data API in the [developer documentation](https://developers.webflow.com/data/reference).
  4 | 
  5 | [![npm shield](https://img.shields.io/npm/v/webflow-mcp-server)](https://www.npmjs.com/package/webflow-mcp-server)
  6 | ![Webflow](https://img.shields.io/badge/webflow-%23146EF5.svg?style=for-the-badge&logo=webflow&logoColor=white)
  7 | 
  8 | ## Prerequisites
  9 | 
 10 | - [Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
 11 | - [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
 12 | - [A Webflow Account](https://webflow.com/signup)
 13 | 
 14 | ## 🚀 Remote installation
 15 | 
 16 | Get started by installing Webflow's remote MCP server. The remote server uses OAuth to authenticate with your Webflow sites, and a companion app that syncs your live canvas with your AI agent.
 17 | 
 18 | ### Requirements
 19 | 
 20 | - Node.js 22.3.0 or higher
 21 | 
 22 | > Note: The MCP server currently supports Node.js 22.3.0 or higher. If you run into version issues, see the [Node.js compatibility guidance.](https://developers.webflow.com/data/v2.0.0/docs/ai-tools#nodejs-compatibility)
 23 | 
 24 | ### Cursor
 25 | 
 26 | #### Add MCP server to Cursor
 27 | 
 28 | 1. Go to `Settings → Cursor Settings → MCP & Integrations`.
 29 | 2. Under MCP Tools, click `+ New MCP Server`.
 30 | 3. Paste the following configuration into `.cursor/mcp.json` (or add the `webflow` part to your existing configuration):
 31 | 
 32 | ```json
 33 | {
 34 |   "mcpServers": {
 35 |     "webflow": {
 36 |       "url": "https://mcp.webflow.com/sse"
 37 |     }
 38 |   }
 39 | }
 40 | ```
 41 | 
 42 | > Tip: You can create a project-level `mcp.json` to avoid repeated auth prompts across multiple Cursor windows. See Cursor’s docs on [configuration locations.](https://docs.cursor.com/en/context/mcp#configuration-locations)
 43 | 
 44 | 4. Save and close the file. Cursor will automatically open an OAuth login page where you can authorize Webflow sites to use with the MCP server.
 45 | 
 46 | #### Open the Webflow Designer
 47 | 
 48 | - Open your site in the Webflow Designer, or ask your AI agent:
 49 | 
 50 | ```text
 51 | Give me a link to open <MY_SITE_NAME> in the Webflow Designer
 52 | ```
 53 | 
 54 | #### Open the MCP Webflow App
 55 | 
 56 | 1. In the Designer, open the Apps panel (press `E`).
 57 | 2. Launch your published "Webflow MCP Bridge App".
 58 | 3. Wait for the app to connect to the MCP server.
 59 | 
 60 | #### Write your first prompt
 61 | 
 62 | Try these in your AI chat:
 63 | 
 64 | ```text
 65 | Analyze my last 5 blog posts and suggest 3 new topic ideas with SEO keywords
 66 | ```
 67 | 
 68 | ```text
 69 | Find older blog posts that mention similar topics and add internal links to my latest post
 70 | ```
 71 | 
 72 | ```text
 73 | Create a hero section card on my home page with a CTA button and responsive design
 74 | ```
 75 | 
 76 | ### Claude desktop
 77 | 
 78 | #### Add MCP server to Claude desktop
 79 | 
 80 | 1. Enable developer mode: `Help → Troubleshooting → Enable Developer Mode`.
 81 | 2. Open developer settings: `File → Settings → Developer`.
 82 | 3. Click `Get Started` or edit the configuration to open `claude_desktop_config.json` and add:
 83 | 
 84 | ```json
 85 | {
 86 |   "mcpServers": {
 87 |     "webflow": {
 88 |       "command": "npx",
 89 |       "args": ["mcp-remote", "https://mcp.webflow.com/sse"]
 90 |     }
 91 |   }
 92 | }
 93 | ```
 94 | 
 95 | 4. Save and restart Claude Desktop (`Cmd/Ctrl + R`). An OAuth login page will open to authorize sites.
 96 | 
 97 | #### Open the Webflow Designer
 98 | 
 99 | - Open your site in the Webflow Designer, or ask your AI agent:
100 | 
101 | ```text
102 | Give me a link to open <MY_SITE_NAME> in the Webflow Designer
103 | ```
104 | 
105 | #### Open the MCP Webflow App
106 | 
107 | 1. In the Designer, open the Apps panel (press `E`).
108 | 2. Launch your published "Webflow MCP Bridge App".
109 | 3. Wait for the app to connect to the MCP server.
110 | 
111 | #### Write your first prompt
112 | 
113 | ```text
114 | Analyze my last 5 blog posts and suggest 3 new topic ideas with SEO keywords
115 | ```
116 | 
117 | ```text
118 | Find older blog posts that mention similar topics and add internal links to my latest post
119 | ```
120 | 
121 | ```text
122 | Create a hero section card on my home page with a CTA button and responsive design
123 | ```
124 | 
125 | ### Reset your OAuth token
126 | 
127 | To reset your OAuth token, run the following command in your terminal.
128 | 
129 | ```bash
130 | rm -rf ~/.mcp-auth
131 | ```
132 | 
133 | ### Node.js compatibility
134 | 
135 | Please see the Node.js [compatibility guidance on Webflow's developer docs.](https://developers.webflow.com/data/v2.0.0/docs/ai-tools#nodejs-compatibility)
136 | 
137 | ---
138 | 
139 | 
140 | ## Local Installation
141 | 
142 | You can also configure the MCP server to run locally. This requires:
143 | 
144 | - Creating and registering your own MCP Bridge App in a Webflow workspace with Admin permissions
145 | - Configuring your AI client to start the local MCP server with a Webflow API token
146 | 
147 | ### 1. Create and publish the MCP bridge app
148 | 
149 | Before connecting the local MCP server to your AI client, you must create and publish the **Webflow MCP Bridge App** in your workspace.
150 | 
151 | ### Steps
152 | 
153 | 1. **Register a Webflow App**
154 |    - Go to your Webflow Workspace and register a new app.  
155 |    - Follow the official guide: [Register an App](https://developers.webflow.com/data/v2.0.0/docs/register-an-app).
156 | 
157 | 2. **Get the MCP Bridge App code**
158 |    - Option A: Download the latest `bundle.zip` from the [releases page](https://github.com/virat21/webflow-mcp-bridge-app/releases).
159 |    - Option B: Clone the repository and build it:
160 |      ```bash
161 |      git clone https://github.com/virat21/webflow-mcp-bridge-app
162 |      cd webflow-mcp-bridge-app
163 |      ```
164 |      - Then build the project following the repository instructions.
165 | 
166 | 3. **Publish the Designer Extension**
167 |    - Go to **Webflow Dashboard → Workspace settings → Apps & Integrations → Develop → Your App**.
168 |    - Click **“Publish Extension Version”**.
169 |    - Upload your built `bundle.zip` file.
170 | 
171 | 4. **Open the App in Designer**
172 |    - Once published, open the MCP Bridge App from the **Designer → Apps panel** in a site within your workspace.
173 | 
174 | ### 2. Configure your AI client
175 | 
176 | #### Cursor
177 | 
178 | Add to `.cursor/mcp.json`:
179 | 
180 | ```json
181 | {
182 |   "mcpServers": {
183 |     "webflow": {
184 |       "command": "npx",
185 |       "args": ["-y", "webflow-mcp-server@latest"],
186 |       "env": {
187 |         "WEBFLOW_TOKEN": "<YOUR_WEBFLOW_TOKEN>"
188 |       }
189 |     }
190 |   }
191 | }
192 | ```
193 | 
194 | #### Claude desktop
195 | 
196 | Add to `claude_desktop_config.json`:
197 | 
198 | ```json
199 | {
200 |   "mcpServers": {
201 |     "webflow": {
202 |       "command": "npx",
203 |       "args": ["-y", "webflow-mcp-server@latest"],
204 |       "env": {
205 |         "WEBFLOW_TOKEN": "<YOUR_WEBFLOW_TOKEN>"
206 |       }
207 |     }
208 |   }
209 | }
210 | ```
211 | 
212 | ### 3. Use the MCP server with the Webflow Designer
213 | 
214 | - Open your site in the Webflow Designer.
215 | - Open the Apps panel (press `E`) and launch your published “Webflow MCP Bridge App”.
216 | - Wait for the app to connect to the MCP server, then use tools from your AI client.
217 | - If the Bridge App prompts for a local connection URL, call the `get_designer_app_connection_info` tool from your AI client and paste the returned `http://localhost:<port>` URL.
218 | 
219 | ### Optional: Run locally via shell
220 | 
221 | ```bash
222 | WEBFLOW_TOKEN="<YOUR_WEBFLOW_TOKEN>" npx -y webflow-mcp-server@latest
223 | ```
224 | 
225 | ```powershell
226 | # PowerShell
227 | $env:WEBFLOW_TOKEN="<YOUR_WEBFLOW_TOKEN>"
228 | npx -y webflow-mcp-server@latest
229 | ```
230 | 
231 | ### Reset your OAuth Token
232 | 
233 | To reset your OAuth token, run the following command in your terminal.
234 | 
235 | ```bash
236 | rm -rf ~/.mcp-auth
237 | ```
238 | 
239 | ### Node.js compatibility
240 | 
241 | Please see the Node.js [compatibility guidance on Webflow's developer docs.](https://developers.webflow.com/data/v2.0.0/docs/ai-tools#nodejs-compatibility)
242 | 
243 | ## ❓ Troubleshooting
244 | 
245 | If you are having issues starting the server in your MCP client e.g. Cursor or Claude Desktop, please try the following.
246 | 
247 | ### Make sure you have a valid Webflow API token
248 | 
249 | 1. Go to [Webflow's API Playground](https://developers.webflow.com/data/reference/token/authorized-by), log in and generate a token, then copy the token from the Request Generator
250 | 2. Replace `YOUR_WEBFLOW_TOKEN` in your MCP client configuration with the token you copied
251 | 3. Save and **restart** your MCP client
252 | 
253 | ### Make sure you have the Node and NPM installed
254 | 
255 | - [Node.js](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
256 | - [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm)
257 | 
258 | Run the following commands to confirm you have Node and NPM installed:
259 | 
260 | ```shell
261 | node -v
262 | npm -v
263 | ```
264 | 
265 | ### Clear your NPM cache
266 | 
267 | Sometimes clearing your [NPM cache](https://docs.npmjs.com/cli/v8/commands/npm-cache) can resolve issues with `npx`.
268 | 
269 | ```shell
270 | npm cache clean --force
271 | ```
272 | 
273 | ### Fix NPM global package permissions
274 | 
275 | If `npm -v` doesn't work for you but `sudo npm -v` does, you may need to fix NPM global package permissions. See the official [NPM docs](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally) for more information.
276 | 
277 | Note: if you are making changes to your shell configuration, you may need to restart your shell for changes to take effect.
278 | 
279 | ## 🛠️ Available tools
280 | 
281 | See the `./tools` directory for a list of available tools
282 | 
283 | # 🗣️ Prompts & resources
284 | 
285 | This implementation **doesn't** include `prompts` or `resources` from the MCP specification. However, this may change in the future when there is broader support across popular MCP clients.
286 | 
287 | ## 📄 Webflow developer resources
288 | 
289 | - [Webflow API Documentation](https://developers.webflow.com/data/reference)
290 | - [Webflow JavaScript SDK](https://github.com/webflow/js-webflow-api)
291 | 
292 | ## ⚠️ Known limitations
293 | 
294 | ### Static page content updates
295 | 
296 | The `pages_update_static_content` endpoint currently only supports updates to localized static pages in secondary locales. Updates to static content in the default locale aren't supported and will result in errors.
297 | 
```

--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------

```markdown
 1 | MIT License
 2 | 
 3 | Copyright (c) 2025 Webflow
 4 | 
 5 | Permission is hereby granted, free of charge, to any person obtaining a copy
 6 | of this software and associated documentation files (the "Software"), to deal
 7 | in the Software without restriction, including without limitation the rights
 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 | 
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 | 
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 | 
```

--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------

```typescript
1 | export * from "./formatResponse";
2 | export * from "./typeGuards";
3 | export * from "./supportStyles";
4 | export * from "./appPort";
5 | export * from "./generateUUIDv4";
6 | 
```

--------------------------------------------------------------------------------
/src/types/RPCType.d.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { WebflowClient } from "webflow-api";
2 | 
3 | export type RPCType = {
4 |   callTool: (toolName: string, args?: any) => Promise<any>;
5 |   getClient: () => WebflowClient;
6 | };
7 | 
```

--------------------------------------------------------------------------------
/src/utils/typeGuards.ts:
--------------------------------------------------------------------------------

```typescript
1 | interface ApiError {
2 |   status: number;
3 |   message?: string;
4 | }
5 | 
6 | export function isApiError(error: unknown): error is ApiError {
7 |   return typeof error === "object" && error !== null && "status" in error;
8 | }
9 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsItemsListItemsRequestSortOrderSchema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from "zod";
2 | 
3 | export const WebflowCollectionsItemsListItemsRequestSortOrderSchema = z
4 |   .enum(["asc", "desc"])
5 |   .optional()
6 |   .describe("Order to sort the items by. Allowed values: asc, desc.");
7 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsItemsListItemsRequestSortBySchema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from "zod";
2 | 
3 | export const WebflowCollectionsItemsListItemsRequestSortBySchema = z
4 |   .enum(["lastPublished", "name", "slug"])
5 |   .optional()
6 |   .describe(
7 |     "Field to sort the items by. Allowed values: lastPublished, name, slug."
8 |   );
9 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsItemsUpdateItemsRequestSchema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from "zod";
2 | import { CollectionItemWithIdInputSchema } from "./CollectionItemWithIdInputSchema";
3 | 
4 | export const WebflowCollectionsItemsUpdateItemsRequestSchema = z.object({
5 |   items: z.array(CollectionItemWithIdInputSchema).optional(),
6 | });
7 | 
```

--------------------------------------------------------------------------------
/src/schemas/SiteIdSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const SiteIdSchema = {
 4 |   siteId: z
 5 |     .string()
 6 |     .describe(
 7 |       "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."
 8 |     ),
 9 | };
10 | 
```

--------------------------------------------------------------------------------
/src/schemas/ComponentPropertyUpdateSchema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from "zod";
2 | 
3 | export const ComponentPropertyUpdateSchema = z.array(
4 |   z.object({
5 |     propertyId: z.string().describe("Unique identifier for the property."),
6 |     text: z.string().describe("New value for the property in this locale."),
7 |   })
8 | ).describe("Array of properties to update for this component.");
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsItemsCreateItemRequestSchema.ts:
--------------------------------------------------------------------------------

```typescript
1 | import { z } from "zod";
2 | import { CollectionItemPostSingleSchema } from "./CollectionItemPostSingleSchema";
3 | 
4 | // NOTE: Cursor agent seems to struggle when provided with z.union(...), so we simplify the type here
5 | export const WebflowCollectionsItemsCreateItemRequestSchema = z.object({
6 |   items: z.array(CollectionItemPostSingleSchema).optional(),
7 | });
8 | 
```

--------------------------------------------------------------------------------
/src/utils/generateUUIDv4.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export function generateUUIDv4(): string {
 2 |   const bytes = crypto.getRandomValues(new Uint8Array(16));
 3 | 
 4 |   bytes[6] = (bytes[6] & 0x0f) | 0x40;
 5 |   bytes[8] = (bytes[8] & 0x3f) | 0x80;
 6 | 
 7 |   return [...bytes]
 8 |     .map((b, i) => {
 9 |       const hex = b.toString(16).padStart(2, "0");
10 |       return [4, 6, 8, 10].includes(i) ? `-${hex}` : hex;
11 |     })
12 |     .join("");
13 | }
14 | 
```

--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "es2017",
 4 |     "module": "es2022",
 5 |     "strict": true,
 6 |     "esModuleInterop": true,
 7 |     "forceConsistentCasingInFileNames": true,
 8 |     "moduleResolution": "bundler",
 9 |     "skipLibCheck": true,
10 |     "rootDir": "src",
11 |     "outDir": "dist"
12 |   },
13 |   "include": ["worker-configuration.d.ts", "src/**/*.ts"],
14 |   "exclude": ["node_modules", "dist"]
15 | }
16 | 
```

--------------------------------------------------------------------------------
/server.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
 3 |   "name": "com.webflow/mcp",
 4 |   "description": "AI-powered design and management for Webflow Sites",
 5 |   "repository": {
 6 |     "url": "https://github.com/webflow/mcp-server",
 7 |     "source": "github"
 8 |   },
 9 |   "version": "2.0.0",
10 |   "remotes": [
11 |     {
12 |       "type": "streamable-http",
13 |       "url": "https://mcp.webflow.com/mcp"
14 |     }
15 |   ]
16 | }
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsFieldUpdateSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import z from "zod";
 2 | 
 3 | export const WebflowCollectionsFieldUpdateSchema = z
 4 |   .object({
 5 |     isRequired: z
 6 |       .boolean()
 7 |       .optional()
 8 |       .describe("Indicates if the field is required in a collection."),
 9 |     displayName: z.string().optional().describe("Name of the field."),
10 |     helpText: z.string().optional().describe("Help text for the field."),
11 |   })
12 |   .describe("Request schema to update collection field metadata.");
13 | 
```

--------------------------------------------------------------------------------
/src/schemas/CollectionItemPostSingleSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const CollectionItemPostSingleSchema = z.object({
 4 |   id: z.string().optional(),
 5 |   cmsLocaleId: z.string().optional(),
 6 |   lastPublished: z.string().optional(),
 7 |   lastUpdated: z.string().optional(),
 8 |   createdOn: z.string().optional(),
 9 |   isArchived: z.boolean().optional(),
10 |   isDraft: z.boolean().optional(),
11 |   fieldData: z.record(z.any()).and(
12 |     z.object({
13 |       name: z.string(),
14 |       slug: z.string(),
15 |     })
16 |   ),
17 | });
18 | 
```

--------------------------------------------------------------------------------
/src/schemas/DEElementIDSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const DEElementIDSchema = {
 4 |   id: z
 5 |     .object({
 6 |       component: z
 7 |         .string()
 8 |         .describe(
 9 |           "The component id of the element to perform action on."
10 |         ),
11 |       element: z
12 |         .string()
13 |         .describe(
14 |           "The element id of the element to perform action on."
15 |         ),
16 |     })
17 |     .describe(
18 |       "The id of the element to perform action on, you can find it from id field on element. e.g id:{component:123,element:456}."
19 |     ),
20 | };
21 | 
```

--------------------------------------------------------------------------------
/src/utils/formatResponse.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export function formatResponse(response: any) {
 2 |   return {
 3 |     content: [{ type: "text" as "text", text: JSON.stringify(response) }],
 4 |   };
 5 | }
 6 | 
 7 | // https://modelcontextprotocol.io/docs/concepts/tools#error-handling-2
 8 | export function formatErrorResponse(error: any) {
 9 |   return {
10 |     isError: true,
11 |     content: [
12 |       {
13 |         type: "text" as "text",
14 |         text: JSON.stringify({
15 |           name: error.name ?? "",
16 |           message: error.message ?? "",
17 |           error: error,
18 |         }),
19 |       },
20 |     ],
21 |   };
22 | }
23 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsCreateRequestSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import z from "zod";
 2 | 
 3 | // NOTE: Cursor agent seems to struggle when provided with z.union(...), so we simplify the type here
 4 | export const WebflowCollectionsCreateRequestSchema = z.object({
 5 |   displayName: z
 6 |     .string()
 7 |     .describe(
 8 |       "Name of the collection. Each collection must have a unique name within the site."
 9 |     ),
10 |   singularName: z.string().describe("Singular name of the collection."),
11 |   slug: z
12 |     .string()
13 |     .optional()
14 |     .describe("Slug of the collection in the site URL structure. "),
15 | });
16 | 
```

--------------------------------------------------------------------------------
/src/schemas/ComponentDomWriteNodesItemSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const ComponentDomWriteNodesItemSchema = z.union([
 4 |   z.object({
 5 |     nodeId: z.string().describe("Unique identifier for the node."),
 6 |     text: z.string().describe("HTML content of the node, including the HTML tag."),
 7 |   }).describe("Text node to be updated."),
 8 |   z.object({
 9 |     nodeId: z.string().describe("Unique identifier for the node."),
10 |     propertyOverrides: z.array(
11 |       z.object({
12 |         propertyId: z.string().describe("Unique identifier for the property."),
13 |         text: z.string().describe("Value used to override a component property."),
14 |       })
15 |     ).describe("Properties to override for this locale's component instances."),
16 |   }).describe("Update text property overrides of a component instance."),
17 | ]).array();
```

--------------------------------------------------------------------------------
/src/tools/localDeMCPConnection.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { RPCType } from "../types/RPCType";
 3 | 
 4 | import {
 5 |   formatErrorResponse,
 6 |   formatResponse,
 7 | } from "../utils/formatResponse";
 8 | export function registerLocalDeMCPConnectionTools(
 9 |   server: McpServer,
10 |   rpc: RPCType
11 | ) {
12 |   const localDeMCPConnectionToolRPCCall = async () => {
13 |     return rpc.callTool("local_de_mcp_connection_tool", {});
14 |   };
15 | 
16 |   server.tool(
17 |     "get_designer_app_connection_info",
18 |     "Get Webflow MCP App Connection Info. if user ask to get Webflow MCP app connection info, use this tool",
19 |     {},
20 |     async () => {
21 |       try {
22 |         return formatResponse(
23 |           await localDeMCPConnectionToolRPCCall()
24 |         );
25 |       } catch (error) {
26 |         return formatErrorResponse(error);
27 |       }
28 |     }
29 |   );
30 | }
31 | 
```

--------------------------------------------------------------------------------
/src/schemas/ReferenceFieldSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const ReferenceFieldSchema = z.object({
 4 |   id: z.string().optional().describe("Unique identifier for the Field."),
 5 |   isEditable: z
 6 |     .boolean()
 7 |     .optional()
 8 |     .describe("Indicates if the field is editable."),
 9 |   isRequired: z
10 |     .boolean()
11 |     .optional()
12 |     .describe("Indicates if the field is required."),
13 |   type: z
14 |     .union([z.literal("MultiReference"), z.literal("Reference")])
15 |     .describe("Type of the field. Choose of these appropriate field types."),
16 |   displayName: z.string().describe("Name of the field."),
17 |   helpText: z.string().optional().describe("Help text for the field."),
18 |   metadata: z
19 |     .object({
20 |       collectionId: z.string(),
21 |     })
22 |     .describe(
23 |       "ID of the referenced collection. Use this only for Reference and MultiReference fields."
24 |     ),
25 | });
26 | 
```

--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | // Data API Tools
 2 | export { registerAiChatTools } from "./aiChat";
 3 | export { registerCmsTools } from "./cms";
 4 | export { registerComponentsTools } from "./components";
 5 | export { registerPagesTools } from "./pages";
 6 | export { registerScriptsTools } from "./scripts";
 7 | export { registerSiteTools } from "./sites";
 8 | // Designer API Tools
 9 | export { registerDEAssetTools } from "./deAsset";
10 | export { registerDEComponentsTools } from "./deComponents";
11 | export { registerDEElementTools } from "./deElement";
12 | export { registerDEPagesTools } from "./dePages";
13 | export { registerDEStyleTools } from "./deStyle";
14 | export { registerDEVariableTools } from "./deVariable";
15 | 
16 | // Rules Tools
17 | export { registerRulesTools } from "./rules";
18 | 
19 | // Only valid for OSS MCP Version, local MCP connection tools
20 | export { registerLocalDeMCPConnectionTools } from "./localDeMCPConnection";
21 | 
```

--------------------------------------------------------------------------------
/.github/workflows/publish-oss.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Publish MCP Server
 2 | 
 3 | on:
 4 |   push:
 5 |     tags:
 6 |       - 'v*.*.*'
 7 | 
 8 | jobs:
 9 |   publish:
10 |     runs-on: ubuntu-latest
11 |     steps:
12 |       - name: Checkout repository
13 |         uses: actions/checkout@v4
14 | 
15 |       - name: Update server.json version from tag
16 |         run: |
17 |           VERSION=${GITHUB_REF_NAME#v}
18 |           echo "Updating server.json to version $VERSION"
19 |           jq --arg v "$VERSION" '.version = $v' server.json > server.json.tmp && mv server.json.tmp server.json
20 | 
21 |       - name: Download mcp-publisher
22 |         run: |
23 |           curl -L "https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher_linux_amd64" -o mcp-publisher
24 |           chmod +x mcp-publisher
25 | 
26 |       - name: Login and Publish to MCP Registry
27 |         run: |
28 |           ./mcp-publisher login dns --domain webflow.com --private-key ${{ secrets.OSS_PRIVATE_KEY }}
29 |           ./mcp-publisher publish
30 | 
```

--------------------------------------------------------------------------------
/src/schemas/OptionFieldSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import z from "zod";
 2 | 
 3 | export const OptionFieldSchema = z.object({
 4 |   id: z.string().optional().describe("Unique identifier for the Field."),
 5 |   isEditable: z
 6 |     .boolean()
 7 |     .optional()
 8 |     .describe("Indicates if the field is editable."),
 9 |   isRequired: z
10 |     .boolean()
11 |     .optional()
12 |     .describe("Indicates if the field is required."),
13 |   type: z
14 |     .literal("Option")
15 |     .describe('Type of the field. Set this to "Option".'),
16 |   displayName: z.string().describe("Name of the field."),
17 |   helpText: z.string().optional().describe("Help text for the field."),
18 |   metadata: z.object({
19 |     options: z.array(
20 |       z
21 |         .object({
22 |           name: z.string().describe("Name of the option."),
23 |           id: z
24 |             .string()
25 |             .optional()
26 |             .describe("Unique identifier for the option."),
27 |         })
28 |         .describe("Array of options for the field.")
29 |     ),
30 |   }),
31 | });
32 | 
```

--------------------------------------------------------------------------------
/src/schemas/StaticFieldSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import z from "zod";
 2 | 
 3 | export const StaticFieldSchema = z.object({
 4 |   id: z.string().optional().describe("Unique identifier for the Field."),
 5 |   isEditable: z
 6 |     .boolean()
 7 |     .optional()
 8 |     .describe("Indicates if the field is editable."),
 9 |   isRequired: z
10 |     .boolean()
11 |     .optional()
12 |     .describe("Indicates if the field is required."),
13 |   type: z
14 |     .union([
15 |       z.literal("Color"),
16 |       z.literal("DateTime"),
17 |       z.literal("Email"),
18 |       z.literal("File"),
19 |       z.literal("Image"),
20 |       z.literal("Link"),
21 |       z.literal("MultiImage"),
22 |       z.literal("Number"),
23 |       z.literal("Phone"),
24 |       z.literal("PlainText"),
25 |       z.literal("RichText"),
26 |       z.literal("Switch"),
27 |       z.literal("Video"),
28 |     ])
29 |     .describe("Type of the field. Choose of these appropriate field types."),
30 |   displayName: z.string().describe("Name of the field."),
31 |   helpText: z.string().optional().describe("Help text for the field."),
32 | });
33 | 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: ci
 2 | 
 3 | on: [push]
 4 | 
 5 | jobs:
 6 |   compile:
 7 |     runs-on: ubuntu-latest
 8 | 
 9 |     steps:
10 |       - name: Checkout repo
11 |         uses: actions/checkout@v3
12 | 
13 |       - name: Set up node
14 |         uses: actions/setup-node@v3
15 | 
16 |       - name: Compile
17 |         run: npm install && npm run build
18 | 
19 |   publish:
20 |     needs: [ compile ]
21 |     if: github.event_name == 'push' && contains(github.ref, 'refs/tags/')
22 |     runs-on: ubuntu-latest
23 |     steps:
24 |       - name: Checkout repo
25 |         uses: actions/checkout@v3
26 |       - name: Set up node
27 |         uses: actions/setup-node@v3
28 |       - name: Install dependencies
29 |         run: npm install
30 |       - name: Build
31 |         run: npm run build
32 | 
33 |       - name: Publish to npm
34 |         run: |
35 |           npm config set //registry.npmjs.org/:_authToken ${NPM_TOKEN}
36 |           if [[ ${GITHUB_REF} == *alpha* ]]; then
37 |             npm publish --access public --tag alpha
38 |           elif [[ ${GITHUB_REF} == *beta* ]]; then
39 |             npm publish --access public --tag beta
40 |           else
41 |             npm publish --access public
42 |           fi
43 |         env:
44 |           NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
45 | 
```

--------------------------------------------------------------------------------
/src/schemas/RegisterInlineSiteScriptSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const RegisterInlineSiteScriptSchema = z
 4 |   .object({
 5 |     sourceCode: z
 6 |       .string()
 7 |       .describe(
 8 |         "The inline script source code (hosted by Webflow). Inline scripts are limited to 2000 characters."
 9 |       ),
10 |     version: z
11 |       .string()
12 |       .describe(
13 |         "A Semantic Version (SemVer) string, denoting the version of the script."
14 |       ),
15 |     canCopy: z
16 |       .boolean()
17 |       .optional()
18 |       .describe(
19 |         "Indicates whether the script can be copied on site duplication and transfer."
20 |       ),
21 |     displayName: z
22 |       .string()
23 |       .describe(
24 |         "User-facing name for the script. Must be between 1 and 50 alphanumeric characters."
25 |       ),
26 |     location: z
27 |       .string()
28 |       .optional()
29 |       .describe(
30 |         'Location where the script is applied. Allowed values: "header", "footer".'
31 |       ),
32 |     attributes: z
33 |       .record(z.any())
34 |       .optional()
35 |       .describe(
36 |         "Developer-specified key/value pairs to be applied as attributes to the script."
37 |       ),
38 |   })
39 |   .describe("Request schema to register an inline script for a site.");
40 | 
```

--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | #!/usr/bin/env node
 2 | 
 3 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
 4 | import { WebflowClient } from "webflow-api";
 5 | import {
 6 |   createMcpServer,
 7 |   registerDesignerTools,
 8 |   registerLocalTools,
 9 |   registerMiscTools,
10 |   registerTools,
11 | } from "./mcp";
12 | import { initDesignerAppBridge } from "./modules/designerAppBridge";
13 | 
14 | // Verify WEBFLOW_TOKEN exists
15 | if (!process.env.WEBFLOW_TOKEN) {
16 |   throw new Error("WEBFLOW_TOKEN is missing");
17 | }
18 | 
19 | // Create a Webflow client
20 | const webflowClient = new WebflowClient({
21 |   accessToken: process.env.WEBFLOW_TOKEN,
22 | });
23 | 
24 | // Return the Webflow client
25 | function getClient() {
26 |   return webflowClient;
27 | }
28 | 
29 | // Configure and run local MCP server (stdio transport)
30 | async function run() {
31 |   const server = createMcpServer();
32 |   const { callTool } = await initDesignerAppBridge();
33 |   registerMiscTools(server);
34 |   registerTools(server, getClient);
35 |   registerDesignerTools(server, {
36 |     callTool,
37 |     getClient,
38 |   });
39 | 
40 |   //Only valid for OSS MCP Version.
41 |   registerLocalTools(server, {
42 |     callTool,
43 |     getClient,
44 |   });
45 | 
46 |   const transport = new StdioServerTransport();
47 |   await server.connect(transport);
48 | }
49 | run();
50 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowPageDomWriteNodesItemSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const WebflowPageDomWriteNodesItemSchema = z
 4 |   .union([
 5 |     z
 6 |       .object({
 7 |         nodeId: z.string().describe("Unique identifier for the node."),
 8 |         text: z
 9 |           .string()
10 |           .describe(
11 |             "HTML content of the node, including the HTML tag. The HTML tags must be the same as what’s returned from the Get Content endpoint."
12 |           ),
13 |       })
14 |       .describe("Text node to be updated."),
15 |     z
16 |       .object({
17 |         nodeId: z.string().describe("Unique identifier for the node."),
18 |         propertyOverrides: z.array(
19 |           z
20 |             .object({
21 |               propertyId: z
22 |                 .string()
23 |                 .describe("Unique identifier for the property."),
24 |               text: z
25 |                 .string()
26 |                 .describe(
27 |                   "Value used to override a component property; must be type-compatible to prevent errors."
28 |                 ),
29 |             })
30 |             .describe(
31 |               "Properties to override for this locale’s component instances."
32 |             )
33 |         ),
34 |       })
35 |       .describe("Update text property overrides of a component instance."),
36 |   ])
37 |   .array();
38 | 
```

--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "webflow-mcp-server",
 3 |   "version": "1.0.0",
 4 |   "bin": "dist/index.js",
 5 |   "files": [
 6 |     "dist"
 7 |   ],
 8 |   "scripts": {
 9 |     "start": "concurrently \"npm run dev:local\" \"npm run inspector:local\"",
10 |     "dev:local": "npm run build:watch",
11 |     "inspector:local": "npx @modelcontextprotocol/inspector -- nodemon --env-file=.env -q --watch dist dist/index.js",
12 |     "dev:cf": "wrangler dev src/index.worker.ts",
13 |     "inspector:cf": "npx @modelcontextprotocol/inspector",
14 |     "deploy:cf": "npm run build && wrangler deploy",
15 |     "types:cf": "wrangler types",
16 |     "build": "tsup src/index.ts src/index.worker.ts --external=cloudflare:workers --dts --clean",
17 |     "build:watch": "tsup src/index.ts src/index.worker.ts --external=cloudflare:workers --dts --watch"
18 |   },
19 |   "dependencies": {
20 |     "@modelcontextprotocol/sdk": "^1.8.0",
21 |     "agents": "^0.0.59",
22 |     "cors": "^2.8.5",
23 |     "express": "^5.1.0",
24 |     "socket.io": "^4.8.1",
25 |     "webflow-api": "3.1.1",
26 |     "zod": "^3.24.2"
27 |   },
28 |   "devDependencies": {
29 |     "@types/cors": "^2.8.19",
30 |     "@types/express": "^5.0.3",
31 |     "@types/node": "^22.13.13",
32 |     "concurrently": "^9.1.2",
33 |     "nodemon": "^3.1.9",
34 |     "tsup": "^8.4.0",
35 |     "typescript": "^5.8.2"
36 |   }
37 | }
38 | 
```

--------------------------------------------------------------------------------
/src/schemas/CollectionItemWithIdInputSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const CollectionItemWithIdInputSchema = z
 4 |   .object({
 5 |     id: z.string().describe("Unique identifier for the item."),
 6 |     cmsLocaleId: z
 7 |       .string()
 8 |       .optional()
 9 |       .describe("Unique identifier for the locale of the CMS Item."),
10 |     lastPublished: z
11 |       .string()
12 |       .optional()
13 |       .describe("Date when the item was last published."),
14 |     lastUpdated: z
15 |       .string()
16 |       .optional()
17 |       .describe("Date when the item was last updated."),
18 |     createdOn: z
19 |       .string()
20 |       .optional()
21 |       .describe("Date when the item was created."),
22 |     isArchived: z
23 |       .boolean()
24 |       .optional()
25 |       .describe("Indicates if the item is archived."),
26 |     isDraft: z
27 |       .boolean()
28 |       .optional()
29 |       .describe("Indicates if the item is a draft."),
30 |     fieldData: z.record(z.any()).and(
31 |       z.object({
32 |         name: z.string().optional().describe("Name of the field."),
33 |         slug: z
34 |           .string()
35 |           .optional()
36 |           .describe(
37 |             "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug."
38 |           ),
39 |       })
40 |     ),
41 |   })
42 |   .describe("Collection item update request schema.");
43 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsItemsCreateItemLiveRequestSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const WebflowCollectionsItemsCreateItemLiveRequestSchema = z.object({
 4 |   items: z
 5 |     .array(
 6 |       z.object({
 7 |         id: z.string().optional(),
 8 |         cmsLocaleId: z
 9 |           .string()
10 |           .optional()
11 |           .describe("Unique identifier for the locale of the CMS Item."),
12 |         lastPublished: z
13 |           .string()
14 |           .optional()
15 |           .describe("Date when the item was last published."),
16 |         lastUpdated: z
17 |           .string()
18 |           .optional()
19 |           .describe("Date when the item was last updated."),
20 |         createdOn: z
21 |           .string()
22 |           .optional()
23 |           .describe("Date when the item was created."),
24 |         isArchived: z
25 |           .boolean()
26 |           .optional()
27 |           .describe("Indicates if the item is archived."),
28 |         isDraft: z
29 |           .boolean()
30 |           .optional()
31 |           .describe("Indicates if the item is a draft."),
32 |         fieldData: z.record(z.any()).and(
33 |           z.object({
34 |             name: z.string().describe("Name of the field."),
35 |             slug: z
36 |               .string()
37 |               .describe(
38 |                 "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug."
39 |               ),
40 |           })
41 |         ),
42 |       })
43 |     )
44 |     .optional()
45 |     .describe("Array of items to be created."),
46 | });
47 | 
```

--------------------------------------------------------------------------------
/src/tools/aiChat.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { randomUUID } from "crypto";
 3 | import { z } from "zod";
 4 | 
 5 | const BASE_URL = "https://developers.webflow.com/";
 6 | const X_FERN_HOST = "developers.webflow.com";
 7 | 
 8 | export function registerAiChatTools(server: McpServer) {
 9 |   server.tool(
10 |     "ask_webflow_ai",
11 |     "Ask Webflow AI about anything related to Webflow API.",
12 |     { message: z.string() },
13 |     async ({ message }) => {
14 |       const result = await postChat(message);
15 |       return {
16 |         content: [{ type: "text", text: result }],
17 |       };
18 |     }
19 |   );
20 | }
21 | 
22 | async function postChat(message: string) {
23 |   const response = await fetch(`${BASE_URL}/api/fern-docs/search/v2/chat`, {
24 |     method: "POST",
25 |     headers: {
26 |       "content-type": "application/json",
27 |       "x-fern-host": X_FERN_HOST,
28 |     },
29 |     body: JSON.stringify({
30 |       messages: [{ role: "user", parts: [{ type: "text", text: message }] }],
31 |       conversationId: randomUUID(),
32 |       url: BASE_URL,
33 |       source: "mcp",
34 |     }),
35 |   });
36 | 
37 |   const result = await streamToString(response);
38 |   return result;
39 | }
40 | 
41 | async function streamToString(response: Response) {
42 |   const reader = response.body?.getReader();
43 |   if (!reader) {
44 |     throw new Error("!reader");
45 |   }
46 | 
47 |   let result = "";
48 |   while (true) {
49 |     const { done, value } = await reader.read();
50 |     if (done) break;
51 | 
52 |     // Convert the Uint8Array to a string and append
53 |     result += new TextDecoder().decode(value);
54 |   }
55 | 
56 |   return result;
57 | }
58 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowCollectionsItemsUpdateItemsLiveRequestSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | // request: Webflow.collections.ItemsUpdateItemsLiveRequest
 4 | export const WebflowCollectionsItemsUpdateItemsLiveRequestSchema = z.object({
 5 |   items: z
 6 |     .array(
 7 |       z.object({
 8 |         id: z.string(),
 9 |         cmsLocaleId: z
10 |           .string()
11 |           .optional()
12 |           .describe("Unique identifier for the locale of the CMS Item."),
13 |         lastPublished: z
14 |           .string()
15 |           .optional()
16 |           .describe("Date when the item was last published."),
17 |         lastUpdated: z
18 |           .string()
19 |           .optional()
20 |           .describe("Date when the item was last updated."),
21 |         createdOn: z
22 |           .string()
23 |           .optional()
24 |           .describe("Date when the item was created."),
25 |         isArchived: z
26 |           .boolean()
27 |           .optional()
28 |           .describe("Indicates if the item is archived."),
29 |         isDraft: z
30 |           .boolean()
31 |           .optional()
32 |           .describe("Indicates if the item is a draft."),
33 |         fieldData: z
34 |           .record(z.any())
35 |           .and(
36 |             z.object({
37 |               name: z.string().optional().describe("Name of the field."),
38 |               slug: z
39 |                 .string()
40 |                 .optional()
41 |                 .describe(
42 |                   "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug."
43 |                 ),
44 |             })
45 |           )
46 |           .optional()
47 |           .describe("Array of items to be updated."),
48 |       })
49 |     )
50 |     .optional(),
51 | });
52 | 
```

--------------------------------------------------------------------------------
/src/utils/appPort.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import net from "net";
 2 | 
 3 | /**
 4 |  * Checks if a given port is free (not currently in use).
 5 |  * @param port - The port number to check.
 6 |  * @returns A Promise that resolves to `true` if the port is free, `false` if it's in use.
 7 |  */
 8 | export function isPortFree(port: number): Promise<boolean> {
 9 |   return new Promise((resolve) => {
10 |     const tester = net
11 |       .createServer()
12 |       .once("error", (err: NodeJS.ErrnoException) => {
13 |         if (err.code === "EADDRINUSE") {
14 |           resolve(false); // Port is in use
15 |         } else {
16 |           resolve(false); // Unexpected error
17 |         }
18 |       })
19 |       .once("listening", () => {
20 |         tester.close(() => resolve(true)); // Port is free
21 |       })
22 |       .listen(port);
23 |   });
24 | }
25 | 
26 | /**
27 |  * Finds the first available port in the given range.
28 |  * @param startPort - The starting port number to check.
29 |  * @param endPort - The ending port number to check.
30 |  * @returns A Promise that resolves to the first free port number.
31 |  * @throws Error if no free port is found in the range.
32 |  */
33 | export async function getFreePort(
34 |   startPort: number,
35 |   endPort: number
36 | ): Promise<number> {
37 |   if (startPort > endPort) {
38 |     throw new Error(
39 |       `Invalid port range: startPort (${startPort}) must be <= endPort (${endPort})`
40 |     );
41 |   }
42 | 
43 |   if (startPort < 1 || endPort > 65535) {
44 |     throw new Error(
45 |       `Port numbers must be between 1 and 65535`
46 |     );
47 |   }
48 | 
49 |   for (let port = startPort; port <= endPort; port++) {
50 |     if (await isPortFree(port)) {
51 |       return port;
52 |     }
53 |   }
54 | 
55 |   throw new Error(
56 |     `No free port found in range ${startPort}-${endPort}`
57 |   );
58 | }
59 | 
```

--------------------------------------------------------------------------------
/src/schemas/index.ts:
--------------------------------------------------------------------------------

```typescript
 1 | export { CollectionItemPostSingleSchema } from "./CollectionItemPostSingleSchema";
 2 | export { CollectionItemWithIdInputSchema } from "./CollectionItemWithIdInputSchema";
 3 | export { ComponentDomWriteNodesItemSchema } from "./ComponentDomWriteNodesItemSchema";
 4 | export { ComponentPropertyUpdateSchema } from "./ComponentPropertyUpdateSchema";
 5 | export { OptionFieldSchema } from "./OptionFieldSchema";
 6 | export { ReferenceFieldSchema } from "./ReferenceFieldSchema";
 7 | export { RegisterInlineSiteScriptSchema } from "./RegisterInlineSiteScriptSchema";
 8 | export { StaticFieldSchema } from "./StaticFieldSchema";
 9 | export { WebflowCollectionsCreateRequestSchema } from "./WebflowCollectionsCreateRequestSchema";
10 | export { WebflowCollectionsFieldUpdateSchema } from "./WebflowCollectionsFieldUpdateSchema";
11 | export { WebflowCollectionsItemsCreateItemLiveRequestSchema } from "./WebflowCollectionsItemsCreateItemLiveRequestSchema";
12 | export { WebflowCollectionsItemsCreateItemRequestSchema } from "./WebflowCollectionsItemsCreateItemRequestSchema";
13 | export { WebflowCollectionsItemsListItemsRequestSortBySchema } from "./WebflowCollectionsItemsListItemsRequestSortBySchema";
14 | export { WebflowCollectionsItemsListItemsRequestSortOrderSchema } from "./WebflowCollectionsItemsListItemsRequestSortOrderSchema";
15 | export { WebflowCollectionsItemsUpdateItemsLiveRequestSchema } from "./WebflowCollectionsItemsUpdateItemsLiveRequestSchema";
16 | export { WebflowCollectionsItemsUpdateItemsRequestSchema } from "./WebflowCollectionsItemsUpdateItemsRequestSchema";
17 | export { WebflowPageDomWriteNodesItemSchema } from "./WebflowPageDomWriteNodesItemSchema";
18 | export { WebflowPageSchema } from "./WebflowPageSchema";
19 | export { SiteIdSchema } from "./SiteIdSchema";
20 | export { DEElementSchema } from "./DEElementSchema";
21 | export { DEElementIDSchema } from "./DEElementIDSchema";
22 | 
```

--------------------------------------------------------------------------------
/src/mcp.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { WebflowClient } from "webflow-api";
 3 | import {
 4 |   registerAiChatTools,
 5 |   registerCmsTools,
 6 |   registerComponentsTools,
 7 |   registerDEAssetTools,
 8 |   registerDEComponentsTools,
 9 |   registerDEElementTools,
10 |   registerDEPagesTools,
11 |   registerPagesTools,
12 |   registerScriptsTools,
13 |   registerSiteTools,
14 |   registerDEStyleTools,
15 |   registerDEVariableTools,
16 |   registerRulesTools,
17 |   registerLocalDeMCPConnectionTools,
18 | } from "./tools";
19 | import { RPCType } from "./types/RPCType";
20 | 
21 | const packageJson = require("../package.json") as any;
22 | 
23 | // Create an MCP server
24 | export function createMcpServer() {
25 |   return new McpServer(
26 |     {
27 |       name: packageJson.name,
28 |       version: packageJson.version,
29 |     },
30 |     {
31 |       instructions: `These tools give you access to the Webflow's Data API. If you are ever unsure about anything Webflow API-related, use the "ask_webflow_ai" tool.`,
32 |     }
33 |   );
34 | }
35 | 
36 | // Common request options, including User-Agent header
37 | export const requestOptions = {
38 |   headers: {
39 |     "User-Agent": `Webflow MCP Server/${packageJson.version}`,
40 |   },
41 | };
42 | 
43 | // Register tools
44 | export function registerTools(
45 |   server: McpServer,
46 |   getClient: () => WebflowClient
47 | ) {
48 |   registerAiChatTools(server);
49 |   registerCmsTools(server, getClient);
50 |   registerComponentsTools(server, getClient);
51 |   registerPagesTools(server, getClient);
52 |   registerScriptsTools(server, getClient);
53 |   registerSiteTools(server, getClient);
54 | }
55 | 
56 | export function registerDesignerTools(
57 |   server: McpServer,
58 |   rpc: RPCType
59 | ) {
60 |   registerDEAssetTools(server, rpc);
61 |   registerDEComponentsTools(server, rpc);
62 |   registerDEElementTools(server, rpc);
63 |   registerDEPagesTools(server, rpc);
64 |   registerDEStyleTools(server, rpc);
65 |   registerDEVariableTools(server, rpc);
66 | }
67 | 
68 | export function registerMiscTools(server: McpServer) {
69 |   registerRulesTools(server);
70 | }
71 | 
72 | /**
73 |  * IMPORTANT: registerLocalTools is only valid for OSS MCP Version
74 |  */
75 | export function registerLocalTools(
76 |   server: McpServer,
77 |   rpc: RPCType
78 | ) {
79 |   registerLocalDeMCPConnectionTools(server, rpc);
80 | }
81 | 
```

--------------------------------------------------------------------------------
/src/tools/sites.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
 2 | import { WebflowClient } from "webflow-api";
 3 | import { z } from "zod";
 4 | import { requestOptions } from "../mcp";
 5 | import { formatErrorResponse, formatResponse } from "../utils";
 6 | 
 7 | export function registerSiteTools(
 8 |   server: McpServer,
 9 |   getClient: () => WebflowClient
10 | ) {
11 |   // GET https://api.webflow.com/v2/sites
12 |   server.tool(
13 |     "sites_list",
14 |     "List all sites accessible to the authenticated user. Returns basic site information including site ID, name, and last published date.",
15 |     async () => {
16 |       try {
17 |         const response = await getClient().sites.list(requestOptions);
18 |         return formatResponse(response);
19 |       } catch (error) {
20 |         return formatErrorResponse(error);
21 |       }
22 |     }
23 |   );
24 | 
25 |   // GET https://api.webflow.com/v2/sites/:site_id
26 |   server.tool(
27 |     "sites_get",
28 |     "Get detailed information about a specific site including its settings, domains, and publishing status.",
29 |     {
30 |       site_id: z.string().describe("Unique identifier for the site."),
31 |     },
32 |     async ({ site_id }) => {
33 |       try {
34 |         const response = await getClient().sites.get(site_id, requestOptions);
35 |         return formatResponse(response);
36 |       } catch (error) {
37 |         return formatErrorResponse(error);
38 |       }
39 |     }
40 |   );
41 | 
42 |   // POST https://api.webflow.com/v2/sites/:site_id/publish
43 |   server.tool(
44 |     "sites_publish",
45 |     "Publish a site to specified domains. This will make the latest changes live on the specified domains.",
46 |     {
47 |       site_id: z.string().describe("Unique identifier for the site."),
48 |       customDomains: z
49 |         .string()
50 |         .array()
51 |         .optional()
52 |         .describe("Array of custom domains to publish the site to."),
53 |       publishToWebflowSubdomain: z
54 |         .boolean()
55 |         .optional()
56 |         .default(false)
57 |         .describe("Whether to publish to the Webflow subdomain."),
58 |     },
59 |     async ({ site_id, customDomains, publishToWebflowSubdomain }) => {
60 |       try {
61 |         const response = await getClient().sites.publish(
62 |           site_id,
63 |           {
64 |             customDomains,
65 |             publishToWebflowSubdomain,
66 |           },
67 |           requestOptions
68 |         );
69 |         return formatResponse(response);
70 |       } catch (error) {
71 |         return formatErrorResponse(error);
72 |       }
73 |     }
74 |   );
75 | }
76 | 
```

--------------------------------------------------------------------------------
/src/schemas/WebflowPageSchema.ts:
--------------------------------------------------------------------------------

```typescript
 1 | import { z } from "zod";
 2 | 
 3 | export const WebflowPageSchema = z.object({
 4 |   id: z.string().describe("Unique identifier for a Page."),
 5 |   siteId: z.string().optional().describe("Unique identifier for the Site."),
 6 |   title: z.string().optional().describe("Title of the page."),
 7 |   slug: z
 8 |     .string()
 9 |     .optional()
10 |     .describe("Slug of the page (derived from title)."),
11 |   parentId: z
12 |     .string()
13 |     .optional()
14 |     .describe("Unique identifier for the parent folder."),
15 |   collectionId: z
16 |     .string()
17 |     .optional()
18 |     .describe(
19 |       "Unique identifier for the linked collection, NULL id the Page is not part of a collection."
20 |     ),
21 |   createdOn: z.date().optional().describe("Date when the page was created."),
22 |   lastUpdated: z
23 |     .date()
24 |     .optional()
25 |     .describe("Date when the page was last updated."),
26 |   archived: z
27 |     .boolean()
28 |     .optional()
29 |     .describe("Indicates if the page is archived."),
30 |   draft: z.boolean().optional().describe("Indicates if the page is a draft."),
31 |   canBranch: z
32 |     .boolean()
33 |     .optional()
34 |     .describe("Indicates if the page can be branched."),
35 |   isBranch: z
36 |     .boolean()
37 |     .optional()
38 |     .describe("Indicates if the page is Branch of another page."),
39 |   isMembersOnly: z
40 |     .boolean()
41 |     .optional()
42 |     .describe(
43 |       "Indicates whether the Page is restricted by Memberships Controls."
44 |     ),
45 |   seo: z
46 |     .object({
47 |       title: z
48 |         .string()
49 |         .optional()
50 |         .describe("The Page title shown in search engine results."),
51 |       description: z
52 |         .string()
53 |         .optional()
54 |         .describe("The Page description shown in search engine results."),
55 |     })
56 |     .optional()
57 |     .describe("SEO-related fields for the page."),
58 |   openGraph: z
59 |     .object({
60 |       title: z
61 |         .string()
62 |         .optional()
63 |         .describe("The title supplied to Open Graph annotations."),
64 |       titleCopied: z
65 |         .boolean()
66 |         .optional()
67 |         .describe(
68 |           "Indicates the Open Graph title was copied from the SEO title."
69 |         ),
70 |       description: z
71 |         .string()
72 |         .optional()
73 |         .describe("The description supplied to Open Graph annotations."),
74 |       descriptionCopied: z
75 |         .boolean()
76 |         .optional()
77 |         .describe(
78 |           "Indicates the Open Graph description was copied from the SEO description."
79 |         ),
80 |     })
81 |     .optional(),
82 |   localeId: z
83 |     .string()
84 |     .optional()
85 |     .describe(
86 |       "Unique identifier for the page locale. Applicable when using localization."
87 |     ),
88 |   publishedPath: z
89 |     .string()
90 |     .optional()
91 |     .describe("Relative path of the published page."),
92 | });
93 | 
```

--------------------------------------------------------------------------------
/src/tools/dePages.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { RPCType } from "../types/RPCType";
  3 | import z from "zod";
  4 | import { SiteIdSchema } from "../schemas";
  5 | import {
  6 |   formatErrorResponse,
  7 |   formatResponse,
  8 | } from "../utils";
  9 | 
 10 | export function registerDEPagesTools(
 11 |   server: McpServer,
 12 |   rpc: RPCType
 13 | ) {
 14 |   const pageToolRPCCall = async (
 15 |     siteId: string,
 16 |     actions: any
 17 |   ) => {
 18 |     return rpc.callTool("page_tool", {
 19 |       siteId,
 20 |       actions: actions || [],
 21 |     });
 22 |   };
 23 | 
 24 |   server.tool(
 25 |     "de_page_tool",
 26 |     "Designer Tool - Page tool to perform actions like create page, create page folder, get current page, switch page",
 27 |     {
 28 |       ...SiteIdSchema,
 29 |       actions: z.array(
 30 |         z.object({
 31 |           create_page: z
 32 |             .object({
 33 |               page_name: z
 34 |                 .string()
 35 |                 .describe("The name of the page to create"),
 36 |               meta_title: z
 37 |                 .string()
 38 |                 .describe(
 39 |                   "The meta title of the page to create"
 40 |                 ),
 41 |               meta_description: z
 42 |                 .string()
 43 |                 .optional()
 44 |                 .describe(
 45 |                   "The meta description of the page to create"
 46 |                 ),
 47 |               page_parent_folder_id: z
 48 |                 .string()
 49 |                 .optional()
 50 |                 .describe(
 51 |                   "The id of the parent page folder to create the page in"
 52 |                 ),
 53 |             })
 54 |             .optional()
 55 |             .describe("Create new page"),
 56 |           create_page_folder: z
 57 |             .object({
 58 |               page_folder_name: z
 59 |                 .string()
 60 |                 .describe(
 61 |                   "The name of the page folder to create"
 62 |                 ),
 63 |               page_folder_parent_id: z
 64 |                 .string()
 65 |                 .optional()
 66 |                 .describe(
 67 |                   "The id of the parent page folder to create the page folder in"
 68 |                 ),
 69 |             })
 70 |             .optional()
 71 |             .describe("Create new page folder"),
 72 | 
 73 |           get_current_page: z
 74 |             .boolean()
 75 |             .optional()
 76 |             .describe(
 77 |               "Get current page active on webflow designer"
 78 |             ),
 79 |           switch_page: z
 80 |             .object({
 81 |               page_id: z
 82 |                 .string()
 83 |                 .describe(
 84 |                   "The id of the page to switch to"
 85 |                 ),
 86 |             })
 87 |             .optional()
 88 |             .describe(
 89 |               "Switch to a page on webflow designer"
 90 |             ),
 91 |         })
 92 |       ),
 93 |     },
 94 |     async ({ siteId, actions }) => {
 95 |       try {
 96 |         return formatResponse(
 97 |           await pageToolRPCCall(siteId, actions)
 98 |         );
 99 |       } catch (error) {
100 |         return formatErrorResponse(error);
101 |       }
102 |     }
103 |   );
104 | }
105 | 
```

--------------------------------------------------------------------------------
/src/schemas/DEElementSchema.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { z } from "zod";
  2 | 
  3 | export const DEElementSchema = z.object({
  4 |   type: z
  5 |     .enum([
  6 |       "Container",
  7 |       "Section",
  8 |       "DivBlock",
  9 |       "Heading",
 10 |       "TextBlock",
 11 |       "Paragraph",
 12 |       "Button",
 13 |       "TextLink",
 14 |       "LinkBlock",
 15 |       "Image",
 16 |       "DOM",
 17 |     ])
 18 |     .describe(
 19 |       "The type of element to create. with DOM type you can create any element. make sure you pass dom_config if you are creating a DOM element."
 20 |     ),
 21 |   set_style: z
 22 |     .object({
 23 |       style_names: z
 24 |         .array(z.string())
 25 |         .describe("The style names to set on the element."),
 26 |     })
 27 |     .optional()
 28 |     .describe(
 29 |       "Set style on the element. it will remove all other styles on the element. and set only the styles passed in style_names."
 30 |     ),
 31 |   set_text: z
 32 |     .object({
 33 |       text: z
 34 |         .string()
 35 |         .describe("The text to set on the element."),
 36 |     })
 37 |     .optional()
 38 |     .describe(
 39 |       "Set text on the element. only valid for text block, paragraph, heading, button, text link, link block."
 40 |     ),
 41 |   set_link: z
 42 |     .object({
 43 |       link_type: z
 44 |         .enum([
 45 |           "url",
 46 |           "file",
 47 |           "page",
 48 |           "element",
 49 |           "email",
 50 |           "phone",
 51 |         ])
 52 |         .describe(
 53 |           "The type of link to set on the element."
 54 |         ),
 55 |       link: z
 56 |         .string()
 57 |         .describe("The link to set on the element."),
 58 |     })
 59 |     .optional()
 60 |     .describe(
 61 |       "Set link on the element. only valid for button, text link, link block."
 62 |     ),
 63 |   set_heading_level: z
 64 |     .object({
 65 |       heading_level: z
 66 |         .number()
 67 |         .min(1)
 68 |         .max(6)
 69 |         .describe(
 70 |           "The heading level to set on the element."
 71 |         ),
 72 |     })
 73 |     .optional()
 74 |     .describe(
 75 |       "Set heading level on the element. only valid for heading."
 76 |     ),
 77 |   set_image_asset: z
 78 |     .object({
 79 |       image_asset_id: z
 80 |         .string()
 81 |         .describe(
 82 |           "The image asset id to set on the element."
 83 |         ),
 84 |       alt_text: z
 85 |         .string()
 86 |         .optional()
 87 |         .describe(
 88 |           "The alt text to set on the image. if not provided it will inherit from the image asset."
 89 |         ),
 90 |     })
 91 |     .optional()
 92 |     .describe(
 93 |       "Set image asset on the element. only valid for image."
 94 |     ),
 95 |   set_dom_config: z
 96 |     .object({
 97 |       dom_tag: z
 98 |         .string()
 99 |         .describe(
100 |           "The tag of the DOM element to create. for example span, code, etc."
101 |         ),
102 |     })
103 |     .optional()
104 |     .describe(
105 |       "Set DOM config on the element. only valid for DOM element."
106 |     ),
107 |   set_attributes: z
108 |     .object({
109 |       attributes: z
110 |         .array(
111 |           z.object({
112 |             name: z
113 |               .string()
114 |               .describe(
115 |                 "The name of the attribute to set."
116 |               ),
117 |             value: z
118 |               .string()
119 |               .describe(
120 |                 "The value of the attribute to set."
121 |               ),
122 |           })
123 |         )
124 |         .describe("The attributes to set on the element."),
125 |     })
126 |     .optional()
127 |     .describe("Set attributes on the element."),
128 | });
129 | 
```

--------------------------------------------------------------------------------
/src/tools/deComponents.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { RPCType } from "../types/RPCType";
  3 | import z from "zod";
  4 | import {
  5 |   DEElementIDSchema,
  6 |   SiteIdSchema,
  7 | } from "../schemas";
  8 | import {
  9 |   formatErrorResponse,
 10 |   formatResponse,
 11 | } from "../utils";
 12 | 
 13 | export function registerDEComponentsTools(
 14 |   server: McpServer,
 15 |   rpc: RPCType
 16 | ) {
 17 |   const componentsToolRPCCall = async (
 18 |     siteId: string,
 19 |     actions: any
 20 |   ) => {
 21 |     return rpc.callTool("component_tool", {
 22 |       siteId,
 23 |       actions: actions || [],
 24 |     });
 25 |   };
 26 | 
 27 |   server.tool(
 28 |     "de_component_tool",
 29 |     "Designer tool - Component tool to perform actions like create component instances, get all components and more.",
 30 |     {
 31 |       ...SiteIdSchema,
 32 |       actions: z.array(
 33 |         z.object({
 34 |           check_if_inside_component_view: z
 35 |             .boolean()
 36 |             .optional()
 37 |             .describe(
 38 |               "Check if inside component view. this helpful to make changes to the component"
 39 |             ),
 40 |           transform_element_to_component: z
 41 |             .object({
 42 |               ...DEElementIDSchema,
 43 |               name: z
 44 |                 .string()
 45 |                 .describe("The name of the component"),
 46 |             })
 47 |             .optional()
 48 |             .describe(
 49 |               "Transform an element to a component"
 50 |             ),
 51 |           insert_component_instance: z
 52 |             .object({
 53 |               parent_element_id: DEElementIDSchema.id,
 54 |               component_id: z
 55 |                 .string()
 56 |                 .describe(
 57 |                   "The id of the component to insert"
 58 |                 ),
 59 |               creation_position: z
 60 |                 .enum(["append", "prepend"])
 61 |                 .describe(
 62 |                   "The position to create component instance on. append to the end of the parent element or prepend to the beginning of the parent element. as child of the parent element."
 63 |                 ),
 64 |             })
 65 |             .optional()
 66 |             .describe(
 67 |               "Insert a component on current active page."
 68 |             ),
 69 |           open_component_view: z
 70 |             .object({
 71 |               component_instance_id: DEElementIDSchema.id,
 72 |             })
 73 |             .optional()
 74 |             .describe(
 75 |               "Open a component instance view for changes or reading."
 76 |             ),
 77 |           close_component_view: z
 78 |             .boolean()
 79 |             .optional()
 80 |             .describe(
 81 |               "Close a component instance view. it will close and open the page view."
 82 |             ),
 83 |           get_all_components: z
 84 |             .boolean()
 85 |             .optional()
 86 |             .describe(
 87 |               "Get all components, only valid if you are connected to Webflow Designer."
 88 |             ),
 89 |           rename_component: z
 90 |             .object({
 91 |               component_id: z
 92 |                 .string()
 93 |                 .describe(
 94 |                   "The id of the component to rename"
 95 |                 ),
 96 |               new_name: z
 97 |                 .string()
 98 |                 .describe("The name of the component"),
 99 |             })
100 |             .optional()
101 |             .describe("Rename a component."),
102 |         })
103 |       ),
104 |     },
105 |     async ({ siteId, actions }) => {
106 |       try {
107 |         return formatResponse(
108 |           await componentsToolRPCCall(siteId, actions)
109 |         );
110 |       } catch (error) {
111 |         return formatErrorResponse(error);
112 |       }
113 |     }
114 |   );
115 | }
116 | 
```

--------------------------------------------------------------------------------
/src/tools/deAsset.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { RPCType } from "../types/RPCType";
  3 | import z from "zod";
  4 | import { SiteIdSchema } from "../schemas";
  5 | import {
  6 |   formatErrorResponse,
  7 |   formatResponse,
  8 | } from "../utils";
  9 | 
 10 | export function registerDEAssetTools(
 11 |   server: McpServer,
 12 |   rpc: RPCType
 13 | ) {
 14 |   const assetToolRPCCall = async (
 15 |     siteId: string,
 16 |     actions: any
 17 |   ) => {
 18 |     return rpc.callTool("asset_tool", {
 19 |       siteId,
 20 |       actions: actions || [],
 21 |     });
 22 |   };
 23 | 
 24 |   const getImagePreviewFromURL = async (
 25 |     url: string,
 26 |     siteId: string
 27 |   ) => {
 28 |     const response = await fetch(url);
 29 |     const contentType =
 30 |       response.headers.get("content-type");
 31 |     if (!contentType || !contentType.startsWith("image/")) {
 32 |       throw new Error(
 33 |         `Expected an image but received MIME type: ${
 34 |           contentType || "unknown"
 35 |         }`
 36 |       );
 37 |     }
 38 |     const arrayBuffer = await response.arrayBuffer();
 39 |     const binary = String.fromCharCode(
 40 |       ...new Uint8Array(arrayBuffer)
 41 |     );
 42 |     const base64 = btoa(binary);
 43 |     return { data: base64, mimeType: contentType, siteId };
 44 |   };
 45 | 
 46 |   server.tool(
 47 |     "asset_tool",
 48 |     "Designer Tool - Asset tool to perform actions like create folder, get all assets and folders, update assets and folders",
 49 |     {
 50 |       ...SiteIdSchema,
 51 |       actions: z.array(
 52 |         z.object({
 53 |           create_folder: z
 54 |             .object({
 55 |               name: z
 56 |                 .string()
 57 |                 .describe(
 58 |                   "The name of the folder to create"
 59 |                 ),
 60 |               parent_folder_id: z
 61 |                 .string()
 62 |                 .optional()
 63 |                 .describe(
 64 |                   "The id of the parent folder to move the folder to."
 65 |                 ),
 66 |             })
 67 |             .optional()
 68 |             .describe("Create a folder on the site"),
 69 |           get_all_assets_and_folders: z
 70 |             .object({
 71 |               query: z
 72 |                 .enum(["all", "folders", "assets"])
 73 |                 .describe(
 74 |                   "Query to get all assets and folders on the site"
 75 |                 ),
 76 |               filter_assets_by_ids: z
 77 |                 .array(z.string())
 78 |                 .describe("Filter assets by ids")
 79 |                 .optional(),
 80 |             })
 81 |             .optional()
 82 |             .describe(
 83 |               "Get all assets and folders on the site"
 84 |             ),
 85 |           update_asset: z
 86 |             .object({
 87 |               asset_id: z
 88 |                 .string()
 89 |                 .describe("The id of the asset to update"),
 90 |               name: z
 91 |                 .string()
 92 |                 .optional()
 93 |                 .describe(
 94 |                   "The name of the asset to update"
 95 |                 ),
 96 |               alt_text: z
 97 |                 .string()
 98 |                 .optional()
 99 |                 .describe(
100 |                   "The alt text of the asset to update"
101 |                 ),
102 |               parent_folder_id: z
103 |                 .string()
104 |                 .optional()
105 |                 .describe(
106 |                   "The id of the parent folder to move the asset to."
107 |                 ),
108 |             })
109 |             .optional()
110 |             .describe("Update an asset on the site"),
111 |         })
112 |       ),
113 |     },
114 |     async ({ siteId, actions }) => {
115 |       try {
116 |         return formatResponse(
117 |           await assetToolRPCCall(siteId, actions)
118 |         );
119 |       } catch (error) {
120 |         return formatErrorResponse(error);
121 |       }
122 |     }
123 |   );
124 | 
125 |   server.tool(
126 |     "get_image_preview",
127 | 
128 |     "Designer Tool - Get image preview from url. this is helpful to get image preview from url.",
129 |     {
130 |       url: z
131 |         .string()
132 |         .describe(
133 |           "The URL of the image to get the preview from"
134 |         ),
135 |       ...SiteIdSchema,
136 |     },
137 |     async ({ url, siteId }) => {
138 |       try {
139 |         const { data, mimeType } =
140 |           await getImagePreviewFromURL(url, siteId);
141 |         return {
142 |           content: [
143 |             {
144 |               type: "image",
145 |               data,
146 |               mimeType,
147 |             },
148 |           ],
149 |         };
150 |       } catch (error) {
151 |         return formatErrorResponse(error);
152 |       }
153 |     }
154 |   );
155 | }
156 | 
```

--------------------------------------------------------------------------------
/src/tools/scripts.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { WebflowClient } from "webflow-api";
  3 | import { ScriptApplyLocation } from "webflow-api/api/types/ScriptApplyLocation";
  4 | import { z } from "zod";
  5 | import { requestOptions } from "../mcp";
  6 | import { RegisterInlineSiteScriptSchema } from "../schemas";
  7 | import { formatErrorResponse, formatResponse, isApiError } from "../utils";
  8 | 
  9 | export function registerScriptsTools(
 10 |   server: McpServer,
 11 |   getClient: () => WebflowClient
 12 | ) {
 13 |   // GET https://api.webflow.com/v2/sites/:site_id/registered_scripts
 14 |   server.tool(
 15 |     "site_registered_scripts_list",
 16 |     "List all registered scripts for a site. To apply a script to a site or page, first register it via the Register Script endpoints, then apply it using the relevant Site or Page endpoints.",
 17 |     {
 18 |       site_id: z.string().describe("Unique identifier for the site."),
 19 |     },
 20 |     async ({ site_id }) => {
 21 |       try {
 22 |         const response = await getClient().scripts.list(
 23 |           site_id,
 24 |           requestOptions
 25 |         );
 26 |         return formatResponse(response);
 27 |       } catch (error) {
 28 |         return formatErrorResponse(error);
 29 |       }
 30 |     }
 31 |   );
 32 | 
 33 |   // GET https://api.webflow.com/v2/sites/:site_id/custom_code
 34 |   server.tool(
 35 |     "site_applied_scripts_list",
 36 |     "Get all scripts applied to a site by the App. To apply a script to a site or page, first register it via the Register Script endpoints, then apply it using the relevant Site or Page endpoints.",
 37 |     {
 38 |       site_id: z.string().describe("Unique identifier for the site."),
 39 |     },
 40 |     async ({ site_id }) => {
 41 |       try {
 42 |         const response = await getClient().sites.scripts.getCustomCode(
 43 |           site_id,
 44 |           requestOptions
 45 |         );
 46 |         return formatResponse(response);
 47 |       } catch (error) {
 48 |         return formatErrorResponse(error);
 49 |       }
 50 |     }
 51 |   );
 52 | 
 53 |   // POST https://api.webflow.com/v2/sites/:site_id/registered_scripts/inline
 54 |   server.tool(
 55 |     "add_inline_site_script",
 56 |     "Register an inline script for a site. Inline scripts are limited to 2000 characters. ",
 57 |     {
 58 |       site_id: z.string().describe("Unique identifier for the site."),
 59 |       request: RegisterInlineSiteScriptSchema,
 60 |     },
 61 |     async ({ site_id, request }) => {
 62 |       const registerScriptResponse = await getClient().scripts.registerInline(
 63 |         site_id,
 64 |         {
 65 |           sourceCode: request.sourceCode,
 66 |           version: request.version,
 67 |           displayName: request.displayName,
 68 |           canCopy: request.canCopy !== undefined ? request.canCopy : true,
 69 |         },
 70 |         requestOptions
 71 |       );
 72 | 
 73 |       let existingScripts: any[] = [];
 74 |       try {
 75 |         const allScriptsResponse =
 76 |           await getClient().sites.scripts.getCustomCode(
 77 |             site_id,
 78 |             requestOptions
 79 |           );
 80 |         existingScripts = allScriptsResponse.scripts || [];
 81 |       } catch (error) {
 82 |         formatErrorResponse(error);
 83 |         existingScripts = [];
 84 |       }
 85 | 
 86 |       const newScript = {
 87 |         id: registerScriptResponse.id ?? " ",
 88 |         location:
 89 |           request.location === "footer"
 90 |             ? ScriptApplyLocation.Footer
 91 |             : ScriptApplyLocation.Header,
 92 |         version: registerScriptResponse.version ?? " ",
 93 |         attributes: request.attributes,
 94 |       };
 95 | 
 96 |       existingScripts.push(newScript);
 97 | 
 98 |       const addedSiteCustomCoderesponse =
 99 |         await getClient().sites.scripts.upsertCustomCode(
100 |           site_id,
101 |           {
102 |             scripts: existingScripts,
103 |           },
104 |           requestOptions
105 |         );
106 | 
107 |       return formatResponse(registerScriptResponse);
108 |     }
109 |   );
110 | 
111 |   server.tool(
112 |     "delete_all_site_scripts",
113 |     {
114 |       site_id: z.string(),
115 |     },
116 |     async ({ site_id }) => {
117 |       try {
118 |         const response = await getClient().sites.scripts.deleteCustomCode(
119 |           site_id,
120 |           requestOptions
121 |         );
122 |         return formatResponse("Custom Code Deleted");
123 |       } catch (error) {
124 |         // If it's a 404, we'll try to clear the scripts another way
125 |         if (isApiError(error) && error.status === 404) {
126 |           return formatResponse(error.message ?? "No custom code found");
127 |         }
128 |         throw error;
129 |       }
130 |     }
131 |   );
132 | }
133 | 
```

--------------------------------------------------------------------------------
/src/modules/designerAppBridge.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import express from "express";
  2 | import http from "http";
  3 | import {
  4 |   Socket,
  5 |   Server as SocketIOServer,
  6 | } from "socket.io";
  7 | import cors from "cors";
  8 | import { RPCType } from "../types/RPCType";
  9 | import { generateUUIDv4, getFreePort } from "../utils";
 10 | 
 11 | type returnType = {
 12 |   callTool: RPCType["callTool"];
 13 | };
 14 | 
 15 | const START_PORT = 1338;
 16 | const END_PORT = 1638;
 17 | 
 18 | const initRPC = (
 19 |   io: SocketIOServer,
 20 |   port: number
 21 | ): returnType => {
 22 |   const url = `http://localhost:${port}`;
 23 |   const siteIdToSocketMap = new Map<string, Set<Socket>>();
 24 |   const pendingToolResponse = new Map<
 25 |     string,
 26 |     (response: any) => void
 27 |   >();
 28 | 
 29 |   io.on("connection", (socket) => {
 30 |     const { siteId } = socket.handshake.query as {
 31 |       siteId: string;
 32 |     };
 33 |     if (!siteId) {
 34 |       socket.emit("error", "Site ID is required");
 35 |       setTimeout(() => {
 36 |         socket.disconnect();
 37 |       }, 1000);
 38 |       return;
 39 |     }
 40 | 
 41 |     if (!siteIdToSocketMap.has(siteId)) {
 42 |       siteIdToSocketMap.set(siteId, new Set());
 43 |     }
 44 |     siteIdToSocketMap.get(siteId)!.add(socket);
 45 | 
 46 |     socket.emit("connection-confirmation", {
 47 |       siteId,
 48 |       message: "Connected to Webflow MCP",
 49 |     });
 50 | 
 51 |     socket.on("tool-call-response", (data) => {
 52 |       const { requestId, data: responseData } = data as {
 53 |         requestId: string;
 54 |         data: any;
 55 |       };
 56 |       if (!requestId) {
 57 |         return;
 58 |       }
 59 |       if (!pendingToolResponse.has(requestId)) {
 60 |         return;
 61 |       }
 62 |       const toolResponse =
 63 |         pendingToolResponse.get(requestId);
 64 |       if (toolResponse) {
 65 |         toolResponse(responseData);
 66 |         pendingToolResponse.delete(requestId);
 67 |       }
 68 |     });
 69 | 
 70 |     socket.on("disconnect", () => {
 71 |       if (siteIdToSocketMap.has(siteId)) {
 72 |         siteIdToSocketMap.get(siteId)?.delete(socket);
 73 |       }
 74 |     });
 75 |   });
 76 |   const callTool = (toolName: string, args: any) => {
 77 |     if (toolName === "local_de_mcp_connection_tool") {
 78 |       return Promise.resolve({
 79 |         status: true,
 80 |         message: `Share this url with the user to connect to the Webflow Designer App. ${url}. Please share complete url with the USER.`,
 81 |         url,
 82 |       });
 83 |     }
 84 |     const { siteId } = args as any;
 85 |     if (!siteId) {
 86 |       return Promise.resolve({
 87 |         status: false,
 88 |         error: "Site ID is required",
 89 |       });
 90 |     }
 91 |     const requestId = `${siteId}-${generateUUIDv4()}`;
 92 |     return new Promise((resolve) => {
 93 |       if (
 94 |         siteIdToSocketMap.has(siteId) &&
 95 |         siteIdToSocketMap.get(siteId)!.size > 0
 96 |       ) {
 97 |         const sockets = siteIdToSocketMap.get(siteId)!;
 98 |         for (const socket of sockets) {
 99 |           socket.emit("call-tool", {
100 |             toolName,
101 |             args,
102 |             siteId,
103 |             requestId,
104 |           });
105 |         }
106 |         const cleanup = () => {
107 |           clearTimeout(timerId);
108 |           pendingToolResponse.delete(requestId);
109 |         };
110 |         const timerId = setTimeout(() => {
111 |           cleanup();
112 |           resolve({
113 |             error: `Tool call timed out, Please check Webflow Designer MCP app is running on Webflow Designer or restart the Webflow Designer App. make sure you are using correct url ${url} on app.`,
114 |           });
115 |         }, 20000); //20 seconds
116 |         const toolResponse = (data: any) => {
117 |           cleanup();
118 |           resolve(data);
119 |         };
120 |         pendingToolResponse.set(requestId, toolResponse);
121 |       } else {
122 |         resolve({
123 |           status: false,
124 |           error:
125 |             "No active Designer app connection to the site, Please make sure Designer app is open and connected, or check the site id is valid, all site ids can be found using the sites_list tool.",
126 |         });
127 |       }
128 |     });
129 |   };
130 | 
131 |   return {
132 |     callTool,
133 |   };
134 | };
135 | 
136 | export const initDesignerAppBridge =
137 |   async (): Promise<returnType> => {
138 |     // Initialize Express app
139 |     const app = express();
140 |     app.use(cors()); // Enable CORS for all routes
141 | 
142 |     // Create HTTP server using the Express app
143 |     const server = http.createServer(app);
144 | 
145 |     // Initialize Socket.IO with the HTTP server
146 |     const io = new SocketIOServer(server, {
147 |       cors: {
148 |         origin: "*", // Allow connections from any origin
149 |         methods: ["GET", "POST", "PUT", "DELETE", "PATCH"], // Allow specified HTTP methods
150 |       },
151 |       pingTimeout: 20000, // Close connection after 20s of inactivity
152 |       transports: ["websocket", "polling"], // Enable both WebSocket and HTTP polling
153 |     });
154 | 
155 |     app.get("/", (_, res) => {
156 |       res.send("Webflow MCP is running");
157 |     });
158 | 
159 |     try {
160 |       const port = await getFreePort(START_PORT, END_PORT);
161 |       server.listen(port);
162 | 
163 |       const rpc = initRPC(io, port);
164 | 
165 |       return rpc;
166 |     } catch (e) {
167 |       return {
168 |         callTool: () => {
169 |           return Promise.resolve({
170 |             status: false,
171 |             error: `Unable to find a free port to start the Webflow Designer App Bridge. Please make sure you have port ${START_PORT}-${END_PORT} free.`,
172 |           });
173 |         },
174 |       };
175 |     }
176 |   };
177 | 
```

--------------------------------------------------------------------------------
/src/tools/pages.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { WebflowClient } from "webflow-api";
  3 | import { z } from "zod";
  4 | import { requestOptions } from "../mcp";
  5 | import {
  6 |   WebflowPageDomWriteNodesItemSchema,
  7 |   WebflowPageSchema,
  8 | } from "../schemas";
  9 | import { formatErrorResponse, formatResponse } from "../utils";
 10 | 
 11 | export function registerPagesTools(
 12 |   server: McpServer,
 13 |   getClient: () => WebflowClient
 14 | ) {
 15 |   // GET https://api.webflow.com/v2/sites/:site_id/pages
 16 |   server.tool(
 17 |     "pages_list",
 18 |     "List all pages within a site. Returns page metadata including IDs, titles, and slugs.",
 19 |     {
 20 |       site_id: z
 21 |         .string()
 22 |         .describe("The site’s unique ID, used to list its pages."),
 23 |       localeId: z
 24 |         .string()
 25 |         .optional()
 26 |         .describe(
 27 |           "Unique identifier for a specific locale. Applicable when using localization."
 28 |         ),
 29 |       limit: z
 30 |         .number()
 31 |         .optional()
 32 |         .describe("Maximum number of records to be returned (max limit: 100)"),
 33 |       offset: z
 34 |         .number()
 35 |         .optional()
 36 |         .describe(
 37 |           "Offset used for pagination if the results have more than limit records."
 38 |         ),
 39 |     },
 40 |     async ({ site_id, localeId, limit, offset }) => {
 41 |       try {
 42 |         const response = await getClient().pages.list(
 43 |           site_id,
 44 |           {
 45 |             localeId,
 46 |             limit,
 47 |             offset,
 48 |           },
 49 |           requestOptions
 50 |         );
 51 |         return formatResponse(response);
 52 |       } catch (error) {
 53 |         return formatErrorResponse(error);
 54 |       }
 55 |     }
 56 |   );
 57 | 
 58 |   // GET https://api.webflow.com/v2/pages/:page_id
 59 |   server.tool(
 60 |     "pages_get_metadata",
 61 |     "Get metadata for a specific page including SEO settings, Open Graph data, and page status (draft/published).",
 62 |     {
 63 |       page_id: z.string().describe("Unique identifier for the page."),
 64 |       localeId: z
 65 |         .string()
 66 |         .optional()
 67 |         .describe(
 68 |           "Unique identifier for a specific locale. Applicable when using localization."
 69 |         ),
 70 |     },
 71 |     async ({ page_id, localeId }) => {
 72 |       try {
 73 |         const response = await getClient().pages.getMetadata(
 74 |           page_id,
 75 |           {
 76 |             localeId,
 77 |           },
 78 |           requestOptions
 79 |         );
 80 |         return formatResponse(response);
 81 |       } catch (error) {
 82 |         return formatErrorResponse(error);
 83 |       }
 84 |     }
 85 |   );
 86 | 
 87 |   // PUT https://api.webflow.com/v2/pages/:page_id
 88 |   server.tool(
 89 |     "pages_update_page_settings",
 90 |     "Update page settings including SEO metadata, Open Graph data, slug, and publishing status.",
 91 |     {
 92 |       page_id: z.string().describe("Unique identifier for the page."),
 93 |       localeId: z
 94 |         .string()
 95 |         .optional()
 96 |         .describe(
 97 |           "Unique identifier for a specific locale. Applicable when using localization."
 98 |         ),
 99 |       body: WebflowPageSchema,
100 |     },
101 |     async ({ page_id, localeId, body }) => {
102 |       try {
103 |         const response = await getClient().pages.updatePageSettings(
104 |           page_id,
105 |           {
106 |             localeId,
107 |             body,
108 |           },
109 |           requestOptions
110 |         );
111 |         return formatResponse(response);
112 |       } catch (error) {
113 |         return formatErrorResponse(error);
114 |       }
115 |     }
116 |   );
117 | 
118 |   // GET https://api.webflow.com/v2/pages/:page_id/dom
119 |   server.tool(
120 |     "pages_get_content",
121 |     "Get the content structure and data for a specific page including all elements and their properties.",
122 |     {
123 |       page_id: z.string().describe("Unique identifier for the page."),
124 |       localeId: z
125 |         .string()
126 |         .optional()
127 |         .describe(
128 |           "Unique identifier for a specific locale. Applicable when using localization."
129 |         ),
130 |       limit: z
131 |         .number()
132 |         .optional()
133 |         .describe("Maximum number of records to be returned (max limit: 100)"),
134 |       offset: z
135 |         .number()
136 |         .optional()
137 |         .describe(
138 |           "Offset used for pagination if the results have more than limit records."
139 |         ),
140 |     },
141 |     async ({ page_id, localeId, limit, offset }) => {
142 |       try {
143 |         const response = await getClient().pages.getContent(
144 |           page_id,
145 |           {
146 |             localeId,
147 |             limit,
148 |             offset,
149 |           },
150 |           requestOptions
151 |         );
152 |         return formatResponse(response);
153 |       } catch (error) {
154 |         return formatErrorResponse(error);
155 |       }
156 |     }
157 |   );
158 | 
159 |   // POST https://api.webflow.com/v2/pages/:page_id/dom
160 |   server.tool(
161 |     "pages_update_static_content",
162 |     "Update content on a static page in secondary locales by modifying text nodes and property overrides.",
163 |     {
164 |       page_id: z.string().describe("Unique identifier for the page."),
165 |       localeId: z
166 |         .string()
167 |         .describe(
168 |           "Unique identifier for a specific locale. Applicable when using localization."
169 |         ),
170 |       nodes: WebflowPageDomWriteNodesItemSchema,
171 |     },
172 |     async ({ page_id, localeId, nodes }) => {
173 |       try {
174 |         const response = await getClient().pages.updateStaticContent(
175 |           page_id,
176 |           {
177 |             localeId,
178 |             nodes,
179 |           },
180 |           requestOptions
181 |         );
182 |         return formatResponse(response);
183 |       } catch (error) {
184 |         return formatErrorResponse(error);
185 |       }
186 |     }
187 |   );
188 | }
189 | 
```

--------------------------------------------------------------------------------
/src/tools/components.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { WebflowClient } from "webflow-api";
  3 | import { z } from "zod";
  4 | import { requestOptions } from "../mcp";
  5 | import {
  6 |   ComponentDomWriteNodesItemSchema,
  7 |   ComponentPropertyUpdateSchema
  8 | } from "../schemas";
  9 | import { formatErrorResponse, formatResponse } from "../utils";
 10 | 
 11 | export function registerComponentsTools(
 12 |   server: McpServer,
 13 |   getClient: () => WebflowClient
 14 | ) {
 15 |   // GET https://api.webflow.com/v2/sites/:site_id/components
 16 |   server.tool(
 17 |     "components_list",
 18 |     "List all components in a site. Returns component metadata including IDs, names, and versions.",
 19 |     {
 20 |       site_id: z.string().describe("Unique identifier for the Site."),
 21 |       limit: z
 22 |         .number()
 23 |         .optional()
 24 |         .describe("Maximum number of records to be returned (max limit: 100)"),
 25 |       offset: z
 26 |         .number()
 27 |         .optional()
 28 |         .describe(
 29 |           "Offset used for pagination if the results have more than limit records."
 30 |         ),
 31 |     },
 32 |     async ({ site_id, limit, offset }) => {
 33 |       try {
 34 |         const response = await getClient().components.list(
 35 |           site_id,
 36 |           {
 37 |             limit,
 38 |             offset,
 39 |           },
 40 |           requestOptions
 41 |         );
 42 |         return formatResponse(response);
 43 |       } catch (error) {
 44 |         return formatErrorResponse(error);
 45 |       }
 46 |     }
 47 |   );
 48 | 
 49 |   // GET https://api.webflow.com/v2/sites/:site_id/components/:component_id/dom
 50 |   server.tool(
 51 |     "components_get_content",
 52 |     "Get the content structure and data for a specific component including text, images, and nested components.",
 53 |     {
 54 |       site_id: z.string().describe("Unique identifier for the Site."),
 55 |       component_id: z.string().describe("Unique identifier for the Component."),
 56 |       localeId: z
 57 |         .string()
 58 |         .optional()
 59 |         .describe(
 60 |           "Unique identifier for a specific locale. Applicable when using localization."
 61 |         ),
 62 |       limit: z
 63 |         .number()
 64 |         .optional()
 65 |         .describe("Maximum number of records to be returned (max limit: 100)"),
 66 |       offset: z
 67 |         .number()
 68 |         .optional()
 69 |         .describe(
 70 |           "Offset used for pagination if the results have more than limit records."
 71 |         ),
 72 |     },
 73 |     async ({ site_id, component_id, localeId, limit, offset }) => {
 74 |       try {
 75 |         const response = await getClient().components.getContent(
 76 |           site_id,
 77 |           component_id,
 78 |           {
 79 |             localeId,
 80 |             limit,
 81 |             offset,
 82 |           },
 83 |           requestOptions
 84 |         );
 85 |         return formatResponse(response);
 86 |       } catch (error) {
 87 |         return formatErrorResponse(error);
 88 |       }
 89 |     }
 90 |   );
 91 | 
 92 |   // POST https://api.webflow.com/v2/sites/:site_id/components/:component_id/dom
 93 |   server.tool(
 94 |     "components_update_content",
 95 |     "Update content on a component in secondary locales by modifying text nodes and property overrides.",
 96 |     {
 97 |       site_id: z.string().describe("Unique identifier for the Site."),
 98 |       component_id: z.string().describe("Unique identifier for the Component."),
 99 |       localeId: z
100 |         .string()
101 |         .describe(
102 |           "Unique identifier for a specific locale. Applicable when using localization."
103 |         ),
104 |       nodes: ComponentDomWriteNodesItemSchema,
105 |     },
106 |     async ({ site_id, component_id, localeId, nodes }) => {
107 |       try {
108 |         const response = await getClient().components.updateContent(
109 |           site_id,
110 |           component_id,
111 |           {
112 |             localeId,
113 |             nodes,
114 |           },
115 |           requestOptions
116 |         );
117 |         return formatResponse(response);
118 |       } catch (error) {
119 |         return formatErrorResponse(error);
120 |       }
121 |     }
122 |   );
123 | 
124 |   // GET https://api.webflow.com/v2/sites/:site_id/components/:component_id/properties
125 |   server.tool(
126 |     "components_get_properties",
127 |     "Get component properties including default values and configuration for a specific component.",
128 |     {
129 |       site_id: z.string().describe("Unique identifier for the Site."),
130 |       component_id: z.string().describe("Unique identifier for the Component."),
131 |       localeId: z
132 |         .string()
133 |         .optional()
134 |         .describe(
135 |           "Unique identifier for a specific locale. Applicable when using localization."
136 |         ),
137 |       limit: z
138 |         .number()
139 |         .optional()
140 |         .describe("Maximum number of records to be returned (max limit: 100)"),
141 |       offset: z
142 |         .number()
143 |         .optional()
144 |         .describe(
145 |           "Offset used for pagination if the results have more than limit records."
146 |         ),
147 |     },
148 |     async ({ site_id, component_id, localeId, limit, offset }) => {
149 |       try {
150 |         const response = await getClient().components.getProperties(
151 |           site_id,
152 |           component_id,
153 |           {
154 |             localeId,
155 |             limit,
156 |             offset,
157 |           },
158 |           requestOptions
159 |         );
160 |         return formatResponse(response);
161 |       } catch (error) {
162 |         return formatErrorResponse(error);
163 |       }
164 |     }
165 |   );
166 | 
167 |   // POST https://api.webflow.com/v2/sites/:site_id/components/:component_id/properties
168 |   server.tool(
169 |     "components_update_properties",
170 |     "Update component properties for localization to customize behavior in different languages.",
171 |     {
172 |       site_id: z.string().describe("Unique identifier for the Site."),
173 |       component_id: z.string().describe("Unique identifier for the Component."),
174 |       localeId: z
175 |         .string()
176 |         .describe(
177 |           "Unique identifier for a specific locale. Applicable when using localization."
178 |         ),
179 |       properties: ComponentPropertyUpdateSchema,
180 |     },
181 |     async ({ site_id, component_id, localeId, properties }) => {
182 |       try {
183 |         const response = await getClient().components.updateProperties(
184 |           site_id,
185 |           component_id,
186 |           {
187 |             localeId,
188 |             properties,
189 |           },
190 |           requestOptions
191 |         );
192 |         return formatResponse(response);
193 |       } catch (error) {
194 |         return formatErrorResponse(error);
195 |       }
196 |     }
197 |   );
198 | }
199 | 
```

--------------------------------------------------------------------------------
/src/tools/deStyle.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { RPCType } from "../types/RPCType";
  3 | import z from "zod";
  4 | import { SiteIdSchema } from "../schemas";
  5 | import {
  6 |   formatErrorResponse,
  7 |   formatResponse,
  8 |   supportDEStyles,
  9 | } from "../utils";
 10 | 
 11 | export function registerDEStyleTools(
 12 |   server: McpServer,
 13 |   rpc: RPCType
 14 | ) {
 15 |   const styleToolRPCCall = async (
 16 |     siteId: string,
 17 |     actions: any
 18 |   ) => {
 19 |     return rpc.callTool("style_tool", {
 20 |       siteId,
 21 |       actions: actions || [],
 22 |     });
 23 |   };
 24 | 
 25 |   server.tool(
 26 |     "style_tool",
 27 |     "Designer Tool - Style tool to perform actions like create style, get all styles, update styles",
 28 |     {
 29 |       ...SiteIdSchema,
 30 |       actions: z.array(
 31 |         z.object({
 32 |           create_style: z
 33 |             .object({
 34 |               name: z
 35 |                 .string()
 36 |                 .describe("The name of the style"),
 37 |               properties: z
 38 |                 .array(
 39 |                   z.object({
 40 |                     property_name: z
 41 |                       .string()
 42 |                       .describe("The name of the property"),
 43 |                     property_value: z
 44 |                       .string()
 45 |                       .optional()
 46 |                       .describe(
 47 |                         "The value of the property"
 48 |                       ),
 49 |                     variable_as_value: z
 50 |                       .string()
 51 |                       .optional()
 52 |                       .describe(
 53 |                         "The variable id to use as the value"
 54 |                       ),
 55 |                   })
 56 |                 )
 57 |                 .describe(
 58 |                   "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"
 59 |                 ),
 60 |               parent_style_name: z
 61 |                 .string()
 62 |                 .optional()
 63 |                 .describe(
 64 |                   "The name of the parent style to create the new style in. this will use to create combo class"
 65 |                 ),
 66 |             })
 67 |             .optional()
 68 |             .describe("Create a new style"),
 69 |           get_styles: z
 70 |             .object({
 71 |               skip_properties: z
 72 |                 .boolean()
 73 |                 .optional()
 74 |                 .describe(
 75 |                   "Whether to skip the properties of the style. to get minimal data."
 76 |                 ),
 77 |               include_all_breakpoints: z
 78 |                 .boolean()
 79 |                 .optional()
 80 |                 .describe(
 81 |                   "Whether to include all breakpoints styles or not. very data intensive."
 82 |                 ),
 83 |               query: z
 84 |                 .enum(["all", "filtered"])
 85 |                 .describe(
 86 |                   "The query to get all styles or filtered styles"
 87 |                 ),
 88 |               filter_ids: z
 89 |                 .array(z.string())
 90 |                 .optional()
 91 |                 .describe(
 92 |                   "The ids of the styles to filter by. should be used with query filtered"
 93 |                 ),
 94 |             })
 95 |             .optional()
 96 |             .describe("Get all styles"),
 97 |           update_style: z
 98 |             .object({
 99 |               style_name: z
100 |                 .string()
101 |                 .describe(
102 |                   "The name of the style to update"
103 |                 ),
104 |               breakpoint_id: z
105 |                 .enum([
106 |                   "xxl",
107 |                   "xl",
108 |                   "large",
109 |                   "main",
110 |                   "medium",
111 |                   "small",
112 |                   "tiny",
113 |                 ])
114 |                 .optional()
115 |                 .describe(
116 |                   "The breakpoint to update the style for"
117 |                 ),
118 |               pseudo: z
119 |                 .enum([
120 |                   "noPseudo",
121 |                   "nth-child(odd)",
122 |                   "nth-child(even)",
123 |                   "first-child",
124 |                   "last-child",
125 |                   "hover",
126 |                   "active",
127 |                   "pressed",
128 |                   "visited",
129 |                   "focus",
130 |                   "focus-visible",
131 |                   "focus-within",
132 |                   "placeholder",
133 |                   "empty",
134 |                   "before",
135 |                   "after",
136 |                 ])
137 |                 .optional()
138 |                 .describe(
139 |                   "The pseudo class to update the style for"
140 |                 ),
141 |               properties: z
142 |                 .array(
143 |                   z.object({
144 |                     property_name: z
145 |                       .string()
146 |                       .describe("The name of the property"),
147 |                     property_value: z
148 |                       .string()
149 |                       .optional()
150 |                       .describe(
151 |                         "The value of the property"
152 |                       ),
153 |                     variable_as_value: z
154 |                       .string()
155 |                       .optional()
156 |                       .describe(
157 |                         "The variable id to use as the value"
158 |                       ),
159 |                   })
160 |                 )
161 |                 .optional()
162 |                 .describe(
163 |                   "The properties to update or add to the style for"
164 |                 ),
165 |               remove_properties: z
166 |                 .array(z.string())
167 |                 .optional()
168 |                 .describe(
169 |                   "The properties to remove from the style"
170 |                 ),
171 |             })
172 |             .optional()
173 |             .describe("Update a style"),
174 |         })
175 |       ),
176 |     },
177 |     async ({ siteId, actions }) => {
178 |       try {
179 |         return formatResponse(
180 |           await styleToolRPCCall(siteId, actions)
181 |         );
182 |       } catch (error) {
183 |         return formatErrorResponse(error);
184 |       }
185 |     }
186 |   );
187 | 
188 |   server.tool(
189 |     "de_learn_more_about_styles",
190 |     "Designer tool - Learn more about styles supported by Webflow Designer." +
191 |       "Please do not use any other styles which is not supported by Webflow Designer." +
192 |       "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.",
193 |     {},
194 |     async ({}) => formatResponse(supportDEStyles)
195 |   );
196 | }
197 | 
```

--------------------------------------------------------------------------------
/src/tools/deVariable.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { RPCType } from "../types/RPCType";
  3 | import z from "zod";
  4 | import { SiteIdSchema } from "../schemas";
  5 | import {
  6 |   formatErrorResponse,
  7 |   formatResponse,
  8 | } from "../utils";
  9 | 
 10 | export function registerDEVariableTools(
 11 |   server: McpServer,
 12 |   rpc: RPCType
 13 | ) {
 14 |   const variableToolRPCCall = async (
 15 |     siteId: string,
 16 |     actions: any
 17 |   ) => {
 18 |     return rpc.callTool("variable_tool", {
 19 |       siteId,
 20 |       actions: actions || [],
 21 |     });
 22 |   };
 23 | 
 24 |   server.tool(
 25 |     "variable_tool",
 26 |     "Designer Tool - Variable tool to perform actions like create variable, get all variables, update variable",
 27 |     {
 28 |       ...SiteIdSchema,
 29 |       actions: z.array(
 30 |         z.object({
 31 |           create_variable_collection: z
 32 |             .object({
 33 |               name: z
 34 |                 .string()
 35 |                 .describe(
 36 |                   "The name of the variable collection to create"
 37 |                 ),
 38 |             })
 39 |             .optional()
 40 |             .describe("Create a new variable collection"),
 41 |           create_variable_mode: z
 42 |             .object({
 43 |               variable_collection_id: z
 44 |                 .string()
 45 |                 .describe(
 46 |                   "The id of the variable collection to create the variable mode in"
 47 |                 ),
 48 |               name: z
 49 |                 .string()
 50 |                 .describe(
 51 |                   "The name of the variable mode to create"
 52 |                 ),
 53 |             })
 54 |             .optional()
 55 |             .describe(
 56 |               "Create a new variable mode in a variable collection"
 57 |             ),
 58 |           get_variable_collections: z
 59 |             .object({
 60 |               query: z
 61 |                 .enum(["all", "filtered"])
 62 |                 .describe(
 63 |                   "The query to get all variable collections"
 64 |                 ),
 65 |               filter_collections_by_ids: z
 66 |                 .array(z.string())
 67 |                 .optional()
 68 |                 .describe(
 69 |                   "The ids of the variable collections to filter by"
 70 |                 ),
 71 |             })
 72 |             .optional()
 73 |             .describe(
 74 |               "Get all variable collections and its modes"
 75 |             ),
 76 |           get_variables: z
 77 |             .object({
 78 |               variable_collection_id: z
 79 |                 .string()
 80 |                 .describe(
 81 |                   "The id of the variable collection to get the variables from"
 82 |                 ),
 83 |               include_all_modes: z
 84 |                 .boolean()
 85 |                 .optional()
 86 |                 .describe(
 87 |                   "Whether to include all modes or not"
 88 |                 ),
 89 |               filter_variables_by_ids: z
 90 |                 .array(z.string())
 91 |                 .optional()
 92 |                 .describe(
 93 |                   "The ids of the variables to filter by"
 94 |                 ),
 95 |             })
 96 |             .optional()
 97 |             .describe(
 98 |               "Get all variables in a variable collection and its modes"
 99 |             ),
100 |           create_color_variable: z
101 |             .object({
102 |               variable_collection_id: z.string(),
103 |               variable_name: z.string(),
104 |               value: z.object({
105 |                 static_value: z.string().optional(),
106 |                 existing_variable_id: z.string().optional(),
107 |               }),
108 |             })
109 |             .optional()
110 |             .describe("Create a new color variable"),
111 |           create_size_variable: z
112 |             .object({
113 |               variable_collection_id: z.string(),
114 |               variable_name: z.string(),
115 |               value: z.object({
116 |                 static_value: z
117 |                   .object({
118 |                     value: z.number(),
119 |                     unit: z.string(),
120 |                   })
121 |                   .optional(),
122 |                 existing_variable_id: z.string().optional(),
123 |               }),
124 |             })
125 |             .optional()
126 |             .describe("Create a new size variable"),
127 |           create_number_variable: z
128 |             .object({
129 |               variable_collection_id: z.string(),
130 |               variable_name: z.string(),
131 |               value: z.object({
132 |                 static_value: z.number().optional(),
133 |                 existing_variable_id: z.string().optional(),
134 |               }),
135 |             })
136 |             .optional()
137 |             .describe("Create a new number variable"),
138 |           create_percentage_variable: z
139 |             .object({
140 |               variable_collection_id: z.string(),
141 |               variable_name: z.string(),
142 |               value: z.object({
143 |                 static_value: z.number().optional(),
144 |                 existing_variable_id: z.string().optional(),
145 |               }),
146 |             })
147 |             .optional()
148 |             .describe("Create a new percentage variable"),
149 |           create_font_family_variable: z
150 |             .object({
151 |               variable_collection_id: z.string(),
152 |               variable_name: z.string(),
153 |               value: z.object({
154 |                 static_value: z.string().optional(),
155 |                 existing_variable_id: z.string().optional(),
156 |               }),
157 |             })
158 |             .optional()
159 |             .describe("Create a new font family variable"),
160 |           update_color_variable: z
161 |             .object({
162 |               variable_collection_id: z.string(),
163 |               variable_id: z.string(),
164 |               mode_id: z.string().optional(),
165 |               value: z.object({
166 |                 static_value: z.string().optional(),
167 |                 existing_variable_id: z.string().optional(),
168 |               }),
169 |             })
170 |             .optional()
171 |             .describe("Update a color variable"),
172 |           update_size_variable: z
173 |             .object({
174 |               variable_collection_id: z.string(),
175 |               variable_id: z.string(),
176 |               mode_id: z.string().optional(),
177 |               value: z.object({
178 |                 static_value: z
179 |                   .object({
180 |                     value: z.number(),
181 |                     unit: z.string(),
182 |                   })
183 |                   .optional(),
184 |                 existing_variable_id: z.string().optional(),
185 |               }),
186 |             })
187 |             .optional()
188 |             .describe("Update a size variable"),
189 |           update_number_variable: z
190 |             .object({
191 |               variable_collection_id: z.string(),
192 |               variable_id: z.string(),
193 |               mode_id: z.string().optional(),
194 |               value: z.object({
195 |                 static_value: z.number().optional(),
196 |                 existing_variable_id: z.string().optional(),
197 |               }),
198 |             })
199 |             .optional()
200 |             .describe("Update a number variable"),
201 |           update_percentage_variable: z
202 |             .object({
203 |               variable_collection_id: z.string(),
204 |               variable_id: z.string(),
205 |               mode_id: z.string().optional(),
206 |               value: z.object({
207 |                 static_value: z.number().optional(),
208 |                 existing_variable_id: z.string().optional(),
209 |               }),
210 |             })
211 |             .optional()
212 |             .describe("Update a percentage variable"),
213 |           update_font_family_variable: z
214 |             .object({
215 |               variable_collection_id: z.string(),
216 |               variable_id: z.string(),
217 |               mode_id: z.string().optional(),
218 |               value: z.object({
219 |                 static_value: z.string().optional(),
220 |                 existing_variable_id: z.string().optional(),
221 |               }),
222 |             })
223 |             .optional()
224 |             .describe("Update a font family variable"),
225 |         })
226 |       ),
227 |     },
228 |     async ({ siteId, actions }) => {
229 |       try {
230 |         return formatResponse(
231 |           await variableToolRPCCall(siteId, actions)
232 |         );
233 |       } catch (error) {
234 |         return formatErrorResponse(error);
235 |       }
236 |     }
237 |   );
238 | }
239 | 
```

--------------------------------------------------------------------------------
/src/tools/deElement.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { RPCType } from "../types/RPCType";
  3 | import z from "zod";
  4 | import {
  5 |   SiteIdSchema,
  6 |   DEElementIDSchema,
  7 |   DEElementSchema,
  8 | } from "../schemas";
  9 | import {
 10 |   formatErrorResponse,
 11 |   formatResponse,
 12 | } from "../utils";
 13 | 
 14 | export const registerDEElementTools = (
 15 |   server: McpServer,
 16 |   rpc: RPCType
 17 | ) => {
 18 |   const elementBuilderRPCCall = async (
 19 |     siteId: string,
 20 |     actions: any
 21 |   ) => {
 22 |     return rpc.callTool("element_builder", {
 23 |       siteId,
 24 |       actions: actions || [],
 25 |     });
 26 |   };
 27 | 
 28 |   const elementToolRPCCall = async (
 29 |     siteId: string,
 30 |     actions: any
 31 |   ) => {
 32 |     return rpc.callTool("element_tool", {
 33 |       siteId,
 34 |       actions: actions || [],
 35 |     });
 36 |   };
 37 | 
 38 |   server.tool(
 39 |     "element_builder",
 40 |     "Designer Tool - Element builder to create element on current active page. only create elements upto max 3 levels deep. divide your elements into smaller elements to create complex structures. recall this tool to create more elements. but max level is upto 3 levels. you can have as many children as you want. but max level is 3 levels.",
 41 |     {
 42 |       ...SiteIdSchema,
 43 |       actions: z.array(
 44 |         z.object({
 45 |           parent_element_id: z
 46 |             .object({
 47 |               component: z
 48 |                 .string()
 49 |                 .describe(
 50 |                   "The component id of the element to perform action on."
 51 |                 ),
 52 |               element: z
 53 |                 .string()
 54 |                 .describe(
 55 |                   "The element id of the element to perform action on."
 56 |                 ),
 57 |             })
 58 |             .describe(
 59 |               "The id of the parent element to create element on, you can find it from id field on element. e.g id:{component:123,element:456}."
 60 |             ),
 61 |           creation_position: z
 62 |             .enum(["append", "prepend"])
 63 |             .describe(
 64 |               "The position to create element on. append to the end of the parent element or prepend to the beginning of the parent element. as child of the parent element."
 65 |             ),
 66 |           element_schema: DEElementSchema.extend({
 67 |             children: z
 68 |               .array(
 69 |                 DEElementSchema.extend({
 70 |                   children: z
 71 |                     .array(
 72 |                       DEElementSchema.extend({
 73 |                         children: z
 74 |                           .array(
 75 |                             DEElementSchema.extend({
 76 |                               children: z
 77 |                                 .array(DEElementSchema)
 78 |                                 .optional(),
 79 |                             })
 80 |                           )
 81 |                           .optional(),
 82 |                       })
 83 |                     )
 84 |                     .optional(),
 85 |                 })
 86 |               )
 87 |               .optional()
 88 |               .describe(
 89 |                 "The children of the element. only valid for container, section, div block, valid DOM elements."
 90 |               ),
 91 |           }).describe(
 92 |             "element schema of element to create."
 93 |           ),
 94 |         })
 95 |       ),
 96 |     },
 97 |     async ({ actions, siteId }) => {
 98 |       try {
 99 |         return formatResponse(
100 |           await elementBuilderRPCCall(siteId, actions)
101 |         );
102 |       } catch (error) {
103 |         return formatErrorResponse(error);
104 |       }
105 |     }
106 |   );
107 | 
108 |   server.tool(
109 |     "element_tool",
110 |     "Designer Tool - Element tool to perform actions like get all elements, get selected element, select element on current active page. and more",
111 |     {
112 |       ...SiteIdSchema,
113 |       actions: z.array(
114 |         z.object({
115 |           get_all_elements: z
116 |             .object({
117 |               query: z
118 |                 .enum(["all"])
119 |                 .describe("Query to get all elements"),
120 |               include_style_properties: z
121 |                 .boolean()
122 |                 .optional()
123 |                 .describe("Include style properties"),
124 |               include_all_breakpoint_styles: z
125 |                 .boolean()
126 |                 .optional()
127 |                 .describe("Include all breakpoints styles"),
128 |             })
129 |             .optional()
130 |             .describe(
131 |               "Get all elements on the current active page"
132 |             ),
133 |           get_selected_element: z
134 |             .boolean()
135 |             .optional()
136 |             .describe(
137 |               "Get selected element on the current active page"
138 |             ),
139 |           select_element: z
140 |             .object({
141 |               ...DEElementIDSchema,
142 |             })
143 |             .optional()
144 |             .describe(
145 |               "Select an element on the current active page"
146 |             ),
147 |           add_or_update_attribute: z
148 |             .object({
149 |               ...DEElementIDSchema,
150 |               attributes: z
151 |                 .array(
152 |                   z.object({
153 |                     name: z
154 |                       .string()
155 |                       .describe(
156 |                         "The name of the attribute to add or update."
157 |                       ),
158 |                     value: z
159 |                       .string()
160 |                       .describe(
161 |                         "The value of the attribute to add or update."
162 |                       ),
163 |                   })
164 |                 )
165 |                 .describe(
166 |                   "The attributes to add or update."
167 |                 ),
168 |             })
169 |             .optional()
170 |             .describe(
171 |               "Add or update an attribute on the element"
172 |             ),
173 |           remove_attribute: z
174 |             .object({
175 |               ...DEElementIDSchema,
176 |               attribute_names: z
177 |                 .array(z.string())
178 |                 .describe(
179 |                   "The names of the attributes to remove."
180 |                 ),
181 |             })
182 |             .optional()
183 |             .describe(
184 |               "Remove an attribute from the element"
185 |             ),
186 |           update_id_attribute: z
187 |             .object({
188 |               ...DEElementIDSchema,
189 |               new_id: z
190 |                 .string()
191 |                 .describe(
192 |                   "The new #id of the element to update the id attribute to."
193 |                 ),
194 |             })
195 |             .optional()
196 |             .describe(
197 |               "Update the #id attribute of the element"
198 |             ),
199 |           set_text: z
200 |             .object({
201 |               ...DEElementIDSchema,
202 |               text: z
203 |                 .string()
204 |                 .describe(
205 |                   "The text to set on the element."
206 |                 ),
207 |             })
208 |             .optional()
209 |             .describe("Set text on the element"),
210 |           set_style: z
211 |             .object({
212 |               ...DEElementIDSchema,
213 |               style_names: z
214 |                 .array(z.string())
215 |                 .describe(
216 |                   "The style names to set on the element."
217 |                 ),
218 |             })
219 |             .optional()
220 |             .describe(
221 |               "Set style on the element. it will remove all other styles on the element. and set only the styles passed in style_names."
222 |             ),
223 |           set_link: z
224 |             .object({
225 |               ...DEElementIDSchema,
226 |               linkType: z
227 |                 .enum([
228 |                   "url",
229 |                   "file",
230 |                   "page",
231 |                   "element",
232 |                   "email",
233 |                   "phone",
234 |                 ])
235 |                 .describe(
236 |                   "The type of the link to update."
237 |                 ),
238 |               link: z
239 |                 .string()
240 |                 .describe(
241 |                   "The link to set on the element. for page pass page id, for element pass json string of id object. e.g id:{component:123,element:456}. for email pass email address. for phone pass phone number. for file pass asset id. for url pass url."
242 |                 ),
243 |             })
244 |             .optional()
245 |             .describe("Set link on the element"),
246 |           set_heading_level: z
247 |             .object({
248 |               ...DEElementIDSchema,
249 |               heading_level: z
250 |                 .number()
251 |                 .min(1)
252 |                 .max(6)
253 |                 .describe(
254 |                   "The heading level to set on the element. 1 to 6."
255 |                 ),
256 |             })
257 |             .optional()
258 |             .describe(
259 |               "Set heading level on the heading element."
260 |             ),
261 |           set_image_asset: z
262 |             .object({
263 |               ...DEElementIDSchema,
264 |               image_asset_id: z
265 |                 .string()
266 |                 .describe(
267 |                   "The image asset id to set on the element."
268 |                 ),
269 |             })
270 |             .optional()
271 |             .describe(
272 |               "Set image asset on the image element"
273 |             ),
274 |         })
275 |       ),
276 |     },
277 |     async ({ actions, siteId }) => {
278 |       try {
279 |         return formatResponse(
280 |           await elementToolRPCCall(siteId, actions)
281 |         );
282 |       } catch (error) {
283 |         return formatErrorResponse(error);
284 |       }
285 |     }
286 |   );
287 | };
288 | 
```

--------------------------------------------------------------------------------
/src/tools/rules.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | 
  3 | export function registerRulesTools(server: McpServer) {
  4 |   server.tool(
  5 |     "webflow_guide_tool",
  6 |     "Provides essential guidelines and best practices for effectively using the Webflow tools. Call this tool to understand recommended workflows and important considerations before performing actions. ALWAYS CALL THIS TOOL FIRST BEFORE CALLING ANY OTHER TOOLS.  ALWAYS CALL THIS TOOL FIRST BEFORE CALLING ANY OTHER TOOLS. ",
  7 |     {},
  8 |     async ({}) => ({
  9 |       content: [
 10 |         {
 11 |           type: "text",
 12 |           text:
 13 |             `Webflow Tool Usage Guidelines:\n` +
 14 |             `- These rules are essential for using tools correctly and helping users achieve their goals. Follow all instructions precisely.\n` +
 15 |             `\n` +
 16 |             `General Rules:\n` +
 17 |             `-- Data Tools are REST API calls, and Designer Tools are UI tools. You must use the correct tool for the action you want to perform.\n` +
 18 |             `-- Webflow does not support shorthand css properties. You must use the longhand property names. 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. to learn more about supported styles, use de_learn_more_about_styles tool.\n` +
 19 |             `-- Do not assume site ID. If a tool requires site_id, you must pass it explicitly. If you're not sure about the site ID, ask the user for it.\n` +
 20 |             `-- Always plan your actions before calling any tool. Do not invoke tools randomly without understanding the full workflow.\n` +
 21 |             `-- After updating or creating an element, the updated/created element is not automatically selected. If you need more information about that element, use element_tool > select_element with the appropriate element ID to select and inspect it.\n` +
 22 |             `-- Do not use CSS shorthand properties when updating or creating styles. Always use longhand property names like "margin-top", "padding-left", "border-width", etc.\n` +
 23 |             `-- When creating or updating elements, most users prefer using existing styles. You should reuse styles if they exist, unless the user explicitly wants new ones.\n` +
 24 |             `\n` +
 25 |             `Element Tool Usage:\n` +
 26 |             `-- To get detailed information about the currently selected element, use element_tool > get_selected_element.\n` +
 27 |             `-- To select a specific element by its ID, use element_tool > select_element.\n` +
 28 |             `-- To retrieve all elements on the current page, use element_tool > get_all_elements. If you only need element structure or class names, set include_style_properties and include_all_breakpoint_styles to false to avoid excessive data and context overflow. Set them to true only if you specifically need style details and all breakpoints.\n` +
 29 |             `-- To add or update attributes on an element, use element_tool > add_or_update_attribute. Only do this if the element's metadata shows canHaveAttributes: true.\n` +
 30 |             `-- To remove an attribute from an element, use element_tool > remove_attribute. This action is also valid only if canHaveAttributes is true for the element.\n` +
 31 |             `-- To update the ID attribute of an element, use element_tool > update_id_attribute. This sets a custom ID (#id) for the element. Do not pass the '#' character in the ID string. If the element already has an ID, you can read it using the domId property.\n` +
 32 |             `-- To set one or more styles on an element, use element_tool > set_style. You must pass one or more valid style names that already exist. If multiple styles are applied, they are treated as combo classes.\n` +
 33 |             `-- To set or update a link for Button, TextLink, or LinkBlock elements, use element_tool > set_link.\n` +
 34 |             `-- To set an image asset on an Image element, use element_tool > set_image_asset. Make sure you pass a valid asset_id. You can retrieve available assets via asset_tool > get_all_assets_and_folders.\n` +
 35 |             `-- To update the heading level for a Heading element, use element_tool > set_heading_level. Valid heading levels are integers from 1 to 6, which correspond to h1 through h6.\n` +
 36 |             `\n` +
 37 |             `Element Builder Tool:\n` +
 38 |             `-- To create a new element, use element_builder. Pass the type of element you want to create. After creation, use element_tool > select_element to select the element and gather additional details if needed.\n` +
 39 |             `\n` +
 40 |             `Asset Tool Usage:\n` +
 41 |             `-- To create an asset folder, use asset_tool > create_folder. Pass the name of the folder. To create a nested folder, pass parent_folder_id. Otherwise, the folder will be created in the root directory.\n` +
 42 |             `-- To retrieve assets and folders, use asset_tool > get_all_assets_and_folders. You can use query as "all", "folders", or "assets". To limit data, use filter_assets_by_ids or search query. Fetch only what you need to avoid context overload.\n` +
 43 |             `-- To update an asset, use asset_tool > update_asset. Pass asset_id and optionally pass name, parent_folder_id, or alt_text to update one or more fields.\n` +
 44 |             `\n` +
 45 |             `Page Tool Usage:\n` +
 46 |             `-- To create a page, use page_tool > create_page. Pass the page name. After creation, the system will automatically switch to the new page. The tool returns the page info.\n` +
 47 |             `-- To create a page folder, use page_tool > create_page_folder. Pass the folder name. To create a nested folder, also pass parent_id. Otherwise, the folder is created in the root.\n` +
 48 |             `-- To get information about the current page, use page_tool > get_current_page. It returns page metadata.\n` +
 49 |             `-- To switch to a different page, use page_tool > switch_page. Pass the page_id to switch to that page.\n` +
 50 |             `\n` +
 51 |             `Style Tool Usage:\n` +
 52 |             `-- To create a new style, use style_tool > create_style. Pass the name. For combo classes, pass parent_style_name. Always use longhand property names when defining style properties.\n` +
 53 |             `-- To update a style, use style_tool > update_style. If breakpoint is not provided, it defaults to the main breakpoint. If pseudo is not provided, it defaults to "noPseudo".\n` +
 54 |             `-- To get existing styles, use style_tool > get_styles. Use filters or queries to retrieve only what's needed. This tool may return a lot of data.\n` +
 55 |             `-- Breakpoint-specific style behavior:\n` +
 56 |             `---- Styles set on xxl (1920px) apply to screens ≥ 1920px.\n` +
 57 |             `---- Styles set on xl (1440px) apply to screens ≥ 1440px.\n` +
 58 |             `---- Styles set on large (1280px) apply to screens ≥ 1280px.\n` +
 59 |             `---- Styles set on main apply to all devices unless overridden at other breakpoints.\n` +
 60 |             `---- Styles set on medium (tablet) apply to screens ≤ 991px.\n` +
 61 |             `---- Styles set on small (mobile landscape) apply to screens ≤ 767px.\n` +
 62 |             `---- Styles set on tiny (mobile portrait) apply to screens ≤ 478px.\n` +
 63 |             `\n` +
 64 |             `Variable Tool Usage:\n` +
 65 |             `-- To create a variable collection, use variable_tool > create_variable_collection. Pass the name.\n` +
 66 |             `-- To create a variable mode, use variable_tool > create_variable_mode. Pass the name and variable_collection_id.\n` +
 67 |             `-- To retrieve variable collections, use variable_tool > get_variable_collections. You can filter by query or use filter_collections_by_ids.\n` +
 68 |             `-- To retrieve variables, use variable_tool > get_variables. You can filter by query or filter_variables_by_ids.\n` +
 69 |             `-- To create variables of different types, use:\n` +
 70 |             `---- variable_tool > create_color_variable\n` +
 71 |             `---- variable_tool > create_size_variable\n` +
 72 |             `---- variable_tool > create_number_variable\n` +
 73 |             `---- variable_tool > create_percentage_variable\n` +
 74 |             `---- variable_tool > create_font_family_variable\n` +
 75 |             `-- In all create_*_variable tools, pass name and variable_collection_id.\n` +
 76 |             `-- To update any variable, use the corresponding update tool (e.g., update_color_variable) with name and variable_collection_id.\n` +
 77 |             `-- To bind a variable with another, use the existing_variable_id field.\n` +
 78 |             `-- In Webflow, variables are linked to styles and function like CSS custom properties.\n` +
 79 |             `\n` +
 80 |             `CMS Data Tool Usage:\n` +
 81 |             `-- To get a list of CMS collections, use cms_tool > get_collection_list. Pass the site_id.\n` +
 82 |             `-- To get a CMS collection details, use cms_tool > get_collection_details. Pass the collection_id.\n` +
 83 |             `-- To create a new CMS collection, use cms_tool > create_collection. Pass the name and site_id.\n` +
 84 |             `-- To create a new CMS collection static field, use cms_tool > create_collection_static_field. Pass the collection_id and data.\n` +
 85 |             `-- To create a new CMS collection option field, use cms_tool > create_collection_option_field. Pass the collection_id and data.\n` +
 86 |             `-- To create a new CMS collection reference field, use cms_tool > create_collection_reference_field. Pass the collection_id and data.\n` +
 87 |             `-- To update a CMS collection field, use cms_tool > update_collection_field. Pass the collection_id and field_id and data.\n` +
 88 |             `-- To create a new CMS collection item, use cms_tool > create_collection_items_live. Pass the collection_id and data.\n` +
 89 |             `-- To update a CMS collection item, use cms_tool > update_collection_items_live. Pass the collection_id and data.\n` +
 90 |             `-- To publish a CMS collection item, use cms_tool > publish_collection_items. Pass the collection_id and item_ids.\n` +
 91 |             `-- To delete a CMS collection item, use cms_tool > delete_collection_items. Pass the collection_id and items.\n` +
 92 |             `\n` +
 93 |             `Pages Data Tool Usage:\n` +
 94 |             `-- To get a list of pages, use pages_tool > get_pages_list. Pass the site_id.\n` +
 95 |             `-- To get the metadata of a page, use pages_tool > get_page_metadata. Pass the page_id.\n` +
 96 |             `-- To update page settings, use pages_tool > update_page_settings. Pass the page_id and data.\n` +
 97 |             `-- To get the content of a page, use pages_tool > get_page_content. Pass the page_id.\n` +
 98 |             `-- To update the static content of a page, use pages_tool > update_page_static_content. Pass the page_id and localeId and nodes.\n` +
 99 |             `\n` +
100 |             `### Important rules for creating elements.\n` +
101 |             `-- Always create styles first if you plan to apply them while creating the element. This ensures style references are valid at the time of creation.\n` +
102 |             `-- Always plan out your actions before calling element_builder. Know exactly what type of element to create, what styles or attributes to apply, and how you will use it.\n` +
103 |             `-- Once an element is created using element_builder, it is not automatically selected. To inspect or modify it, use element_tool > select_element and pass the element ID returned from the creation response.\n` +
104 |             `-- Only Container, Section, DivBlock, some valid DOM elements can have children.\n`,
105 |         },
106 |       ],
107 |     })
108 |   );
109 | }
110 | 
```

--------------------------------------------------------------------------------
/src/tools/cms.ts:
--------------------------------------------------------------------------------

```typescript
  1 | import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
  2 | import { WebflowClient } from "webflow-api";
  3 | import { z } from "zod";
  4 | import { requestOptions } from "../mcp";
  5 | import {
  6 |   OptionFieldSchema,
  7 |   ReferenceFieldSchema,
  8 |   StaticFieldSchema,
  9 |   WebflowCollectionsCreateRequestSchema,
 10 |   WebflowCollectionsFieldUpdateSchema,
 11 |   WebflowCollectionsItemsCreateItemLiveRequestSchema,
 12 |   WebflowCollectionsItemsCreateItemRequestSchema,
 13 |   WebflowCollectionsItemsListItemsRequestSortBySchema,
 14 |   WebflowCollectionsItemsListItemsRequestSortOrderSchema,
 15 |   WebflowCollectionsItemsUpdateItemsLiveRequestSchema,
 16 |   WebflowCollectionsItemsUpdateItemsRequestSchema,
 17 | } from "../schemas";
 18 | import { formatErrorResponse, formatResponse } from "../utils";
 19 | 
 20 | export function registerCmsTools(
 21 |   server: McpServer,
 22 |   getClient: () => WebflowClient
 23 | ) {
 24 |   // GET https://api.webflow.com/v2/sites/:site_id/collections
 25 |   server.tool(
 26 |     "collections_list",
 27 |     "List all CMS collections in a site. Returns collection metadata including IDs, names, and schemas.",
 28 |     {
 29 |       site_id: z.string().describe("Unique identifier for the Site."),
 30 |     },
 31 |     async ({ site_id }) => {
 32 |       try {
 33 |         const response = await getClient().collections.list(
 34 |           site_id,
 35 |           requestOptions
 36 |         );
 37 |         return formatResponse(response);
 38 |       } catch (error) {
 39 |         return formatErrorResponse(error);
 40 |       }
 41 |     }
 42 |   );
 43 | 
 44 |   // GET https://api.webflow.com/v2/collections/:collection_id
 45 |   server.tool(
 46 |     "collections_get",
 47 |     "Get detailed information about a specific CMS collection including its schema and field definitions.",
 48 |     {
 49 |       collection_id: z
 50 |         .string()
 51 |         .describe("Unique identifier for the Collection."),
 52 |     },
 53 |     async ({ collection_id }) => {
 54 |       try {
 55 |         const response = await getClient().collections.get(
 56 |           collection_id,
 57 |           requestOptions
 58 |         );
 59 |         return formatResponse(response);
 60 |       } catch (error) {
 61 |         return formatErrorResponse(error);
 62 |       }
 63 |     }
 64 |   );
 65 | 
 66 |   // POST https://api.webflow.com/v2/sites/:site_id/collections
 67 |   server.tool(
 68 |     "collections_create",
 69 |     "Create a new CMS collection in a site with specified name and schema.",
 70 |     {
 71 |       site_id: z.string().describe("Unique identifier for the Site."),
 72 |       request: WebflowCollectionsCreateRequestSchema,
 73 |     },
 74 |     async ({ site_id, request }) => {
 75 |       try {
 76 |         const response = await getClient().collections.create(
 77 |           site_id,
 78 |           request,
 79 |           requestOptions
 80 |         );
 81 |         return formatResponse(response);
 82 |       } catch (error) {
 83 |         return formatErrorResponse(error);
 84 |       }
 85 |     }
 86 |   );
 87 | 
 88 |   // POST https://api.webflow.com/v2/collections/:collection_id/fields
 89 |   server.tool(
 90 |     "collection_fields_create_static",
 91 |     "Create a new static field in a CMS collection (e.g., text, number, date, etc.).",
 92 |     {
 93 |       collection_id: z
 94 |         .string()
 95 |         .describe("Unique identifier for the Collection."),
 96 |       request: StaticFieldSchema,
 97 |     },
 98 |     async ({ collection_id, request }) => {
 99 |       try {
100 |         const response = await getClient().collections.fields.create(
101 |           collection_id,
102 |           request,
103 |           requestOptions
104 |         );
105 |         return formatResponse(response);
106 |       } catch (error) {
107 |         return formatErrorResponse(error);
108 |       }
109 |     }
110 |   );
111 | 
112 |   // POST https://api.webflow.com/v2/collections/:collection_id/fields
113 |   server.tool(
114 |     "collection_fields_create_option",
115 |     "Create a new option field in a CMS collection with predefined choices.",
116 |     {
117 |       collection_id: z
118 |         .string()
119 |         .describe("Unique identifier for the Collection."),
120 |       request: OptionFieldSchema,
121 |     },
122 |     async ({ collection_id, request }) => {
123 |       try {
124 |         const response = await getClient().collections.fields.create(
125 |           collection_id,
126 |           request,
127 |           requestOptions
128 |         );
129 |         return formatResponse(response);
130 |       } catch (error) {
131 |         return formatErrorResponse(error);
132 |       }
133 |     }
134 |   );
135 | 
136 |   // POST https://api.webflow.com/v2/collections/:collection_id/fields
137 |   server.tool(
138 |     "collection_fields_create_reference",
139 |     "Create a new reference field in a CMS collection that links to items in another collection.",
140 |     {
141 |       collection_id: z
142 |         .string()
143 |         .describe("Unique identifier for the Collection."),
144 |       request: ReferenceFieldSchema,
145 |     },
146 |     async ({ collection_id, request }) => {
147 |       try {
148 |         const response = await getClient().collections.fields.create(
149 |           collection_id,
150 |           request,
151 |           requestOptions
152 |         );
153 |         return formatResponse(response);
154 |       } catch (error) {
155 |         return formatErrorResponse(error);
156 |       }
157 |     }
158 |   );
159 | 
160 |   // PATCH https://api.webflow.com/v2/collections/:collection_id/fields/:field_id
161 |   server.tool(
162 |     "collection_fields_update",
163 |     "Update properties of an existing field in a CMS collection.",
164 |     {
165 |       collection_id: z
166 |         .string()
167 |         .describe("Unique identifier for the Collection."),
168 |       field_id: z.string().describe("Unique identifier for the Field."),
169 |       request: WebflowCollectionsFieldUpdateSchema,
170 |     },
171 |     async ({ collection_id, field_id, request }) => {
172 |       try {
173 |         const response = await getClient().collections.fields.update(
174 |           collection_id,
175 |           field_id,
176 |           request,
177 |           requestOptions
178 |         );
179 |         return formatResponse(response);
180 |       } catch (error) {
181 |         return formatErrorResponse(error);
182 |       }
183 |     }
184 |   );
185 | 
186 |   // POST https://api.webflow.com/v2/collections/:collection_id/items/live
187 |   // NOTE: Cursor agent seems to struggle when provided with z.union(...), so we simplify the type here
188 |   server.tool(
189 |     "collections_items_create_item_live",
190 |     "Create and publish new items in a CMS collection directly to the live site.",
191 |     {
192 |       collection_id: z
193 |         .string()
194 |         .describe("Unique identifier for the Collection."),
195 |       request: WebflowCollectionsItemsCreateItemLiveRequestSchema,
196 |     },
197 |     async ({ collection_id, request }) => {
198 |       try {
199 |         const response = await getClient().collections.items.createItemLive(
200 |           collection_id,
201 |           request,
202 |           requestOptions
203 |         );
204 |         return formatResponse(response);
205 |       } catch (error) {
206 |         return formatErrorResponse(error);
207 |       }
208 |     }
209 |   );
210 | 
211 |   // PATCH https://api.webflow.com/v2/collections/:collection_id/items/live
212 |   server.tool(
213 |     "collections_items_update_items_live",
214 |     "Update and publish existing items in a CMS collection directly to the live site.",
215 |     {
216 |       collection_id: z
217 |         .string()
218 |         .describe("Unique identifier for the Collection."),
219 |       request: WebflowCollectionsItemsUpdateItemsLiveRequestSchema,
220 |     },
221 |     async ({ collection_id, request }) => {
222 |       try {
223 |         const response = await getClient().collections.items.updateItemsLive(
224 |           collection_id,
225 |           request,
226 |           requestOptions
227 |         );
228 |         return formatResponse(response);
229 |       } catch (error) {
230 |         return formatErrorResponse(error);
231 |       }
232 |     }
233 |   );
234 | 
235 |   // GET https://api.webflow.com/v2/collections/:collection_id/items
236 |   server.tool(
237 |     "collections_items_list_items",
238 |     "List items in a CMS collection with optional filtering and sorting.",
239 |     {
240 |       collection_id: z
241 |         .string()
242 |         .describe("Unique identifier for the Collection."),
243 |       cmsLocaleId: z
244 |         .string()
245 |         .optional()
246 |         .describe("Unique identifier for the locale of the CMS Item."),
247 |       limit: z
248 |         .number()
249 |         .optional()
250 |         .describe("Maximum number of records to be returned (max limit: 100)"),
251 |       offset: z
252 |         .number()
253 |         .optional()
254 |         .describe(
255 |           "Offset used for pagination if the results have more than limit records."
256 |         ),
257 |       name: z.string().optional().describe("Name of the field."),
258 |       slug: z
259 |         .string()
260 |         .optional()
261 |         .describe(
262 |           "URL structure of the Item in your site. Note: Updates to an item slug will break all links referencing the old slug."
263 |         ),
264 |       sortBy: WebflowCollectionsItemsListItemsRequestSortBySchema,
265 |       sortOrder: WebflowCollectionsItemsListItemsRequestSortOrderSchema,
266 |     },
267 |     async ({
268 |       collection_id,
269 |       cmsLocaleId,
270 |       offset,
271 |       limit,
272 |       name,
273 |       slug,
274 |       sortBy,
275 |       sortOrder,
276 |     }) => {
277 |       try {
278 |         const response = await getClient().collections.items.listItems(
279 |           collection_id,
280 |           {
281 |             cmsLocaleId,
282 |             offset,
283 |             limit,
284 |             name,
285 |             slug,
286 |             sortBy,
287 |             sortOrder,
288 |           },
289 |           requestOptions
290 |         );
291 |         return formatResponse(response);
292 |       } catch (error) {
293 |         return formatErrorResponse(error);
294 |       }
295 |     }
296 |   );
297 | 
298 |   // POST https://api.webflow.com/v2/collections/:collection_id/items
299 |   server.tool(
300 |     "collections_items_create_item",
301 |     "Create new items in a CMS collection as drafts.",
302 |     {
303 |       collection_id: z.string(),
304 |       request: WebflowCollectionsItemsCreateItemRequestSchema,
305 |     },
306 |     async ({ collection_id, request }) => {
307 |       try {
308 |         const response = await getClient().collections.items.createItem(
309 |           collection_id,
310 |           request,
311 |           requestOptions
312 |         );
313 |         return formatResponse(response);
314 |       } catch (error) {
315 |         return formatErrorResponse(error);
316 |       }
317 |     }
318 |   );
319 | 
320 |   // PATCH https://api.webflow.com/v2/collections/:collection_id/items
321 |   server.tool(
322 |     "collections_items_update_items",
323 |     "Update existing items in a CMS collection as drafts.",
324 |     {
325 |       collection_id: z
326 |         .string()
327 |         .describe("Unique identifier for the Collection."),
328 |       request: WebflowCollectionsItemsUpdateItemsRequestSchema,
329 |     },
330 |     async ({ collection_id, request }) => {
331 |       try {
332 |         const response = await getClient().collections.items.updateItems(
333 |           collection_id,
334 |           request,
335 |           requestOptions
336 |         );
337 |         return formatResponse(response);
338 |       } catch (error) {
339 |         return formatErrorResponse(error);
340 |       }
341 |     }
342 |   );
343 | 
344 |   // POST https://api.webflow.com/v2/collections/:collection_id/items/publish
345 |   server.tool(
346 |     "collections_items_publish_items",
347 |     "Publish draft items in a CMS collection to make them live.",
348 |     {
349 |       collection_id: z
350 |         .string()
351 |         .describe("Unique identifier for the Collection."),
352 |       itemIds: z
353 |         .array(z.string())
354 |         .describe("Array of item IDs to be published."),
355 |     },
356 |     async ({ collection_id, itemIds }) => {
357 |       try {
358 |         const response = await getClient().collections.items.publishItem(
359 |           collection_id,
360 |           {
361 |             itemIds: itemIds,
362 |           },
363 |           requestOptions
364 |         );
365 |         return formatResponse(response);
366 |       } catch (error) {
367 |         return formatErrorResponse(error);
368 |       }
369 |     }
370 |   );
371 | 
372 | 
373 |    // DEL https://api.webflow.com/v2/collections/:collection_id/items/
374 |    server.tool(
375 |     "collections_items_delete_item",
376 |     "Delete an item in a CMS collection. Items will only be deleted in the primary locale unless a cmsLocaleId is included in the request. ",
377 |     {
378 |       collection_id: z.string().describe("Unique identifier for the Collection."),
379 |       itemId: z.string().describe("Item ID to be deleted."),
380 |       cmsLocaleIds: z.string().optional().describe("Unique identifier for the locale of the CMS Item."),
381 |     },
382 |     async ({ collection_id, itemId, cmsLocaleIds }) => {
383 |       try {
384 |       const response = await getClient().collections.items.deleteItem(
385 |         collection_id,
386 |         itemId,
387 |         { cmsLocaleId: cmsLocaleIds},
388 |         requestOptions
389 |       );
390 |       return formatResponse(JSON.stringify("Item deleted"));
391 |     } catch (error) {
392 |       return formatErrorResponse(error);
393 |     }
394 |     }
395 |   );
396 | }
397 | 
```
Page 1/2FirstPrevNextLast