mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
446 lines
14 KiB
TypeScript
446 lines
14 KiB
TypeScript
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 });
|
|
},
|
|
);
|
|
}
|
|
}
|