# 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 | [](https://github.com/modelcontextprotocol)
4 | [](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 | });
```