Vercel MCP Server
by Quegenx
- vercel-mcp-server
- src
- components
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { z } from "zod";
import { handleResponse, BASE_URL, DEFAULT_ACCESS_TOKEN } from "../index.js";
// Common parameter schemas
const teamParams = {
teamId: z.string().optional().describe("The Team identifier to perform the request on behalf of"),
slug: z.string().optional().describe("The Team slug to perform the request on behalf of")
};
const projectRoleEnum = z.enum(["ADMIN", "PROJECT_VIEWER", "PROJECT_DEVELOPER"]);
export function registerAccessGroupTools(server: McpServer) {
// Create access group project
server.tool(
"create_access_group_project",
"Create an access group project",
{
accessGroupIdOrName: z.string().describe("The access group ID or name"),
projectId: z.string().max(256).describe("The ID of the project"),
role: projectRoleEnum.describe("The project role that will be added to this Access Group"),
...teamParams
},
async ({ accessGroupIdOrName, projectId, role, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${accessGroupIdOrName}/projects`);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
body: JSON.stringify({ projectId, role }),
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access group project created:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Create access group
server.tool(
"create_access_group",
"Create a new access group",
{
name: z.string().max(50).regex(/^[A-z0-9_ -]+$/).describe("The name of the access group"),
projects: z.array(z.object({
projectId: z.string(),
role: projectRoleEnum
})).optional().describe("List of projects to add to the access group"),
membersToAdd: z.array(z.string()).optional().describe("List of members to add to the access group"),
...teamParams
},
async ({ name, projects, membersToAdd, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups`);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
body: JSON.stringify({
name,
...(projects && { projects }),
...(membersToAdd && { membersToAdd })
}),
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access group created:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Delete access group project
server.tool(
"delete_access_group_project",
"Delete an access group project",
{
accessGroupIdOrName: z.string().describe("The access group ID or name"),
projectId: z.string().describe("The project ID"),
...teamParams
},
async ({ accessGroupIdOrName, projectId, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${accessGroupIdOrName}/projects/${projectId}`);
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}`,
},
});
await handleResponse(response);
return {
content: [{ type: "text", text: "Access group project deleted successfully" }],
};
}
);
// Delete access group
server.tool(
"delete_access_group",
"Delete an access group",
{
idOrName: z.string().describe("The access group ID or name"),
...teamParams
},
async ({ idOrName, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${idOrName}`);
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}`,
},
});
await handleResponse(response);
return {
content: [{ type: "text", text: "Access group deleted successfully" }],
};
}
);
// List access groups
server.tool(
"list_access_groups",
"List access groups for a team, project or member",
{
projectId: z.string().optional().describe("Filter access groups by project"),
search: z.string().optional().describe("Search for access groups by name"),
membersLimit: z.number().min(1).max(100).optional().describe("Number of members to include in the response"),
projectsLimit: z.number().min(1).max(100).optional().describe("Number of projects to include in the response"),
limit: z.number().min(1).max(100).optional().describe("Limit how many access group should be returned"),
next: z.string().optional().describe("Continuation cursor to retrieve the next page of results"),
...teamParams
},
async ({ projectId, search, membersLimit, projectsLimit, limit, next, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups`);
const queryParams = new URLSearchParams();
if (projectId) queryParams.append("projectId", projectId);
if (search) queryParams.append("search", search);
if (membersLimit) queryParams.append("membersLimit", membersLimit.toString());
if (projectsLimit) queryParams.append("projectsLimit", projectsLimit.toString());
if (limit) queryParams.append("limit", limit.toString());
if (next) queryParams.append("next", next);
if (teamId) queryParams.append("teamId", teamId);
if (slug) queryParams.append("slug", slug);
const response = await fetch(`${url.toString()}?${queryParams.toString()}`, {
headers: {
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access groups:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// List access group members
server.tool(
"list_access_group_members",
"List members of an access group",
{
idOrName: z.string().describe("The ID or name of the Access Group"),
limit: z.number().min(1).max(100).optional().describe("Limit how many access group members should be returned"),
next: z.string().optional().describe("Continuation cursor to retrieve the next page of results"),
search: z.string().optional().describe("Search project members by their name, username, and email"),
...teamParams
},
async ({ idOrName, limit, next, search, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${idOrName}/members`);
const queryParams = new URLSearchParams();
if (limit) queryParams.append("limit", limit.toString());
if (next) queryParams.append("next", next);
if (search) queryParams.append("search", search);
if (teamId) queryParams.append("teamId", teamId);
if (slug) queryParams.append("slug", slug);
const response = await fetch(`${url.toString()}?${queryParams.toString()}`, {
headers: {
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access group members:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// List access group projects
server.tool(
"list_access_group_projects",
"List projects of an access group",
{
idOrName: z.string().describe("The ID or name of the Access Group"),
limit: z.number().min(1).max(100).optional().describe("Limit how many access group projects should be returned"),
next: z.string().optional().describe("Continuation cursor to retrieve the next page of results"),
...teamParams
},
async ({ idOrName, limit, next, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${idOrName}/projects`);
const queryParams = new URLSearchParams();
if (limit) queryParams.append("limit", limit.toString());
if (next) queryParams.append("next", next);
if (teamId) queryParams.append("teamId", teamId);
if (slug) queryParams.append("slug", slug);
const response = await fetch(`${url.toString()}?${queryParams.toString()}`, {
headers: {
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access group projects:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Get access group
server.tool(
"get_access_group",
"Read an access group",
{
idOrName: z.string().describe("The access group ID or name"),
...teamParams
},
async ({ idOrName, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${idOrName}`);
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: `Access group details:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Get access group project
server.tool(
"get_access_group_project",
"Read an access group project",
{
accessGroupIdOrName: z.string().describe("The access group ID or name"),
projectId: z.string().describe("The project ID"),
...teamParams
},
async ({ accessGroupIdOrName, projectId, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${accessGroupIdOrName}/projects/${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: `Access group project details:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Update access group
server.tool(
"update_access_group",
"Update an access group",
{
idOrName: z.string().describe("The access group ID or name"),
name: z.string().max(50).regex(/^[A-z0-9_ -]+$/).optional().describe("The name of the access group"),
projects: z.array(z.object({
projectId: z.string(),
role: projectRoleEnum
})).optional().describe("List of projects to update"),
membersToAdd: z.array(z.string()).optional().describe("List of members to add to the access group"),
membersToRemove: z.array(z.string()).optional().describe("List of members to remove from the access group"),
...teamParams
},
async ({ idOrName, name, projects, membersToAdd, membersToRemove, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${idOrName}`);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
body: JSON.stringify({
...(name && { name }),
...(projects && { projects }),
...(membersToAdd && { membersToAdd }),
...(membersToRemove && { membersToRemove })
}),
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access group updated:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Update access group project
server.tool(
"update_access_group_project",
"Update an access group project",
{
accessGroupIdOrName: z.string().describe("The access group ID or name"),
projectId: z.string().describe("The project ID"),
role: projectRoleEnum.describe("The project role that will be added to this Access Group"),
...teamParams
},
async ({ accessGroupIdOrName, projectId, role, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/access-groups/${accessGroupIdOrName}/projects/${projectId}`);
if (teamId) url.searchParams.append("teamId", teamId);
if (slug) url.searchParams.append("slug", slug);
const response = await fetch(url.toString(), {
method: "PATCH",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${DEFAULT_ACCESS_TOKEN}`,
},
body: JSON.stringify({ role }),
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Access group project updated:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
}