#
tokens: 11244/50000 14/14 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | 20
```

--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------

```
 1 | node_modules
 2 | build
 3 | dist
 4 | .env
 5 | .env.local
 6 | .git
 7 | .gitignore
 8 | README.md
 9 | .vscode
10 | .idea
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | .DS_Store
15 | Thumbs.db
16 | docs
17 | *.md
```

--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------

```
1 | # Netlify Access Token (Required)
2 | # Get this from: https://app.netlify.com/user/applications#personal-access-tokens
3 | NETLIFY_ACCESS_TOKEN=your_access_token_here
4 | 
```

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

```
 1 | # Dependencies
 2 | node_modules/
 3 | 
 4 | # Build output
 5 | build/
 6 | dist/
 7 | 
 8 | # Environment variables
 9 | .env
10 | .env.local
11 | 
12 | # IDE
13 | .vscode/
14 | .idea/
15 | 
16 | # Logs
17 | npm-debug.log*
18 | yarn-debug.log*
19 | yarn-error.log*
20 | 
21 | # OS
22 | .DS_Store
23 | Thumbs.db
```

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

```markdown
  1 | # Netlify MCP Server
  2 | 
  3 | [![MCP Server](https://img.shields.io/badge/MCP-Server-blue)](https://github.com/modelcontextprotocol)
  4 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
  5 | 
  6 | 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.
  7 | 
  8 | ## Features
  9 | 
 10 | - 🚀 Create new sites from GitHub repositories
 11 | - 📋 List existing Netlify sites with pagination
 12 | - 🔍 Get detailed site information
 13 | - 🗑️ Delete sites
 14 | - 🔐 Secure authentication with Netlify API
 15 | - ⚡ Built with TypeScript for type safety
 16 | - 🐳 Docker support for easy deployment
 17 | 
 18 | ## Requirements
 19 | 
 20 | - Node.js 18 or higher
 21 | - A Netlify account with API access
 22 | - A GitHub repository for deploying sites
 23 | 
 24 | ## Installation
 25 | 
 26 | ### From Source
 27 | 
 28 | 1. Clone this repository:
 29 | ```bash
 30 | git clone https://github.com/MCERQUA/netlify-mcp.git
 31 | cd netlify-mcp
 32 | ```
 33 | 
 34 | 2. Install dependencies:
 35 | ```bash
 36 | npm install
 37 | ```
 38 | 
 39 | 3. Build the project:
 40 | ```bash
 41 | npm run build
 42 | ```
 43 | 
 44 | ### Using Docker
 45 | 
 46 | ```bash
 47 | docker build -t netlify-mcp .
 48 | docker run -e NETLIFY_ACCESS_TOKEN=your_token_here netlify-mcp
 49 | ```
 50 | 
 51 | ## Configuration
 52 | 
 53 | ### Getting Your Netlify Access Token
 54 | 
 55 | 1. Create a Netlify account at [https://app.netlify.com/signup](https://app.netlify.com/signup)
 56 | 2. Go to User Settings > Applications > Personal access tokens
 57 | 3. Click "New access token"
 58 | 4. Give it a name (e.g., "MCP Integration")
 59 | 5. Copy the generated token
 60 | 
 61 | ### Setting Up MCP
 62 | 
 63 | 1. Create a `.env` file in the project root:
 64 | ```env
 65 | NETLIFY_ACCESS_TOKEN=your_token_here
 66 | ```
 67 | 
 68 | 2. Add the server to your MCP settings configuration:
 69 | ```json
 70 | {
 71 |   "mcpServers": {
 72 |     "netlify": {
 73 |       "command": "node",
 74 |       "args": ["path/to/netlify-mcp/build/index.js"],
 75 |       "env": {
 76 |         "NETLIFY_ACCESS_TOKEN": "your_token_here"
 77 |       },
 78 |       "disabled": false,
 79 |       "autoApprove": []
 80 |     }
 81 |   }
 82 | }
 83 | ```
 84 | 
 85 | ## Available Tools
 86 | 
 87 | ### createSiteFromGitHub
 88 | Create a new Netlify site from a GitHub repository.
 89 | 
 90 | ```typescript
 91 | interface CreateSiteFromGitHubArgs {
 92 |   name: string;          // Name for the new site (subdomain)
 93 |   repo: string;          // GitHub repository (format: owner/repo)
 94 |   branch?: string;       // Branch to deploy from (default: main)
 95 |   buildCommand: string;  // Build command to run
 96 |   publishDir: string;    // Directory containing the built files
 97 |   envVars?: Record<string, string>; // Environment variables
 98 | }
 99 | ```
100 | 
101 | ### listSites
102 | List all Netlify sites you have access to.
103 | 
104 | ```typescript
105 | interface ListSitesArgs {
106 |   filter?: 'all' | 'owner' | 'guest';  // Filter for sites
107 |   page?: number;         // Page number for pagination
108 |   perPage?: number;      // Items per page (max 100)
109 | }
110 | ```
111 | 
112 | ### getSite
113 | Get detailed information about a specific site.
114 | 
115 | ```typescript
116 | interface GetSiteArgs {
117 |   siteId: string;  // ID or name of the site
118 | }
119 | ```
120 | 
121 | ### deleteSite
122 | Delete a Netlify site.
123 | 
124 | ```typescript
125 | interface DeleteSiteArgs {
126 |   siteId: string;  // ID or name of the site
127 | }
128 | ```
129 | 
130 | ## Documentation
131 | 
132 | For more detailed information, see:
133 | - [Setup Guide](docs/SETUP.md)
134 | - [API Documentation](docs/API.md)
135 | - [Usage Examples](docs/EXAMPLES.md)
136 | 
137 | ## Development
138 | 
139 | ```bash
140 | # Run in development mode with auto-rebuild
141 | npm run dev
142 | 
143 | # Clean build artifacts
144 | npm run clean
145 | 
146 | # Build the project
147 | npm run build
148 | ```
149 | 
150 | ## Troubleshooting
151 | 
152 | ### Common Issues
153 | 
154 | 1. **"NETLIFY_ACCESS_TOKEN environment variable is required"**
155 |    - Make sure you've set the token in your environment or `.env` file
156 | 
157 | 2. **"Failed to create site: 401 Unauthorized"**
158 |    - Your access token might be invalid or expired
159 |    - Generate a new token from Netlify settings
160 | 
161 | 3. **"Invalid repo format"**
162 |    - Ensure the repository is in format `owner/repo`
163 |    - Example: `facebook/react`, not `https://github.com/facebook/react`
164 | 
165 | ## Contributing
166 | 
167 | Contributions are welcome! Please feel free to submit a Pull Request.
168 | 
169 | 1. Fork the repository
170 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
171 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`)
172 | 4. Push to the branch (`git push origin feature/amazing-feature`)
173 | 5. Open a Pull Request
174 | 
175 | ## License
176 | 
177 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
178 | 
179 | ## Acknowledgments
180 | 
181 | - [Model Context Protocol](https://github.com/modelcontextprotocol) for the MCP framework
182 | - [Netlify](https://www.netlify.com) for their excellent deployment platform
```

--------------------------------------------------------------------------------
/smithery.yml:
--------------------------------------------------------------------------------

```yaml
 1 | # Smithery Configuration for Netlify MCP Server
 2 | name: netlify-mcp
 3 | description: MCP server for managing Netlify sites
 4 | version: 1.0.1
 5 | author: MCERQUA
 6 | 
 7 | server:
 8 |   command: node
 9 |   args:
10 |     - build/index.js
11 |   env:
12 |     NODE_ENV: production
13 | 
14 | build:
15 |   dockerfile: Dockerfile
16 | 
17 | required_env:
18 |   - NETLIFY_ACCESS_TOKEN
19 | 
20 | capabilities:
21 |   - tools
22 | 
23 | tags:
24 |   - netlify
25 |   - deployment
26 |   - mcp
27 |   - ci-cd
```

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

```json
 1 | {
 2 |   "compilerOptions": {
 3 |     "target": "ES2022",
 4 |     "module": "NodeNext",
 5 |     "moduleResolution": "NodeNext",
 6 |     "esModuleInterop": true,
 7 |     "allowSyntheticDefaultImports": true,
 8 |     "strict": true,
 9 |     "outDir": "build",
10 |     "skipLibCheck": true,
11 |     "forceConsistentCasingInFileNames": true,
12 |     "resolveJsonModule": true,
13 |     "declaration": true,
14 |     "declarationMap": true,
15 |     "sourceMap": true,
16 |     "lib": ["ES2022"]
17 |   },
18 |   "include": ["src/**/*"],
19 |   "exclude": ["node_modules", "build"]
20 | }
```

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

```yaml
 1 | name: CI
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ main, develop ]
 6 |   pull_request:
 7 |     branches: [ main ]
 8 | 
 9 | jobs:
10 |   build:
11 |     runs-on: ubuntu-latest
12 | 
13 |     strategy:
14 |       matrix:
15 |         node-version: [18.x, 20.x]
16 | 
17 |     steps:
18 |     - uses: actions/checkout@v3
19 |     
20 |     - name: Use Node.js ${{ matrix.node-version }}
21 |       uses: actions/setup-node@v3
22 |       with:
23 |         node-version: ${{ matrix.node-version }}
24 |         cache: 'npm'
25 |     
26 |     - name: Install dependencies
27 |       run: npm ci
28 |     
29 |     - name: Build
30 |       run: npm run build
31 |     
32 |     - name: Check TypeScript
33 |       run: npx tsc --noEmit
```

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

```dockerfile
 1 | FROM node:20-alpine AS builder
 2 | 
 3 | WORKDIR /app
 4 | 
 5 | # Copy package files
 6 | COPY package*.json ./
 7 | 
 8 | # Install dependencies
 9 | RUN npm ci --only=production && \
10 |     npm cache clean --force
11 | 
12 | # Copy TypeScript files and build
13 | COPY tsconfig.json ./
14 | COPY src ./src
15 | 
16 | RUN npm install -g typescript && \
17 |     npm run build
18 | 
19 | # Production stage
20 | FROM node:20-alpine
21 | 
22 | WORKDIR /app
23 | 
24 | # Copy built application and dependencies
25 | COPY --from=builder /app/build ./build
26 | COPY --from=builder /app/node_modules ./node_modules
27 | COPY --from=builder /app/package.json ./
28 | 
29 | # Create a non-root user
30 | RUN addgroup -g 1001 -S nodejs && \
31 |     adduser -S nodejs -u 1001
32 | 
33 | USER nodejs
34 | 
35 | # Set environment variable for production
36 | ENV NODE_ENV=production
37 | 
38 | # Run the application
39 | CMD ["node", "build/index.js"]
```

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

```json
 1 | {
 2 |   "name": "netlify-mcp-server",
 3 |   "version": "1.0.1",
 4 |   "description": "MCP server for Netlify integration",
 5 |   "type": "module",
 6 |   "main": "build/index.js",
 7 |   "bin": {
 8 |     "netlify-mcp": "./build/index.js"
 9 |   },
10 |   "scripts": {
11 |     "build": "tsc",
12 |     "start": "node build/index.js",
13 |     "dev": "tsc --watch",
14 |     "test": "echo \"Error: no test specified\" && exit 1",
15 |     "clean": "rm -rf build"
16 |   },
17 |   "dependencies": {
18 |     "@modelcontextprotocol/sdk": "^0.6.0",
19 |     "axios": "^1.7.2",
20 |     "dotenv": "^16.4.5"
21 |   },
22 |   "devDependencies": {
23 |     "@types/node": "^20.14.0",
24 |     "typescript": "^5.5.0"
25 |   },
26 |   "keywords": [
27 |     "mcp",
28 |     "netlify",
29 |     "deployment",
30 |     "model-context-protocol"
31 |   ],
32 |   "author": "MCERQUA",
33 |   "license": "MIT",
34 |   "repository": {
35 |     "type": "git",
36 |     "url": "git+https://github.com/MCERQUA/netlify-mcp.git"
37 |   },
38 |   "bugs": {
39 |     "url": "https://github.com/MCERQUA/netlify-mcp/issues"
40 |   },
41 |   "homepage": "https://github.com/MCERQUA/netlify-mcp#readme",
42 |   "engines": {
43 |     "node": ">=18.0.0"
44 |   }
45 | }
```

--------------------------------------------------------------------------------
/docs/SETUP.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Setup Guide
  2 | 
  3 | This guide provides detailed instructions for setting up the Netlify MCP server.
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | - Node.js (v16 or higher)
  8 | - npm or yarn
  9 | - A Netlify account
 10 | - A GitHub account (for deploying sites from GitHub)
 11 | 
 12 | ## Installation Steps
 13 | 
 14 | ### 1. Install the Package
 15 | 
 16 | ```bash
 17 | git clone https://github.com/MCERQUA/netlify-mcp.git
 18 | cd netlify-mcp
 19 | npm install
 20 | npm run build
 21 | ```
 22 | 
 23 | ### 2. Get Your Netlify Access Token
 24 | 
 25 | 1. Create a Netlify account:
 26 |    - Go to [https://app.netlify.com/signup](https://app.netlify.com/signup)
 27 |    - Sign up using your preferred method
 28 | 
 29 | 2. Generate an access token:
 30 |    - Log in to your Netlify account
 31 |    - Navigate to User Settings (click your avatar in the top right)
 32 |    - Go to Applications > Personal access tokens
 33 |    - Click "New access token"
 34 |    - Give it a name (e.g., "MCP Integration")
 35 |    - Copy the generated token immediately (you won't be able to see it again)
 36 | 
 37 | ### 3. Configure Environment Variables
 38 | 
 39 | 1. Create a `.env` file in the project root:
 40 | ```env
 41 | NETLIFY_ACCESS_TOKEN=your_token_here
 42 | ```
 43 | 
 44 | 2. Add this file to `.gitignore` (already done in this repository)
 45 | 
 46 | ### 4. Configure MCP Settings
 47 | 
 48 | Add the server configuration to your MCP settings file:
 49 | 
 50 | #### For VSCode Claude Extension
 51 | Location: `~/.vscode/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json`
 52 | 
 53 | ```json
 54 | {
 55 |   "mcpServers": {
 56 |     "netlify": {
 57 |       "command": "node",
 58 |       "args": ["path/to/netlify-mcp/build/index.js"],
 59 |       "env": {
 60 |         "NETLIFY_ACCESS_TOKEN": "your_token_here"
 61 |       },
 62 |       "disabled": false,
 63 |       "autoApprove": []
 64 |     }
 65 |   }
 66 | }
 67 | ```
 68 | 
 69 | #### For Claude Desktop App
 70 | Location: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
 71 | or `%APPDATA%\Claude\claude_desktop_config.json` (Windows)
 72 | 
 73 | Use the same configuration format as above.
 74 | 
 75 | ## Verification
 76 | 
 77 | To verify your setup:
 78 | 
 79 | 1. Ensure the MCP server is running
 80 | 2. Try listing your Netlify sites using the `listSites` tool
 81 | 3. If successful, you should see a list of your Netlify sites
 82 | 
 83 | ## Troubleshooting
 84 | 
 85 | ### Common Issues
 86 | 
 87 | 1. "NETLIFY_ACCESS_TOKEN environment variable is required"
 88 |    - Ensure your token is correctly set in the MCP settings configuration
 89 |    - Verify the token is valid in your Netlify account
 90 | 
 91 | 2. "Failed to list sites"
 92 |    - Check your internet connection
 93 |    - Verify your access token has the correct permissions
 94 |    - Ensure you're using the latest version of the server
 95 | 
 96 | 3. Build errors
 97 |    - Ensure Node.js is version 16 or higher
 98 |    - Try deleting `node_modules` and running `npm install` again
 99 | 
100 | ## Security Notes
101 | 
102 | - Never commit your `.env` file or expose your access token
103 | - Regularly rotate your Netlify access token
104 | - Use environment variables for all sensitive information
105 | - Consider using different tokens for development and production
106 | 
107 | ## Next Steps
108 | 
109 | - Read the [API Documentation](API.md) for available tools
110 | - Check out the [Usage Examples](EXAMPLES.md) for common scenarios
111 | - Join our community for support and updates
```

--------------------------------------------------------------------------------
/docs/API.md:
--------------------------------------------------------------------------------

```markdown
  1 | # API Documentation
  2 | 
  3 | This document provides detailed information about each tool available in the Netlify MCP server.
  4 | 
  5 | ## Tools Overview
  6 | 
  7 | The Netlify MCP server provides four main tools:
  8 | - createSiteFromGitHub
  9 | - listSites
 10 | - getSite
 11 | - deleteSite
 12 | 
 13 | ## Detailed API Reference
 14 | 
 15 | ### createSiteFromGitHub
 16 | 
 17 | Create a new Netlify site from a GitHub repository.
 18 | 
 19 | ```typescript
 20 | interface CreateSiteFromGitHubArgs {
 21 |   name: string;          // Name for the new site
 22 |   repo: string;          // GitHub repository (format: owner/repo)
 23 |   branch: string;        // Branch to deploy from
 24 |   buildCommand: string;  // Build command to run
 25 |   publishDir: string;    // Directory containing the built files
 26 | }
 27 | ```
 28 | 
 29 | #### Example Usage:
 30 | ```typescript
 31 | {
 32 |   "name": "my-awesome-site",
 33 |   "repo": "username/repo-name",
 34 |   "branch": "main",
 35 |   "buildCommand": "npm run build",
 36 |   "publishDir": "dist"
 37 | }
 38 | ```
 39 | 
 40 | #### Response:
 41 | ```typescript
 42 | {
 43 |   "id": "site-id",
 44 |   "name": "my-awesome-site",
 45 |   "url": "https://my-awesome-site.netlify.app",
 46 |   // ... additional site details
 47 | }
 48 | ```
 49 | 
 50 | ### listSites
 51 | 
 52 | List all Netlify sites you have access to.
 53 | 
 54 | ```typescript
 55 | interface ListSitesArgs {
 56 |   filter?: 'all' | 'owner' | 'guest';  // Optional filter for sites
 57 | }
 58 | ```
 59 | 
 60 | #### Example Usage:
 61 | ```typescript
 62 | {
 63 |   "filter": "owner"  // Only show sites you own
 64 | }
 65 | ```
 66 | 
 67 | #### Response:
 68 | ```typescript
 69 | [
 70 |   {
 71 |     "id": "site-id-1",
 72 |     "name": "site-1",
 73 |     "url": "https://site-1.netlify.app",
 74 |     // ... site details
 75 |   },
 76 |   // ... more sites
 77 | ]
 78 | ```
 79 | 
 80 | ### getSite
 81 | 
 82 | Get detailed information about a specific site.
 83 | 
 84 | ```typescript
 85 | interface GetSiteArgs {
 86 |   siteId: string;  // ID of the site to retrieve
 87 | }
 88 | ```
 89 | 
 90 | #### Example Usage:
 91 | ```typescript
 92 | {
 93 |   "siteId": "123abc"
 94 | }
 95 | ```
 96 | 
 97 | #### Response:
 98 | ```typescript
 99 | {
100 |   "id": "123abc",
101 |   "name": "my-site",
102 |   "url": "https://my-site.netlify.app",
103 |   "build_settings": {
104 |     "repo_url": "https://github.com/username/repo",
105 |     "branch": "main",
106 |     "cmd": "npm run build",
107 |     "dir": "dist"
108 |   },
109 |   // ... additional site details
110 | }
111 | ```
112 | 
113 | ### deleteSite
114 | 
115 | Delete a Netlify site.
116 | 
117 | ```typescript
118 | interface DeleteSiteArgs {
119 |   siteId: string;  // ID of the site to delete
120 | }
121 | ```
122 | 
123 | #### Example Usage:
124 | ```typescript
125 | {
126 |   "siteId": "123abc"
127 | }
128 | ```
129 | 
130 | #### Response:
131 | ```typescript
132 | {
133 |   "success": true,
134 |   "message": "Site 123abc deleted successfully"
135 | }
136 | ```
137 | 
138 | ## Error Handling
139 | 
140 | All tools follow a consistent error handling pattern:
141 | 
142 | ```typescript
143 | interface McpError {
144 |   code: ErrorCode;     // Error code from MCP SDK
145 |   message: string;     // Human-readable error message
146 | }
147 | ```
148 | 
149 | Common error codes:
150 | - `InvalidParams`: Missing or invalid parameters
151 | - `InternalError`: Server-side error (includes Netlify API errors)
152 | - `MethodNotFound`: Unknown tool name
153 | 
154 | ## Rate Limiting
155 | 
156 | The Netlify API has rate limits that this MCP server adheres to. If you encounter rate limiting:
157 | - The server will return an error with code `InternalError`
158 | - The error message will indicate rate limiting
159 | - Wait before retrying the request
160 | 
161 | ## Best Practices
162 | 
163 | 1. Always handle errors in your implementation
164 | 2. Use meaningful site names
165 | 3. Verify repository access before creating sites
166 | 4. Clean up unused sites
167 | 5. Use appropriate build settings for your project
168 | 
169 | ## Additional Resources
170 | 
171 | - [Netlify API Documentation](https://docs.netlify.com/api/get-started/)
172 | - [GitHub Repository Integration Guide](https://docs.netlify.com/configure-builds/repo-permissions-linking/)
173 | - [Build Settings Documentation](https://docs.netlify.com/configure-builds/get-started/)
```

--------------------------------------------------------------------------------
/docs/EXAMPLES.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Usage Examples
  2 | 
  3 | This document provides practical examples of using the Netlify MCP server tools in various scenarios.
  4 | 
  5 | ## Common Use Cases
  6 | 
  7 | ### 1. Deploy a React Application
  8 | 
  9 | ```typescript
 10 | // Create a new site from a React repository
 11 | await use_mcp_tool({
 12 |   server_name: "netlify",
 13 |   tool_name: "createSiteFromGitHub",
 14 |   arguments: {
 15 |     name: "my-react-app",
 16 |     repo: "username/react-project",
 17 |     branch: "main",
 18 |     buildCommand: "npm run build",
 19 |     publishDir: "build"
 20 |   }
 21 | });
 22 | ```
 23 | 
 24 | ### 2. Deploy a Next.js Application
 25 | 
 26 | ```typescript
 27 | // Create a new site from a Next.js repository
 28 | await use_mcp_tool({
 29 |   server_name: "netlify",
 30 |   tool_name: "createSiteFromGitHub",
 31 |   arguments: {
 32 |     name: "nextjs-blog",
 33 |     repo: "username/nextjs-blog",
 34 |     branch: "main",
 35 |     buildCommand: "next build",
 36 |     publishDir: ".next"
 37 |   }
 38 | });
 39 | ```
 40 | 
 41 | ### 3. Manage Multiple Sites
 42 | 
 43 | ```typescript
 44 | // List all your sites
 45 | const sites = await use_mcp_tool({
 46 |   server_name: "netlify",
 47 |   tool_name: "listSites",
 48 |   arguments: {
 49 |     filter: "owner"
 50 |   }
 51 | });
 52 | 
 53 | // Get details for a specific site
 54 | const siteDetails = await use_mcp_tool({
 55 |   server_name: "netlify",
 56 |   tool_name: "getSite",
 57 |   arguments: {
 58 |     siteId: sites[0].id
 59 |   }
 60 | });
 61 | 
 62 | // Delete an unused site
 63 | await use_mcp_tool({
 64 |   server_name: "netlify",
 65 |   tool_name: "deleteSite",
 66 |   arguments: {
 67 |     siteId: "site-to-delete-id"
 68 |   }
 69 | });
 70 | ```
 71 | 
 72 | ### 4. Deploy a Static Site
 73 | 
 74 | ```typescript
 75 | // Create a new site from a static HTML/CSS/JS repository
 76 | await use_mcp_tool({
 77 |   server_name: "netlify",
 78 |   tool_name: "createSiteFromGitHub",
 79 |   arguments: {
 80 |     name: "static-portfolio",
 81 |     repo: "username/portfolio",
 82 |     branch: "main",
 83 |     buildCommand: "",  // No build needed for static sites
 84 |     publishDir: "."
 85 |   }
 86 | });
 87 | ```
 88 | 
 89 | ### 5. Deploy a Vue.js Application
 90 | 
 91 | ```typescript
 92 | // Create a new site from a Vue.js repository
 93 | await use_mcp_tool({
 94 |   server_name: "netlify",
 95 |   tool_name: "createSiteFromGitHub",
 96 |   arguments: {
 97 |     name: "vue-app",
 98 |     repo: "username/vue-project",
 99 |     branch: "main",
100 |     buildCommand: "npm run build",
101 |     publishDir: "dist"
102 |   }
103 | });
104 | ```
105 | 
106 | ## Error Handling Examples
107 | 
108 | ### Handle Rate Limiting
109 | 
110 | ```typescript
111 | try {
112 |   await use_mcp_tool({
113 |     server_name: "netlify",
114 |     tool_name: "listSites",
115 |     arguments: {}
116 |   });
117 | } catch (error) {
118 |   if (error.message.includes('rate limit')) {
119 |     // Wait and retry
120 |     await new Promise(resolve => setTimeout(resolve, 60000));
121 |     // Retry the request
122 |     // ...
123 |   }
124 | }
125 | ```
126 | 
127 | ### Handle Missing Parameters
128 | 
129 | ```typescript
130 | try {
131 |   await use_mcp_tool({
132 |     server_name: "netlify",
133 |     tool_name: "createSiteFromGitHub",
134 |     arguments: {
135 |       name: "my-site"
136 |       // Missing required parameters will throw an error
137 |     }
138 |   });
139 | } catch (error) {
140 |   console.error('Missing required parameters:', error.message);
141 | }
142 | ```
143 | 
144 | ## Best Practices Examples
145 | 
146 | ### 1. Verify Site Creation
147 | 
148 | ```typescript
149 | // Create site and verify it's accessible
150 | const newSite = await use_mcp_tool({
151 |   server_name: "netlify",
152 |   tool_name: "createSiteFromGitHub",
153 |   arguments: {
154 |     name: "new-project",
155 |     repo: "username/project",
156 |     branch: "main",
157 |     buildCommand: "npm run build",
158 |     publishDir: "dist"
159 |   }
160 | });
161 | 
162 | // Verify site creation
163 | const siteDetails = await use_mcp_tool({
164 |   server_name: "netlify",
165 |   tool_name: "getSite",
166 |   arguments: {
167 |     siteId: newSite.id
168 |   }
169 | });
170 | ```
171 | 
172 | ### 2. Clean Up Old Sites
173 | 
174 | ```typescript
175 | // List and clean up unused sites
176 | const sites = await use_mcp_tool({
177 |   server_name: "netlify",
178 |   tool_name: "listSites",
179 |   arguments: {
180 |     filter: "owner"
181 |   }
182 | });
183 | 
184 | // Delete sites matching certain criteria
185 | for (const site of sites) {
186 |   if (site.name.startsWith('test-')) {
187 |     await use_mcp_tool({
188 |       server_name: "netlify",
189 |       tool_name: "deleteSite",
190 |       arguments: {
191 |         siteId: site.id
192 |       }
193 |     });
194 |   }
195 | }
196 | ```
197 | 
198 | ## Additional Tips
199 | 
200 | 1. Always use meaningful site names that reflect the project
201 | 2. Include error handling for all operations
202 | 3. Verify successful deployment after site creation
203 | 4. Use appropriate build settings for your framework
204 | 5. Clean up unused sites regularly
205 | 
206 | ## Common Issues and Solutions
207 | 
208 | 1. Build Failures
209 |    - Verify build command is correct for your framework
210 |    - Ensure all dependencies are properly specified
211 |    - Check publish directory matches your project structure
212 | 
213 | 2. Repository Access
214 |    - Ensure repository is public or Netlify has access
215 |    - Verify branch name is correct
216 |    - Check repository path format (username/repo)
217 | 
218 | 3. Deployment Issues
219 |    - Verify build output is in the correct directory
220 |    - Check for environment variables needed for build
221 |    - Review build logs for specific errors
```

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

```typescript
  1 | #!/usr/bin/env node
  2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
  3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
  4 | import {
  5 |   CallToolRequestSchema,
  6 |   ErrorCode,
  7 |   ListToolsRequestSchema,
  8 |   McpError,
  9 | } from '@modelcontextprotocol/sdk/types.js';
 10 | import axios, { AxiosInstance, AxiosError } from 'axios';
 11 | import * as dotenv from 'dotenv';
 12 | 
 13 | // Load environment variables from .env file
 14 | dotenv.config();
 15 | 
 16 | const NETLIFY_API = 'https://api.netlify.com/api/v1';
 17 | const ACCESS_TOKEN = process.env.NETLIFY_ACCESS_TOKEN;
 18 | 
 19 | if (!ACCESS_TOKEN) {
 20 |   console.error('Error: NETLIFY_ACCESS_TOKEN environment variable is required');
 21 |   console.error('Please set it in your environment or create a .env file');
 22 |   process.exit(1);
 23 | }
 24 | 
 25 | interface CreateSiteFromGitHubArgs {
 26 |   name: string;
 27 |   repo: string;
 28 |   branch: string;
 29 |   buildCommand: string;
 30 |   publishDir: string;
 31 |   envVars?: Record<string, string>;
 32 | }
 33 | 
 34 | interface ListSitesArgs {
 35 |   filter?: 'all' | 'owner' | 'guest';
 36 |   page?: number;
 37 |   perPage?: number;
 38 | }
 39 | 
 40 | interface GetSiteArgs {
 41 |   siteId: string;
 42 | }
 43 | 
 44 | interface DeleteSiteArgs {
 45 |   siteId: string;
 46 | }
 47 | 
 48 | interface NetlifyErrorResponse {
 49 |   message?: string;
 50 |   error?: string;
 51 |   errors?: Array<{ message: string }>;
 52 | }
 53 | 
 54 | class NetlifyServer {
 55 |   private server: Server;
 56 |   private axiosInstance: AxiosInstance;
 57 | 
 58 |   constructor() {
 59 |     this.server = new Server(
 60 |       {
 61 |         name: 'netlify-mcp-server',
 62 |         version: '1.0.1',
 63 |       },
 64 |       {
 65 |         capabilities: {
 66 |           tools: {},
 67 |         },
 68 |       }
 69 |     );
 70 | 
 71 |     // Initialize axios instance with base configuration
 72 |     this.axiosInstance = axios.create({
 73 |       baseURL: NETLIFY_API,
 74 |       headers: {
 75 |         'Content-Type': 'application/json',
 76 |         'Authorization': `Bearer ${ACCESS_TOKEN}`
 77 |       },
 78 |       timeout: 30000, // 30 second timeout
 79 |     });
 80 | 
 81 |     this.setupToolHandlers();
 82 |     
 83 |     // Error handling
 84 |     this.server.onerror = (error) => console.error('[MCP Error]', error);
 85 |     process.on('SIGINT', async () => {
 86 |       await this.server.close();
 87 |       process.exit(0);
 88 |     });
 89 |   }
 90 | 
 91 |   private formatNetlifyError(error: AxiosError<NetlifyErrorResponse>): string {
 92 |     if (error.response?.data) {
 93 |       const data = error.response.data;
 94 |       if (data.message) return data.message;
 95 |       if (data.error) return data.error;
 96 |       if (data.errors && data.errors.length > 0) {
 97 |         return data.errors.map(e => e.message).join(', ');
 98 |       }
 99 |     }
100 |     return error.message || 'Unknown error occurred';
101 |   }
102 | 
103 |   private setupToolHandlers() {
104 |     this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
105 |       tools: [
106 |         {
107 |           name: 'createSiteFromGitHub',
108 |           description: 'Create a new Netlify site from a GitHub repository',
109 |           inputSchema: {
110 |             type: 'object',
111 |             properties: {
112 |               name: {
113 |                 type: 'string',
114 |                 description: 'Name for the new site (will be used as subdomain)',
115 |               },
116 |               repo: {
117 |                 type: 'string',
118 |                 description: 'GitHub repository in format owner/repo',
119 |               },
120 |               branch: {
121 |                 type: 'string',
122 |                 description: 'Branch to deploy from (default: main)',
123 |                 default: 'main',
124 |               },
125 |               buildCommand: {
126 |                 type: 'string',
127 |                 description: 'Build command to run (e.g., npm run build)',
128 |               },
129 |               publishDir: {
130 |                 type: 'string',
131 |                 description: 'Directory containing the built files to publish (e.g., dist, build)',
132 |               },
133 |               envVars: {
134 |                 type: 'object',
135 |                 description: 'Environment variables for the build process',
136 |                 additionalProperties: { type: 'string' },
137 |               },
138 |             },
139 |             required: ['name', 'repo', 'buildCommand', 'publishDir'],
140 |           },
141 |         },
142 |         {
143 |           name: 'listSites',
144 |           description: 'List Netlify sites',
145 |           inputSchema: {
146 |             type: 'object',
147 |             properties: {
148 |               filter: {
149 |                 type: 'string',
150 |                 enum: ['all', 'owner', 'guest'],
151 |                 description: 'Filter sites by access type',
152 |                 default: 'all',
153 |               },
154 |               page: {
155 |                 type: 'number',
156 |                 description: 'Page number for pagination',
157 |                 default: 1,
158 |               },
159 |               perPage: {
160 |                 type: 'number',
161 |                 description: 'Number of sites per page (max 100)',
162 |                 default: 20,
163 |               },
164 |             },
165 |           },
166 |         },
167 |         {
168 |           name: 'getSite',
169 |           description: 'Get details of a specific site',
170 |           inputSchema: {
171 |             type: 'object',
172 |             properties: {
173 |               siteId: {
174 |                 type: 'string',
175 |                 description: 'ID or name of the site to retrieve',
176 |               },
177 |             },
178 |             required: ['siteId'],
179 |           },
180 |         },
181 |         {
182 |           name: 'deleteSite',
183 |           description: 'Delete a site',
184 |           inputSchema: {
185 |             type: 'object',
186 |             properties: {
187 |               siteId: {
188 |                 type: 'string',
189 |                 description: 'ID or name of the site to delete',
190 |               },
191 |             },
192 |             required: ['siteId'],
193 |           },
194 |         },
195 |       ],
196 |     }));
197 | 
198 |     this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
199 |       switch (request.params.name) {
200 |         case 'createSiteFromGitHub': {
201 |           const args = request.params.arguments as unknown as CreateSiteFromGitHubArgs;
202 |           if (!args?.name || !args?.repo || !args?.buildCommand || !args?.publishDir) {
203 |             throw new McpError(
204 |               ErrorCode.InvalidParams,
205 |               'Missing required parameters: name, repo, buildCommand, and publishDir are required'
206 |             );
207 |           }
208 | 
209 |           // Validate repo format
210 |           if (!args.repo.match(/^[\w.-]+\/[\w.-]+$/)) {
211 |             throw new McpError(
212 |               ErrorCode.InvalidParams,
213 |               'Invalid repo format. Must be in format: owner/repo'
214 |             );
215 |           }
216 | 
217 |           try {
218 |             const siteData = {
219 |               name: args.name,
220 |               repo: {
221 |                 provider: 'github',
222 |                 repo: args.repo,
223 |                 branch: args.branch || 'main',
224 |                 cmd: args.buildCommand,
225 |                 dir: args.publishDir,
226 |               },
227 |             };
228 | 
229 |             // Add environment variables if provided
230 |             if (args.envVars) {
231 |               siteData.repo['env'] = args.envVars;
232 |             }
233 | 
234 |             const response = await this.axiosInstance.post('/sites', siteData);
235 | 
236 |             return {
237 |               content: [
238 |                 {
239 |                   type: 'text',
240 |                   text: JSON.stringify({
241 |                     success: true,
242 |                     site: {
243 |                       id: response.data.id,
244 |                       name: response.data.name,
245 |                       url: response.data.url,
246 |                       admin_url: response.data.admin_url,
247 |                       deploy_url: response.data.deploy_url,
248 |                       created_at: response.data.created_at,
249 |                     },
250 |                     message: `Site created successfully! Visit ${response.data.admin_url} to manage it.`,
251 |                   }, null, 2),
252 |                 },
253 |               ],
254 |             };
255 |           } catch (error) {
256 |             if (axios.isAxiosError(error)) {
257 |               throw new McpError(
258 |                 ErrorCode.InternalError,
259 |                 `Failed to create site: ${this.formatNetlifyError(error)}`
260 |               );
261 |             }
262 |             throw error;
263 |           }
264 |         }
265 | 
266 |         case 'listSites': {
267 |           const args = request.params.arguments as unknown as ListSitesArgs;
268 |           try {
269 |             const params: any = {};
270 |             if (args?.filter && args.filter !== 'all') {
271 |               params.filter = args.filter;
272 |             }
273 |             if (args?.page) {
274 |               params.page = args.page;
275 |             }
276 |             if (args?.perPage) {
277 |               params.per_page = Math.min(args.perPage, 100);
278 |             }
279 | 
280 |             const response = await this.axiosInstance.get('/sites', { params });
281 | 
282 |             const sites = response.data.map((site: any) => ({
283 |               id: site.id,
284 |               name: site.name,
285 |               url: site.url,
286 |               admin_url: site.admin_url,
287 |               created_at: site.created_at,
288 |               updated_at: site.updated_at,
289 |               published_deploy: site.published_deploy ? {
290 |                 id: site.published_deploy.id,
291 |                 created_at: site.published_deploy.created_at,
292 |               } : null,
293 |             }));
294 | 
295 |             return {
296 |               content: [
297 |                 {
298 |                   type: 'text',
299 |                   text: JSON.stringify({
300 |                     success: true,
301 |                     sites,
302 |                     count: sites.length,
303 |                   }, null, 2),
304 |                 },
305 |               ],
306 |             };
307 |           } catch (error) {
308 |             if (axios.isAxiosError(error)) {
309 |               throw new McpError(
310 |                 ErrorCode.InternalError,
311 |                 `Failed to list sites: ${this.formatNetlifyError(error)}`
312 |               );
313 |             }
314 |             throw error;
315 |           }
316 |         }
317 | 
318 |         case 'getSite': {
319 |           const args = request.params.arguments as unknown as GetSiteArgs;
320 |           if (!args?.siteId) {
321 |             throw new McpError(
322 |               ErrorCode.InvalidParams,
323 |               'Missing required parameter: siteId'
324 |             );
325 |           }
326 |           try {
327 |             const response = await this.axiosInstance.get(`/sites/${args.siteId}`);
328 |             return {
329 |               content: [
330 |                 {
331 |                   type: 'text',
332 |                   text: JSON.stringify({
333 |                     success: true,
334 |                     site: response.data,
335 |                   }, null, 2),
336 |                 },
337 |               ],
338 |             };
339 |           } catch (error) {
340 |             if (axios.isAxiosError(error)) {
341 |               if (error.response?.status === 404) {
342 |                 throw new McpError(
343 |                   ErrorCode.InvalidParams,
344 |                   `Site not found: ${args.siteId}`
345 |                 );
346 |               }
347 |               throw new McpError(
348 |                 ErrorCode.InternalError,
349 |                 `Failed to get site: ${this.formatNetlifyError(error)}`
350 |               );
351 |             }
352 |             throw error;
353 |           }
354 |         }
355 | 
356 |         case 'deleteSite': {
357 |           const args = request.params.arguments as unknown as DeleteSiteArgs;
358 |           if (!args?.siteId) {
359 |             throw new McpError(
360 |               ErrorCode.InvalidParams,
361 |               'Missing required parameter: siteId'
362 |             );
363 |           }
364 |           try {
365 |             await this.axiosInstance.delete(`/sites/${args.siteId}`);
366 |             return {
367 |               content: [
368 |                 {
369 |                   type: 'text',
370 |                   text: JSON.stringify({
371 |                     success: true,
372 |                     message: `Site ${args.siteId} deleted successfully`,
373 |                   }, null, 2),
374 |                 },
375 |               ],
376 |             };
377 |           } catch (error) {
378 |             if (axios.isAxiosError(error)) {
379 |               if (error.response?.status === 404) {
380 |                 throw new McpError(
381 |                   ErrorCode.InvalidParams,
382 |                   `Site not found: ${args.siteId}`
383 |                 );
384 |               }
385 |               throw new McpError(
386 |                 ErrorCode.InternalError,
387 |                 `Failed to delete site: ${this.formatNetlifyError(error)}`
388 |               );
389 |             }
390 |             throw error;
391 |           }
392 |         }
393 | 
394 |         default:
395 |           throw new McpError(
396 |             ErrorCode.MethodNotFound,
397 |             `Unknown tool: ${request.params.name}`
398 |           );
399 |       }
400 |     });
401 |   }
402 | 
403 |   async run() {
404 |     const transport = new StdioServerTransport();
405 |     await this.server.connect(transport);
406 |     console.error('Netlify MCP server running on stdio');
407 |   }
408 | }
409 | 
410 | const server = new NetlifyServer();
411 | server.run().catch((error) => {
412 |   console.error('Failed to start server:', error);
413 |   process.exit(1);
414 | });
```