#
tokens: 5030/50000 7/7 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package.json
├── README.md
├── smithery.yaml
├── src
│   └── index.ts
└── tsconfig.json
```

# Files

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

```
 1 | # Dependencies
 2 | node_modules/
 3 | package-lock.json
 4 | yarn.lock
 5 | 
 6 | # Build outputs
 7 | dist/
 8 | build/
 9 | *.tsbuildinfo
10 | 
11 | # Environment variables
12 | .env
13 | .env.local
14 | .env.*.local
15 | 
16 | # Logs
17 | logs/
18 | *.log
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 | 
23 | # IDE specific files
24 | .vscode/
25 | 
26 | # Temporary files
27 | *.tmp
28 | *.temp
29 | .cache/
```

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

```markdown
  1 | # Webflow MCP Server
  2 | [![smithery badge](https://smithery.ai/badge/@kapilduraphe/webflow-mcp-server)](https://smithery.ai/server/@kapilduraphe/webflow-mcp-server)
  3 | 
  4 | This MCP server enables Claude to interact with Webflow's APIs.
  5 | 
  6 | <a href="https://glama.ai/mcp/servers/un9r0vtmku">
  7 |   <img width="380" height="200" src="https://glama.ai/mcp/servers/un9r0vtmku/badge" alt="Webflow Server MCP server" />
  8 | </a>
  9 | 
 10 | ## Prerequisites
 11 | 
 12 | - Node.js (v16 or higher)
 13 | - Claude Desktop App
 14 | - Webflow Account
 15 | - Webflow API Token (Site token or OAuth Acces Token)
 16 | 
 17 | ## Setup Instructions
 18 | 
 19 | ### 1. Create a Webflow API Token
 20 | 
 21 | - Log in to your Webflow account
 22 | - Navigate to Site Settings > Apps & Integrations
 23 | - Generate a new API token
 24 | - Copy the token value (you won't be able to see it again)
 25 | 
 26 | Alternatively, you can also generate an OAuth Access Token.
 27 | 
 28 | ### 2. Initial Project Setup
 29 | 
 30 | Install dependencies:
 31 | 
 32 | ```bash
 33 | npm install
 34 | ```
 35 | 
 36 | ### 3. Configure Environment Variables
 37 | 
 38 | Create a `.env` file for local development (don't commit this file):
 39 | 
 40 | ```plaintext
 41 | WEBFLOW_API_TOKEN=your-api-token
 42 | ```
 43 | 
 44 | ### 4. Configure Claude Desktop
 45 | 
 46 | Open your Claude Desktop configuration file:
 47 | 
 48 | For MacOS:
 49 | 
 50 | ```bash
 51 | code ~/Library/Application\ Support/Claude/claude_desktop_config.json
 52 | ```
 53 | 
 54 | For Windows:
 55 | 
 56 | ```bash
 57 | code %AppData%\Claude\claude_desktop_config.json
 58 | ```
 59 | 
 60 | Add or update the configuration:
 61 | 
 62 | ```json
 63 | {
 64 |     "mcpServers": {
 65 |         "webflow": {
 66 |             "command": "node",
 67 |             "args": [
 68 |                 "/ABSOLUTE/PATH/TO/YOUR/build/index.js"
 69 |             ],
 70 |             "env": {
 71 |                 "WEBFLOW_API_TOKEN": "your-api-token"
 72 |             }
 73 |         }
 74 |     }
 75 | }
 76 | ```
 77 | 
 78 | Save the file and restart Claude Desktop.
 79 | 
 80 | ### Installing via Smithery
 81 | 
 82 | To install Webflow MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@kapilduraphe/webflow-mcp-server):
 83 | 
 84 | ```bash
 85 | npx -y @smithery/cli install @kapilduraphe/webflow-mcp-server --client claude
 86 | ```
 87 | 
 88 | ## Available Tools
 89 | 
 90 | The server currently provides the following tools:
 91 | 
 92 | ### get_sites
 93 | 
 94 | Retrieves a list of all Webflow sites accessible to the authenticated user. Returns detailed information including:
 95 | 
 96 | - Site Display Name and Short Name
 97 | - Site ID and Workspace ID
 98 | - Creation, Last Updated, and Last Published Dates
 99 | - Preview URL
100 | - Time Zone settings
101 | - Custom Domains configuration
102 | - Localization settings (primary and secondary locales)
103 | - Data collection preferences
104 | 
105 | ### get_site
106 | 
107 | Retrieves detailed information about a specific Webflow site by ID. Requires a siteId parameter and returns the same detailed information as get_sites for a single site.
108 | 
109 | ## Type Definitions
110 | 
111 | ```typescript
112 | interface WebflowApiError {
113 |     status?: number;
114 |     message: string;
115 |     code?: string;
116 | }
117 | 
118 | interface WebflowCustomDomain {
119 |     id: string;
120 |     url: string;
121 |     lastPublished: string;
122 | }
123 | 
124 | interface WebflowLocale {
125 |     id: string;
126 |     cmsLocaleId: string;
127 |     enabled: boolean;
128 |     displayName: string;
129 |     redirect: boolean;
130 |     subdirectory: string;
131 |     tag: string;
132 | }
133 | 
134 | interface WebflowSite {
135 |     id: string;
136 |     workspaceId: string;
137 |     createdOn: string;
138 |     displayName: string;
139 |     shortName: string;
140 |     lastPublished: string;
141 |     lastUpdated: string;
142 |     previewUrl: string;
143 |     timeZone: string;
144 |     parentFolderId?: string;
145 |     customDomains: WebflowCustomDomain[];
146 |     locales: {
147 |         primary: WebflowLocale;
148 |         secondary: WebflowLocale[];
149 |     };
150 |     dataCollectionEnabled: boolean;
151 |     dataCollectionType: string;
152 | }
153 | ```
154 | 
155 | ## Error Handling
156 | 
157 | The server handles various error scenarios:
158 | 
159 | ### Environment Errors
160 | 
161 | - Missing WEBFLOW_API_TOKEN
162 | - Invalid API token
163 | 
164 | ## Troubleshooting
165 | 
166 | ### Common Issues
167 | 
168 | #### Tools not appearing in Claude
169 | 
170 | - Check Claude Desktop logs
171 | - Verify WEBFLOW_API_TOKEN is set correctly
172 | - Ensure the path to index.js is absolute and correct
173 | 
174 | #### Authentication Errors
175 | 
176 | - Verify your API token is valid
177 | - Check if the token has the necessary permissions
178 | - Ensure the token hasn't expired
179 | 
180 | ### Viewing Logs
181 | 
182 | To view server logs:
183 | 
184 | For MacOS/Linux:
185 | 
186 | ```bash
187 | tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
188 | ```
189 | 
190 | For Windows:
191 | 
192 | ```powershell
193 | Get-Content -Path "$env:AppData\Claude\Logs\mcp*.log" -Wait -Tail 20
194 | ```
195 | 
196 | ### Environment Variables
197 | 
198 | If you're getting environment variable errors, verify:
199 | 
200 | - `WEBFLOW_API_TOKEN`: Should be a valid API token
201 | 
202 | ## Security Considerations
203 | 
204 | - Keep your API token secure
205 | - Don't commit credentials to version control
206 | - Use environment variables for sensitive data
207 | - Regularly rotate API tokens
208 | - Monitor API usage in Webflow
209 | - Use minimum required permissions for API token
210 | 
211 | ## Support
212 | 
213 | If you encounter any issues:
214 | 
215 | - Check the troubleshooting section above
216 | - Review Claude Desktop logs
217 | - Examine the server's error output
218 | - Check Webflow's API documentation
219 | 
220 | ## License
221 | 
222 | MIT License - See LICENSE file for details.
```

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

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

--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
 2 | 
 3 | startCommand:
 4 |   type: stdio
 5 |   configSchema:
 6 |     # JSON Schema defining the configuration options for the MCP.
 7 |     type: object
 8 |     required:
 9 |       - webflowApiToken
10 |     properties:
11 |       webflowApiToken:
12 |         type: string
13 |         description: The API token for accessing Webflow's APIs.
14 |   commandFunction:
15 |     # A function that produces the CLI command to start the MCP on stdio.
16 |     |-
17 |     (config) => ({command:'node',args:['dist/index.js'],env:{WEBFLOW_API_TOKEN:config.webflowApiToken}})
18 | 
```

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

```json
 1 | {
 2 |     "name": "webflow-mcp-server",
 3 |     "version": "1.0.0",
 4 |     "description": "An MCP server for using Webflow APIs",
 5 |     "type": "module",
 6 |     "license": "MIT",
 7 |     "bin": {
 8 |       "mcp-server-okta": "dist/index.js"
 9 |     },
10 |     "files": [
11 |       "dist"
12 |     ],
13 |     "scripts": {
14 |       "build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
15 |        "prepare": "npm run build",
16 |       "watch": "tsc --watch"
17 |     },
18 |     
19 |     "dependencies": {
20 |       "@modelcontextprotocol/sdk": "^1.0.4",
21 |       "webflow-api": "^3.0.0",
22 |       "dotenv": "^16.4.1",
23 |       "zod": "^3.22.4"
24 |     },
25 |     "devDependencies": {
26 |       "@types/node": "^20.11.5",
27 |       "prettier": "^3.2.4",
28 |       "ts-node": "^10.9.2",
29 |       "typescript": "^5.3.3"
30 |     }
31 |   }
```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
 2 | # Stage 1: Build
 3 | FROM node:16-alpine AS builder
 4 | 
 5 | # Set working directory
 6 | WORKDIR /app
 7 | 
 8 | # Copy package.json and package-lock.json
 9 | COPY package.json ./
10 | 
11 | # Install dependencies
12 | RUN npm install --ignore-scripts
13 | 
14 | # Copy the source code
15 | COPY . .
16 | 
17 | # Build the project
18 | RUN npm run build
19 | 
20 | # Stage 2: Run
21 | FROM node:16-alpine
22 | 
23 | # Set working directory
24 | WORKDIR /app
25 | 
26 | # Copy only the necessary files
27 | COPY --from=builder /app/dist /app/dist
28 | COPY package.json ./
29 | 
30 | # Set environment variables (you should set this in your environment or secrets)
31 | ENV WEBFLOW_API_TOKEN=your-api-token
32 | 
33 | # Install production dependencies
34 | RUN npm install --production
35 | 
36 | # Start the server
37 | ENTRYPOINT ["node", "dist/index.js"]
38 | 
```

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

```typescript
  1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  2 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
  3 | import {
  4 |   CallToolRequestSchema,
  5 |   ListToolsRequestSchema,
  6 | } from "@modelcontextprotocol/sdk/types.js";
  7 | 
  8 | import { WebflowClient } from "webflow-api";
  9 | import { z } from "zod";
 10 | 
 11 | const accessToken =
 12 |   process.env.WEBFLOW_API_TOKEN ||
 13 |   (() => {
 14 |     throw new Error("WEBFLOW_API_TOKEN is not defined");
 15 |   })();
 16 | 
 17 | // Initialize the server
 18 | const server = new Server(
 19 |   {
 20 |     name: "webflow-mcp-server",
 21 |     version: "1.0.0",
 22 |   },
 23 |   {
 24 |     capabilities: {
 25 |       tools: {},
 26 |     },
 27 |   }
 28 | );
 29 | 
 30 | const schemas = {
 31 |   toolInputs: {
 32 |     getSite: z.object({
 33 |       siteId: z.string().min(1, "Site ID is required"),
 34 |     }),
 35 |     getSites: z.object({}),
 36 |   },
 37 | };
 38 | 
 39 | interface WebflowApiError {
 40 |   status?: number;
 41 |   message: string;
 42 |   code?: string;
 43 | }
 44 | 
 45 | type ToolHandler = (args: unknown) => Promise<{
 46 |   content: Array<{ type: "text"; text: string }>;
 47 | }>;
 48 | 
 49 | // Utility functions
 50 | function isWebflowApiError(error: unknown): error is WebflowApiError {
 51 |   return error !== null && typeof error === "object" && "code" in error;
 52 | }
 53 | 
 54 | function formatDate(date: Date | undefined | null): string {
 55 |   if (!date) return "N/A";
 56 |   return date.toLocaleString();
 57 | }
 58 | 
 59 | // Tool definitions
 60 | const TOOL_DEFINITIONS = [
 61 |   {
 62 |     name: "get_site",
 63 |     description:
 64 |       "Retrieve detailed information about a specific Webflow site by ID, including workspace, creation date, display name, and publishing details",
 65 |     inputSchema: {
 66 |       type: "object",
 67 |       properties: {
 68 |         siteId: {
 69 |           type: "string",
 70 |           description: "The unique identifier of the Webflow site",
 71 |         },
 72 |       },
 73 |       required: ["siteId"],
 74 |     },
 75 |   },
 76 |   {
 77 |     name: "get_sites",
 78 |     description:
 79 |       "Retrieve a list of all Webflow sites accessible to the authenticated user",
 80 |     inputSchema: {
 81 |       type: "object",
 82 |       properties: {},
 83 |       required: [],
 84 |     },
 85 |   },
 86 | ];
 87 | 
 88 | // Tool handlers
 89 | const toolHandlers: Record<string, ToolHandler> = {
 90 |   get_site: async (args: unknown) => {
 91 |     const { siteId } = schemas.toolInputs.getSite.parse(args);
 92 | 
 93 |     try {
 94 |       const webflow = new WebflowClient({ accessToken });
 95 |       const site = await webflow.sites.get(siteId);
 96 | 
 97 |       if (!site) {
 98 |         throw new Error("Site not found");
 99 |       }
100 | 
101 |       const formattedSite = `• Site Details:
102 |             ID: ${site.id}
103 |             Display Name: ${site.displayName}
104 |             Short Name: ${site.shortName}
105 |           
106 |           - Workspace Information:
107 |             Workspace ID: ${site.workspaceId}
108 |           
109 |           - Dates:
110 |             Created On: ${formatDate(site?.createdOn)}
111 |             Last Published: ${formatDate(site?.lastPublished)}
112 |           
113 |           - URLs:
114 |             Preview URL: ${site.previewUrl || "N/A"}`;
115 | 
116 |       return {
117 |         content: [
118 |           {
119 |             type: "text" as const,
120 |             text: formattedSite,
121 |           },
122 |         ],
123 |       };
124 |     } catch (error: unknown) {
125 |       if (isWebflowApiError(error) && error.code === "NOT_FOUND") {
126 |         return {
127 |           content: [
128 |             {
129 |               type: "text" as const,
130 |               text: `Site with ID ${siteId} not found.`,
131 |             },
132 |           ],
133 |         };
134 |       }
135 |       console.error("Error fetching site:", error);
136 |       throw new Error("Failed to fetch site details");
137 |     }
138 |   },
139 | 
140 |   get_sites: async () => {
141 |     try {
142 |       const webflow = new WebflowClient({ accessToken });
143 |       const { sites } = await webflow.sites.list();
144 | 
145 |       if (!Array.isArray(sites) || sites.length === 0) {
146 |         return {
147 |           content: [
148 |             {
149 |               type: "text" as const,
150 |               text: "No sites found for this account.",
151 |             },
152 |           ],
153 |         };
154 |       }
155 | 
156 |       const formattedSites = sites
157 |         .map(
158 |           (site) => `
159 | • Site: ${site.displayName}
160 |   - ID: ${site.id}
161 |   - Workspace: ${site.workspaceId}
162 |   - Created: ${formatDate(site?.createdOn)}
163 |   - Last Published: ${formatDate(site?.lastPublished)}
164 |   - Preview URL: ${site.previewUrl || "N/A"}
165 | `
166 |         )
167 |         .join("\n");
168 | 
169 |       return {
170 |         content: [
171 |           {
172 |             type: "text" as const,
173 |             text: `Found ${sites.length} sites:\n${formattedSites}`,
174 |           },
175 |         ],
176 |       };
177 |     } catch (error: unknown) {
178 |       console.error("Error fetching sites:", error);
179 |       throw new Error("Failed to fetch sites list");
180 |     }
181 |   },
182 | };
183 | 
184 | // Register tool handlers
185 | server.setRequestHandler(ListToolsRequestSchema, async () => {
186 |   console.error("Tools requested by client");
187 |   return { tools: TOOL_DEFINITIONS };
188 | });
189 | 
190 | server.setRequestHandler(CallToolRequestSchema, async (request) => {
191 |   const { name, arguments: args } = request.params;
192 | 
193 |   try {
194 |     const handler = toolHandlers[name as keyof typeof toolHandlers];
195 |     if (!handler) {
196 |       throw new Error(`Unknown tool: ${name}`);
197 |     }
198 | 
199 |     return await handler(args);
200 |   } catch (error) {
201 |     console.error(`Error executing tool ${name}:`, error);
202 |     throw error;
203 |   }
204 | });
205 | 
206 | // Start the server
207 | async function main() {
208 |   try {
209 |     // Check for required environment variables
210 |     const requiredEnvVars = ["WEBFLOW_API_TOKEN"];
211 | 
212 |     const missingVars = requiredEnvVars.filter(
213 |       (varName) => !process.env[varName]
214 |     );
215 |     if (missingVars.length > 0) {
216 |       console.error(
217 |         `Missing required environment variables: ${missingVars.join(", ")}`
218 |       );
219 |       process.exit(1);
220 |     }
221 | 
222 |     console.error("Starting server with env vars:", {
223 |       WEBFLOW_API_TOKEN: "[REDACTED]",
224 |     });
225 | 
226 |     const transport = new StdioServerTransport();
227 |     console.error("Created transport");
228 | 
229 |     await server.connect(transport);
230 |     console.error("Connected to transport");
231 | 
232 |     console.error("Webflow MCP Server running on stdio");
233 |   } catch (error) {
234 |     console.error("Startup error:", error);
235 |     process.exit(1);
236 |   }
237 | }
238 | 
239 | main().catch((error) => {
240 |   console.error("Fatal error in main():", error);
241 |   process.exit(1);
242 | });
243 | 
```