Vercel MCP Server
by Quegenx
- vercel-mcp-server
- src
- components
import { z } from "zod";
import { handleResponse } from "../utils/response.js";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { BASE_URL, DEFAULT_ACCESS_TOKEN } from "../config/constants.js";
export function registerWebhookTools(server: McpServer) {
// Create a webhook
server.tool(
"create_webhook",
"Creates a webhook",
{
url: z.string().url().regex(/^https?:\/\//).describe("The webhook URL"),
events: z.array(z.enum([
"budget.reached",
"budget.reset",
"domain.created",
"deployment.created",
"deployment.error"
])).min(1).describe("Events to subscribe to"),
projectIds: z.array(z.string()).min(1).max(50).describe("Project IDs to watch"),
teamId: z.string().optional().describe("Team ID to perform the request on behalf of"),
slug: z.string().optional().describe("Team slug to perform the request on behalf of")
},
async ({ url, events, projectIds, teamId, slug }) => {
const urlObj = new URL(`${BASE_URL}/v1/webhooks`);
if (teamId) urlObj.searchParams.append("teamId", teamId);
if (slug) urlObj.searchParams.append("slug", slug);
const response = await fetch(urlObj.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
body: JSON.stringify({
url,
events,
projectIds
}),
});
const data = await handleResponse(response);
return {
content: [
{ type: "text", text: `Webhook created:\n${JSON.stringify(data, null, 2)}` },
],
};
}
);
// Delete a webhook
server.tool(
"delete_webhook",
"Deletes a webhook",
{
id: z.string().describe("Webhook ID to delete"),
teamId: z.string().optional().describe("Team ID to perform the request on behalf of"),
slug: z.string().optional().describe("Team slug to perform the request on behalf of")
},
async ({ id, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/webhooks/${id}`);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
method: "DELETE",
headers: {
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
});
if (response.status === 204) {
return {
content: [
{ type: "text", text: `Webhook ${id} was successfully deleted` },
],
};
}
const errorData = await response.text();
throw new Error(`Failed to delete webhook: ${response.status} - ${errorData}`);
}
);
// List webhooks
server.tool(
"list_webhooks",
"Get a list of webhooks",
{
projectId: z.string().regex(/^[a-zA-z0-9_]+$/).optional().describe("Filter by project ID"),
teamId: z.string().optional().describe("Team ID to perform the request on behalf of"),
slug: z.string().optional().describe("Team slug to perform the request on behalf of")
},
async ({ projectId, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/webhooks`);
if (projectId) url.searchParams.append("projectId", projectId);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
});
const data = await handleResponse(response);
return {
content: [
{ type: "text", text: `Webhooks:\n${JSON.stringify(data, null, 2)}` },
],
};
}
);
// Get a webhook
server.tool(
"get_webhook",
"Get a webhook",
{
id: z.string().describe("Webhook ID"),
teamId: z.string().optional().describe("Team ID to perform the request on behalf of"),
slug: z.string().optional().describe("Team slug to perform the request on behalf of")
},
async ({ id, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/webhooks/${id}`);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
});
const data = await handleResponse(response);
return {
content: [
{ type: "text", text: `Webhook details:\n${JSON.stringify(data, null, 2)}` },
],
};
}
);
}