├── .gitignore ├── CLAUDE.md ├── Dockerfile ├── README.md ├── package-lock.json ├── package.json ├── src ├── constants │ └── tools.ts ├── index.ts ├── tools │ ├── deployments │ │ ├── handlers.ts │ │ ├── schema.ts │ │ └── types.ts │ ├── environments │ │ ├── handlers.ts │ │ ├── schema.ts │ │ └── type.ts │ ├── projects │ │ ├── handlers.ts │ │ ├── schema.ts │ │ └── types.ts │ └── teams │ │ ├── handlers.ts │ │ ├── schema.ts │ │ └── types.ts └── utils │ ├── api.ts │ └── config.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | build 4 | .env 5 | .DS_Store 6 | .aider* 7 | claude.md 8 | CLAUDE.md 9 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Repository Overview 6 | 7 | This is a Model Context Protocol (MCP) integration for Vercel's REST API. It implements tools for interacting with Vercel's APIs, enabling LLMs and other applications to programmatically manage Vercel deployments, projects, teams and environment variables. 8 | 9 | ## Commands 10 | 11 | ### Development 12 | 13 | ```bash 14 | # Install dependencies 15 | npm install 16 | 17 | # Run the MCP server in development mode 18 | npm run dev 19 | 20 | # Build the project 21 | npm run build 22 | 23 | # Start the production server 24 | npm start 25 | ``` 26 | 27 | ### Docker Commands 28 | 29 | ```bash 30 | # Build the Docker image 31 | docker build -t vercel-mcp . 32 | 33 | # Run in development mode with live reload 34 | docker build --target builder -t vercel-mcp-dev . 35 | docker run -it --rm \ 36 | -e VERCEL_API_TOKEN=your_token_here \ 37 | -p 3399:3399 \ 38 | -v $(pwd)/src:/app/src \ 39 | vercel-mcp-dev 40 | 41 | # Run in production mode 42 | docker run -it --rm \ 43 | -e VERCEL_API_TOKEN=your_token_here \ 44 | -p 3399:3399 \ 45 | vercel-mcp 46 | ``` 47 | 48 | ## Environment Variables 49 | 50 | - `VERCEL_API_TOKEN`: Required Vercel API token for authentication with Vercel API. 51 | 52 | ## Architecture 53 | 54 | This project is built as a Model Context Protocol (MCP) server that implements a set of tools for interacting with the Vercel API. Key components include: 55 | 56 | 1. **Server Initialization (`src/index.ts`)**: 57 | 58 | - Entry point that creates an MCP server instance 59 | - Registers tool handlers and configures error handling 60 | 61 | 2. **Tools Definition (`src/constants/tools.ts`)**: 62 | 63 | - Defines the schemas and descriptions for all available tools 64 | - Each tool has a defined name, description, and input schema 65 | 66 | 3. **Tool Handlers (src/tools//handlers.ts)**: 67 | 68 | - Implements the actual tool functionality 69 | - Four main domains: deployments, environments, projects, and teams 70 | - Each handler validates inputs, makes API calls, and formats responses 71 | 72 | 4. **API Utilities (`src/utils/api.ts` & `src/utils/config.ts`)**: 73 | 74 | - Handles authentication and API requests to Vercel 75 | - Configures API endpoints and tokens 76 | 77 | 5. **Type Definitions (src/tools//types.ts)**: 78 | - TypeScript interfaces for Vercel API responses and parameters 79 | - Ensures type safety throughout the application 80 | 81 | The application follows a modular architecture where: 82 | 83 | - Each Vercel API domain has its own directory with handlers, schemas, and types 84 | - The server routes tool calls to the appropriate handler based on the tool name 85 | - All handlers use a common API utility for making authenticated requests to Vercel 86 | 87 | ## Development Workflow 88 | 89 | 1. Define a new tool in `src/constants/tools.ts` 90 | 2. Create handler, schema, and types files in the appropriate domain directory 91 | 3. Register the tool handler in `src/index.ts` 92 | 4. Test the tool functionality with an MCP client 93 | 94 | When adding a new tool, follow the pattern established in existing tools: 95 | 96 | 1. Create a schema definition using Zod 97 | 2. Implement a handler function that validates inputs and calls the Vercel API 98 | 3. Define types for request parameters and response data 99 | 4. Register the tool in the `VERCEL_TOOLS` array and add the handler to the switch statement in index.ts 100 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:22.12-alpine as builder 2 | 3 | WORKDIR /app 4 | COPY package.json package-lock.json tsconfig.json ./ 5 | RUN --mount=type=cache,target=/root/.npm npm install 6 | COPY src ./src 7 | RUN npm run build 8 | 9 | FROM node:22.12-alpine AS release 10 | WORKDIR /app 11 | COPY --from=builder /app/package.json /app/package.json 12 | COPY --from=builder /app/package-lock.json /app/package-lock.json 13 | COPY --from=builder /app/build /app/build 14 | 15 | ENV NODE_ENV=production 16 | RUN npm ci --ignore-scripts --omit-dev 17 | 18 | ENTRYPOINT ["node", "build/index.js"] 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vercel MCP Integration 2 | 3 | A Model Context Protocol (MCP) integration for Vercel's REST API, providing programmatic access to Vercel deployment management through AI Assistants like Claude and Cursor. 4 | 5 | ## 📋 Overview Last updated: May 2025 6 | 7 | This MCP server implements Vercel's core API endpoints as tools, enabling: 8 | 9 | - Deployment monitoring & management 10 | - Environment variable retrieval 11 | - Project deployment status tracking 12 | - Team creation and management 13 | - CI/CD pipeline integration 14 | 15 | ## ✨ Features 16 | 17 | ### Current Tools 18 | 19 | - `vercel-list-all-deployments` - List deployments with filtering 20 | - `vercel-get-deployment` - Retrieve specific deployment details 21 | - `vercel-list-deployment-files` - List files in a deployment 22 | - `vercel-create-deployment` - Create new deployments 23 | - `vercel-create-project` - Create new Vercel projects 24 | - `vercel-create-environment-variables` - Create multiple environment variables 25 | - `vercel-get-environments` - Access project environment variables 26 | - `vercel-create-custom-environment` - Create custom environments for projects 27 | - `vercel-list-projects` - List all projects with pagination 28 | - `vercel-list-all-teams` - List all accessible teams 29 | - `vercel-create-team` - Create a new team with custom slug and name 30 | 31 | ## 🛣️ Roadmap 32 | 33 | - [x] Deployment creation workflow 34 | - [x] Project management tools 35 | - [x] Team management integration (List & Create teams) 36 | - [ ] Real-time deployment monitoring 37 | - [x] Advanced error handling 38 | - [ ] Deployment metrics dashboard 39 | 40 | ## Tools 41 | 42 | ### `vercel-list-all-deployments` 43 | 44 | List deployments under the authenticated user or team 45 | 46 | - **Inputs**: 47 | - `app` (string): Filter by deployment name 48 | - `projectId` (string): Filter by project ID/name 49 | - `state` (string): Filter by state (BUILDING, ERROR, INITIALIZING, QUEUED, READY, CANCELED) 50 | - `target` (string): Filter by environment (production/preview) 51 | - `limit` (number): Number of deployments to return 52 | - **Returns**: Array of deployment objects with status, URLs, and metadata 53 | 54 | ### `vercel-get-deployment` 55 | 56 | Get detailed information about a specific deployment 57 | 58 | - **Inputs**: 59 | - `idOrUrl` (string): Deployment ID or URL (required) 60 | - `teamId` (string): Team ID for request scoping 61 | - **Returns**: Full deployment details including build logs, domains, and environment variables 62 | 63 | ### `vercel-list-deployment-files` 64 | 65 | List all files of a Vercel deployment 66 | 67 | - **Inputs**: 68 | - `id` (string): The unique deployment identifier (required) 69 | - `teamId` (string): Team identifier to perform the request on behalf of 70 | - `slug` (string): Team slug to perform the request on behalf of 71 | - **Returns**: Array of file objects with properties like name, type, MIME content type, and file permissions 72 | 73 | ### `vercel-create-deployment` 74 | 75 | Create a new Vercel deployment using the v13/deployments API endpoint 76 | 77 | - **Inputs**: 78 | - **Identification Parameters**: 79 | - `name` (string): Deployment/project name 80 | - `project` (string): Project ID/name (required unless deploymentId is provided) 81 | - `deploymentId` (string): ID of a previous deployment to redeploy (required unless project is provided) 82 | - `slug` (string): A unique URL-friendly identifier 83 | - `teamId` (string): Team ID for scoping 84 | - `customEnvironmentSlugOrId` (string): Custom environment slug or ID 85 | - **Configuration Parameters**: 86 | - `target` (string): Environment (production/preview/development, default: production) 87 | - `regions` (string[]): Deployment regions 88 | - `functions` (object): Serverless functions configuration 89 | - `routes` (array): Array of route definitions 90 | - `cleanUrls` (boolean): Enable or disable Clean URLs 91 | - `trailingSlash` (boolean): Enable or disable trailing slashes 92 | - `public` (boolean): Make the deployment public 93 | - `ignoreCommand` (string): Command to check whether files should be ignored 94 | - **Source Control Parameters**: 95 | - `gitSource` (object): Git source information 96 | - `type` (string): Git provider type (github/gitlab/bitbucket) 97 | - `repoId` (string/number): Repository ID 98 | - `ref` (string): Git reference (branch/tag) 99 | - `sha` (string): Git commit SHA 100 | - `gitMetadata` (object): Git metadata for the deployment 101 | - `commitAuthorName` (string): Commit author name 102 | - `commitMessage` (string): Commit message 103 | - `commitRef` (string): Git reference 104 | - `commitSha` (string): Commit SHA 105 | - `remoteUrl` (string): Git remote URL 106 | - `dirty` (boolean): If the working directory has uncommitted changes 107 | - `projectSettings` (object): Project-specific settings 108 | - `buildCommand` (string): Custom build command 109 | - `devCommand` (string): Custom development command 110 | - `framework` (string): Framework preset 111 | - `installCommand` (string): Custom install command 112 | - `outputDirectory` (string): Build output directory 113 | - `rootDirectory` (string): Project root directory 114 | - `nodeVersion` (string): Node.js version 115 | - `serverlessFunctionRegion` (string): Region for serverless functions 116 | - `meta` (object): Additional metadata for the deployment 117 | - `monorepoManager` (string): Monorepo manager (turborepo, nx, etc.) 118 | - **File Parameters (for non-git deployments)**: 119 | - `files` (array): Files to deploy 120 | - `file` (string): File path 121 | - `data` (string): File content 122 | - `encoding` (string): Content encoding (base64/utf-8) 123 | - **Other Flags**: 124 | - `forceNew` (boolean): Force new deployment even if identical exists 125 | - `withCache` (boolean): Enable or disable build cache 126 | - `autoAssignCustomDomains` (boolean): Automatically assign custom domains 127 | - `withLatestCommit` (boolean): Include the latest commit in the deployment 128 | - **Returns**: Created deployment details with status URLs, build information, and access links 129 | 130 | ### `vercel-create-project` 131 | 132 | Create a new Vercel project 133 | 134 | - **Inputs**: 135 | - `name` (string): Project name (required) 136 | - `framework` (string): Framework preset 137 | - `buildCommand` (string): Custom build command 138 | - `devCommand` (string): Custom dev command 139 | - `outputDirectory` (string): Build output directory 140 | - `teamId` (string): Team ID for scoping 141 | - **Returns**: Project configuration with deployment settings 142 | 143 | ### `vercel-create-environment-variables` 144 | 145 | Create multiple environment variables for a project 146 | 147 | - **Inputs**: 148 | 149 | - `projectId` (string): Target project ID (required) 150 | - `teamId` (string): Team ID for request scoping 151 | - `environmentVariables` (array): Environment variables to create 152 | - `key` (string): Variable name (required) 153 | - `value` (string): Variable value (required) 154 | - `target` (string[]): Deployment targets (production/preview/development) 155 | - `type` (string): Variable type (system/encrypted/plain/sensitive) 156 | - `gitBranch` (string): Optional git branch for variable 157 | 158 | - **Returns**: Object with created variables and any skipped entries 159 | 160 | ### `vercel-create-custom-environment` 161 | 162 | Create a custom environment for a Vercel project. Custom environments cannot be named 'Production' or 'Preview'. 163 | 164 | - **Inputs**: 165 | - `idOrName` (string): The unique project identifier or project name (required) 166 | - `name` (string): Name for the custom environment (required, cannot be 'Production' or 'Preview') 167 | - `description` (string): Description of the custom environment 168 | - `branchMatcher` (object): Branch matching configuration 169 | - `type` (string): Type of branch matching (startsWith/endsWith/contains/exactMatch/regex) 170 | - `pattern` (string): Pattern to match branches against 171 | - `teamId` (string): Team ID to perform the request on behalf of 172 | - `slug` (string): Team slug to perform the request on behalf of 173 | - **Returns**: Created custom environment details including ID, slug, type, description, branch matcher configuration, and domains 174 | 175 | ### `vercel-list-all-teams` 176 | 177 | List all teams accessible to authenticated user 178 | 179 | - **Inputs**: 180 | - `limit` (number): Maximum results to return 181 | - `since` (number): Timestamp for teams created after 182 | - `until` (number): Timestamp for teams created before 183 | - `teamId` (string): Team ID for request scoping 184 | - **Returns**: Paginated list of team objects with metadata 185 | 186 | ### `vercel-create-team` 187 | 188 | Create a new Vercel team 189 | 190 | - **Inputs**: 191 | - `slug` (string): A unique identifier for the team (required) 192 | - `name` (string): A display name for the team (optional) 193 | - **Returns**: Created team details including ID, slug, and billing information 194 | 195 | ### `vercel-list-projects` 196 | 197 | List all projects under the authenticated user or team 198 | 199 | - **Inputs**: 200 | - `limit` (number): Maximum number of projects to return 201 | - `from` (number): Timestamp for projects created/updated after this time 202 | - `teamId` (string): Team ID for request scoping 203 | - `search` (string): Search projects by name 204 | - `repoUrl` (string): Filter by repository URL 205 | - `gitForkProtection` (string): Specify PR authorization from forks (0/1) 206 | - **Returns**: List of project objects with metadata including: 207 | - `id`: Project ID 208 | - `name`: Project name 209 | - `framework`: Associated framework 210 | - `latestDeployments`: Array of recent deployments 211 | - `createdAt`: Creation timestamp 212 | - Additional properties like targets, accountId, etc. 213 | 214 | ## 🚀 Getting Started 215 | 216 | ### Prerequisites 217 | 218 | - Node.js 18+ 219 | - Vercel API Token 220 | - MCP Client (Claude, Cursor, or other AI Assistants that support MCP) 221 | 222 | ### Installation 223 | 224 | ```bash 225 | git clone [your-repo-url] 226 | cd vercel-mcp 227 | npm install 228 | ``` 229 | 230 | ### Configuration 231 | 232 | 1. Create `.env` file: 233 | 234 | ```env 235 | VERCEL_API_TOKEN=your_api_token_here 236 | ``` 237 | 238 | 2. Start MCP server: 239 | 240 | ```bash 241 | npm start 242 | ``` 243 | 244 | ## 🔗 Integrating with AI Assistants 245 | 246 | ### Integrating with Claude 247 | 248 | Claude supports MCP tools via its Anthropic Console or Claude Code interface. 249 | 250 | 1. Start the MCP server locally with `npm start` 251 | 2. In Claude Code, use the `/connect` command: 252 | ``` 253 | /connect mcp --path [path-to-server] 254 | ``` 255 | For CLI-based servers using stdio, specify the path to the server executable 256 | 3. Claude will automatically discover the available Vercel tools 257 | 4. You can then ask Claude to perform Vercel operations, for example: 258 | ``` 259 | Please list my recent Vercel deployments using the vercel-list-all-deployments tool 260 | ``` 261 | 5. Alternatively, you can expose the MCP server as an HTTP server with a tool like `mcp-proxy` 262 | ```bash 263 | npm install -g @modelcontextprotocol/proxy 264 | mcp-proxy --stdio --cmd "npm start" --port 3399 265 | ``` 266 | Then connect in Claude: `/connect mcp --url http://localhost:3399` 267 | 268 | ### Integrating with Cursor 269 | 270 | Cursor has built-in support for MCP tools through its extension system. 271 | 272 | 1. Start the MCP server with `npm start` 273 | 2. In Cursor, access Settings → Tools 274 | 3. Under "Model Context Protocol (MCP)", click "+ Add MCP tool" 275 | 4. Configure a new connection: 276 | - For stdio transport: Point to the executable path 277 | - For HTTP transport: Specify the URL (e.g., http://localhost:3399) 278 | 5. Cursor will automatically discover the available Vercel tools 279 | 6. Use Cursor's AI features to interact with your Vercel deployments by mentioning the tools in your prompts 280 | 281 | ### Programmatic Integration 282 | 283 | You can also use the Model Context Protocol SDK to integrate with the server programmatically in your own applications: 284 | 285 | ```javascript 286 | import { Client } from "@modelcontextprotocol/sdk/client"; 287 | 288 | // Create an MCP client connected to a stdio transport 289 | const client = new Client({ 290 | transport: "stdio", 291 | cmd: "npm --prefix /path/to/vercel-mcp start", 292 | }); 293 | 294 | // Or connect to an HTTP transport 295 | const httpClient = new Client({ 296 | transport: "http", 297 | url: "http://localhost:3399", 298 | }); 299 | 300 | // Connect to the server 301 | await client.connect(); 302 | 303 | // List available tools 304 | const { tools } = await client.listTools(); 305 | console.log( 306 | "Available tools:", 307 | tools.map((t) => t.name) 308 | ); 309 | 310 | // Call a tool 311 | const result = await client.callTool({ 312 | name: "vercel-list-all-deployments", 313 | args: { limit: 5 }, 314 | }); 315 | 316 | console.log("Deployments:", result); 317 | 318 | // You can also use this in an Express server: 319 | app.post("/api/deployments", async (req, res) => { 320 | try { 321 | const result = await client.callTool({ 322 | name: "vercel-list-all-deployments", 323 | args: req.body, 324 | }); 325 | res.json(result); 326 | } catch (error) { 327 | res.status(500).json({ error: error.message }); 328 | } 329 | }); 330 | ``` 331 | 332 | ## 🛠️ Tool Usage Examples 333 | 334 | ### List Deployments 335 | 336 | ```javascript 337 | const response = await mcpClient.callTool({ 338 | name: "vercel-list-all-deployments", 339 | args: { 340 | limit: 5, 341 | target: "production", 342 | }, 343 | }); 344 | ``` 345 | 346 | ### Get Specific Deployment 347 | 348 | ```javascript 349 | const deployment = await mcpClient.callTool({ 350 | name: "vercel-get-deployment", 351 | args: { 352 | idOrUrl: "dpl_5WJWYSyB7BpgTj3EuwF37WMRBXBtPQ2iTMJHJBJyRfd", 353 | }, 354 | }); 355 | ``` 356 | 357 | ### List Deployment Files 358 | 359 | ```javascript 360 | const files = await mcpClient.callTool({ 361 | name: "vercel-list-deployment-files", 362 | args: { 363 | id: "dpl_5WJWYSyB7BpgTj3EuwF37WMRBXBtPQ2iTMJHJBJyRfd", 364 | teamId: "team_1a2b3c4d5e6f7g8h9i0j1k2l", // Optional 365 | }, 366 | }); 367 | ``` 368 | 369 | ### List Projects 370 | 371 | ```javascript 372 | const projects = await mcpClient.callTool({ 373 | name: "vercel-list-projects", 374 | args: { 375 | limit: 10, 376 | teamId: "team_1a2b3c4d5e6f7g8h9i0j1k2l", // Optional 377 | search: "my-app", // Optional 378 | }, 379 | }); 380 | ``` 381 | 382 | ### Create a Deployment 383 | 384 | ```javascript 385 | // Create a basic deployment 386 | const basicDeployment = await mcpClient.callTool({ 387 | name: "vercel-create-deployment", 388 | args: { 389 | project: "my-project-id", 390 | target: "production", 391 | teamId: "team_1a2b3c4d5e6f7g8h9i0j1k2l", // Optional 392 | }, 393 | }); 394 | 395 | // Redeploy an existing deployment 396 | const redeployment = await mcpClient.callTool({ 397 | name: "vercel-create-deployment", 398 | args: { 399 | deploymentId: "dpl_123abc456def", 400 | }, 401 | }); 402 | 403 | // Create a deployment with Git source (from GitHub) 404 | const gitDeployment = await mcpClient.callTool({ 405 | name: "vercel-create-deployment", 406 | args: { 407 | project: "my-project-id", 408 | gitSource: { 409 | type: "github", 410 | ref: "main", 411 | }, 412 | gitMetadata: { 413 | commitMessage: "add method to measure Interaction to Next Paint", 414 | commitAuthorName: "developer", 415 | remoteUrl: "https://github.com/vercel/next.js", 416 | }, 417 | }, 418 | }); 419 | 420 | // Create a deployment with custom files 421 | const fileDeployment = await mcpClient.callTool({ 422 | name: "vercel-create-deployment", 423 | args: { 424 | name: "my-instant-deployment", 425 | project: "my-deployment-project", 426 | files: [ 427 | { 428 | file: "index.html", 429 | data: "PGgxPkhlbGxvIFdvcmxkPC9oMT4=", // Base64 encoded

Hello World

430 | encoding: "base64", 431 | }, 432 | ], 433 | projectSettings: { 434 | framework: "nextjs", 435 | buildCommand: "next build", 436 | installCommand: "npm install", 437 | nodeVersion: "18.x", 438 | }, 439 | }, 440 | }); 441 | ``` 442 | 443 | ### Create a New Team 444 | 445 | ```javascript 446 | const team = await mcpClient.callTool({ 447 | name: "vercel-create-team", 448 | args: { 449 | slug: "my-awesome-team", 450 | name: "My Awesome Team", 451 | }, 452 | }); 453 | ``` 454 | 455 | ### Create a Custom Environment 456 | 457 | ```javascript 458 | const customEnv = await mcpClient.callTool({ 459 | name: "vercel-create-custom-environment", 460 | args: { 461 | idOrName: "my-project-id", 462 | name: "staging", 463 | description: "Staging environment for QA testing", 464 | branchMatcher: { 465 | type: "startsWith", 466 | pattern: "staging/", 467 | }, 468 | teamId: "team_1a2b3c4d5e6f7g8h9i0j1k2l", // Optional 469 | }, 470 | }); 471 | ``` 472 | 473 | ## 🐳 Docker Deployment 474 | 475 | ### Build the Image 476 | 477 | ```bash 478 | docker build -t vercel-mcp . 479 | ``` 480 | 481 | ### Run Container 482 | 483 | ```bash 484 | docker run -it --rm \ 485 | -e VERCEL_API_TOKEN=your_token_here \ 486 | -p 3399:3399 \ 487 | vercel-mcp 488 | ``` 489 | 490 | ### Production Deployment 491 | 492 | ```bash 493 | docker run -d \ 494 | --name vercel-mcp \ 495 | --restart unless-stopped \ 496 | -e VERCEL_API_TOKEN=your_token_here \ 497 | -p 3399:3399 \ 498 | vercel-mcp 499 | ``` 500 | 501 | ### Development with Live Reload 502 | 503 | ```bash 504 | docker build --target builder -t vercel-mcp-dev . 505 | docker run -it --rm \ 506 | -e VERCEL_API_TOKEN=your_token_here \ 507 | -p 3399:3399 \ 508 | -v $(pwd)/src:/app/src \ 509 | vercel-mcp-dev 510 | ``` 511 | 512 | ## 🗂️ Project Structure 513 | 514 | ``` 515 | src/ 516 | ├── constants/ # Tool definitions 517 | ├── tools/ 518 | │ ├── deployments/ # Deployment handlers 519 | │ │ ├── handlers.ts 520 | │ │ ├── schema.ts 521 | │ │ └── types.ts 522 | │ └── environments/# Environment management 523 | ├── utils/ # API helpers 524 | └── index.ts # Server entrypoint 525 | ``` 526 | 527 | ## 🔧 Configuration 528 | 529 | ### Environment Variables 530 | 531 | | Variable | Description | Required | 532 | | ------------------ | ------------------- | -------- | 533 | | `VERCEL_API_TOKEN` | Vercel access token | Yes | 534 | 535 | ## 🤝 Contributing 536 | 537 | 1. Fork the repository 538 | 2. Create feature branch (`git checkout -b feature/amazing-feature`) 539 | 3. Commit changes (`git commit -m 'Add amazing feature'`) 540 | 4. Push to branch (`git push origin feature/amazing-feature`) 541 | 5. Open Pull Request 542 | 543 | ## 📄 License 544 | 545 | MIT License - see [LICENSE](LICENSE) for details 546 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vercel", 3 | "version": "1.0.0", 4 | "lockfileVersion": 3, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "vercel", 9 | "version": "1.0.0", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@modelcontextprotocol/sdk": "^1.0.4", 13 | "dotenv": "^16.5.0", 14 | "node-fetch": "^3.3.2", 15 | "zod": "^3.24.1" 16 | }, 17 | "devDependencies": { 18 | "@types/node": "^22.10.2", 19 | "ts-node": "^10.9.2", 20 | "typescript": "^5.7.2" 21 | } 22 | }, 23 | "node_modules/@cspotcode/source-map-support": { 24 | "version": "0.8.1", 25 | "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", 26 | "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", 27 | "dev": true, 28 | "license": "MIT", 29 | "dependencies": { 30 | "@jridgewell/trace-mapping": "0.3.9" 31 | }, 32 | "engines": { 33 | "node": ">=12" 34 | } 35 | }, 36 | "node_modules/@jridgewell/resolve-uri": { 37 | "version": "3.1.2", 38 | "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", 39 | "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", 40 | "dev": true, 41 | "license": "MIT", 42 | "engines": { 43 | "node": ">=6.0.0" 44 | } 45 | }, 46 | "node_modules/@jridgewell/sourcemap-codec": { 47 | "version": "1.5.0", 48 | "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", 49 | "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", 50 | "dev": true, 51 | "license": "MIT" 52 | }, 53 | "node_modules/@jridgewell/trace-mapping": { 54 | "version": "0.3.9", 55 | "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", 56 | "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", 57 | "dev": true, 58 | "license": "MIT", 59 | "dependencies": { 60 | "@jridgewell/resolve-uri": "^3.0.3", 61 | "@jridgewell/sourcemap-codec": "^1.4.10" 62 | } 63 | }, 64 | "node_modules/@modelcontextprotocol/sdk": { 65 | "version": "1.0.4", 66 | "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.0.4.tgz", 67 | "integrity": "sha512-C+jw1lF6HSGzs7EZpzHbXfzz9rj9him4BaoumlTciW/IDDgIpweF/qiCWKlP02QKg5PPcgY6xY2WCt5y2tpYow==", 68 | "license": "MIT", 69 | "dependencies": { 70 | "content-type": "^1.0.5", 71 | "raw-body": "^3.0.0", 72 | "zod": "^3.23.8" 73 | } 74 | }, 75 | "node_modules/@modelcontextprotocol/sdk/node_modules/iconv-lite": { 76 | "version": "0.6.3", 77 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 78 | "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 79 | "license": "MIT", 80 | "dependencies": { 81 | "safer-buffer": ">= 2.1.2 < 3.0.0" 82 | }, 83 | "engines": { 84 | "node": ">=0.10.0" 85 | } 86 | }, 87 | "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { 88 | "version": "3.0.0", 89 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 90 | "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 91 | "license": "MIT", 92 | "dependencies": { 93 | "bytes": "3.1.2", 94 | "http-errors": "2.0.0", 95 | "iconv-lite": "0.6.3", 96 | "unpipe": "1.0.0" 97 | }, 98 | "engines": { 99 | "node": ">= 0.8" 100 | } 101 | }, 102 | "node_modules/@tsconfig/node10": { 103 | "version": "1.0.11", 104 | "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", 105 | "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", 106 | "dev": true, 107 | "license": "MIT" 108 | }, 109 | "node_modules/@tsconfig/node12": { 110 | "version": "1.0.11", 111 | "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", 112 | "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", 113 | "dev": true, 114 | "license": "MIT" 115 | }, 116 | "node_modules/@tsconfig/node14": { 117 | "version": "1.0.3", 118 | "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", 119 | "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", 120 | "dev": true, 121 | "license": "MIT" 122 | }, 123 | "node_modules/@tsconfig/node16": { 124 | "version": "1.0.4", 125 | "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", 126 | "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", 127 | "dev": true, 128 | "license": "MIT" 129 | }, 130 | "node_modules/@types/node": { 131 | "version": "22.10.2", 132 | "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz", 133 | "integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==", 134 | "dev": true, 135 | "license": "MIT", 136 | "dependencies": { 137 | "undici-types": "~6.20.0" 138 | } 139 | }, 140 | "node_modules/acorn": { 141 | "version": "8.14.0", 142 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", 143 | "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", 144 | "dev": true, 145 | "license": "MIT", 146 | "bin": { 147 | "acorn": "bin/acorn" 148 | }, 149 | "engines": { 150 | "node": ">=0.4.0" 151 | } 152 | }, 153 | "node_modules/acorn-walk": { 154 | "version": "8.3.4", 155 | "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", 156 | "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", 157 | "dev": true, 158 | "license": "MIT", 159 | "dependencies": { 160 | "acorn": "^8.11.0" 161 | }, 162 | "engines": { 163 | "node": ">=0.4.0" 164 | } 165 | }, 166 | "node_modules/arg": { 167 | "version": "4.1.3", 168 | "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", 169 | "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", 170 | "dev": true, 171 | "license": "MIT" 172 | }, 173 | "node_modules/bytes": { 174 | "version": "3.1.2", 175 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 176 | "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 177 | "license": "MIT", 178 | "engines": { 179 | "node": ">= 0.8" 180 | } 181 | }, 182 | "node_modules/content-type": { 183 | "version": "1.0.5", 184 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 185 | "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 186 | "license": "MIT", 187 | "engines": { 188 | "node": ">= 0.6" 189 | } 190 | }, 191 | "node_modules/create-require": { 192 | "version": "1.1.1", 193 | "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", 194 | "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", 195 | "dev": true, 196 | "license": "MIT" 197 | }, 198 | "node_modules/data-uri-to-buffer": { 199 | "version": "4.0.1", 200 | "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", 201 | "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", 202 | "license": "MIT", 203 | "engines": { 204 | "node": ">= 12" 205 | } 206 | }, 207 | "node_modules/depd": { 208 | "version": "2.0.0", 209 | "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 210 | "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 211 | "license": "MIT", 212 | "engines": { 213 | "node": ">= 0.8" 214 | } 215 | }, 216 | "node_modules/diff": { 217 | "version": "4.0.2", 218 | "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", 219 | "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", 220 | "dev": true, 221 | "license": "BSD-3-Clause", 222 | "engines": { 223 | "node": ">=0.3.1" 224 | } 225 | }, 226 | "node_modules/dotenv": { 227 | "version": "16.5.0", 228 | "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", 229 | "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", 230 | "license": "BSD-2-Clause", 231 | "engines": { 232 | "node": ">=12" 233 | }, 234 | "funding": { 235 | "url": "https://dotenvx.com" 236 | } 237 | }, 238 | "node_modules/fetch-blob": { 239 | "version": "3.2.0", 240 | "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz", 241 | "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==", 242 | "funding": [ 243 | { 244 | "type": "github", 245 | "url": "https://github.com/sponsors/jimmywarting" 246 | }, 247 | { 248 | "type": "paypal", 249 | "url": "https://paypal.me/jimmywarting" 250 | } 251 | ], 252 | "license": "MIT", 253 | "dependencies": { 254 | "node-domexception": "^1.0.0", 255 | "web-streams-polyfill": "^3.0.3" 256 | }, 257 | "engines": { 258 | "node": "^12.20 || >= 14.13" 259 | } 260 | }, 261 | "node_modules/formdata-polyfill": { 262 | "version": "4.0.10", 263 | "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz", 264 | "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==", 265 | "license": "MIT", 266 | "dependencies": { 267 | "fetch-blob": "^3.1.2" 268 | }, 269 | "engines": { 270 | "node": ">=12.20.0" 271 | } 272 | }, 273 | "node_modules/http-errors": { 274 | "version": "2.0.0", 275 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 276 | "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 277 | "license": "MIT", 278 | "dependencies": { 279 | "depd": "2.0.0", 280 | "inherits": "2.0.4", 281 | "setprototypeof": "1.2.0", 282 | "statuses": "2.0.1", 283 | "toidentifier": "1.0.1" 284 | }, 285 | "engines": { 286 | "node": ">= 0.8" 287 | } 288 | }, 289 | "node_modules/inherits": { 290 | "version": "2.0.4", 291 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 292 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 293 | "license": "ISC" 294 | }, 295 | "node_modules/make-error": { 296 | "version": "1.3.6", 297 | "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", 298 | "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", 299 | "dev": true, 300 | "license": "ISC" 301 | }, 302 | "node_modules/node-domexception": { 303 | "version": "1.0.0", 304 | "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", 305 | "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", 306 | "funding": [ 307 | { 308 | "type": "github", 309 | "url": "https://github.com/sponsors/jimmywarting" 310 | }, 311 | { 312 | "type": "github", 313 | "url": "https://paypal.me/jimmywarting" 314 | } 315 | ], 316 | "license": "MIT", 317 | "engines": { 318 | "node": ">=10.5.0" 319 | } 320 | }, 321 | "node_modules/node-fetch": { 322 | "version": "3.3.2", 323 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz", 324 | "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==", 325 | "license": "MIT", 326 | "dependencies": { 327 | "data-uri-to-buffer": "^4.0.0", 328 | "fetch-blob": "^3.1.4", 329 | "formdata-polyfill": "^4.0.10" 330 | }, 331 | "engines": { 332 | "node": "^12.20.0 || ^14.13.1 || >=16.0.0" 333 | }, 334 | "funding": { 335 | "type": "opencollective", 336 | "url": "https://opencollective.com/node-fetch" 337 | } 338 | }, 339 | "node_modules/safer-buffer": { 340 | "version": "2.1.2", 341 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 342 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 343 | "license": "MIT" 344 | }, 345 | "node_modules/setprototypeof": { 346 | "version": "1.2.0", 347 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 348 | "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 349 | "license": "ISC" 350 | }, 351 | "node_modules/statuses": { 352 | "version": "2.0.1", 353 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 354 | "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 355 | "license": "MIT", 356 | "engines": { 357 | "node": ">= 0.8" 358 | } 359 | }, 360 | "node_modules/toidentifier": { 361 | "version": "1.0.1", 362 | "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 363 | "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 364 | "license": "MIT", 365 | "engines": { 366 | "node": ">=0.6" 367 | } 368 | }, 369 | "node_modules/ts-node": { 370 | "version": "10.9.2", 371 | "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", 372 | "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", 373 | "dev": true, 374 | "license": "MIT", 375 | "dependencies": { 376 | "@cspotcode/source-map-support": "^0.8.0", 377 | "@tsconfig/node10": "^1.0.7", 378 | "@tsconfig/node12": "^1.0.7", 379 | "@tsconfig/node14": "^1.0.0", 380 | "@tsconfig/node16": "^1.0.2", 381 | "acorn": "^8.4.1", 382 | "acorn-walk": "^8.1.1", 383 | "arg": "^4.1.0", 384 | "create-require": "^1.1.0", 385 | "diff": "^4.0.1", 386 | "make-error": "^1.1.1", 387 | "v8-compile-cache-lib": "^3.0.1", 388 | "yn": "3.1.1" 389 | }, 390 | "bin": { 391 | "ts-node": "dist/bin.js", 392 | "ts-node-cwd": "dist/bin-cwd.js", 393 | "ts-node-esm": "dist/bin-esm.js", 394 | "ts-node-script": "dist/bin-script.js", 395 | "ts-node-transpile-only": "dist/bin-transpile.js", 396 | "ts-script": "dist/bin-script-deprecated.js" 397 | }, 398 | "peerDependencies": { 399 | "@swc/core": ">=1.2.50", 400 | "@swc/wasm": ">=1.2.50", 401 | "@types/node": "*", 402 | "typescript": ">=2.7" 403 | }, 404 | "peerDependenciesMeta": { 405 | "@swc/core": { 406 | "optional": true 407 | }, 408 | "@swc/wasm": { 409 | "optional": true 410 | } 411 | } 412 | }, 413 | "node_modules/typescript": { 414 | "version": "5.7.2", 415 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", 416 | "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", 417 | "dev": true, 418 | "license": "Apache-2.0", 419 | "bin": { 420 | "tsc": "bin/tsc", 421 | "tsserver": "bin/tsserver" 422 | }, 423 | "engines": { 424 | "node": ">=14.17" 425 | } 426 | }, 427 | "node_modules/undici-types": { 428 | "version": "6.20.0", 429 | "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", 430 | "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", 431 | "dev": true, 432 | "license": "MIT" 433 | }, 434 | "node_modules/unpipe": { 435 | "version": "1.0.0", 436 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 437 | "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 438 | "license": "MIT", 439 | "engines": { 440 | "node": ">= 0.8" 441 | } 442 | }, 443 | "node_modules/v8-compile-cache-lib": { 444 | "version": "3.0.1", 445 | "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", 446 | "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", 447 | "dev": true, 448 | "license": "MIT" 449 | }, 450 | "node_modules/web-streams-polyfill": { 451 | "version": "3.3.3", 452 | "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz", 453 | "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==", 454 | "license": "MIT", 455 | "engines": { 456 | "node": ">= 8" 457 | } 458 | }, 459 | "node_modules/yn": { 460 | "version": "3.1.1", 461 | "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", 462 | "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", 463 | "dev": true, 464 | "license": "MIT", 465 | "engines": { 466 | "node": ">=6" 467 | } 468 | }, 469 | "node_modules/zod": { 470 | "version": "3.24.1", 471 | "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.1.tgz", 472 | "integrity": "sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==", 473 | "license": "MIT", 474 | "funding": { 475 | "url": "https://github.com/sponsors/colinhacks" 476 | } 477 | } 478 | } 479 | } 480 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vercel", 3 | "version": "1.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "node --loader ts-node/esm ./src/index.ts", 8 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", 9 | "start": "node build/index.js" 10 | }, 11 | "files": [ 12 | "build" 13 | ], 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "description": "", 18 | "dependencies": { 19 | "@modelcontextprotocol/sdk": "^1.0.4", 20 | "dotenv": "^16.5.0", 21 | "node-fetch": "^3.3.2", 22 | "zod": "^3.24.1" 23 | }, 24 | "devDependencies": { 25 | "@types/node": "^22.10.2", 26 | "ts-node": "^10.9.2", 27 | "typescript": "^5.7.2" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/constants/tools.ts: -------------------------------------------------------------------------------- 1 | import { Tool } from "@modelcontextprotocol/sdk/types.js"; 2 | 3 | export const VERCEL_ALL_DEPLOYMENTS_TOOL: Tool = { 4 | name: "vercel-list-all-deployments", 5 | description: "List deployments under the authenticated user or team.", 6 | inputSchema: { 7 | type: "object", 8 | properties: { 9 | app: { 10 | type: "string", 11 | description: "Name of the deployment", 12 | }, 13 | limit: { 14 | type: "number", 15 | description: "Number of deployments to return", 16 | }, 17 | projectId: { 18 | type: "string", 19 | description: "Filter deployments from the given ID or name", 20 | }, 21 | state: { 22 | type: "string", 23 | description: 24 | "Filter deployments based on their state (BUILDING, ERROR, INITIALIZING, QUEUED, READY, CANCELED)", 25 | }, 26 | target: { 27 | type: "string", 28 | description: "Filter deployments based on the environment", 29 | }, 30 | }, 31 | }, 32 | }; 33 | 34 | export const VERCEL_GET_ENVIRONMENTS_TOOL: Tool = { 35 | name: "vercel-get-environments", 36 | description: "Retrieve environment variables for a project by ID or name", 37 | inputSchema: { 38 | type: "object", 39 | properties: { 40 | idOrName: { 41 | type: "string", 42 | description: 43 | "The project ID or name to retrieve environment variables for", 44 | }, 45 | }, 46 | required: ["idOrName"], 47 | }, 48 | }; 49 | 50 | export const VERCEL_GET_DEPLOYMENT_TOOL: Tool = { 51 | name: "vercel-get-deployment", 52 | description: "Get a deployment by its ID or URL", 53 | inputSchema: { 54 | type: "object", 55 | properties: { 56 | idOrUrl: { 57 | type: "string", 58 | description: "ID or URL of the deployment to retrieve", 59 | }, 60 | teamId: { 61 | type: "string", 62 | description: "Team ID to scope the request", 63 | }, 64 | }, 65 | required: ["idOrUrl"], 66 | }, 67 | }; 68 | 69 | export const VERCEL_LIST_DEPLOYMENT_FILES_TOOL: Tool = { 70 | name: "vercel-list-deployment-files", 71 | description: "List all files of a Vercel deployment", 72 | inputSchema: { 73 | type: "object", 74 | properties: { 75 | id: { 76 | type: "string", 77 | description: "The unique deployment identifier", 78 | }, 79 | teamId: { 80 | type: "string", 81 | description: "Team identifier to perform the request on behalf of", 82 | }, 83 | slug: { 84 | type: "string", 85 | description: "Team slug to perform the request on behalf of", 86 | }, 87 | }, 88 | required: ["id"], 89 | }, 90 | }; 91 | 92 | export const VERCEL_CREATE_DEPLOYMENT_TOOL: Tool = { 93 | name: "vercel-create-deployment", 94 | description: "Create a new Vercel deployment with the v13/deployments API", 95 | inputSchema: { 96 | type: "object", 97 | properties: { 98 | // Identification parameters 99 | name: { 100 | type: "string", 101 | description: "Name of the deployment/project", 102 | }, 103 | project: { 104 | type: "string", 105 | description: "Project ID or name (required unless deploymentId is provided)", 106 | }, 107 | deploymentId: { 108 | type: "string", 109 | description: "ID of a previous deployment to redeploy (required unless project is provided)", 110 | }, 111 | slug: { 112 | type: "string", 113 | description: "A unique URL-friendly identifier", 114 | }, 115 | teamId: { 116 | type: "string", 117 | description: "Team ID for scoping", 118 | }, 119 | customEnvironmentSlugOrId: { 120 | type: "string", 121 | description: "Custom environment slug or ID", 122 | }, 123 | 124 | // Configuration parameters 125 | target: { 126 | type: "string", 127 | description: "Deployment target environment", 128 | enum: ["production", "preview", "development"], 129 | default: "production" 130 | }, 131 | regions: { 132 | type: "array", 133 | items: { type: "string" }, 134 | description: "Regions to deploy to", 135 | }, 136 | functions: { 137 | type: "object", 138 | description: "Serverless functions configuration" 139 | }, 140 | routes: { 141 | type: "array", 142 | description: "Array of route definitions" 143 | }, 144 | cleanUrls: { 145 | type: "boolean", 146 | description: "Enable or disable Clean URLs" 147 | }, 148 | trailingSlash: { 149 | type: "boolean", 150 | description: "Enable or disable trailing slashes" 151 | }, 152 | public: { 153 | type: "boolean", 154 | description: "Make the deployment public" 155 | }, 156 | ignoreCommand: { 157 | type: "string", 158 | description: "Command to check whether files should be ignored" 159 | }, 160 | 161 | // Source control parameters 162 | gitSource: { 163 | type: "object", 164 | description: "Git source information", 165 | properties: { 166 | type: { 167 | type: "string", 168 | enum: ["github", "gitlab", "bitbucket"], 169 | description: "Git provider type" 170 | }, 171 | repoId: { 172 | type: ["string", "number"], 173 | description: "Repository ID" 174 | }, 175 | ref: { 176 | type: "string", 177 | description: "Git reference (branch/tag)" 178 | }, 179 | sha: { 180 | type: "string", 181 | description: "Git commit SHA" 182 | } 183 | } 184 | }, 185 | gitMetadata: { 186 | type: "object", 187 | description: "Git metadata for the deployment", 188 | properties: { 189 | commitAuthorName: { 190 | type: "string", 191 | description: "Commit author name" 192 | }, 193 | commitMessage: { 194 | type: "string", 195 | description: "Commit message" 196 | }, 197 | commitRef: { 198 | type: "string", 199 | description: "Commit reference" 200 | }, 201 | commitSha: { 202 | type: "string", 203 | description: "Commit SHA" 204 | }, 205 | remoteUrl: { 206 | type: "string", 207 | description: "Git remote URL" 208 | }, 209 | dirty: { 210 | type: "boolean", 211 | description: "Indicates if the working directory has uncommitted changes" 212 | } 213 | } 214 | }, 215 | projectSettings: { 216 | type: "object", 217 | description: "Project-specific settings", 218 | properties: { 219 | buildCommand: { 220 | type: "string", 221 | description: "Custom build command" 222 | }, 223 | devCommand: { 224 | type: "string", 225 | description: "Custom development command" 226 | }, 227 | installCommand: { 228 | type: "string", 229 | description: "Custom install command" 230 | }, 231 | outputDirectory: { 232 | type: "string", 233 | description: "Directory where build output is located" 234 | }, 235 | rootDirectory: { 236 | type: "string", 237 | description: "Directory where the project is located" 238 | }, 239 | framework: { 240 | type: "string", 241 | description: "Framework preset" 242 | }, 243 | nodeVersion: { 244 | type: "string", 245 | description: "Node.js version" 246 | }, 247 | serverlessFunctionRegion: { 248 | type: "string", 249 | description: "Region for serverless functions" 250 | }, 251 | skipGitConnectDuringLink: { 252 | type: "boolean", 253 | description: "Skip Git connection during project link" 254 | } 255 | } 256 | }, 257 | meta: { 258 | type: "object", 259 | description: "Additional metadata for the deployment" 260 | }, 261 | monorepoManager: { 262 | type: "string", 263 | description: "Monorepo manager (e.g., turborepo, nx)" 264 | }, 265 | 266 | // File upload (for non-git deployments) 267 | files: { 268 | type: "array", 269 | description: "Files to deploy (for non-git deployments)", 270 | items: { 271 | type: "object", 272 | properties: { 273 | file: { 274 | type: "string", 275 | description: "File path" 276 | }, 277 | data: { 278 | type: "string", 279 | description: "File content" 280 | }, 281 | encoding: { 282 | type: "string", 283 | enum: ["base64", "utf-8"], 284 | description: "File content encoding" 285 | } 286 | } 287 | } 288 | }, 289 | 290 | // Other flags 291 | forceNew: { 292 | type: "boolean", 293 | description: "Force new deployment even if identical exists", 294 | }, 295 | withCache: { 296 | type: "boolean", 297 | description: "Enable or disable build cache" 298 | }, 299 | autoAssignCustomDomains: { 300 | type: "boolean", 301 | description: "Automatically assign custom domains to the deployment" 302 | }, 303 | withLatestCommit: { 304 | type: "boolean", 305 | description: "Include the latest commit in the deployment" 306 | } 307 | }, 308 | }, 309 | }; 310 | 311 | export const VERCEL_LIST_TEAMS_TOOL: Tool = { 312 | name: "vercel-list-all-teams", 313 | description: "List all teams under the authenticated account", 314 | inputSchema: { 315 | type: "object", 316 | properties: { 317 | limit: { 318 | type: "number", 319 | description: "Maximum number of teams to return", 320 | }, 321 | since: { 322 | type: "number", 323 | description: 324 | "Timestamp in milliseconds to get teams created after this time", 325 | }, 326 | until: { 327 | type: "number", 328 | description: 329 | "Timestamp in milliseconds to get teams created before this time", 330 | }, 331 | teamId: { 332 | type: "string", 333 | description: "Team ID to scope the request", 334 | }, 335 | }, 336 | }, 337 | }; 338 | 339 | export const VERCEL_CREATE_TEAM_TOOL: Tool = { 340 | name: "vercel-create-team", 341 | description: "Create a new Vercel team", 342 | inputSchema: { 343 | type: "object", 344 | properties: { 345 | slug: { 346 | type: "string", 347 | description: "A unique identifier for the team", 348 | }, 349 | name: { 350 | type: "string", 351 | description: "A display name for the team", 352 | }, 353 | }, 354 | required: ["slug"], 355 | }, 356 | }; 357 | 358 | export const VERCEL_CREATE_PROJECT_TOOL: Tool = { 359 | name: "vercel-create-project", 360 | description: "Create a new Vercel project", 361 | inputSchema: { 362 | type: "object", 363 | properties: { 364 | name: { 365 | type: "string", 366 | description: "Name of the project", 367 | }, 368 | framework: { 369 | type: "string", 370 | description: "Framework preset", 371 | }, 372 | buildCommand: { 373 | type: "string", 374 | description: "Build command", 375 | }, 376 | devCommand: { 377 | type: "string", 378 | description: "Development command", 379 | }, 380 | installCommand: { 381 | type: "string", 382 | description: "Install command", 383 | }, 384 | outputDirectory: { 385 | type: "string", 386 | description: "Output directory", 387 | }, 388 | publicSource: { 389 | type: "boolean", 390 | description: "Make project public", 391 | }, 392 | rootDirectory: { 393 | type: "string", 394 | description: "Root directory", 395 | }, 396 | serverlessFunctionRegion: { 397 | type: "string", 398 | description: "Serverless function region", 399 | }, 400 | skipGitConnectDuringLink: { 401 | type: "boolean", 402 | description: "Skip Git connection", 403 | }, 404 | teamId: { 405 | type: "string", 406 | description: "Team ID for scoping", 407 | }, 408 | }, 409 | required: ["name"], 410 | }, 411 | }; 412 | 413 | export const VERCEL_LIST_PROJECTS_TOOL: Tool = { 414 | name: "vercel-list-projects", 415 | description: "List all projects under the authenticated user or team", 416 | inputSchema: { 417 | type: "object", 418 | properties: { 419 | limit: { 420 | type: "number", 421 | description: "Maximum number of projects to return" 422 | }, 423 | from: { 424 | type: "number", 425 | description: "Projects created/updated after this timestamp" 426 | }, 427 | teamId: { 428 | type: "string", 429 | description: "Team ID for request scoping" 430 | }, 431 | search: { 432 | type: "string", 433 | description: "Search projects by name" 434 | }, 435 | repoUrl: { 436 | type: "string", 437 | description: "Filter by repository URL" 438 | }, 439 | gitForkProtection: { 440 | type: "string", 441 | enum: ["0", "1"], 442 | description: "Specify PR authorization from forks (0/1)" 443 | } 444 | } 445 | } 446 | }; 447 | 448 | export const VERCEL_CREATE_ENVIRONMENT_VARIABLES_TOOL: Tool = { 449 | name: "vercel-create-environment-variables", 450 | description: "Create environment variables for a Vercel project", 451 | inputSchema: { 452 | type: "object", 453 | properties: { 454 | projectId: { 455 | type: "string", 456 | description: "Project ID to create environment variables for", 457 | }, 458 | teamId: { 459 | type: "string", 460 | description: "Team ID for scoping", 461 | }, 462 | environmentVariables: { 463 | type: "array", 464 | description: "Array of environment variables to create", 465 | items: { 466 | type: "object", 467 | properties: { 468 | key: { 469 | type: "string", 470 | description: "Environment variable key name", 471 | }, 472 | value: { 473 | type: "string", 474 | description: "Environment variable value", 475 | }, 476 | target: { 477 | type: "array", 478 | items: { 479 | type: "string", 480 | enum: ["production", "preview", "development"], 481 | }, 482 | description: "Target environments for this variable", 483 | }, 484 | type: { 485 | type: "string", 486 | enum: ["system", "encrypted", "plain", "sensitive"], 487 | description: "Type of environment variable", 488 | }, 489 | gitBranch: { 490 | type: "string", 491 | description: "Git branch to apply this variable to (optional)", 492 | }, 493 | }, 494 | required: ["key", "value", "target", "type"], 495 | }, 496 | }, 497 | }, 498 | required: ["projectId", "environmentVariables"], 499 | }, 500 | }; 501 | 502 | export const VERCEL_CREATE_CUSTOM_ENVIRONMENT_TOOL: Tool = { 503 | name: "vercel-create-custom-environment", 504 | description: "Create a custom environment for a Vercel project. Note: Cannot be named 'Production' or 'Preview'", 505 | inputSchema: { 506 | type: "object", 507 | properties: { 508 | idOrName: { 509 | type: "string", 510 | description: "The unique project identifier or project name", 511 | }, 512 | name: { 513 | type: "string", 514 | description: "Name for the custom environment (cannot be 'Production' or 'Preview')", 515 | }, 516 | description: { 517 | type: "string", 518 | description: "Description of the custom environment", 519 | }, 520 | branchMatcher: { 521 | type: "object", 522 | description: "Branch matching configuration for the custom environment", 523 | properties: { 524 | type: { 525 | type: "string", 526 | enum: ["startsWith", "endsWith", "contains", "exactMatch", "regex"], 527 | description: "Type of branch matching" 528 | }, 529 | pattern: { 530 | type: "string", 531 | description: "Pattern to match branches against" 532 | } 533 | } 534 | }, 535 | teamId: { 536 | type: "string", 537 | description: "Team ID to perform the request on behalf of", 538 | }, 539 | slug: { 540 | type: "string", 541 | description: "Team slug to perform the request on behalf of", 542 | }, 543 | }, 544 | required: ["idOrName", "name"], 545 | }, 546 | }; 547 | 548 | export const VERCEL_TOOLS = [ 549 | VERCEL_ALL_DEPLOYMENTS_TOOL, 550 | VERCEL_GET_ENVIRONMENTS_TOOL, 551 | VERCEL_GET_DEPLOYMENT_TOOL, 552 | VERCEL_LIST_DEPLOYMENT_FILES_TOOL, 553 | VERCEL_CREATE_DEPLOYMENT_TOOL, 554 | VERCEL_CREATE_PROJECT_TOOL, 555 | VERCEL_LIST_TEAMS_TOOL, 556 | VERCEL_CREATE_TEAM_TOOL, 557 | VERCEL_CREATE_ENVIRONMENT_VARIABLES_TOOL, 558 | VERCEL_CREATE_CUSTOM_ENVIRONMENT_TOOL, 559 | VERCEL_LIST_PROJECTS_TOOL, 560 | ] as const; 561 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 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 | ListToolsRequestSchema, 7 | } from "@modelcontextprotocol/sdk/types.js"; 8 | import { VERCEL_TOOLS } from "./constants/tools.js"; 9 | import { 10 | handleAllDeployments, 11 | handleCreateDeployment, 12 | handleGetDeployment, 13 | handleListDeploymentFiles, 14 | } from "./tools/deployments/handlers.js"; 15 | import { 16 | handleCreateProject, 17 | handleCreateEnvironmentVariables, 18 | handleListProjects, 19 | } from "./tools/projects/handlers.js"; 20 | import { handleGetEnvironments, handleCreateCustomEnvironment } from "./tools/environments/handlers.js"; 21 | import { handleListTeams, handleCreateTeam } from "./tools/teams/handlers.js"; 22 | 23 | const server = new Server( 24 | { 25 | name: "vercel", 26 | version: "0.1.0", 27 | }, 28 | { 29 | capabilities: { 30 | tools: {}, 31 | resources: {}, 32 | }, 33 | }, 34 | ); 35 | 36 | // Set up request handlers 37 | server.setRequestHandler(ListToolsRequestSchema, async () => { 38 | return { 39 | tools: VERCEL_TOOLS, 40 | }; 41 | }); 42 | 43 | server.setRequestHandler(CallToolRequestSchema, async (request) => { 44 | try { 45 | const { name, args } = request.params; 46 | switch (name) { 47 | case "vercel-list-all-deployments": 48 | return await handleAllDeployments(args); 49 | case "vercel-get-environments": 50 | return await handleGetEnvironments(request.params as any); 51 | case "vercel-get-deployment": 52 | return await handleGetDeployment(args); 53 | case "vercel-list-deployment-files": 54 | return await handleListDeploymentFiles(args); 55 | case "vercel-create-deployment": 56 | return await handleCreateDeployment(args); 57 | case "vercel-create-project": 58 | return await handleCreateProject(args); 59 | case "vercel-list-all-teams": 60 | return await handleListTeams(args); 61 | case "vercel-create-team": 62 | return await handleCreateTeam(args); 63 | case "vercel-create-environment-variables": 64 | return await handleCreateEnvironmentVariables(args); 65 | case "vercel-create-custom-environment": 66 | return await handleCreateCustomEnvironment(args); 67 | case "vercel-list-projects": 68 | return await handleListProjects(args); 69 | default: 70 | throw new Error(`Unknown tool: ${name}`); 71 | } 72 | } catch (error) { 73 | return { 74 | content: [ 75 | { 76 | type: "text", 77 | text: `Error: ${ 78 | error instanceof Error ? error.message : String(error) 79 | }`, 80 | isError: true, 81 | }, 82 | ], 83 | }; 84 | } 85 | }); 86 | 87 | // Start the server 88 | async function main() { 89 | const transport = new StdioServerTransport(); 90 | await server.connect(transport); 91 | console.error("Vercel MCP Server running on stdio"); 92 | } 93 | 94 | main().catch((error) => { 95 | console.error("Fatal error in main():", error); 96 | process.exit(1); 97 | }); 98 | -------------------------------------------------------------------------------- /src/tools/deployments/handlers.ts: -------------------------------------------------------------------------------- 1 | import { vercelFetch } from "../../utils/api.js"; 2 | import type { Deployment, DeploymentsResponse, DeploymentFilesResponse } from "./types.js"; 3 | import { 4 | ListDeploymentsArgumentsSchema, 5 | GetDeploymentArgumentsSchema, 6 | CreateDeploymentArgumentsSchema, 7 | ListDeploymentFilesArgumentsSchema, 8 | } from "./schema.js"; 9 | 10 | export async function handleGetDeployment(params: any = {}) { 11 | try { 12 | const { idOrUrl, teamId } = GetDeploymentArgumentsSchema.parse(params); 13 | 14 | let url = `v13/deployments/${encodeURIComponent(idOrUrl)}`; 15 | if (teamId) url += `?teamId=${teamId}`; 16 | 17 | const data = await vercelFetch(url); 18 | 19 | if (!data) { 20 | return { 21 | content: [{ type: "text", text: "Failed to retrieve deployment" }], 22 | }; 23 | } 24 | 25 | return { 26 | content: [{ type: "text", text: JSON.stringify(data, null, 2) }], 27 | }; 28 | } catch (error) { 29 | return { 30 | content: [{ type: "text", text: `Error: ${error}` }], 31 | }; 32 | } 33 | } 34 | 35 | /** 36 | * Creates a new deployment 37 | * @param params The parameters for creating a deployment 38 | * @returns Deployment details 39 | */ 40 | /** 41 | * Create a new Vercel deployment using the v13/deployments API 42 | * @param params The parameters for creating a deployment 43 | * @returns The details of the created deployment 44 | */ 45 | export async function handleCreateDeployment(params: any = {}) { 46 | try { 47 | // Log the received parameters for debugging (noted in the server logs) 48 | console.log("Received deployment params:", JSON.stringify(params)); 49 | 50 | // Validation and cleaning of parameters 51 | const validatedParams = CreateDeploymentArgumentsSchema.parse(params); 52 | console.log("Validated params:", JSON.stringify(validatedParams)); 53 | 54 | // Extraction of URL parameters (teamId is a URL parameter, not a body parameter) 55 | let endpoint = "v13/deployments"; 56 | const queryParams = new URLSearchParams(); 57 | 58 | // Handling of the teamId parameter 59 | if (validatedParams.teamId) { 60 | queryParams.append("teamId", validatedParams.teamId); 61 | // Remove teamId from the body request 62 | delete validatedParams.teamId; 63 | } 64 | 65 | // Construction of the URL with parameters 66 | if (queryParams.toString()) { 67 | endpoint += `?${queryParams.toString()}`; 68 | } 69 | 70 | // Preparation of the body request by removing undefined/null values 71 | const deploymentData: Record = {}; 72 | Object.entries(validatedParams).forEach(([key, value]) => { 73 | if (value !== undefined && value !== null) { 74 | deploymentData[key] = value; 75 | } 76 | }); 77 | 78 | // Make the API request 79 | const data = await vercelFetch(endpoint, { 80 | method: "POST", 81 | body: JSON.stringify(deploymentData), 82 | }); 83 | 84 | if (!data) { 85 | return { 86 | content: [ 87 | { 88 | type: "text", 89 | text: "Failed to create deployment", 90 | isError: true, 91 | }, 92 | ], 93 | }; 94 | } 95 | 96 | // Return successful deployment response 97 | return { 98 | content: [ 99 | { 100 | type: "text", 101 | text: `Deployment created successfully: ${data.url}`, 102 | }, 103 | { 104 | type: "text", 105 | text: JSON.stringify(data, null, 2), 106 | }, 107 | ], 108 | }; 109 | } catch (error) { 110 | return { 111 | content: [ 112 | { 113 | type: "text", 114 | text: `Error creating deployment: ${ 115 | error instanceof Error ? error.message : String(error) 116 | }`, 117 | isError: true, 118 | }, 119 | ], 120 | }; 121 | } 122 | } 123 | 124 | export async function handleAllDeployments(params: any = {}) { 125 | try { 126 | const { app, projectId, state, target, teamId, limit } = 127 | ListDeploymentsArgumentsSchema.parse(params); 128 | 129 | let url = limit 130 | ? `v6/deployments?limit=${limit}` 131 | : "v6/deployments?limit=50"; 132 | 133 | if (app) url += `&app=${app}`; 134 | if (projectId) url += `&projectId=${projectId}`; 135 | if (state) url += `&state=${state}`; 136 | if (target) url += `&target=${target}`; 137 | if (teamId) url += `&teamId=${teamId}`; 138 | 139 | const data = await vercelFetch(url); 140 | 141 | if (!data) { 142 | return { 143 | content: [{ type: "text", text: "Failed to retrieve deployments" }], 144 | }; 145 | } 146 | 147 | return { 148 | content: [ 149 | { type: "text", text: JSON.stringify(data.deployments, null, 2) }, 150 | ], 151 | }; 152 | } catch (error) { 153 | return { 154 | content: [{ type: "text", text: `Error: ${error}` }], 155 | }; 156 | } 157 | } 158 | 159 | /** 160 | * List all files of a deployment 161 | * @param params Parameters including deployment ID and optional team identifier 162 | * @returns List of files in the deployment 163 | */ 164 | export async function handleListDeploymentFiles(params: any = {}) { 165 | try { 166 | const { id, teamId, slug } = ListDeploymentFilesArgumentsSchema.parse(params); 167 | 168 | // Build the URL with optional query parameters 169 | let url = `v6/deployments/${encodeURIComponent(id)}/files`; 170 | const queryParams = new URLSearchParams(); 171 | 172 | if (teamId) queryParams.append("teamId", teamId); 173 | if (slug) queryParams.append("slug", slug); 174 | 175 | if (queryParams.toString()) { 176 | url += `?${queryParams.toString()}`; 177 | } 178 | 179 | const data = await vercelFetch(url); 180 | 181 | if (!data || !data.files) { 182 | return { 183 | content: [{ 184 | type: "text", 185 | text: "Failed to retrieve deployment files or no files found", 186 | isError: true 187 | }], 188 | }; 189 | } 190 | 191 | // Return the files with formatting to make them easier to read 192 | return { 193 | content: [ 194 | { 195 | type: "text", 196 | text: `Found ${data.files.length} files in deployment ${id}`, 197 | }, 198 | { 199 | type: "text", 200 | text: JSON.stringify(data.files, null, 2), 201 | }, 202 | ], 203 | }; 204 | } catch (error) { 205 | return { 206 | content: [ 207 | { 208 | type: "text", 209 | text: `Error listing deployment files: ${ 210 | error instanceof Error ? error.message : String(error) 211 | }`, 212 | isError: true, 213 | }, 214 | ], 215 | }; 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /src/tools/deployments/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const ListDeploymentsArgumentsSchema = z.object({ 4 | app: z.string().optional(), 5 | limit: z.number().optional(), 6 | projectId: z.string().optional(), 7 | state: z.string().optional(), 8 | target: z.string().optional(), 9 | teamId: z.string().optional(), 10 | }); 11 | 12 | export const GetDeploymentArgumentsSchema = z.object({ 13 | idOrUrl: z.string(), 14 | teamId: z.string().optional(), 15 | }); 16 | 17 | export const ListDeploymentFilesArgumentsSchema = z.object({ 18 | id: z.string().describe("The unique deployment identifier"), 19 | teamId: z.string().optional().describe("Team identifier to perform the request on behalf of"), 20 | slug: z.string().optional().describe("Team slug to perform the request on behalf of"), 21 | }); 22 | 23 | export const GitSourceSchema = z.object({ 24 | type: z.literal("github").or(z.literal("gitlab").or(z.literal("bitbucket"))), 25 | repoId: z.number().or(z.string()).optional(), 26 | ref: z.string().optional(), 27 | sha: z.string().optional(), 28 | prId: z.number().or(z.string()).optional(), 29 | }); 30 | 31 | export const GitMetadataSchema = z.object({ 32 | commitAuthorName: z.string().optional(), 33 | commitMessage: z.string().optional(), 34 | commitRef: z.string().optional(), 35 | commitSha: z.string().optional(), 36 | repoId: z.number().or(z.string()).optional(), 37 | remoteUrl: z.string().optional(), 38 | dirty: z.boolean().optional(), 39 | }); 40 | 41 | export const ProjectSettingsSchema = z.object({ 42 | buildCommand: z.string().optional(), 43 | devCommand: z.string().optional(), 44 | framework: z.string().optional(), 45 | installCommand: z.string().optional(), 46 | outputDirectory: z.string().optional(), 47 | rootDirectory: z.string().optional(), 48 | nodeVersion: z.string().optional(), 49 | commandForIgnoringBuildStep: z.string().optional(), 50 | serverlessFunctionRegion: z.string().optional(), 51 | skipGitConnectDuringLink: z.boolean().optional(), 52 | sourceFilesOutsideRootDirectory: z.boolean().optional(), 53 | }); 54 | 55 | export const FileSchema = z.object({ 56 | file: z.string(), 57 | data: z.string(), 58 | encoding: z.enum(["base64", "utf-8"]).optional(), 59 | }); 60 | 61 | /** 62 | * Schéma de validation pour la création de déploiements Vercel 63 | * Compatible avec l'API Vercel v13/deployments 64 | */ 65 | export const CreateDeploymentArgumentsSchema = z.record(z.any()).transform((data) => { 66 | // Transforme tous les paramètres en un objet utilisable 67 | const result: Record = {}; 68 | 69 | // Nettoyage des accents graves (backticks) dans les clés 70 | const cleanedData: Record = {}; 71 | Object.keys(data).forEach(key => { 72 | const cleanKey = key.replace(/`/g, ''); 73 | cleanedData[cleanKey] = data[key]; 74 | }); 75 | 76 | // Identification et projet 77 | if (cleanedData.name) result.name = String(cleanedData.name); 78 | if (cleanedData.project) result.project = String(cleanedData.project); 79 | if (cleanedData.deploymentId) result.deploymentId = String(cleanedData.deploymentId); 80 | if (cleanedData.slug) result.slug = String(cleanedData.slug); 81 | if (cleanedData.teamId) result.teamId = String(cleanedData.teamId); 82 | if (cleanedData.customEnvironmentSlugOrId) result.customEnvironmentSlugOrId = String(cleanedData.customEnvironmentSlugOrId); 83 | 84 | // Configuration du déploiement 85 | result.target = cleanedData.target ? String(cleanedData.target) : 'production'; 86 | if (cleanedData.regions) result.regions = cleanedData.regions; 87 | if (cleanedData.functions) result.functions = cleanedData.functions; 88 | if (cleanedData.routes) result.routes = cleanedData.routes; 89 | if (cleanedData.cleanUrls !== undefined) result.cleanUrls = Boolean(cleanedData.cleanUrls); 90 | if (cleanedData.trailingSlash !== undefined) result.trailingSlash = Boolean(cleanedData.trailingSlash); 91 | if (cleanedData.public !== undefined) result.public = Boolean(cleanedData.public); 92 | if (cleanedData.ignoreCommand) result.ignoreCommand = String(cleanedData.ignoreCommand); 93 | 94 | // Métadonnées et contrôle de source 95 | if (cleanedData.gitSource) result.gitSource = cleanedData.gitSource; 96 | if (cleanedData.gitMetadata) result.gitMetadata = cleanedData.gitMetadata; 97 | if (cleanedData.projectSettings) result.projectSettings = cleanedData.projectSettings; 98 | if (cleanedData.meta) result.meta = cleanedData.meta; 99 | if (cleanedData.monorepoManager) result.monorepoManager = String(cleanedData.monorepoManager); 100 | 101 | // Fichiers (pour les déploiements instantanés) 102 | if (cleanedData.files) result.files = cleanedData.files; 103 | 104 | // Flags et options 105 | if (cleanedData.forceNew) result.forceNew = Boolean(cleanedData.forceNew) ? 1 : undefined; 106 | if (cleanedData.withCache !== undefined) result.withCache = Boolean(cleanedData.withCache); 107 | if (cleanedData.autoAssignCustomDomains !== undefined) result.autoAssignCustomDomains = Boolean(cleanedData.autoAssignCustomDomains); 108 | if (cleanedData.withLatestCommit !== undefined) result.withLatestCommit = Boolean(cleanedData.withLatestCommit); 109 | 110 | return result; 111 | }); 112 | -------------------------------------------------------------------------------- /src/tools/deployments/types.ts: -------------------------------------------------------------------------------- 1 | export interface DeploymentGitSource { 2 | type: "github" | "gitlab" | "bitbucket"; 3 | repoId?: number | string; 4 | ref?: string; 5 | sha?: string; 6 | prId?: number | string; 7 | } 8 | 9 | export interface DeploymentGitMetadata { 10 | commitAuthorName?: string; 11 | commitMessage?: string; 12 | commitRef?: string; 13 | commitSha?: string; 14 | repoId?: number | string; 15 | } 16 | 17 | export interface DeploymentProjectSettings { 18 | buildCommand?: string; 19 | devCommand?: string; 20 | framework?: string; 21 | installCommand?: string; 22 | outputDirectory?: string; 23 | rootDirectory?: string; 24 | nodeVersion?: string; 25 | } 26 | 27 | export interface DeploymentFile { 28 | file: string; 29 | data: string; 30 | encoding?: "base64" | "utf-8"; 31 | } 32 | 33 | export interface Deployment { 34 | uid: string; 35 | id: string; 36 | name?: string; 37 | state: string; 38 | target: string; 39 | url: string; 40 | inspectorUrl: string; 41 | createdAt: number; 42 | alias: string[]; 43 | regions: string[]; 44 | builds?: { 45 | src: string; 46 | use: string; 47 | config?: Record; 48 | }[]; 49 | meta?: { 50 | githubCommitAuthorName?: string; 51 | githubCommitMessage?: string; 52 | githubRepoId?: string; 53 | githubRepo?: string; 54 | }; 55 | creator?: { 56 | uid: string; 57 | email: string; 58 | username: string; 59 | }; 60 | readyState?: string; 61 | projectId?: string; 62 | buildingAt?: number; 63 | readyAt?: number; 64 | } 65 | 66 | export interface DeploymentsResponse { 67 | deployments: Deployment[]; 68 | } 69 | 70 | export interface DeploymentFile { 71 | name: string; 72 | type: string; 73 | uid: string; 74 | contentType: string; 75 | mode: number; 76 | size?: number; 77 | symlink?: string; 78 | children?: DeploymentFile[]; 79 | } 80 | 81 | export interface DeploymentFilesResponse { 82 | files: DeploymentFile[]; 83 | } 84 | -------------------------------------------------------------------------------- /src/tools/environments/handlers.ts: -------------------------------------------------------------------------------- 1 | import { vercelFetch } from "../../utils/api.js"; 2 | import { GetEnvironmentsParams, CustomEnvironmentResponse } from "./type.js"; 3 | import { CreateCustomEnvironmentSchema } from "./schema.js"; 4 | 5 | /** 6 | * Retrieves environment variables for a Vercel project specified by ID or name 7 | * @param params - Parameters containing the ID or name of the project 8 | * @returns A formatted response containing the environment variables or an error message 9 | */ 10 | export async function handleGetEnvironments(params: GetEnvironmentsParams) { 11 | try { 12 | // Validation des paramètres d'entrée 13 | if (!params || !params.arguments) { 14 | const errorMsg = "Invalid request: Missing required arguments"; 15 | console.error(errorMsg); 16 | return { 17 | content: [{ type: "text", text: errorMsg }], 18 | }; 19 | } 20 | 21 | const { idOrName } = params.arguments; 22 | 23 | if (!idOrName || typeof idOrName !== "string") { 24 | const errorMsg = `Invalid request: idOrName parameter must be a non-empty string, received: ${JSON.stringify( 25 | idOrName, 26 | )}`; 27 | console.error(errorMsg); 28 | return { 29 | content: [{ type: "text", text: errorMsg }], 30 | }; 31 | } 32 | 33 | console.log(`Fetching environment variables for project: ${idOrName}`); 34 | 35 | // Appel à l'API Vercel v10 36 | const data = await vercelFetch( 37 | `v10/projects/${encodeURIComponent(idOrName)}/env`, 38 | { method: "GET" }, 39 | ); 40 | 41 | // Validation de la réponse 42 | if (!data || !data.envs || !Array.isArray(data.envs)) { 43 | const errorMsg = `Failed to retrieve environment variables for project: ${idOrName}`; 44 | console.error(errorMsg, { data }); 45 | return { 46 | content: [{ type: "text", text: errorMsg }], 47 | }; 48 | } 49 | 50 | // Formatage de la réponse réussie 51 | const envCount = data.envs.length; 52 | return { 53 | content: [ 54 | { 55 | type: "text", 56 | text: `Retrieving ${envCount} environment variable${ 57 | envCount !== 1 ? "s" : "" 58 | } for project: ${idOrName}`, 59 | }, 60 | { 61 | type: "text", 62 | text: JSON.stringify(data.envs, null, 2), 63 | }, 64 | ], 65 | }; 66 | } catch (error) { 67 | // Gestion des erreurs 68 | const errorMsg = 69 | error instanceof Error 70 | ? `${error.name}: ${error.message}` 71 | : String(error); 72 | 73 | console.error("Error retrieving environment variables:", error); 74 | 75 | return { 76 | content: [ 77 | { 78 | type: "text", 79 | text: `Error retrieving environment variables: ${errorMsg}`, 80 | }, 81 | ], 82 | }; 83 | } 84 | } 85 | 86 | /** 87 | * Creates a custom environment for a Vercel project 88 | * @param params - Parameters containing the project ID/name and environment configuration 89 | * @returns A formatted response containing the created custom environment or an error message 90 | */ 91 | export async function handleCreateCustomEnvironment(params: any) { 92 | try { 93 | // Validate input parameters 94 | const validationResult = CreateCustomEnvironmentSchema.safeParse(params); 95 | 96 | if (!validationResult.success) { 97 | const errorMsg = `Invalid request: ${validationResult.error.issues.map(issue => issue.message).join(", ")}`; 98 | console.error(errorMsg); 99 | return { 100 | content: [{ type: "text", text: errorMsg }], 101 | }; 102 | } 103 | 104 | const { idOrName, name, description, branchMatcher, teamId, slug } = validationResult.data; 105 | 106 | console.log(`Creating custom environment '${name}' for project: ${idOrName}`); 107 | 108 | // Build request body 109 | const requestBody: any = { 110 | name, 111 | }; 112 | 113 | if (description) { 114 | requestBody.description = description; 115 | } 116 | 117 | if (branchMatcher) { 118 | requestBody.branchMatcher = branchMatcher; 119 | } 120 | 121 | // Build query parameters 122 | const queryParams = new URLSearchParams(); 123 | if (teamId) { 124 | queryParams.append("teamId", teamId); 125 | } 126 | if (slug) { 127 | queryParams.append("slug", slug); 128 | } 129 | 130 | const queryString = queryParams.toString(); 131 | const endpoint = `v9/projects/${encodeURIComponent(idOrName)}/custom-environments${queryString ? `?${queryString}` : ""}`; 132 | 133 | // Call Vercel API v9 134 | const data = await vercelFetch( 135 | endpoint, 136 | { 137 | method: "POST", 138 | headers: { 139 | "Content-Type": "application/json", 140 | }, 141 | body: JSON.stringify(requestBody), 142 | } 143 | ); 144 | 145 | // Validate response 146 | if (!data || !data.id) { 147 | const errorMsg = `Failed to create custom environment for project: ${idOrName}`; 148 | console.error(errorMsg, { data }); 149 | return { 150 | content: [{ type: "text", text: errorMsg }], 151 | }; 152 | } 153 | 154 | // Format successful response 155 | return { 156 | content: [ 157 | { 158 | type: "text", 159 | text: `Successfully created custom environment '${name}' for project: ${idOrName}`, 160 | }, 161 | { 162 | type: "text", 163 | text: JSON.stringify(data, null, 2), 164 | }, 165 | ], 166 | }; 167 | } catch (error) { 168 | // Handle errors 169 | const errorMsg = 170 | error instanceof Error 171 | ? `${error.name}: ${error.message}` 172 | : String(error); 173 | 174 | console.error("Error creating custom environment:", error); 175 | 176 | return { 177 | content: [ 178 | { 179 | type: "text", 180 | text: `Error creating custom environment: ${errorMsg}`, 181 | }, 182 | ], 183 | }; 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/tools/environments/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const EnvironmentVariableSchema = z.object({ 4 | type: z.string(), 5 | value: z.string(), 6 | target: z.array(z.string()).optional(), 7 | gitBranch: z.string().optional(), 8 | }); 9 | 10 | export const CreateEnvironmentSchema = z.object({ 11 | projectId: z.string(), 12 | key: z.string(), 13 | value: z.string(), 14 | target: z.array(z.string()).optional(), 15 | type: z.string().optional(), 16 | gitBranch: z.string().optional(), 17 | }); 18 | 19 | export const CreateCustomEnvironmentSchema = z.object({ 20 | idOrName: z.string(), 21 | name: z.string().refine((val) => val.toLowerCase() !== 'production' && val.toLowerCase() !== 'preview', { 22 | message: "Custom environment name cannot be 'Production' or 'Preview'", 23 | }), 24 | description: z.string().optional(), 25 | branchMatcher: z.object({ 26 | type: z.enum(["startsWith", "endsWith", "contains", "exactMatch", "regex"]), 27 | pattern: z.string(), 28 | }).optional(), 29 | teamId: z.string().optional(), 30 | slug: z.string().optional(), 31 | }); 32 | -------------------------------------------------------------------------------- /src/tools/environments/type.ts: -------------------------------------------------------------------------------- 1 | // Interface for the environment variables according to the Vercel v10 API 2 | export interface EnvironmentVariable { 3 | id: string; 4 | key: string; 5 | value: string; 6 | target: string[]; 7 | type: string; 8 | configurationId: string | null; 9 | createdAt: number; 10 | updatedAt: number; 11 | gitBranch?: string; 12 | } 13 | 14 | export interface EnvironmentVariablesResponse { 15 | envs: EnvironmentVariable[]; 16 | } 17 | 18 | // Interface for the input parameters 19 | export interface GetEnvironmentsParams { 20 | arguments: { 21 | idOrName: string; 22 | }; 23 | } 24 | 25 | // Interface for creating custom environment 26 | export interface CreateCustomEnvironmentParams { 27 | idOrName: string; 28 | name: string; 29 | description?: string; 30 | branchMatcher?: { 31 | type: "startsWith" | "endsWith" | "contains" | "exactMatch" | "regex"; 32 | pattern: string; 33 | }; 34 | teamId?: string; 35 | slug?: string; 36 | } 37 | 38 | // Interface for custom environment response 39 | export interface CustomEnvironmentResponse { 40 | id: string; 41 | slug: string; 42 | type: string; 43 | description: string; 44 | branchMatcher: { 45 | type: string; 46 | pattern: string; 47 | }; 48 | domains: Array<{ 49 | domain: string; 50 | redirect?: string; 51 | redirectStatusCode?: number; 52 | }>; 53 | createdAt: number; 54 | updatedAt: number; 55 | } 56 | -------------------------------------------------------------------------------- /src/tools/projects/handlers.ts: -------------------------------------------------------------------------------- 1 | import { vercelFetch } from "../../utils/api.js"; 2 | import { VERCEL_API } from "../../utils/config.js"; 3 | import { 4 | CreateProjectArgumentsSchema, 5 | CreateEnvironmentVariablesSchema, 6 | ListProjectsArgumentsSchema, 7 | } from "./schema.js"; 8 | import type { 9 | ProjectResponse, 10 | EnvironmentVariablesResponse, 11 | ListProjectsResponse, 12 | } from "./types.js"; 13 | 14 | /** 15 | * Create environment variables for a project 16 | * @param params - The parameters for creating environment variables 17 | * @returns The response from the API 18 | */ 19 | export async function handleCreateEnvironmentVariables(params: any = {}) { 20 | try { 21 | const { projectId, teamId, environmentVariables } = 22 | CreateEnvironmentVariablesSchema.parse(params); 23 | 24 | const url = `v10/projects/${encodeURIComponent(projectId)}/env${ 25 | teamId ? `?teamId=${teamId}` : "" 26 | }`; 27 | 28 | const data = await vercelFetch(url, { 29 | method: "POST", 30 | body: JSON.stringify(environmentVariables), 31 | }); 32 | 33 | return { 34 | content: [ 35 | { 36 | type: "text", 37 | text: `Successfully created ${data?.created.length} environment variables`, 38 | }, 39 | { 40 | type: "text", 41 | text: JSON.stringify(data, null, 2), 42 | }, 43 | ], 44 | }; 45 | } catch (error) { 46 | return { 47 | content: [ 48 | { 49 | type: "text", 50 | text: `Error: ${ 51 | error instanceof Error 52 | ? error.message 53 | : "Failed to create environment variables" 54 | }`, 55 | isError: true, 56 | }, 57 | ], 58 | }; 59 | } 60 | } 61 | 62 | /** 63 | * List projects 64 | * @param params - The parameters for listing projects 65 | * @returns The response from the API 66 | */ 67 | export async function handleListProjects(params: any = {}) { 68 | try { 69 | const { limit, from, teamId, search, repoUrl, gitForkProtection } = 70 | ListProjectsArgumentsSchema.parse(params); 71 | 72 | // Build the query URL with parameters 73 | let endpoint = "v9/projects"; 74 | const queryParams = new URLSearchParams(); 75 | 76 | if (limit) queryParams.append("limit", limit.toString()); 77 | if (from) queryParams.append("from", from.toString()); 78 | if (teamId) queryParams.append("teamId", teamId); 79 | if (search) queryParams.append("search", search); 80 | if (repoUrl) queryParams.append("repoUrl", repoUrl); 81 | if (gitForkProtection) queryParams.append("gitForkProtection", gitForkProtection); 82 | 83 | if (queryParams.toString()) { 84 | endpoint += `?${queryParams.toString()}`; 85 | } 86 | 87 | const data = await vercelFetch(endpoint); 88 | 89 | if (!data || !data.projects) { 90 | return { 91 | content: [ 92 | { 93 | type: "text", 94 | text: "No projects found or invalid response from API", 95 | isError: true 96 | } 97 | ] 98 | }; 99 | } 100 | 101 | return { 102 | content: [ 103 | { 104 | type: "text", 105 | text: `Found ${data.projects.length} projects`, 106 | }, 107 | { 108 | type: "text", 109 | text: JSON.stringify(data.projects, null, 2), 110 | }, 111 | ], 112 | }; 113 | } catch (error) { 114 | return { 115 | content: [ 116 | { 117 | type: "text", 118 | text: `Error: ${ 119 | error instanceof Error ? error.message : "Failed to list projects" 120 | }`, 121 | isError: true, 122 | }, 123 | ], 124 | }; 125 | } 126 | } 127 | 128 | /** 129 | * Create a project 130 | * @param params - The parameters for creating a project 131 | * @returns The response from the API 132 | */ 133 | export async function handleCreateProject(params: any = {}) { 134 | try { 135 | const { 136 | name, 137 | framework, 138 | buildCommand, 139 | devCommand, 140 | installCommand, 141 | outputDirectory, 142 | publicSource, 143 | rootDirectory, 144 | serverlessFunctionRegion, 145 | skipGitConnectDuringLink, 146 | teamId, 147 | } = CreateProjectArgumentsSchema.parse(params); 148 | 149 | const url = `v11/projects${teamId ? `?teamId=${teamId}` : ""}`; 150 | 151 | const projectData = { 152 | name, 153 | framework, 154 | buildCommand, 155 | devCommand, 156 | installCommand, 157 | outputDirectory, 158 | publicSource, 159 | rootDirectory, 160 | serverlessFunctionRegion, 161 | skipGitConnectDuringLink, 162 | }; 163 | 164 | const data = await vercelFetch(url, { 165 | method: "POST", 166 | body: JSON.stringify(projectData), 167 | }); 168 | 169 | return { 170 | content: [ 171 | { 172 | type: "text", 173 | text: `Project ${data?.name} (${data?.id}) created successfully`, 174 | }, 175 | { 176 | type: "text", 177 | text: JSON.stringify(data, null, 2), 178 | }, 179 | ], 180 | }; 181 | } catch (error) { 182 | return { 183 | content: [ 184 | { 185 | type: "text", 186 | text: `Error: ${ 187 | error instanceof Error ? error.message : "Failed to create project" 188 | }`, 189 | isError: true, 190 | }, 191 | ], 192 | }; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/tools/projects/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const EnvironmentVariableSchema = z.object({ 4 | key: z.string().min(1, "Key is required"), 5 | value: z.string().min(1, "Value is required"), 6 | target: z.array(z.enum(["production", "preview", "development"])), 7 | type: z.enum(["system", "encrypted", "plain", "sensitive"]), 8 | gitBranch: z.string().optional(), 9 | }); 10 | 11 | export const CreateEnvironmentVariablesSchema = z.object({ 12 | projectId: z.string().min(1, "Project ID is required"), 13 | teamId: z.string().optional(), 14 | environmentVariables: z.array(EnvironmentVariableSchema).min(1), 15 | }); 16 | 17 | export const CreateProjectArgumentsSchema = z.object({ 18 | name: z.string().min(1, "Project name is required"), 19 | framework: z.string().optional(), 20 | buildCommand: z.string().optional(), 21 | devCommand: z.string().optional(), 22 | installCommand: z.string().optional(), 23 | outputDirectory: z.string().optional(), 24 | publicSource: z.boolean().optional(), 25 | rootDirectory: z.string().optional(), 26 | serverlessFunctionRegion: z.string().optional(), 27 | skipGitConnectDuringLink: z.boolean().optional(), 28 | teamId: z.string().min(1, "Team ID is required"), 29 | }); 30 | 31 | export const ListProjectsArgumentsSchema = z.object({ 32 | limit: z.number().int().positive().optional().describe("Number of projects to return"), 33 | from: z.number().int().optional().describe("Projects created/updated after this timestamp"), 34 | teamId: z.string().optional().describe("Team ID for request scoping"), 35 | search: z.string().optional().describe("Search projects by name"), 36 | repoUrl: z.string().optional().describe("Filter by repository URL"), 37 | gitForkProtection: z.enum(["0", "1"]).optional().describe("Specify PR authorization from forks (0/1)"), 38 | }); 39 | -------------------------------------------------------------------------------- /src/tools/projects/types.ts: -------------------------------------------------------------------------------- 1 | export interface Project { 2 | id: string; 3 | name: string; 4 | accountId: string; 5 | framework: string | null; 6 | latestDeployments: { 7 | alias: string[]; 8 | }[]; 9 | targets: { 10 | production: { 11 | alias: string[]; 12 | }; 13 | }; 14 | createdAt: number; 15 | updatedAt: number; 16 | } 17 | 18 | export interface EnvironmentVariable { 19 | key: string; 20 | value: string; 21 | target: string[]; 22 | type: string; 23 | gitBranch?: string; 24 | createdAt: number; 25 | updatedAt: number; 26 | } 27 | 28 | export interface EnvironmentVariablesResponse { 29 | created: EnvironmentVariable[]; 30 | skipped: { 31 | key: string; 32 | code: string; 33 | message: string; 34 | }[]; 35 | } 36 | 37 | export interface ListProjectsResponse { 38 | projects: Project[]; 39 | pagination: { 40 | count: number; 41 | next: number | null; 42 | prev: number | null; 43 | }; 44 | } 45 | 46 | export interface ProjectResponse { 47 | id: string; 48 | name: string; 49 | accountId: string; 50 | createdAt: number; 51 | updatedAt: number; 52 | framework: string | null; 53 | } 54 | -------------------------------------------------------------------------------- /src/tools/teams/handlers.ts: -------------------------------------------------------------------------------- 1 | import { vercelFetch } from "../../utils/api.js"; 2 | import { 3 | ListTeamsArgumentsSchema, 4 | CreateTeamArgumentsSchema, 5 | } from "./schema.js"; 6 | import type { TeamsResponse, CreateTeamResponse } from "./types.js"; 7 | 8 | export async function handleListTeams(params: any = {}) { 9 | try { 10 | const { limit, since, until } = ListTeamsArgumentsSchema.parse(params); 11 | 12 | let url = "v2/teams"; 13 | const queryParams = new URLSearchParams(); 14 | 15 | if (limit) queryParams.append("limit", limit.toString()); 16 | if (since) queryParams.append("since", since.toString()); 17 | if (until) queryParams.append("until", until.toString()); 18 | 19 | if (queryParams.toString()) { 20 | url += `?${queryParams.toString()}`; 21 | } 22 | 23 | const data = await vercelFetch(url); 24 | 25 | return { 26 | content: [ 27 | { 28 | type: "text", 29 | text: `Found ${data?.teams.length} teams`, 30 | }, 31 | { 32 | type: "text", 33 | text: JSON.stringify(data?.teams, null, 2), 34 | }, 35 | ], 36 | }; 37 | } catch (error) { 38 | return { 39 | content: [ 40 | { 41 | type: "text", 42 | text: `Error: ${ 43 | error instanceof Error ? error.message : "Failed to list teams" 44 | }`, 45 | isError: true, 46 | }, 47 | ], 48 | }; 49 | } 50 | } 51 | 52 | /** 53 | * Creates a new team. 54 | * @param params The parameters for creating a team 55 | * @returns The created team details 56 | */ 57 | export async function handleCreateTeam(params: any = {}) { 58 | try { 59 | const { slug, name } = CreateTeamArgumentsSchema.parse(params); 60 | 61 | const url = "v1/teams"; 62 | const teamData = { 63 | slug, 64 | ...(name && { name }), 65 | }; 66 | 67 | const data = await vercelFetch(url, { 68 | method: "POST", 69 | body: JSON.stringify(teamData), 70 | }); 71 | 72 | console.log("data", data); 73 | if (!data) { 74 | return { 75 | content: [ 76 | { type: "text", text: "Failed to create team", isError: true }, 77 | ], 78 | }; 79 | } 80 | 81 | return { 82 | content: [ 83 | { 84 | type: "text", 85 | text: `Team created successfully: ${data.slug} (${data.id})`, 86 | }, 87 | { 88 | type: "text", 89 | text: JSON.stringify(data, null, 2), 90 | }, 91 | ], 92 | }; 93 | } catch (error) { 94 | return { 95 | content: [ 96 | { 97 | type: "text", 98 | text: `Error creating team: ${ 99 | error instanceof Error ? error.message : String(error) 100 | }`, 101 | isError: true, 102 | }, 103 | ], 104 | }; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/tools/teams/schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const ListTeamsArgumentsSchema = z.object({ 4 | limit: z.number().optional(), 5 | since: z.number().optional(), 6 | until: z.number().optional(), 7 | }); 8 | 9 | export const CreateTeamArgumentsSchema = z.object({ 10 | slug: z.string().min(1).max(48).describe("A unique identifier for the team"), 11 | name: z.string().optional().describe("A display name for the team") 12 | }); 13 | -------------------------------------------------------------------------------- /src/tools/teams/types.ts: -------------------------------------------------------------------------------- 1 | export interface Team { 2 | id: string; 3 | slug: string; 4 | name: string; 5 | avatar: string | null; 6 | creatorId: string; 7 | created: string; 8 | updated: string; 9 | } 10 | 11 | export interface TeamsResponse { 12 | teams: Team[]; 13 | pagination: { 14 | count: number; 15 | next: number | null; 16 | prev: number | null; 17 | }; 18 | } 19 | 20 | export interface CreateTeamResponse { 21 | id: string; 22 | slug: string; 23 | billing: Record; 24 | } 25 | -------------------------------------------------------------------------------- /src/utils/api.ts: -------------------------------------------------------------------------------- 1 | import fetch from "node-fetch"; 2 | import { VERCEL_API, VERCEL_API_TOKEN } from "./config.js"; 3 | 4 | export async function vercelFetch( 5 | endpoint: string, 6 | options: RequestInit = {}, 7 | ): Promise { 8 | try { 9 | const headers = { 10 | Authorization: `Bearer ${VERCEL_API_TOKEN}`, 11 | "Content-Type": "application/json", 12 | ...options.headers, 13 | }; 14 | 15 | const response = await fetch(`${VERCEL_API}${endpoint}`, { 16 | ...options, 17 | headers, 18 | } as any); 19 | 20 | if (!response.ok) { 21 | throw new Error(`Vercel API error: ${response.status}`); 22 | } 23 | 24 | return (await response.json()) as T; 25 | } catch (error) { 26 | console.error("Vercel API error:", error); 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/utils/config.ts: -------------------------------------------------------------------------------- 1 | import dotenv from 'dotenv'; 2 | 3 | // Load environment variables from .env file 4 | dotenv.config(); 5 | 6 | export function getVercelApiToken(): string { 7 | const vercelApiToken = process.env.VERCEL_API_TOKEN; 8 | if (!vercelApiToken) { 9 | console.error("VERCEL_API_TOKEN environment variable is not set"); 10 | process.exit(1); 11 | } 12 | return vercelApiToken; 13 | } 14 | 15 | export const VERCEL_API_TOKEN = getVercelApiToken(); 16 | export const VERCEL_API = "https://api.vercel.com/"; 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2022", 4 | "module": "ESNext", 5 | "moduleResolution": "Bundler", 6 | "strict": true, 7 | "esModuleInterop": true, 8 | "skipLibCheck": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "rootDir": "./src", 11 | "outDir": "./build" 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules"] 15 | } 16 | --------------------------------------------------------------------------------