#
tokens: 3474/50000 7/7 files
lines: off (toggle) GitHub
raw markdown copy
# 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
[![smithery badge](https://smithery.ai/badge/@kapilduraphe/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);
});

```