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")
};
export function registerProjectMemberTools(server: McpServer) {
// Add Project Member
server.tool(
"add_project_member",
"Adds a new member to a project",
{
idOrName: z.string().describe("The ID or name of the Project"),
uid: z.string().max(256).optional().describe("The ID of the team member that should be added to this project"),
username: z.string().max(256).optional().describe("The username of the team member that should be added to this project"),
email: z.string().email().optional().describe("The email of the team member that should be added to this project"),
role: z.enum(["ADMIN", "PROJECT_DEVELOPER", "PROJECT_VIEWER"]).describe("The project role of the member that will be added"),
...teamParams
},
async ({ idOrName, uid, username, email, role, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/projects/${idOrName}/members`);
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({
...(uid && { uid }),
...(username && { username }),
...(email && { email }),
role,
}),
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Project member added:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// List Project Members
server.tool(
"list_project_members",
"Lists all members of a project",
{
idOrName: z.string().describe("The ID or name of the Project"),
limit: z.number().min(1).max(100).optional().describe("Limit how many project members should be returned"),
since: z.number().optional().describe("Timestamp in milliseconds to only include members added since then"),
until: z.number().optional().describe("Timestamp in milliseconds to only include members added until then"),
search: z.string().optional().describe("Search project members by their name, username, and email"),
...teamParams
},
async ({ idOrName, limit, since, until, search, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/projects/${idOrName}/members`);
const queryParams = new URLSearchParams();
if (limit) queryParams.append("limit", limit.toString());
if (since) queryParams.append("since", since.toString());
if (until) queryParams.append("until", until.toString());
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: `Project members:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
// Remove Project Member
server.tool(
"remove_project_member",
"Remove a member from a specific project",
{
idOrName: z.string().describe("The ID or name of the Project"),
uid: z.string().describe("The user ID of the member"),
...teamParams
},
async ({ idOrName, uid, teamId, slug }) => {
const url = new URL(`${BASE_URL}/v1/projects/${idOrName}/members/${uid}`);
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}`,
},
});
const data = await handleResponse(response);
return {
content: [{ type: "text", text: `Project member removed:\n${JSON.stringify(data, null, 2)}` }],
};
}
);
}