# Directory Structure
```
├── .dockerignore
├── .env.example
├── .github
│   └── workflows
│       └── ci.yml
├── .gitignore
├── .nvmrc
├── Dockerfile
├── docs
│   ├── API.md
│   ├── EXAMPLES.md
│   └── SETUP.md
├── LICENSE
├── package.json
├── README.md
├── smithery.yml
├── src
│   └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
```
20
```
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
```
node_modules
build
dist
.env
.env.local
.git
.gitignore
README.md
.vscode
.idea
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.DS_Store
Thumbs.db
docs
*.md
```
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
# Netlify Access Token (Required)
# Get this from: https://app.netlify.com/user/applications#personal-access-tokens
NETLIFY_ACCESS_TOKEN=your_access_token_here
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
# Build output
build/
dist/
# Environment variables
.env
.env.local
# IDE
.vscode/
.idea/
# Logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# OS
.DS_Store
Thumbs.db
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Netlify MCP Server
[](https://github.com/modelcontextprotocol)
[](https://opensource.org/licenses/MIT)
A Model Context Protocol (MCP) server for managing Netlify sites. This server enables seamless integration with Netlify's API through MCP, allowing you to create, manage, and deploy sites directly from your MCP-enabled environment.
## Features
- 🚀 Create new sites from GitHub repositories
- 📋 List existing Netlify sites with pagination
- 🔍 Get detailed site information
- 🗑️ Delete sites
- 🔐 Secure authentication with Netlify API
- ⚡ Built with TypeScript for type safety
- 🐳 Docker support for easy deployment
## Requirements
- Node.js 18 or higher
- A Netlify account with API access
- A GitHub repository for deploying sites
## Installation
### From Source
1. Clone this repository:
```bash
git clone https://github.com/MCERQUA/netlify-mcp.git
cd netlify-mcp
```
2. Install dependencies:
```bash
npm install
```
3. Build the project:
```bash
npm run build
```
### Using Docker
```bash
docker build -t netlify-mcp .
docker run -e NETLIFY_ACCESS_TOKEN=your_token_here netlify-mcp
```
## Configuration
### Getting Your Netlify Access Token
1. Create a Netlify account at [https://app.netlify.com/signup](https://app.netlify.com/signup)
2. Go to User Settings > Applications > Personal access tokens
3. Click "New access token"
4. Give it a name (e.g., "MCP Integration")
5. Copy the generated token
### Setting Up MCP
1. Create a `.env` file in the project root:
```env
NETLIFY_ACCESS_TOKEN=your_token_here
```
2. Add the server to your MCP settings configuration:
```json
{
  "mcpServers": {
    "netlify": {
      "command": "node",
      "args": ["path/to/netlify-mcp/build/index.js"],
      "env": {
        "NETLIFY_ACCESS_TOKEN": "your_token_here"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```
## Available Tools
### createSiteFromGitHub
Create a new Netlify site from a GitHub repository.
```typescript
interface CreateSiteFromGitHubArgs {
  name: string;          // Name for the new site (subdomain)
  repo: string;          // GitHub repository (format: owner/repo)
  branch?: string;       // Branch to deploy from (default: main)
  buildCommand: string;  // Build command to run
  publishDir: string;    // Directory containing the built files
  envVars?: Record<string, string>; // Environment variables
}
```
### listSites
List all Netlify sites you have access to.
```typescript
interface ListSitesArgs {
  filter?: 'all' | 'owner' | 'guest';  // Filter for sites
  page?: number;         // Page number for pagination
  perPage?: number;      // Items per page (max 100)
}
```
### getSite
Get detailed information about a specific site.
```typescript
interface GetSiteArgs {
  siteId: string;  // ID or name of the site
}
```
### deleteSite
Delete a Netlify site.
```typescript
interface DeleteSiteArgs {
  siteId: string;  // ID or name of the site
}
```
## Documentation
For more detailed information, see:
- [Setup Guide](docs/SETUP.md)
- [API Documentation](docs/API.md)
- [Usage Examples](docs/EXAMPLES.md)
## Development
```bash
# Run in development mode with auto-rebuild
npm run dev
# Clean build artifacts
npm run clean
# Build the project
npm run build
```
## Troubleshooting
### Common Issues
1. **"NETLIFY_ACCESS_TOKEN environment variable is required"**
   - Make sure you've set the token in your environment or `.env` file
2. **"Failed to create site: 401 Unauthorized"**
   - Your access token might be invalid or expired
   - Generate a new token from Netlify settings
3. **"Invalid repo format"**
   - Ensure the repository is in format `owner/repo`
   - Example: `facebook/react`, not `https://github.com/facebook/react`
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
1. Fork the repository
2. Create your feature branch (`git checkout -b feature/amazing-feature`)
3. Commit your changes (`git commit -m 'Add some amazing feature'`)
4. Push to the branch (`git push origin feature/amazing-feature`)
5. Open a Pull Request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Acknowledgments
- [Model Context Protocol](https://github.com/modelcontextprotocol) for the MCP framework
- [Netlify](https://www.netlify.com) for their excellent deployment platform
```
--------------------------------------------------------------------------------
/smithery.yml:
--------------------------------------------------------------------------------
```yaml
# Smithery Configuration for Netlify MCP Server
name: netlify-mcp
description: MCP server for managing Netlify sites
version: 1.0.1
author: MCERQUA
server:
  command: node
  args:
    - build/index.js
  env:
    NODE_ENV: production
build:
  dockerfile: Dockerfile
required_env:
  - NETLIFY_ACCESS_TOKEN
capabilities:
  - tools
tags:
  - netlify
  - deployment
  - mcp
  - ci-cd
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "outDir": "build",
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "lib": ["ES2022"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "build"]
}
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
name: CI
on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main ]
jobs:
  build:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18.x, 20.x]
    steps:
    - uses: actions/checkout@v3
    
    - name: Use Node.js ${{ matrix.node-version }}
      uses: actions/setup-node@v3
      with:
        node-version: ${{ matrix.node-version }}
        cache: 'npm'
    
    - name: Install dependencies
      run: npm ci
    
    - name: Build
      run: npm run build
    
    - name: Check TypeScript
      run: npx tsc --noEmit
```
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
```dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
# Copy package files
COPY package*.json ./
# Install dependencies
RUN npm ci --only=production && \
    npm cache clean --force
# Copy TypeScript files and build
COPY tsconfig.json ./
COPY src ./src
RUN npm install -g typescript && \
    npm run build
# Production stage
FROM node:20-alpine
WORKDIR /app
# Copy built application and dependencies
COPY --from=builder /app/build ./build
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# Create a non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nodejs -u 1001
USER nodejs
# Set environment variable for production
ENV NODE_ENV=production
# Run the application
CMD ["node", "build/index.js"]
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
  "name": "netlify-mcp-server",
  "version": "1.0.1",
  "description": "MCP server for Netlify integration",
  "type": "module",
  "main": "build/index.js",
  "bin": {
    "netlify-mcp": "./build/index.js"
  },
  "scripts": {
    "build": "tsc",
    "start": "node build/index.js",
    "dev": "tsc --watch",
    "test": "echo \"Error: no test specified\" && exit 1",
    "clean": "rm -rf build"
  },
  "dependencies": {
    "@modelcontextprotocol/sdk": "^0.6.0",
    "axios": "^1.7.2",
    "dotenv": "^16.4.5"
  },
  "devDependencies": {
    "@types/node": "^20.14.0",
    "typescript": "^5.5.0"
  },
  "keywords": [
    "mcp",
    "netlify",
    "deployment",
    "model-context-protocol"
  ],
  "author": "MCERQUA",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/MCERQUA/netlify-mcp.git"
  },
  "bugs": {
    "url": "https://github.com/MCERQUA/netlify-mcp/issues"
  },
  "homepage": "https://github.com/MCERQUA/netlify-mcp#readme",
  "engines": {
    "node": ">=18.0.0"
  }
}
```
--------------------------------------------------------------------------------
/docs/SETUP.md:
--------------------------------------------------------------------------------
```markdown
# Setup Guide
This guide provides detailed instructions for setting up the Netlify MCP server.
## Prerequisites
- Node.js (v16 or higher)
- npm or yarn
- A Netlify account
- A GitHub account (for deploying sites from GitHub)
## Installation Steps
### 1. Install the Package
```bash
git clone https://github.com/MCERQUA/netlify-mcp.git
cd netlify-mcp
npm install
npm run build
```
### 2. Get Your Netlify Access Token
1. Create a Netlify account:
   - Go to [https://app.netlify.com/signup](https://app.netlify.com/signup)
   - Sign up using your preferred method
2. Generate an access token:
   - Log in to your Netlify account
   - Navigate to User Settings (click your avatar in the top right)
   - Go to Applications > Personal access tokens
   - Click "New access token"
   - Give it a name (e.g., "MCP Integration")
   - Copy the generated token immediately (you won't be able to see it again)
### 3. Configure Environment Variables
1. Create a `.env` file in the project root:
```env
NETLIFY_ACCESS_TOKEN=your_token_here
```
2. Add this file to `.gitignore` (already done in this repository)
### 4. Configure MCP Settings
Add the server configuration to your MCP settings file:
#### For VSCode Claude Extension
Location: `~/.vscode/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
```json
{
  "mcpServers": {
    "netlify": {
      "command": "node",
      "args": ["path/to/netlify-mcp/build/index.js"],
      "env": {
        "NETLIFY_ACCESS_TOKEN": "your_token_here"
      },
      "disabled": false,
      "autoApprove": []
    }
  }
}
```
#### For Claude Desktop App
Location: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
Use the same configuration format as above.
## Verification
To verify your setup:
1. Ensure the MCP server is running
2. Try listing your Netlify sites using the `listSites` tool
3. If successful, you should see a list of your Netlify sites
## Troubleshooting
### Common Issues
1. "NETLIFY_ACCESS_TOKEN environment variable is required"
   - Ensure your token is correctly set in the MCP settings configuration
   - Verify the token is valid in your Netlify account
2. "Failed to list sites"
   - Check your internet connection
   - Verify your access token has the correct permissions
   - Ensure you're using the latest version of the server
3. Build errors
   - Ensure Node.js is version 16 or higher
   - Try deleting `node_modules` and running `npm install` again
## Security Notes
- Never commit your `.env` file or expose your access token
- Regularly rotate your Netlify access token
- Use environment variables for all sensitive information
- Consider using different tokens for development and production
## Next Steps
- Read the [API Documentation](API.md) for available tools
- Check out the [Usage Examples](EXAMPLES.md) for common scenarios
- Join our community for support and updates
```
--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------
```markdown
# API Documentation
This document provides detailed information about each tool available in the Netlify MCP server.
## Tools Overview
The Netlify MCP server provides four main tools:
- createSiteFromGitHub
- listSites
- getSite
- deleteSite
## Detailed API Reference
### createSiteFromGitHub
Create a new Netlify site from a GitHub repository.
```typescript
interface CreateSiteFromGitHubArgs {
  name: string;          // Name for the new site
  repo: string;          // GitHub repository (format: owner/repo)
  branch: string;        // Branch to deploy from
  buildCommand: string;  // Build command to run
  publishDir: string;    // Directory containing the built files
}
```
#### Example Usage:
```typescript
{
  "name": "my-awesome-site",
  "repo": "username/repo-name",
  "branch": "main",
  "buildCommand": "npm run build",
  "publishDir": "dist"
}
```
#### Response:
```typescript
{
  "id": "site-id",
  "name": "my-awesome-site",
  "url": "https://my-awesome-site.netlify.app",
  // ... additional site details
}
```
### listSites
List all Netlify sites you have access to.
```typescript
interface ListSitesArgs {
  filter?: 'all' | 'owner' | 'guest';  // Optional filter for sites
}
```
#### Example Usage:
```typescript
{
  "filter": "owner"  // Only show sites you own
}
```
#### Response:
```typescript
[
  {
    "id": "site-id-1",
    "name": "site-1",
    "url": "https://site-1.netlify.app",
    // ... site details
  },
  // ... more sites
]
```
### getSite
Get detailed information about a specific site.
```typescript
interface GetSiteArgs {
  siteId: string;  // ID of the site to retrieve
}
```
#### Example Usage:
```typescript
{
  "siteId": "123abc"
}
```
#### Response:
```typescript
{
  "id": "123abc",
  "name": "my-site",
  "url": "https://my-site.netlify.app",
  "build_settings": {
    "repo_url": "https://github.com/username/repo",
    "branch": "main",
    "cmd": "npm run build",
    "dir": "dist"
  },
  // ... additional site details
}
```
### deleteSite
Delete a Netlify site.
```typescript
interface DeleteSiteArgs {
  siteId: string;  // ID of the site to delete
}
```
#### Example Usage:
```typescript
{
  "siteId": "123abc"
}
```
#### Response:
```typescript
{
  "success": true,
  "message": "Site 123abc deleted successfully"
}
```
## Error Handling
All tools follow a consistent error handling pattern:
```typescript
interface McpError {
  code: ErrorCode;     // Error code from MCP SDK
  message: string;     // Human-readable error message
}
```
Common error codes:
- `InvalidParams`: Missing or invalid parameters
- `InternalError`: Server-side error (includes Netlify API errors)
- `MethodNotFound`: Unknown tool name
## Rate Limiting
The Netlify API has rate limits that this MCP server adheres to. If you encounter rate limiting:
- The server will return an error with code `InternalError`
- The error message will indicate rate limiting
- Wait before retrying the request
## Best Practices
1. Always handle errors in your implementation
2. Use meaningful site names
3. Verify repository access before creating sites
4. Clean up unused sites
5. Use appropriate build settings for your project
## Additional Resources
- [Netlify API Documentation](https://docs.netlify.com/api/get-started/)
- [GitHub Repository Integration Guide](https://docs.netlify.com/configure-builds/repo-permissions-linking/)
- [Build Settings Documentation](https://docs.netlify.com/configure-builds/get-started/)
```
--------------------------------------------------------------------------------
/docs/EXAMPLES.md:
--------------------------------------------------------------------------------
```markdown
# Usage Examples
This document provides practical examples of using the Netlify MCP server tools in various scenarios.
## Common Use Cases
### 1. Deploy a React Application
```typescript
// Create a new site from a React repository
await use_mcp_tool({
  server_name: "netlify",
  tool_name: "createSiteFromGitHub",
  arguments: {
    name: "my-react-app",
    repo: "username/react-project",
    branch: "main",
    buildCommand: "npm run build",
    publishDir: "build"
  }
});
```
### 2. Deploy a Next.js Application
```typescript
// Create a new site from a Next.js repository
await use_mcp_tool({
  server_name: "netlify",
  tool_name: "createSiteFromGitHub",
  arguments: {
    name: "nextjs-blog",
    repo: "username/nextjs-blog",
    branch: "main",
    buildCommand: "next build",
    publishDir: ".next"
  }
});
```
### 3. Manage Multiple Sites
```typescript
// List all your sites
const sites = await use_mcp_tool({
  server_name: "netlify",
  tool_name: "listSites",
  arguments: {
    filter: "owner"
  }
});
// Get details for a specific site
const siteDetails = await use_mcp_tool({
  server_name: "netlify",
  tool_name: "getSite",
  arguments: {
    siteId: sites[0].id
  }
});
// Delete an unused site
await use_mcp_tool({
  server_name: "netlify",
  tool_name: "deleteSite",
  arguments: {
    siteId: "site-to-delete-id"
  }
});
```
### 4. Deploy a Static Site
```typescript
// Create a new site from a static HTML/CSS/JS repository
await use_mcp_tool({
  server_name: "netlify",
  tool_name: "createSiteFromGitHub",
  arguments: {
    name: "static-portfolio",
    repo: "username/portfolio",
    branch: "main",
    buildCommand: "",  // No build needed for static sites
    publishDir: "."
  }
});
```
### 5. Deploy a Vue.js Application
```typescript
// Create a new site from a Vue.js repository
await use_mcp_tool({
  server_name: "netlify",
  tool_name: "createSiteFromGitHub",
  arguments: {
    name: "vue-app",
    repo: "username/vue-project",
    branch: "main",
    buildCommand: "npm run build",
    publishDir: "dist"
  }
});
```
## Error Handling Examples
### Handle Rate Limiting
```typescript
try {
  await use_mcp_tool({
    server_name: "netlify",
    tool_name: "listSites",
    arguments: {}
  });
} catch (error) {
  if (error.message.includes('rate limit')) {
    // Wait and retry
    await new Promise(resolve => setTimeout(resolve, 60000));
    // Retry the request
    // ...
  }
}
```
### Handle Missing Parameters
```typescript
try {
  await use_mcp_tool({
    server_name: "netlify",
    tool_name: "createSiteFromGitHub",
    arguments: {
      name: "my-site"
      // Missing required parameters will throw an error
    }
  });
} catch (error) {
  console.error('Missing required parameters:', error.message);
}
```
## Best Practices Examples
### 1. Verify Site Creation
```typescript
// Create site and verify it's accessible
const newSite = await use_mcp_tool({
  server_name: "netlify",
  tool_name: "createSiteFromGitHub",
  arguments: {
    name: "new-project",
    repo: "username/project",
    branch: "main",
    buildCommand: "npm run build",
    publishDir: "dist"
  }
});
// Verify site creation
const siteDetails = await use_mcp_tool({
  server_name: "netlify",
  tool_name: "getSite",
  arguments: {
    siteId: newSite.id
  }
});
```
### 2. Clean Up Old Sites
```typescript
// List and clean up unused sites
const sites = await use_mcp_tool({
  server_name: "netlify",
  tool_name: "listSites",
  arguments: {
    filter: "owner"
  }
});
// Delete sites matching certain criteria
for (const site of sites) {
  if (site.name.startsWith('test-')) {
    await use_mcp_tool({
      server_name: "netlify",
      tool_name: "deleteSite",
      arguments: {
        siteId: site.id
      }
    });
  }
}
```
## Additional Tips
1. Always use meaningful site names that reflect the project
2. Include error handling for all operations
3. Verify successful deployment after site creation
4. Use appropriate build settings for your framework
5. Clean up unused sites regularly
## Common Issues and Solutions
1. Build Failures
   - Verify build command is correct for your framework
   - Ensure all dependencies are properly specified
   - Check publish directory matches your project structure
2. Repository Access
   - Ensure repository is public or Netlify has access
   - Verify branch name is correct
   - Check repository path format (username/repo)
3. Deployment Issues
   - Verify build output is in the correct directory
   - Check for environment variables needed for build
   - Review build logs for specific errors
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema,
  ErrorCode,
  ListToolsRequestSchema,
  McpError,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosInstance, AxiosError } from 'axios';
import * as dotenv from 'dotenv';
// Load environment variables from .env file
dotenv.config();
const NETLIFY_API = 'https://api.netlify.com/api/v1';
const ACCESS_TOKEN = process.env.NETLIFY_ACCESS_TOKEN;
if (!ACCESS_TOKEN) {
  console.error('Error: NETLIFY_ACCESS_TOKEN environment variable is required');
  console.error('Please set it in your environment or create a .env file');
  process.exit(1);
}
interface CreateSiteFromGitHubArgs {
  name: string;
  repo: string;
  branch: string;
  buildCommand: string;
  publishDir: string;
  envVars?: Record<string, string>;
}
interface ListSitesArgs {
  filter?: 'all' | 'owner' | 'guest';
  page?: number;
  perPage?: number;
}
interface GetSiteArgs {
  siteId: string;
}
interface DeleteSiteArgs {
  siteId: string;
}
interface NetlifyErrorResponse {
  message?: string;
  error?: string;
  errors?: Array<{ message: string }>;
}
class NetlifyServer {
  private server: Server;
  private axiosInstance: AxiosInstance;
  constructor() {
    this.server = new Server(
      {
        name: 'netlify-mcp-server',
        version: '1.0.1',
      },
      {
        capabilities: {
          tools: {},
        },
      }
    );
    // Initialize axios instance with base configuration
    this.axiosInstance = axios.create({
      baseURL: NETLIFY_API,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${ACCESS_TOKEN}`
      },
      timeout: 30000, // 30 second timeout
    });
    this.setupToolHandlers();
    
    // Error handling
    this.server.onerror = (error) => console.error('[MCP Error]', error);
    process.on('SIGINT', async () => {
      await this.server.close();
      process.exit(0);
    });
  }
  private formatNetlifyError(error: AxiosError<NetlifyErrorResponse>): string {
    if (error.response?.data) {
      const data = error.response.data;
      if (data.message) return data.message;
      if (data.error) return data.error;
      if (data.errors && data.errors.length > 0) {
        return data.errors.map(e => e.message).join(', ');
      }
    }
    return error.message || 'Unknown error occurred';
  }
  private setupToolHandlers() {
    this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        {
          name: 'createSiteFromGitHub',
          description: 'Create a new Netlify site from a GitHub repository',
          inputSchema: {
            type: 'object',
            properties: {
              name: {
                type: 'string',
                description: 'Name for the new site (will be used as subdomain)',
              },
              repo: {
                type: 'string',
                description: 'GitHub repository in format owner/repo',
              },
              branch: {
                type: 'string',
                description: 'Branch to deploy from (default: main)',
                default: 'main',
              },
              buildCommand: {
                type: 'string',
                description: 'Build command to run (e.g., npm run build)',
              },
              publishDir: {
                type: 'string',
                description: 'Directory containing the built files to publish (e.g., dist, build)',
              },
              envVars: {
                type: 'object',
                description: 'Environment variables for the build process',
                additionalProperties: { type: 'string' },
              },
            },
            required: ['name', 'repo', 'buildCommand', 'publishDir'],
          },
        },
        {
          name: 'listSites',
          description: 'List Netlify sites',
          inputSchema: {
            type: 'object',
            properties: {
              filter: {
                type: 'string',
                enum: ['all', 'owner', 'guest'],
                description: 'Filter sites by access type',
                default: 'all',
              },
              page: {
                type: 'number',
                description: 'Page number for pagination',
                default: 1,
              },
              perPage: {
                type: 'number',
                description: 'Number of sites per page (max 100)',
                default: 20,
              },
            },
          },
        },
        {
          name: 'getSite',
          description: 'Get details of a specific site',
          inputSchema: {
            type: 'object',
            properties: {
              siteId: {
                type: 'string',
                description: 'ID or name of the site to retrieve',
              },
            },
            required: ['siteId'],
          },
        },
        {
          name: 'deleteSite',
          description: 'Delete a site',
          inputSchema: {
            type: 'object',
            properties: {
              siteId: {
                type: 'string',
                description: 'ID or name of the site to delete',
              },
            },
            required: ['siteId'],
          },
        },
      ],
    }));
    this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
      switch (request.params.name) {
        case 'createSiteFromGitHub': {
          const args = request.params.arguments as unknown as CreateSiteFromGitHubArgs;
          if (!args?.name || !args?.repo || !args?.buildCommand || !args?.publishDir) {
            throw new McpError(
              ErrorCode.InvalidParams,
              'Missing required parameters: name, repo, buildCommand, and publishDir are required'
            );
          }
          // Validate repo format
          if (!args.repo.match(/^[\w.-]+\/[\w.-]+$/)) {
            throw new McpError(
              ErrorCode.InvalidParams,
              'Invalid repo format. Must be in format: owner/repo'
            );
          }
          try {
            const siteData = {
              name: args.name,
              repo: {
                provider: 'github',
                repo: args.repo,
                branch: args.branch || 'main',
                cmd: args.buildCommand,
                dir: args.publishDir,
              },
            };
            // Add environment variables if provided
            if (args.envVars) {
              siteData.repo['env'] = args.envVars;
            }
            const response = await this.axiosInstance.post('/sites', siteData);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    success: true,
                    site: {
                      id: response.data.id,
                      name: response.data.name,
                      url: response.data.url,
                      admin_url: response.data.admin_url,
                      deploy_url: response.data.deploy_url,
                      created_at: response.data.created_at,
                    },
                    message: `Site created successfully! Visit ${response.data.admin_url} to manage it.`,
                  }, null, 2),
                },
              ],
            };
          } catch (error) {
            if (axios.isAxiosError(error)) {
              throw new McpError(
                ErrorCode.InternalError,
                `Failed to create site: ${this.formatNetlifyError(error)}`
              );
            }
            throw error;
          }
        }
        case 'listSites': {
          const args = request.params.arguments as unknown as ListSitesArgs;
          try {
            const params: any = {};
            if (args?.filter && args.filter !== 'all') {
              params.filter = args.filter;
            }
            if (args?.page) {
              params.page = args.page;
            }
            if (args?.perPage) {
              params.per_page = Math.min(args.perPage, 100);
            }
            const response = await this.axiosInstance.get('/sites', { params });
            const sites = response.data.map((site: any) => ({
              id: site.id,
              name: site.name,
              url: site.url,
              admin_url: site.admin_url,
              created_at: site.created_at,
              updated_at: site.updated_at,
              published_deploy: site.published_deploy ? {
                id: site.published_deploy.id,
                created_at: site.published_deploy.created_at,
              } : null,
            }));
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    success: true,
                    sites,
                    count: sites.length,
                  }, null, 2),
                },
              ],
            };
          } catch (error) {
            if (axios.isAxiosError(error)) {
              throw new McpError(
                ErrorCode.InternalError,
                `Failed to list sites: ${this.formatNetlifyError(error)}`
              );
            }
            throw error;
          }
        }
        case 'getSite': {
          const args = request.params.arguments as unknown as GetSiteArgs;
          if (!args?.siteId) {
            throw new McpError(
              ErrorCode.InvalidParams,
              'Missing required parameter: siteId'
            );
          }
          try {
            const response = await this.axiosInstance.get(`/sites/${args.siteId}`);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    success: true,
                    site: response.data,
                  }, null, 2),
                },
              ],
            };
          } catch (error) {
            if (axios.isAxiosError(error)) {
              if (error.response?.status === 404) {
                throw new McpError(
                  ErrorCode.InvalidParams,
                  `Site not found: ${args.siteId}`
                );
              }
              throw new McpError(
                ErrorCode.InternalError,
                `Failed to get site: ${this.formatNetlifyError(error)}`
              );
            }
            throw error;
          }
        }
        case 'deleteSite': {
          const args = request.params.arguments as unknown as DeleteSiteArgs;
          if (!args?.siteId) {
            throw new McpError(
              ErrorCode.InvalidParams,
              'Missing required parameter: siteId'
            );
          }
          try {
            await this.axiosInstance.delete(`/sites/${args.siteId}`);
            return {
              content: [
                {
                  type: 'text',
                  text: JSON.stringify({
                    success: true,
                    message: `Site ${args.siteId} deleted successfully`,
                  }, null, 2),
                },
              ],
            };
          } catch (error) {
            if (axios.isAxiosError(error)) {
              if (error.response?.status === 404) {
                throw new McpError(
                  ErrorCode.InvalidParams,
                  `Site not found: ${args.siteId}`
                );
              }
              throw new McpError(
                ErrorCode.InternalError,
                `Failed to delete site: ${this.formatNetlifyError(error)}`
              );
            }
            throw error;
          }
        }
        default:
          throw new McpError(
            ErrorCode.MethodNotFound,
            `Unknown tool: ${request.params.name}`
          );
      }
    });
  }
  async run() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error('Netlify MCP server running on stdio');
  }
}
const server = new NetlifyServer();
server.run().catch((error) => {
  console.error('Failed to start server:', error);
  process.exit(1);
});
```