# Directory Structure
```
├── .gitignore
├── Dockerfile
├── LICENSE
├── package.json
├── README.md
├── smithery.yaml
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
package-lock.json
yarn.lock
# Build outputs
dist/
build/
*.tsbuildinfo
# Environment variables
.env
.env.local
.env.*.local
# Logs
logs/
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# IDE specific files
.vscode/
# Temporary files
*.tmp
*.temp
.cache/
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Webflow MCP Server
[](https://smithery.ai/server/@kapilduraphe/webflow-mcp-server)
This MCP server enables Claude to interact with Webflow's APIs.
<a href="https://glama.ai/mcp/servers/un9r0vtmku">
<img width="380" height="200" src="https://glama.ai/mcp/servers/un9r0vtmku/badge" alt="Webflow Server MCP server" />
</a>
## Prerequisites
- Node.js (v16 or higher)
- Claude Desktop App
- Webflow Account
- Webflow API Token (Site token or OAuth Acces Token)
## Setup Instructions
### 1. Create a Webflow API Token
- Log in to your Webflow account
- Navigate to Site Settings > Apps & Integrations
- Generate a new API token
- Copy the token value (you won't be able to see it again)
Alternatively, you can also generate an OAuth Access Token.
### 2. Initial Project Setup
Install dependencies:
```bash
npm install
```
### 3. Configure Environment Variables
Create a `.env` file for local development (don't commit this file):
```plaintext
WEBFLOW_API_TOKEN=your-api-token
```
### 4. Configure Claude Desktop
Open your Claude Desktop configuration file:
For MacOS:
```bash
code ~/Library/Application\ Support/Claude/claude_desktop_config.json
```
For Windows:
```bash
code %AppData%\Claude\claude_desktop_config.json
```
Add or update the configuration:
```json
{
"mcpServers": {
"webflow": {
"command": "node",
"args": [
"/ABSOLUTE/PATH/TO/YOUR/build/index.js"
],
"env": {
"WEBFLOW_API_TOKEN": "your-api-token"
}
}
}
}
```
Save the file and restart Claude Desktop.
### Installing via Smithery
To install Webflow MCP Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@kapilduraphe/webflow-mcp-server):
```bash
npx -y @smithery/cli install @kapilduraphe/webflow-mcp-server --client claude
```
## Available Tools
The server currently provides the following tools:
### get_sites
Retrieves a list of all Webflow sites accessible to the authenticated user. Returns detailed information including:
- Site Display Name and Short Name
- Site ID and Workspace ID
- Creation, Last Updated, and Last Published Dates
- Preview URL
- Time Zone settings
- Custom Domains configuration
- Localization settings (primary and secondary locales)
- Data collection preferences
### get_site
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.
## Type Definitions
```typescript
interface WebflowApiError {
status?: number;
message: string;
code?: string;
}
interface WebflowCustomDomain {
id: string;
url: string;
lastPublished: string;
}
interface WebflowLocale {
id: string;
cmsLocaleId: string;
enabled: boolean;
displayName: string;
redirect: boolean;
subdirectory: string;
tag: string;
}
interface WebflowSite {
id: string;
workspaceId: string;
createdOn: string;
displayName: string;
shortName: string;
lastPublished: string;
lastUpdated: string;
previewUrl: string;
timeZone: string;
parentFolderId?: string;
customDomains: WebflowCustomDomain[];
locales: {
primary: WebflowLocale;
secondary: WebflowLocale[];
};
dataCollectionEnabled: boolean;
dataCollectionType: string;
}
```
## Error Handling
The server handles various error scenarios:
### Environment Errors
- Missing WEBFLOW_API_TOKEN
- Invalid API token
## Troubleshooting
### Common Issues
#### Tools not appearing in Claude
- Check Claude Desktop logs
- Verify WEBFLOW_API_TOKEN is set correctly
- Ensure the path to index.js is absolute and correct
#### Authentication Errors
- Verify your API token is valid
- Check if the token has the necessary permissions
- Ensure the token hasn't expired
### Viewing Logs
To view server logs:
For MacOS/Linux:
```bash
tail -n 20 -f ~/Library/Logs/Claude/mcp*.log
```
For Windows:
```powershell
Get-Content -Path "$env:AppData\Claude\Logs\mcp*.log" -Wait -Tail 20
```
### Environment Variables
If you're getting environment variable errors, verify:
- `WEBFLOW_API_TOKEN`: Should be a valid API token
## Security Considerations
- Keep your API token secure
- Don't commit credentials to version control
- Use environment variables for sensitive data
- Regularly rotate API tokens
- Monitor API usage in Webflow
- Use minimum required permissions for API token
## Support
If you encounter any issues:
- Check the troubleshooting section above
- Review Claude Desktop logs
- Examine the server's error output
- Check Webflow's API documentation
## License
MIT License - See LICENSE file for details.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"allowJs": true,
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
# Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
startCommand:
type: stdio
configSchema:
# JSON Schema defining the configuration options for the MCP.
type: object
required:
- webflowApiToken
properties:
webflowApiToken:
type: string
description: The API token for accessing Webflow's APIs.
commandFunction:
# A function that produces the CLI command to start the MCP on stdio.
|-
(config) => ({command:'node',args:['dist/index.js'],env:{WEBFLOW_API_TOKEN:config.webflowApiToken}})
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "webflow-mcp-server",
"version": "1.0.0",
"description": "An MCP server for using Webflow APIs",
"type": "module",
"license": "MIT",
"bin": {
"mcp-server-okta": "dist/index.js"
},
"files": [
"dist"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('dist/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.4",
"webflow-api": "^3.0.0",
"dotenv": "^16.4.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/node": "^20.11.5",
"prettier": "^3.2.4",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
# Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile
# Stage 1: Build
FROM node:16-alpine AS builder
# Set working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package.json ./
# Install dependencies
RUN npm install --ignore-scripts
# Copy the source code
COPY . .
# Build the project
RUN npm run build
# Stage 2: Run
FROM node:16-alpine
# Set working directory
WORKDIR /app
# Copy only the necessary files
COPY --from=builder /app/dist /app/dist
COPY package.json ./
# Set environment variables (you should set this in your environment or secrets)
ENV WEBFLOW_API_TOKEN=your-api-token
# Install production dependencies
RUN npm install --production
# Start the server
ENTRYPOINT ["node", "dist/index.js"]
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { WebflowClient } from "webflow-api";
import { z } from "zod";
const accessToken =
process.env.WEBFLOW_API_TOKEN ||
(() => {
throw new Error("WEBFLOW_API_TOKEN is not defined");
})();
// Initialize the server
const server = new Server(
{
name: "webflow-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {},
},
}
);
const schemas = {
toolInputs: {
getSite: z.object({
siteId: z.string().min(1, "Site ID is required"),
}),
getSites: z.object({}),
},
};
interface WebflowApiError {
status?: number;
message: string;
code?: string;
}
type ToolHandler = (args: unknown) => Promise<{
content: Array<{ type: "text"; text: string }>;
}>;
// Utility functions
function isWebflowApiError(error: unknown): error is WebflowApiError {
return error !== null && typeof error === "object" && "code" in error;
}
function formatDate(date: Date | undefined | null): string {
if (!date) return "N/A";
return date.toLocaleString();
}
// Tool definitions
const TOOL_DEFINITIONS = [
{
name: "get_site",
description:
"Retrieve detailed information about a specific Webflow site by ID, including workspace, creation date, display name, and publishing details",
inputSchema: {
type: "object",
properties: {
siteId: {
type: "string",
description: "The unique identifier of the Webflow site",
},
},
required: ["siteId"],
},
},
{
name: "get_sites",
description:
"Retrieve a list of all Webflow sites accessible to the authenticated user",
inputSchema: {
type: "object",
properties: {},
required: [],
},
},
];
// Tool handlers
const toolHandlers: Record<string, ToolHandler> = {
get_site: async (args: unknown) => {
const { siteId } = schemas.toolInputs.getSite.parse(args);
try {
const webflow = new WebflowClient({ accessToken });
const site = await webflow.sites.get(siteId);
if (!site) {
throw new Error("Site not found");
}
const formattedSite = `• Site Details:
ID: ${site.id}
Display Name: ${site.displayName}
Short Name: ${site.shortName}
- Workspace Information:
Workspace ID: ${site.workspaceId}
- Dates:
Created On: ${formatDate(site?.createdOn)}
Last Published: ${formatDate(site?.lastPublished)}
- URLs:
Preview URL: ${site.previewUrl || "N/A"}`;
return {
content: [
{
type: "text" as const,
text: formattedSite,
},
],
};
} catch (error: unknown) {
if (isWebflowApiError(error) && error.code === "NOT_FOUND") {
return {
content: [
{
type: "text" as const,
text: `Site with ID ${siteId} not found.`,
},
],
};
}
console.error("Error fetching site:", error);
throw new Error("Failed to fetch site details");
}
},
get_sites: async () => {
try {
const webflow = new WebflowClient({ accessToken });
const { sites } = await webflow.sites.list();
if (!Array.isArray(sites) || sites.length === 0) {
return {
content: [
{
type: "text" as const,
text: "No sites found for this account.",
},
],
};
}
const formattedSites = sites
.map(
(site) => `
• Site: ${site.displayName}
- ID: ${site.id}
- Workspace: ${site.workspaceId}
- Created: ${formatDate(site?.createdOn)}
- Last Published: ${formatDate(site?.lastPublished)}
- Preview URL: ${site.previewUrl || "N/A"}
`
)
.join("\n");
return {
content: [
{
type: "text" as const,
text: `Found ${sites.length} sites:\n${formattedSites}`,
},
],
};
} catch (error: unknown) {
console.error("Error fetching sites:", error);
throw new Error("Failed to fetch sites list");
}
},
};
// Register tool handlers
server.setRequestHandler(ListToolsRequestSchema, async () => {
console.error("Tools requested by client");
return { tools: TOOL_DEFINITIONS };
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
const handler = toolHandlers[name as keyof typeof toolHandlers];
if (!handler) {
throw new Error(`Unknown tool: ${name}`);
}
return await handler(args);
} catch (error) {
console.error(`Error executing tool ${name}:`, error);
throw error;
}
});
// Start the server
async function main() {
try {
// Check for required environment variables
const requiredEnvVars = ["WEBFLOW_API_TOKEN"];
const missingVars = requiredEnvVars.filter(
(varName) => !process.env[varName]
);
if (missingVars.length > 0) {
console.error(
`Missing required environment variables: ${missingVars.join(", ")}`
);
process.exit(1);
}
console.error("Starting server with env vars:", {
WEBFLOW_API_TOKEN: "[REDACTED]",
});
const transport = new StdioServerTransport();
console.error("Created transport");
await server.connect(transport);
console.error("Connected to transport");
console.error("Webflow MCP Server running on stdio");
} catch (error) {
console.error("Startup error:", error);
process.exit(1);
}
}
main().catch((error) => {
console.error("Fatal error in main():", error);
process.exit(1);
});
```