Skip to main content
Glama
vercel.ts15 kB
/** * Vercel namespace - Vercel deployment and domain management */ import axios from 'axios'; import { MCPServer } from '../core/server.js'; import { ConfigMissingError, wrapError, RateLimitedError } from '../core/errors.js'; import { VercelProject, VercelProjectDetail, VercelDeployment, VercelDeploymentDetail, VercelLogLine, VercelDomain, DeploymentTarget, ListProjectsResponse, GetProjectResponse, DeployResponse, RedeployResponse, ListDeploymentsResponse, GetDeploymentResponse, PromoteResponse, LogsResponse, EnvListResponse, EnvSetResponse, ListDomainsResponse, AddDomainResponse, RemoveDomainResponse } from '../types/vercel.js'; export class VercelNamespace { private mcpServer: MCPServer; private apiToken?: string; private teamId?: string; private baseUrl = 'https://api.vercel.com'; constructor(mcpServer: MCPServer) { this.mcpServer = mcpServer; const env = mcpServer.getEnvConfig(); this.apiToken = env.VERCEL_TOKEN; this.teamId = env.VERCEL_TEAM_ID; this.registerTools(); } private checkAuth(): void { if (!this.apiToken) { throw new ConfigMissingError('VERCEL_TOKEN'); } } private async makeRequest<T>( method: string, path: string, data?: any, params?: any ): Promise<T> { this.checkAuth(); try { const response = await axios({ method, url: `${this.baseUrl}${path}`, headers: { 'Authorization': `Bearer ${this.apiToken}`, 'Content-Type': 'application/json' }, data, params: { ...params, ...(this.teamId ? { teamId: this.teamId } : {}) }, timeout: 60000 }); return response.data; } catch (error: any) { if (error.response?.status === 429) { const retryAfter = error.response.headers['x-ratelimit-reset']; const retryMs = retryAfter ? Math.max(0, parseInt(retryAfter) * 1000 - Date.now()) : undefined; throw new RateLimitedError(retryMs); } throw wrapError(error); } } private registerTools(): void { const registry = this.mcpServer.getRegistry(); registry.registerTool( 'vercel.projects_list', { name: 'vercel.projects_list', description: 'List Vercel projects', inputSchema: { type: 'object', properties: { team_id: { type: 'string' } } } }, this.projectsList.bind(this) ); registry.registerTool( 'vercel.project_get', { name: 'vercel.project_get', description: 'Get Vercel project details', inputSchema: { type: 'object', properties: { project_id: { type: 'string' } }, required: ['project_id'] } }, this.projectGet.bind(this) ); registry.registerTool( 'vercel.deploy', { name: 'vercel.deploy', description: 'Deploy to Vercel', inputSchema: { type: 'object', properties: { project_id: { type: 'string' }, source: { type: 'object', properties: { git_sha: { type: 'string' }, build_id: { type: 'string' } } }, target: { type: 'string', enum: ['production', 'preview'] } }, required: ['project_id', 'source'] } }, this.deploy.bind(this) ); registry.registerTool( 'vercel.redeploy', { name: 'vercel.redeploy', description: 'Redeploy a Vercel deployment', inputSchema: { type: 'object', properties: { project_id: { type: 'string' }, from_deployment_id: { type: 'string' }, from_git_sha: { type: 'string' }, target: { type: 'string', enum: ['production', 'preview'] } }, required: ['project_id'] } }, this.redeploy.bind(this) ); registry.registerTool( 'vercel.deployments_list', { name: 'vercel.deployments_list', description: 'List Vercel deployments', inputSchema: { type: 'object', properties: { project_id: { type: 'string' }, limit: { type: 'number' }, cursor: { type: 'string' } }, required: ['project_id'] } }, this.deploymentsList.bind(this) ); registry.registerTool( 'vercel.deployment_get', { name: 'vercel.deployment_get', description: 'Get deployment details', inputSchema: { type: 'object', properties: { deployment_id: { type: 'string' } }, required: ['deployment_id'] } }, this.deploymentGet.bind(this) ); registry.registerTool( 'vercel.deployment_cancel', { name: 'vercel.deployment_cancel', description: 'Cancel a deployment', inputSchema: { type: 'object', properties: { deployment_id: { type: 'string' } }, required: ['deployment_id'] } }, this.deploymentCancel.bind(this) ); registry.registerTool( 'vercel.promote', { name: 'vercel.promote', description: 'Promote deployment to production', inputSchema: { type: 'object', properties: { deployment_id: { type: 'string' } }, required: ['deployment_id'] } }, this.promote.bind(this) ); registry.registerTool( 'vercel.logs', { name: 'vercel.logs', description: 'Get deployment logs', inputSchema: { type: 'object', properties: { deployment_id: { type: 'string' }, since: { type: 'string' }, tail: { type: 'number' } }, required: ['deployment_id'] } }, this.logs.bind(this) ); registry.registerTool( 'vercel.env_list', { name: 'vercel.env_list', description: 'List environment variables', inputSchema: { type: 'object', properties: { project_id: { type: 'string' } }, required: ['project_id'] } }, this.envList.bind(this) ); registry.registerTool( 'vercel.env_set', { name: 'vercel.env_set', description: 'Set environment variables', inputSchema: { type: 'object', properties: { project_id: { type: 'string' }, vars: { type: 'object' } }, required: ['project_id', 'vars'] } }, this.envSet.bind(this) ); registry.registerTool( 'vercel.domains_list', { name: 'vercel.domains_list', description: 'List project domains', inputSchema: { type: 'object', properties: { project_id: { type: 'string' } }, required: ['project_id'] } }, this.domainsList.bind(this) ); registry.registerTool( 'vercel.domain_add', { name: 'vercel.domain_add', description: 'Add domain to project', inputSchema: { type: 'object', properties: { project_id: { type: 'string' }, name: { type: 'string' } }, required: ['project_id', 'name'] } }, this.domainAdd.bind(this) ); registry.registerTool( 'vercel.domain_remove', { name: 'vercel.domain_remove', description: 'Remove domain from project', inputSchema: { type: 'object', properties: { project_id: { type: 'string' }, name: { type: 'string' } }, required: ['project_id', 'name'] } }, this.domainRemove.bind(this) ); } private async projectsList(params: { team_id?: string; }): Promise<ListProjectsResponse> { const response = await this.makeRequest<any>( 'GET', '/v9/projects', undefined, params.team_id ? { teamId: params.team_id } : undefined ); const projects: VercelProject[] = response.projects.map((p: any) => ({ id: p.id, name: p.name, slug: p.name // Vercel uses name as slug })); return { projects }; } private async projectGet(params: { project_id: string; }): Promise<GetProjectResponse> { const response = await this.makeRequest<any>( 'GET', `/v9/projects/${params.project_id}` ); return { id: response.id, name: response.name, slug: response.name, framework: response.framework, targets: response.targets || ['production', 'preview'], env: response.env ? Object.keys(response.env) : [] }; } private async deploy(params: { project_id: string; source: { git_sha?: string; build_id?: string }; target?: DeploymentTarget; }): Promise<DeployResponse> { const deploymentData: any = { name: params.project_id, target: params.target || 'preview', gitSource: params.source.git_sha ? { ref: params.source.git_sha, type: 'github' } : undefined }; const response = await this.makeRequest<any>( 'POST', '/v13/deployments', deploymentData ); return { deployment_id: response.id, url: response.url, status: response.readyState }; } private async redeploy(params: { project_id: string; from_deployment_id?: string; from_git_sha?: string; target?: DeploymentTarget; }): Promise<RedeployResponse> { let source: any = {}; if (params.from_deployment_id) { // Get deployment details to copy const deployment = await this.deploymentGet({ deployment_id: params.from_deployment_id }); if (deployment.git_sha) { source.git_sha = deployment.git_sha; } } else if (params.from_git_sha) { source.git_sha = params.from_git_sha; } return this.deploy({ project_id: params.project_id, source, target: params.target }); } private async deploymentsList(params: { project_id: string; limit?: number; cursor?: string; }): Promise<ListDeploymentsResponse> { const response = await this.makeRequest<any>( 'GET', '/v6/deployments', undefined, { projectId: params.project_id, limit: params.limit || 20, until: params.cursor } ); const deployments: VercelDeployment[] = response.deployments.map((d: any) => ({ id: d.uid, created_at: new Date(d.created).toISOString(), ready_state: d.readyState, url: d.url, git_sha: d.meta?.githubCommitSha })); return { deployments, next_cursor: response.pagination?.next }; } private async deploymentGet(params: { deployment_id: string; }): Promise<GetDeploymentResponse> { const response = await this.makeRequest<any>( 'GET', `/v13/deployments/${params.deployment_id}` ); return { id: response.id || response.uid, project_id: response.projectId, created_at: new Date(response.created || Date.now()).toISOString(), ready_state: response.readyState, inspector_url: response.inspectorUrl, url: response.url, git_sha: response.meta?.githubCommitSha }; } private async deploymentCancel(params: { deployment_id: string; }): Promise<{ ok: true }> { await this.makeRequest<any>( 'PATCH', `/v12/deployments/${params.deployment_id}/cancel` ); return { ok: true }; } private async promote(params: { deployment_id: string; }): Promise<PromoteResponse> { const deployment = await this.deploymentGet(params); // Promote by creating an alias const response = await this.makeRequest<any>( 'POST', `/v2/deployments/${params.deployment_id}/aliases`, { alias: deployment.url?.replace('https://', '').replace('.vercel.app', '') } ); return { ok: true, url: `https://${response.alias}` }; } private async logs(params: { deployment_id: string; since?: string; tail?: number; }): Promise<LogsResponse> { const response = await this.makeRequest<any[]>( 'GET', `/v2/deployments/${params.deployment_id}/events`, undefined, { since: params.since ? new Date(params.since).getTime() : undefined, limit: params.tail || 100 } ); const lines: VercelLogLine[] = response.map(event => ({ ts: new Date(event.created).toISOString(), message: event.text || event.payload?.text || '' })); return { lines }; } private async envList(params: { project_id: string; }): Promise<EnvListResponse> { const response = await this.makeRequest<any>( 'GET', `/v8/projects/${params.project_id}/env` ); const vars: Record<string, string> = {}; for (const envVar of response.envs) { vars[envVar.key] = envVar.value; } return { vars }; } private async envSet(params: { project_id: string; vars: Record<string, string>; }): Promise<EnvSetResponse> { // Vercel requires individual API calls for each env var for (const [key, value] of Object.entries(params.vars)) { await this.makeRequest<any>( 'POST', `/v10/projects/${params.project_id}/env`, { key, value, target: ['production', 'preview', 'development'], type: 'encrypted' } ); } return { ok: true }; } private async domainsList(params: { project_id: string; }): Promise<ListDomainsResponse> { const response = await this.makeRequest<any>( 'GET', `/v9/projects/${params.project_id}/domains` ); const domains: VercelDomain[] = response.domains.map((d: any) => ({ name: d.name, verified: d.verified, apex: d.apexName === d.name })); return { domains }; } private async domainAdd(params: { project_id: string; name: string; }): Promise<AddDomainResponse> { const response = await this.makeRequest<any>( 'POST', `/v10/projects/${params.project_id}/domains`, { name: params.name } ); return { name: response.name, verified: response.verified }; } private async domainRemove(params: { project_id: string; name: string; }): Promise<RemoveDomainResponse> { await this.makeRequest<any>( 'DELETE', `/v9/projects/${params.project_id}/domains/${params.name}` ); return { ok: true }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/JacobFV/mcp-fullstack'

If you have feedback or need assistance with the MCP directory API, please join our Discord server