import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import { ApiClient } from "../api-client.js"; import { API_PATHS } from "../constants.js"; import { formatCurrency } from "../formatting.js"; import type { ProjectMember, PaginatedResponse } from "../types.js"; export function registerProjectMemberTools(server: McpServer, api: ApiClient, orgId: string) { server.registerTool( "solidtime_list_project_members", { title: "List Project Members", description: "List all members assigned to a specific project.", inputSchema: { project_id: z.string().uuid().describe("Project UUID"), }, annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (params) => { const result = await api.get>( API_PATHS.projectMembers(orgId, params.project_id) ); const members = result.data ?? []; if (members.length === 0) { return { content: [{ type: "text", text: "No project members found." }] }; } const lines = members.map((m) => formatProjectMember(m)); lines.unshift(`Found ${members.length} project members:\n`); return { content: [{ type: "text", text: lines.join("\n\n") }] }; } ); server.registerTool( "solidtime_add_project_member", { title: "Add Project Member", description: "Add a member to a project with an optional custom billable rate.", inputSchema: { project_id: z.string().uuid().describe("Project UUID"), member_id: z.string().uuid().describe("Member UUID to add"), billable_rate: z .number() .int() .min(0) .optional() .describe("Custom billable rate in cents for this member on this project"), }, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true, }, }, async (params) => { const body: Record = { member_id: params.member_id, }; if (params.billable_rate !== undefined) body.billable_rate = params.billable_rate; const result = await api.post<{ data: ProjectMember }>( API_PATHS.projectMembers(orgId, params.project_id), body ); return { content: [ { type: "text", text: `Project member added.\n\n${formatProjectMember(result.data)}` }, ], }; } ); server.registerTool( "solidtime_update_project_member", { title: "Update Project Member", description: "Update a project member's billable rate.", inputSchema: { id: z.string().uuid().describe("Project member UUID to update"), billable_rate: z .number() .int() .min(0) .describe("New billable rate in cents for this project member"), }, annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, }, async (params) => { const result = await api.put<{ data: ProjectMember }>( API_PATHS.projectMember(orgId, params.id), { billable_rate: params.billable_rate } ); return { content: [ { type: "text", text: `Project member updated.\n\n${formatProjectMember(result.data)}`, }, ], }; } ); server.registerTool( "solidtime_remove_project_member", { title: "Remove Project Member", description: "Remove a member from a project.", inputSchema: { id: z.string().uuid().describe("Project member UUID to remove"), }, annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true, }, }, async (params) => { await api.delete(API_PATHS.projectMember(orgId, params.id)); return { content: [{ type: "text", text: `Project member ${params.id} removed.` }] }; } ); } function formatProjectMember(pm: ProjectMember): string { const parts = [ `ID: ${pm.id}`, `Member ID: ${pm.member_id}`, `Project ID: ${pm.project_id}`, ]; if (pm.billable_rate !== null) { parts.push(`Billable Rate: ${formatCurrency(pm.billable_rate)}/h`); } return parts.join("\n"); }