import { ADDON_IDS } from '../../addons'; import { isAddonEnabled } from '../../services/adminService'; import { isDemoUser, getCurrentUser } from '../../services/authService'; import { getOwnPlan, getActivePlan, getActivePlanId, getPlanData, updatePlan, setUserColor, sendInvite as sendVacayInvite, acceptInvite, declineInvite, cancelInvite, dissolvePlan, getAvailableUsers, listYears, addYear, deleteYear, getEntries as getVacayEntries, toggleEntry, toggleCompanyHoliday, getStats as getVacayStats, updateStats as updateVacayStats, addHolidayCalendar, updateHolidayCalendar, deleteHolidayCalendar, getCountries as getHolidayCountries, getHolidays, } from '../../services/vacayService'; import { canRead, canWrite } from '../scopes'; import { TOOL_ANNOTATIONS_READONLY, TOOL_ANNOTATIONS_WRITE, TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT, demoDenied, ok, } from './_shared'; import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { z } from 'zod'; export function registerVacayTools(server: McpServer, userId: number, scopes: string[] | null): void { const R = canRead(scopes, 'vacay'); const W = canWrite(scopes, 'vacay'); if (isAddonEnabled(ADDON_IDS.VACAY)) { if (R) server.registerTool( 'get_vacay_plan', { description: "Get the current user's active vacation plan (own or joined).", inputSchema: {}, annotations: TOOL_ANNOTATIONS_READONLY, }, async () => { const plan = getPlanData(userId); return ok({ plan }); }, ); if (W) server.registerTool( 'update_vacay_plan', { description: 'Update vacation plan settings (weekends blocking, holidays, carry-over).', inputSchema: { block_weekends: z.boolean().optional(), holidays_enabled: z.boolean().optional(), holidays_region: z.string().nullable().optional(), company_holidays_enabled: z.boolean().optional(), carry_over_enabled: z.boolean().optional(), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ block_weekends, holidays_enabled, holidays_region, company_holidays_enabled, carry_over_enabled }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); await updatePlan( planId, { block_weekends, holidays_enabled, holidays_region, company_holidays_enabled, carry_over_enabled }, undefined, ); return ok({ success: true }); }, ); if (W) server.registerTool( 'set_vacay_color', { description: "Set the current user's color in the vacation plan calendar.", inputSchema: { color: z.string().describe('Hex color e.g. #6366f1'), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ color }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); setUserColor(userId, planId, color, undefined); return ok({ success: true }); }, ); if (R) server.registerTool( 'get_available_vacay_users', { description: 'List users who can be invited to the current vacation plan.', inputSchema: {}, annotations: TOOL_ANNOTATIONS_READONLY, }, async () => { const planId = getActivePlanId(userId); const users = getAvailableUsers(userId, planId); return ok({ users }); }, ); if (W) server.registerTool( 'send_vacay_invite', { description: 'Invite a user to join the vacation plan by their user ID.', inputSchema: { targetUserId: z.number().int().positive(), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ targetUserId }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const me = getCurrentUser(userId); if (!me) return { content: [{ type: 'text' as const, text: 'User not found.' }], isError: true }; const result = sendVacayInvite(planId, userId, me.username, me.email, targetUserId); if (result.error) return { content: [{ type: 'text' as const, text: result.error }], isError: true }; return ok({ success: true }); }, ); if (W) server.registerTool( 'accept_vacay_invite', { description: "Accept a pending invitation to join another user's vacation plan.", inputSchema: { planId: z.number().int().positive(), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ planId }) => { if (isDemoUser(userId)) return demoDenied(); const result = acceptInvite(userId, planId, undefined); if (result.error) return { content: [{ type: 'text' as const, text: result.error }], isError: true }; return ok({ success: true }); }, ); if (W) server.registerTool( 'decline_vacay_invite', { description: 'Decline a pending vacation plan invitation.', inputSchema: { planId: z.number().int().positive(), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ planId }) => { declineInvite(userId, planId, undefined); return ok({ success: true }); }, ); if (W) server.registerTool( 'cancel_vacay_invite', { description: 'Cancel an outgoing invitation (owner cancels invite they sent).', inputSchema: { targetUserId: z.number().int().positive(), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ targetUserId }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); cancelInvite(planId, targetUserId); return ok({ success: true }); }, ); if (W) server.registerTool( 'dissolve_vacay_plan', { description: 'Dissolve the shared plan — all members are removed and everyone returns to their own individual plan.', inputSchema: {}, annotations: TOOL_ANNOTATIONS_DELETE, }, async () => { if (isDemoUser(userId)) return demoDenied(); dissolvePlan(userId, undefined); return ok({ success: true }); }, ); if (R) server.registerTool( 'list_vacay_years', { description: 'List calendar years tracked in the current vacation plan.', inputSchema: {}, annotations: TOOL_ANNOTATIONS_READONLY, }, async () => { const planId = getActivePlanId(userId); const years = listYears(planId); return ok({ years }); }, ); if (W) server.registerTool( 'add_vacay_year', { description: 'Add a calendar year to the vacation plan.', inputSchema: { year: z.number().int().min(2000).max(2100), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ year }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const years = addYear(planId, year, undefined); return ok({ years }); }, ); if (W) server.registerTool( 'delete_vacay_year', { description: 'Remove a calendar year from the vacation plan.', inputSchema: { year: z.number().int(), }, annotations: TOOL_ANNOTATIONS_DELETE, }, async ({ year }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const years = deleteYear(planId, year, undefined); return ok({ years }); }, ); if (R) server.registerTool( 'get_vacay_entries', { description: 'Get all vacation day entries for a plan and year.', inputSchema: { year: z.number().int(), }, annotations: TOOL_ANNOTATIONS_READONLY, }, async ({ year }) => { const planId = getActivePlanId(userId); const entries = getVacayEntries(planId, String(year)); return ok({ entries }); }, ); if (W) server.registerTool( 'toggle_vacay_entry', { description: 'Toggle a day on or off as a vacation day for the current user.', inputSchema: { date: z.string().describe('ISO date YYYY-MM-DD'), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ date }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const result = toggleEntry(userId, planId, date, undefined); return ok(result); }, ); if (W) server.registerTool( 'toggle_company_holiday', { description: 'Toggle a date as a company holiday for the whole plan.', inputSchema: { date: z.string(), note: z.string().optional(), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ date, note }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const result = toggleCompanyHoliday(planId, date, note, undefined); return ok(result); }, ); if (R) server.registerTool( 'get_vacay_stats', { description: 'Get vacation statistics for a specific year (days used, remaining, carried over).', inputSchema: { year: z.number().int(), }, annotations: TOOL_ANNOTATIONS_READONLY, }, async ({ year }) => { const planId = getActivePlanId(userId); const stats = getVacayStats(planId, year); return ok({ stats }); }, ); if (W) server.registerTool( 'update_vacay_stats', { description: 'Update the vacation day allowance for a specific user and year.', inputSchema: { year: z.number().int(), vacationDays: z.number().int().min(0), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ year, vacationDays }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); updateVacayStats(userId, planId, year, vacationDays, undefined); return ok({ success: true }); }, ); if (W) server.registerTool( 'add_holiday_calendar', { description: 'Add a public holiday calendar (by region code) to the vacation plan.', inputSchema: { region: z.string().describe('Country/region code e.g. US, GB, DE'), label: z.string().nullable().optional(), color: z.string().optional(), sortOrder: z.number().int().optional(), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ region, label, color, sortOrder }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const calendar = addHolidayCalendar(planId, region, label ?? null, color, sortOrder, undefined); return ok({ calendar }); }, ); if (W) server.registerTool( 'update_holiday_calendar', { description: 'Update label or color for a holiday calendar.', inputSchema: { calendarId: z.number().int().positive(), label: z.string().nullable().optional(), color: z.string().optional(), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ calendarId, label, color }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); const cal = updateHolidayCalendar(calendarId, planId, { label, color }, undefined); if (!cal) return { content: [{ type: 'text' as const, text: 'Holiday calendar not found.' }], isError: true }; return ok({ calendar: cal }); }, ); if (W) server.registerTool( 'delete_holiday_calendar', { description: 'Remove a holiday calendar from the vacation plan.', inputSchema: { calendarId: z.number().int().positive(), }, annotations: TOOL_ANNOTATIONS_DELETE, }, async ({ calendarId }) => { if (isDemoUser(userId)) return demoDenied(); const planId = getActivePlanId(userId); deleteHolidayCalendar(calendarId, planId, undefined); return ok({ success: true }); }, ); if (R) server.registerTool( 'list_holiday_countries', { description: 'List countries available for public holiday calendars.', inputSchema: {}, annotations: TOOL_ANNOTATIONS_READONLY, }, async () => { const result = await getHolidayCountries(); if (result.error) return { content: [{ type: 'text' as const, text: result.error }], isError: true }; return ok({ countries: result.data }); }, ); if (R) server.registerTool( 'list_holidays', { description: 'List public holidays for a country and year.', inputSchema: { country: z.string().describe('ISO 3166-1 alpha-2 code'), year: z.number().int(), }, annotations: TOOL_ANNOTATIONS_READONLY, }, async ({ country, year }) => { const result = await getHolidays(String(year), country); if (result.error) return { content: [{ type: 'text' as const, text: result.error }], isError: true }; return ok({ holidays: result.data }); }, ); } }