mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 14:21:46 +00:00
feat(mcp): introduce OAuth 2.1 auth and enforce addon gating
OAuth 2.1 authentication for MCP:
- Add OAuth 2.1 authorization server with PKCE support (routes/oauth.ts)
- Add OAuth service for client CRUD, auth-code flow, and token management (services/oauthService.ts)
- Add typed scope definitions and enforcement helpers (mcp/scopes.ts)
- Add OAuth consent UI page (OAuthAuthorizePage.tsx)
- Add client-side scope labels and descriptions (api/oauthScopes.ts)
- Integrate OAuth token auth into MCP handler alongside existing static tokens
- All OAuth endpoints gated on `mcp` addon
Addon gating across MCP tools, resources, and prompts:
- Add typed ADDON_IDS constant (server/src/addons.ts) replacing all string literals
- Gate budget tools and resources (trip-budget, per-person, settlement) on `budget` addon
- Gate packing tools and resources (trip-packing, trip-packing-bags, trip-todos) on `packing` addon
- Gate todos tools on `packing` addon (mirrors web UI Lists tab behavior)
- Expand atlas gate to cover full tool body (bucket-list + country tools no longer leak)
- Expand collab gate to cover full tool body (collab notes no longer leak)
- Gate packing-list and budget-overview MCP prompts on their respective addons
- Gate get_trip_summary sections per addon; blank packing/budget/collab_notes/todos when disabled
- Remove trip-files resource and files field from get_trip_summary
- Replace all isAddonEnabled('literal') calls with ADDON_IDS constants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+38
-12
@@ -4,7 +4,9 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
|
||||
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp';
|
||||
import { User } from '../types';
|
||||
import { verifyMcpToken, verifyJwtToken } from '../services/authService';
|
||||
import { getUserByAccessToken } from '../services/oauthService';
|
||||
import { isAddonEnabled } from '../services/adminService';
|
||||
import { ADDON_IDS } from '../addons';
|
||||
import { registerResources } from './resources';
|
||||
import { registerTools } from './tools';
|
||||
|
||||
@@ -12,6 +14,10 @@ interface McpSession {
|
||||
server: McpServer;
|
||||
transport: StreamableHTTPServerTransport;
|
||||
userId: number;
|
||||
/** null = static trek_ token or JWT (full access); string[] = OAuth 2.1 scopes */
|
||||
scopes: string[] | null;
|
||||
/** true when authenticated via static trek_ token — triggers deprecation prompt */
|
||||
isStaticToken: boolean;
|
||||
lastActivity: number;
|
||||
}
|
||||
|
||||
@@ -73,30 +79,49 @@ const sessionSweepInterval = setInterval(() => {
|
||||
// Prevent the interval from keeping the process alive if nothing else is running
|
||||
sessionSweepInterval.unref();
|
||||
|
||||
function verifyToken(authHeader: string | undefined): User | null {
|
||||
interface VerifyTokenResult {
|
||||
user: User;
|
||||
/** null = full access (static token or JWT); string[] = OAuth 2.1 scoped access */
|
||||
scopes: string[] | null;
|
||||
isStaticToken: boolean;
|
||||
}
|
||||
|
||||
function verifyToken(authHeader: string | undefined): VerifyTokenResult | null {
|
||||
const token = authHeader && authHeader.split(' ')[1];
|
||||
if (!token) return null;
|
||||
|
||||
// Long-lived MCP API token (trek_...)
|
||||
if (token.startsWith('trek_')) {
|
||||
return verifyMcpToken(token);
|
||||
// OAuth 2.1 access token (trekoa_...)
|
||||
if (token.startsWith('trekoa_')) {
|
||||
const result = getUserByAccessToken(token);
|
||||
if (!result) return null;
|
||||
return { user: result.user, scopes: result.scopes, isStaticToken: false };
|
||||
}
|
||||
|
||||
// Short-lived JWT
|
||||
return verifyJwtToken(token);
|
||||
// Long-lived static MCP token (trek_...) — full access + deprecation notice
|
||||
if (token.startsWith('trek_')) {
|
||||
const user = verifyMcpToken(token);
|
||||
if (!user) return null;
|
||||
return { user, scopes: null, isStaticToken: true };
|
||||
}
|
||||
|
||||
// Short-lived JWT (TREK web session used directly) — full access, no notice
|
||||
const user = verifyJwtToken(token);
|
||||
if (!user) return null;
|
||||
return { user, scopes: null, isStaticToken: false };
|
||||
}
|
||||
|
||||
export async function mcpHandler(req: Request, res: Response): Promise<void> {
|
||||
if (!isAddonEnabled('mcp')) {
|
||||
if (!isAddonEnabled(ADDON_IDS.MCP)) {
|
||||
res.status(403).json({ error: 'MCP is not enabled' });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = verifyToken(req.headers['authorization']);
|
||||
if (!user) {
|
||||
const tokenResult = verifyToken(req.headers['authorization']);
|
||||
if (!tokenResult) {
|
||||
res.status(401).json({ error: 'Access token required' });
|
||||
return;
|
||||
}
|
||||
const { user, scopes, isStaticToken } = tokenResult;
|
||||
|
||||
if (isRateLimited(user.id)) {
|
||||
res.status(429).json({ error: 'Too many requests. Please slow down.' });
|
||||
@@ -150,13 +175,14 @@ export async function mcpHandler(req: Request, res: Response): Promise<void> {
|
||||
},
|
||||
});
|
||||
registerResources(server, user.id);
|
||||
registerTools(server, user.id);
|
||||
registerTools(server, user.id, scopes, isStaticToken);
|
||||
|
||||
const transport = new StreamableHTTPServerTransport({
|
||||
sessionIdGenerator: () => randomUUID(),
|
||||
onsessioninitialized: (sid) => {
|
||||
sessions.set(sid, { server, transport, userId: user.id, lastActivity: Date.now() });
|
||||
console.log(`[MCP] Session ${sid} created for user ${user.id}. Active sessions: ${sessions.size}`);
|
||||
sessions.set(sid, { server, transport, userId: user.id, scopes, isStaticToken, lastActivity: Date.now() });
|
||||
const authMethod = isStaticToken ? 'static-token' : scopes ? `oauth(${scopes.join(',')})` : 'jwt';
|
||||
console.log(`[MCP] Session ${sid} created for user ${user.id} [${authMethod}]. Active sessions: ${sessions.size}`);
|
||||
},
|
||||
onsessionclosed: (sid) => {
|
||||
sessions.delete(sid);
|
||||
|
||||
+13
-26
@@ -9,12 +9,12 @@ import { listReservations } from '../services/reservationService';
|
||||
import { listNotes as listDayNotes } from '../services/dayNoteService';
|
||||
import { listNotes as listCollabNotes, listPolls, listMessages } from '../services/collabService';
|
||||
import { listItems as listTodoItems } from '../services/todoService';
|
||||
import { listFiles } from '../services/fileService';
|
||||
import { listCategories } from '../services/categoryService';
|
||||
import { listBucketList, listVisitedCountries, getStats as getAtlasStats, listManuallyVisitedRegions } from '../services/atlasService';
|
||||
import { getNotifications } from '../services/inAppNotifications';
|
||||
import { getActivePlanId, getActivePlan, getPlanData, getEntries as getVacayEntries, getHolidays } from '../services/vacayService';
|
||||
import { isAddonEnabled } from '../services/adminService';
|
||||
import { ADDON_IDS } from '../addons';
|
||||
|
||||
function parseId(value: string | string[]): number | null {
|
||||
const n = Number(Array.isArray(value) ? value[0] : value);
|
||||
@@ -95,7 +95,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Budget items
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.BUDGET)) server.registerResource(
|
||||
'trip-budget',
|
||||
new ResourceTemplate('trek://trips/{tripId}/budget', { list: undefined }),
|
||||
{ description: 'Budget and expense items for a trip', mimeType: 'application/json' },
|
||||
@@ -108,7 +108,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Packing checklist
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.PACKING)) server.registerResource(
|
||||
'trip-packing',
|
||||
new ResourceTemplate('trek://trips/{tripId}/packing', { list: undefined }),
|
||||
{ description: 'Packing checklist for a trip', mimeType: 'application/json' },
|
||||
@@ -176,7 +176,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Collab notes for a trip
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.COLLAB)) server.registerResource(
|
||||
'trip-collab-notes',
|
||||
new ResourceTemplate('trek://trips/{tripId}/collab-notes', { list: undefined }),
|
||||
{ description: 'Shared collaborative notes for a trip', mimeType: 'application/json' },
|
||||
@@ -188,21 +188,8 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
// Trip files (active, not trash)
|
||||
server.registerResource(
|
||||
'trip-files',
|
||||
new ResourceTemplate('trek://trips/{tripId}/files', { list: undefined }),
|
||||
{ description: 'Active files attached to a trip (excludes trash)', mimeType: 'application/json' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const files = listFiles(id, false);
|
||||
return jsonContent(uri.href, files);
|
||||
}
|
||||
);
|
||||
|
||||
// Trip to-do list
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.PACKING)) server.registerResource(
|
||||
'trip-todos',
|
||||
new ResourceTemplate('trek://trips/{tripId}/todos', { list: undefined }),
|
||||
{ description: 'To-do items for a trip, ordered by position', mimeType: 'application/json' },
|
||||
@@ -226,7 +213,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// User's bucket list
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.ATLAS)) server.registerResource(
|
||||
'bucket-list',
|
||||
'trek://bucket-list',
|
||||
{ description: 'Your personal travel bucket list', mimeType: 'application/json' },
|
||||
@@ -237,7 +224,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// User's visited countries
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.ATLAS)) server.registerResource(
|
||||
'visited-countries',
|
||||
'trek://visited-countries',
|
||||
{ description: 'Countries you have marked as visited in Atlas', mimeType: 'application/json' },
|
||||
@@ -248,7 +235,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Budget per-person summary
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.BUDGET)) server.registerResource(
|
||||
'trip-budget-per-person',
|
||||
new ResourceTemplate('trek://trips/{tripId}/budget/per-person', { list: undefined }),
|
||||
{ description: 'Per-person budget summary for a trip (total spent per member, split breakdown)', mimeType: 'application/json' },
|
||||
@@ -261,7 +248,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Budget settlement
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.BUDGET)) server.registerResource(
|
||||
'trip-budget-settlement',
|
||||
new ResourceTemplate('trek://trips/{tripId}/budget/settlement', { list: undefined }),
|
||||
{ description: 'Suggested settlement transactions to balance who owes whom', mimeType: 'application/json' },
|
||||
@@ -274,7 +261,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Packing bags
|
||||
server.registerResource(
|
||||
if (isAddonEnabled(ADDON_IDS.PACKING)) server.registerResource(
|
||||
'trip-packing-bags',
|
||||
new ResourceTemplate('trek://trips/{tripId}/packing/bags', { list: undefined }),
|
||||
{ description: 'All packing bags for a trip with their members', mimeType: 'application/json' },
|
||||
@@ -298,7 +285,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
);
|
||||
|
||||
// Atlas stats and regions (addon-gated)
|
||||
if (isAddonEnabled('atlas')) {
|
||||
if (isAddonEnabled(ADDON_IDS.ATLAS)) {
|
||||
server.registerResource(
|
||||
'atlas-stats',
|
||||
'trek://atlas/stats',
|
||||
@@ -321,7 +308,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
}
|
||||
|
||||
// Collab polls & messages (addon-gated)
|
||||
if (isAddonEnabled('collab')) {
|
||||
if (isAddonEnabled(ADDON_IDS.COLLAB)) {
|
||||
server.registerResource(
|
||||
'trip-collab-polls',
|
||||
new ResourceTemplate('trek://trips/{tripId}/collab/polls', { list: undefined }),
|
||||
@@ -348,7 +335,7 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
}
|
||||
|
||||
// Vacay resources (addon-gated)
|
||||
if (isAddonEnabled('vacay')) {
|
||||
if (isAddonEnabled(ADDON_IDS.VACAY)) {
|
||||
server.registerResource(
|
||||
'vacay-plan',
|
||||
'trek://vacay/plan',
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
// ---------------------------------------------------------------------------
|
||||
// OAuth 2.1 scope definitions for TREK MCP
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export const SCOPES = {
|
||||
TRIPS_READ: 'trips:read',
|
||||
TRIPS_WRITE: 'trips:write',
|
||||
TRIPS_DELETE: 'trips:delete',
|
||||
PLACES_READ: 'places:read',
|
||||
PLACES_WRITE: 'places:write',
|
||||
PACKING_READ: 'packing:read',
|
||||
PACKING_WRITE: 'packing:write',
|
||||
BUDGET_READ: 'budget:read',
|
||||
BUDGET_WRITE: 'budget:write',
|
||||
RESERVATIONS_READ: 'reservations:read',
|
||||
RESERVATIONS_WRITE: 'reservations:write',
|
||||
COLLAB_READ: 'collab:read',
|
||||
COLLAB_WRITE: 'collab:write',
|
||||
NOTIFICATIONS_READ: 'notifications:read',
|
||||
NOTIFICATIONS_WRITE: 'notifications:write',
|
||||
VACAY_READ: 'vacay:read',
|
||||
VACAY_WRITE: 'vacay:write',
|
||||
MEDIA_READ: 'media:read',
|
||||
} as const;
|
||||
|
||||
export type Scope = typeof SCOPES[keyof typeof SCOPES];
|
||||
|
||||
export const ALL_SCOPES: Scope[] = Object.values(SCOPES) as Scope[];
|
||||
|
||||
export interface ScopeInfo {
|
||||
label: string;
|
||||
description: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
export const SCOPE_INFO: Record<Scope, ScopeInfo> = {
|
||||
'trips:read': { label: 'View trips & itineraries', description: 'Read trips, days, day notes, members, and share links', group: 'Trips' },
|
||||
'trips:write': { label: 'Edit trips & itineraries', description: 'Create and update trips, days, notes, and manage members', group: 'Trips' },
|
||||
'trips:delete': { label: 'Delete trips', description: 'Permanently delete entire trips — this action is irreversible', group: 'Trips' },
|
||||
'places:read': { label: 'View places & map data', description: 'Read places, day assignments, tags, categories, and visited countries', group: 'Places' },
|
||||
'places:write': { label: 'Manage places', description: 'Create, update, and delete places, assignments, tags, and atlas entries', group: 'Places' },
|
||||
'packing:read': { label: 'View packing lists', description: 'Read packing items, bags, and category assignees', group: 'Packing' },
|
||||
'packing:write': { label: 'Manage packing lists', description: 'Add, update, delete, toggle, and reorder packing items and bags', group: 'Packing' },
|
||||
'budget:read': { label: 'View budget', description: 'Read budget items and expense breakdown', group: 'Budget' },
|
||||
'budget:write': { label: 'Manage budget', description: 'Create, update, and delete budget items', group: 'Budget' },
|
||||
'reservations:read': { label: 'View reservations', description: 'Read reservations and accommodation details', group: 'Reservations' },
|
||||
'reservations:write': { label: 'Manage reservations', description: 'Create, update, delete, and reorder reservations', group: 'Reservations' },
|
||||
'collab:read': { label: 'View collaboration', description: 'Read collab notes, polls, messages, and to-do items', group: 'Collaboration' },
|
||||
'collab:write': { label: 'Manage collaboration', description: 'Create, update, and delete collab notes, todos, polls, and messages', group: 'Collaboration' },
|
||||
'notifications:read': { label: 'View notifications', description: 'Read in-app notifications and unread counts', group: 'Notifications' },
|
||||
'notifications:write': { label: 'Manage notifications', description: 'Mark notifications as read and respond to them', group: 'Notifications' },
|
||||
'vacay:read': { label: 'View vacation plans', description: 'Read vacation planning data, entries, and stats', group: 'Vacation' },
|
||||
'vacay:write': { label: 'Manage vacation plans', description: 'Create and manage vacation entries, holidays, and team plans', group: 'Vacation' },
|
||||
'media:read': { label: 'Maps & weather data', description: 'Search locations, resolve map URLs, and fetch weather forecasts', group: 'Media' },
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Scope enforcement helpers
|
||||
// null scopes = static trek_ token = full access
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** trips:read OR trips:write OR trips:delete all grant read access to trips */
|
||||
export function canReadTrips(scopes: string[] | null): boolean {
|
||||
if (!scopes) return true;
|
||||
return scopes.some(s => s === 'trips:read' || s === 'trips:write' || s === 'trips:delete');
|
||||
}
|
||||
|
||||
/** group:write grants write access; for trips canReadTrips handles read */
|
||||
export function canWrite(scopes: string[] | null, group: string): boolean {
|
||||
if (!scopes) return true;
|
||||
return scopes.includes(`${group}:write`);
|
||||
}
|
||||
|
||||
/** group:read OR group:write grant read access */
|
||||
export function canRead(scopes: string[] | null, group: string): boolean {
|
||||
if (!scopes) return true;
|
||||
return scopes.some(s => s === `${group}:read` || s === `${group}:write`);
|
||||
}
|
||||
|
||||
/** trips:delete is a separate scope from trips:write */
|
||||
export function canDeleteTrips(scopes: string[] | null): boolean {
|
||||
if (!scopes) return true;
|
||||
return scopes.includes('trips:delete');
|
||||
}
|
||||
|
||||
export function validateScopes(requestedScopes: string[]): { valid: boolean; invalid: string[] } {
|
||||
const invalid = requestedScopes.filter(s => !ALL_SCOPES.includes(s as Scope));
|
||||
return { valid: invalid.length === 0, invalid };
|
||||
}
|
||||
+16
-16
@@ -15,34 +15,34 @@ import { registerTripTools } from './tools/trips';
|
||||
import { registerVacayTools } from './tools/vacay';
|
||||
import { registerMcpPrompts } from './tools/prompts';
|
||||
|
||||
export function registerTools(server: McpServer, userId: number): void {
|
||||
registerTripTools(server, userId);
|
||||
export function registerTools(server: McpServer, userId: number, scopes: string[] | null, isStaticToken = false): void {
|
||||
registerTripTools(server, userId, scopes);
|
||||
|
||||
registerPlaceTools(server, userId);
|
||||
registerPlaceTools(server, userId, scopes);
|
||||
|
||||
registerBudgetTools(server, userId);
|
||||
registerBudgetTools(server, userId, scopes);
|
||||
|
||||
registerPackingTools(server, userId);
|
||||
registerPackingTools(server, userId, scopes);
|
||||
|
||||
registerReservationTools(server, userId);
|
||||
registerReservationTools(server, userId, scopes);
|
||||
|
||||
registerDayTools(server, userId);
|
||||
registerDayTools(server, userId, scopes);
|
||||
|
||||
registerAssignmentTools(server, userId);
|
||||
registerAssignmentTools(server, userId, scopes);
|
||||
|
||||
registerTagTools(server, userId);
|
||||
registerTagTools(server, userId, scopes);
|
||||
|
||||
registerMapsWeatherTools(server, userId);
|
||||
registerMapsWeatherTools(server, userId, scopes);
|
||||
|
||||
registerNotificationTools(server, userId);
|
||||
registerNotificationTools(server, userId, scopes);
|
||||
|
||||
registerAtlasTools(server, userId);
|
||||
registerAtlasTools(server, userId, scopes);
|
||||
|
||||
registerCollabTools(server, userId);
|
||||
registerCollabTools(server, userId, scopes);
|
||||
|
||||
registerVacayTools(server, userId);
|
||||
registerVacayTools(server, userId, scopes);
|
||||
|
||||
registerTodoTools(server, userId);
|
||||
registerTodoTools(server, userId, scopes);
|
||||
|
||||
registerMcpPrompts(server, userId);
|
||||
registerMcpPrompts(server, userId, isStaticToken);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,15 @@ import {
|
||||
TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerAssignmentTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'places');
|
||||
const W = canWrite(scopes, 'places');
|
||||
|
||||
export function registerAssignmentTools(server: McpServer, userId: number): void {
|
||||
// --- ASSIGNMENTS ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'assign_place_to_day',
|
||||
{
|
||||
description: 'Assign a place to a specific day in a trip.',
|
||||
@@ -42,7 +46,7 @@ export function registerAssignmentTools(server: McpServer, userId: number): void
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'unassign_place',
|
||||
{
|
||||
description: 'Remove a place assignment from a day.',
|
||||
@@ -64,7 +68,7 @@ export function registerAssignmentTools(server: McpServer, userId: number): void
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_assignment_time',
|
||||
{
|
||||
description: 'Set the start and/or end time for a place assignment on a day (e.g. "09:00", "11:30"). Pass null to clear a time.',
|
||||
@@ -91,7 +95,7 @@ export function registerAssignmentTools(server: McpServer, userId: number): void
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'move_assignment',
|
||||
{
|
||||
description: 'Move a place assignment to a different day.',
|
||||
@@ -113,7 +117,7 @@ export function registerAssignmentTools(server: McpServer, userId: number): void
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_assignment_participants',
|
||||
{
|
||||
description: 'Get the list of users participating in a specific place assignment.',
|
||||
@@ -130,7 +134,7 @@ export function registerAssignmentTools(server: McpServer, userId: number): void
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'set_assignment_participants',
|
||||
{
|
||||
description: 'Set the participants for a place assignment (replaces current list).',
|
||||
@@ -152,7 +156,7 @@ export function registerAssignmentTools(server: McpServer, userId: number): void
|
||||
|
||||
// --- REORDER ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'reorder_day_assignments',
|
||||
{
|
||||
description: 'Reorder places within a day by providing the assignment IDs in the desired order.',
|
||||
|
||||
@@ -7,16 +7,23 @@ import {
|
||||
markRegionVisited, unmarkRegionVisited, getCountryPlaces, updateBucketItem,
|
||||
} from '../../services/atlasService';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import {
|
||||
TOOL_ANNOTATIONS_WRITE, TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
TOOL_ANNOTATIONS_READONLY,
|
||||
demoDenied, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerAtlasTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'places');
|
||||
const W = canWrite(scopes, 'places');
|
||||
|
||||
if (!isAddonEnabled(ADDON_IDS.ATLAS)) return;
|
||||
|
||||
export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
// --- BUCKET LIST ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_bucket_list_item',
|
||||
{
|
||||
description: 'Add a destination to your personal travel bucket list.',
|
||||
@@ -36,7 +43,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_bucket_list_item',
|
||||
{
|
||||
description: 'Remove an item from your travel bucket list.',
|
||||
@@ -55,7 +62,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- ATLAS ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'mark_country_visited',
|
||||
{
|
||||
description: 'Mark a country as visited in your Atlas.',
|
||||
@@ -71,7 +78,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'unmark_country_visited',
|
||||
{
|
||||
description: 'Remove a country from your visited countries in Atlas.',
|
||||
@@ -89,8 +96,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- ATLAS EXPANDED ---
|
||||
|
||||
if (isAddonEnabled('atlas')) {
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_atlas_stats',
|
||||
{
|
||||
description: 'Get atlas statistics — total visited countries, region counts, continent breakdown.',
|
||||
@@ -103,7 +109,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_visited_regions',
|
||||
{
|
||||
description: 'List all manually visited sub-country regions for the current user.',
|
||||
@@ -116,7 +122,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'mark_region_visited',
|
||||
{
|
||||
description: 'Mark a sub-country region as visited.',
|
||||
@@ -135,7 +141,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'unmark_region_visited',
|
||||
{
|
||||
description: 'Remove a region from the visited list.',
|
||||
@@ -151,7 +157,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_country_atlas_places',
|
||||
{
|
||||
description: 'Get places saved in the user\'s atlas for a specific country.',
|
||||
@@ -166,7 +172,7 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_bucket_list_item',
|
||||
{
|
||||
description: 'Update a bucket list item (notes, name, target date, location).',
|
||||
@@ -188,5 +194,4 @@ export function registerAtlasTools(server: McpServer, userId: number): void {
|
||||
return ok({ item });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,11 +12,17 @@ import {
|
||||
TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canWrite } from '../scopes';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
|
||||
export function registerBudgetTools(server: McpServer, userId: number): void {
|
||||
export function registerBudgetTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const W = canWrite(scopes, 'budget');
|
||||
|
||||
if (isAddonEnabled(ADDON_IDS.BUDGET)) {
|
||||
// --- BUDGET ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_budget_item',
|
||||
{
|
||||
description: 'Add a budget/expense item to a trip.',
|
||||
@@ -38,7 +44,7 @@ export function registerBudgetTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_budget_item',
|
||||
{
|
||||
description: 'Delete a budget item from a trip.',
|
||||
@@ -60,7 +66,7 @@ export function registerBudgetTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- BUDGET (update) ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_budget_item',
|
||||
{
|
||||
description: 'Update an existing budget/expense item in a trip.',
|
||||
@@ -88,7 +94,7 @@ export function registerBudgetTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- BUDGET ADVANCED ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'set_budget_item_members',
|
||||
{
|
||||
description: 'Set which trip members are splitting a budget item (replaces current member list).',
|
||||
@@ -108,7 +114,7 @@ export function registerBudgetTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'toggle_budget_member_paid',
|
||||
{
|
||||
description: 'Mark or unmark a member as having paid their share of a budget item.',
|
||||
@@ -128,4 +134,5 @@ export function registerBudgetTools(server: McpServer, userId: number): void {
|
||||
return ok({ member });
|
||||
}
|
||||
);
|
||||
} // isAddonEnabled(BUDGET)
|
||||
}
|
||||
|
||||
@@ -8,16 +8,23 @@ import {
|
||||
listMessages, createMessage, deleteMessage, addOrRemoveReaction,
|
||||
} from '../../services/collabService';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import {
|
||||
safeBroadcast, TOOL_ANNOTATIONS_WRITE, TOOL_ANNOTATIONS_DELETE,
|
||||
TOOL_ANNOTATIONS_NON_IDEMPOTENT, TOOL_ANNOTATIONS_READONLY,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerCollabTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'collab');
|
||||
const W = canWrite(scopes, 'collab');
|
||||
|
||||
if (!isAddonEnabled(ADDON_IDS.COLLAB)) return;
|
||||
|
||||
export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
// --- COLLAB NOTES ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_collab_note',
|
||||
{
|
||||
description: 'Create a shared collaborative note on a trip (visible to all trip members in the Collab tab).',
|
||||
@@ -40,7 +47,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_collab_note',
|
||||
{
|
||||
description: 'Edit an existing collaborative note on a trip.',
|
||||
@@ -65,7 +72,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_collab_note',
|
||||
{
|
||||
description: 'Delete a collaborative note from a trip.',
|
||||
@@ -87,9 +94,8 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- COLLAB POLLS & CHAT ---
|
||||
|
||||
if (isAddonEnabled('collab')) {
|
||||
server.registerTool(
|
||||
'list_collab_polls',
|
||||
if (R) server.registerTool(
|
||||
'list_collab_polls',
|
||||
{
|
||||
description: 'List all polls for a trip.',
|
||||
inputSchema: {
|
||||
@@ -104,7 +110,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_collab_poll',
|
||||
{
|
||||
description: 'Create a new poll in the collab panel.',
|
||||
@@ -126,7 +132,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'vote_collab_poll',
|
||||
{
|
||||
description: 'Vote on a poll option (or remove vote if already voted for that option).',
|
||||
@@ -146,7 +152,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'close_collab_poll',
|
||||
{
|
||||
description: 'Close a poll so no more votes can be cast.',
|
||||
@@ -166,7 +172,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_collab_poll',
|
||||
{
|
||||
description: 'Delete a poll and all its votes.',
|
||||
@@ -186,7 +192,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_collab_messages',
|
||||
{
|
||||
description: 'List chat messages for a trip (most recent 100, oldest-first).',
|
||||
@@ -203,7 +209,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'send_collab_message',
|
||||
{
|
||||
description: "Send a chat message to a trip's collab channel.",
|
||||
@@ -224,7 +230,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_collab_message',
|
||||
{
|
||||
description: 'Delete a chat message (only the message owner can delete their own messages).',
|
||||
@@ -244,7 +250,7 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'react_collab_message',
|
||||
{
|
||||
description: 'Toggle a reaction emoji on a chat message (adds if not present, removes if already reacted).',
|
||||
@@ -264,5 +270,4 @@ export function registerCollabTools(server: McpServer, userId: number): void {
|
||||
return ok({ reactions: result.reactions });
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,11 @@ import {
|
||||
TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canWrite } from '../scopes';
|
||||
|
||||
export function registerDayTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
if (!canWrite(scopes, 'trips')) return;
|
||||
|
||||
export function registerDayTools(server: McpServer, userId: number): void {
|
||||
// --- DAYS ---
|
||||
|
||||
server.registerTool(
|
||||
|
||||
@@ -6,8 +6,11 @@ import {
|
||||
TOOL_ANNOTATIONS_READONLY,
|
||||
ok,
|
||||
} from './_shared';
|
||||
import { canRead } from '../scopes';
|
||||
|
||||
export function registerMapsWeatherTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
if (!canRead(scopes, 'media')) return;
|
||||
|
||||
export function registerMapsWeatherTools(server: McpServer, userId: number): void {
|
||||
// --- MAPS EXTRAS ---
|
||||
|
||||
server.registerTool(
|
||||
|
||||
@@ -11,11 +11,15 @@ import {
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerNotificationTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'notifications');
|
||||
const W = canWrite(scopes, 'notifications');
|
||||
|
||||
export function registerNotificationTools(server: McpServer, userId: number): void {
|
||||
// --- NOTIFICATIONS ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_notifications',
|
||||
{
|
||||
description: 'List in-app notifications for the current user.',
|
||||
@@ -32,7 +36,7 @@ export function registerNotificationTools(server: McpServer, userId: number): vo
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_unread_notification_count',
|
||||
{
|
||||
description: 'Get the number of unread in-app notifications.',
|
||||
@@ -45,7 +49,7 @@ export function registerNotificationTools(server: McpServer, userId: number): vo
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'mark_notification_read',
|
||||
{
|
||||
description: 'Mark a single notification as read.',
|
||||
@@ -62,7 +66,7 @@ export function registerNotificationTools(server: McpServer, userId: number): vo
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'mark_notification_unread',
|
||||
{
|
||||
description: 'Mark a single notification as unread.',
|
||||
@@ -79,7 +83,7 @@ export function registerNotificationTools(server: McpServer, userId: number): vo
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'mark_all_notifications_read',
|
||||
{
|
||||
description: "Mark all of the current user's notifications as read.",
|
||||
|
||||
@@ -16,11 +16,19 @@ import {
|
||||
TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
|
||||
export function registerPackingTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'packing');
|
||||
const W = canWrite(scopes, 'packing');
|
||||
|
||||
if (!isAddonEnabled(ADDON_IDS.PACKING)) return;
|
||||
|
||||
export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
// --- PACKING ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_packing_item',
|
||||
{
|
||||
description: 'Add an item to the packing checklist for a trip.',
|
||||
@@ -40,7 +48,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'toggle_packing_item',
|
||||
{
|
||||
description: 'Check or uncheck a packing item.',
|
||||
@@ -61,7 +69,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_packing_item',
|
||||
{
|
||||
description: 'Remove an item from the packing checklist.',
|
||||
@@ -83,7 +91,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- PACKING (update) ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_packing_item',
|
||||
{
|
||||
description: 'Rename a packing item or change its category.',
|
||||
@@ -108,7 +116,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- PACKING ADVANCED ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'reorder_packing_items',
|
||||
{
|
||||
description: 'Set the display order of packing items within a trip.',
|
||||
@@ -127,7 +135,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_packing_bags',
|
||||
{
|
||||
description: 'List all packing bags for a trip.',
|
||||
@@ -143,7 +151,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_packing_bag',
|
||||
{
|
||||
description: 'Create a new packing bag (e.g. "Carry-on", "Checked bag").',
|
||||
@@ -163,7 +171,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_packing_bag',
|
||||
{
|
||||
description: 'Rename or recolor a packing bag.',
|
||||
@@ -188,7 +196,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_packing_bag',
|
||||
{
|
||||
description: 'Delete a packing bag (items in the bag are unassigned, not deleted).',
|
||||
@@ -207,7 +215,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'set_bag_members',
|
||||
{
|
||||
description: 'Assign trip members to a packing bag (determines who packs what bag).',
|
||||
@@ -227,7 +235,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_packing_category_assignees',
|
||||
{
|
||||
description: 'Get which trip members are assigned to each packing category.',
|
||||
@@ -243,7 +251,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'set_packing_category_assignees',
|
||||
{
|
||||
description: 'Assign trip members to a packing category.',
|
||||
@@ -263,7 +271,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'apply_packing_template',
|
||||
{
|
||||
description: 'Apply a packing template to a trip (adds items from the template).',
|
||||
@@ -283,7 +291,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'save_packing_template',
|
||||
{
|
||||
description: 'Save the current packing list as a reusable template.',
|
||||
@@ -301,7 +309,7 @@ export function registerPackingTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'bulk_import_packing',
|
||||
{
|
||||
description: 'Import multiple packing items at once from a list.',
|
||||
|
||||
@@ -10,11 +10,15 @@ import {
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerPlaceTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'places');
|
||||
const W = canWrite(scopes, 'places');
|
||||
|
||||
export function registerPlaceTools(server: McpServer, userId: number): void {
|
||||
// --- PLACES ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_place',
|
||||
{
|
||||
description: 'Add a new place/POI to a trip. Set google_place_id or osm_id (from search_place) so the app can show opening hours and ratings.',
|
||||
@@ -43,7 +47,7 @@ export function registerPlaceTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_place',
|
||||
{
|
||||
description: 'Update an existing place in a trip.',
|
||||
@@ -80,7 +84,7 @@ export function registerPlaceTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_place',
|
||||
{
|
||||
description: 'Delete a place from a trip.',
|
||||
@@ -100,7 +104,7 @@ export function registerPlaceTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_places',
|
||||
{
|
||||
description: 'List all places/POIs in a trip, optionally filtered by assignment status. Use assignment=unassigned to find orphan activities not yet scheduled on any day.',
|
||||
@@ -122,7 +126,7 @@ export function registerPlaceTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- CATEGORIES ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_categories',
|
||||
{
|
||||
description: 'List all available place categories with their id, name, icon and color. Use category_id when creating or updating places.',
|
||||
@@ -137,7 +141,7 @@ export function registerPlaceTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- SEARCH ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'search_place',
|
||||
{
|
||||
description: 'Search for a real-world place by name or address. Returns results with osm_id (and google_place_id if configured). Use these IDs when calling create_place so the app can display opening hours and ratings.',
|
||||
|
||||
@@ -3,8 +3,30 @@ import { z } from 'zod';
|
||||
import { canAccessTrip } from '../../db/database';
|
||||
import { getTripSummary } from '../../services/tripService';
|
||||
import { listItems as listPackingItems } from '../../services/packingService';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
|
||||
export function registerMcpPrompts(server: McpServer, _userId: number): void {
|
||||
export function registerMcpPrompts(server: McpServer, _userId: number, isStaticToken = false): void {
|
||||
if (isStaticToken) {
|
||||
server.registerPrompt(
|
||||
'token_auth_notice',
|
||||
{
|
||||
title: 'Static Token Auth Notice',
|
||||
description: 'Notification that this session uses a static API token which will be deprecated',
|
||||
argsSchema: {},
|
||||
},
|
||||
async () => ({
|
||||
description: 'Static token deprecation notice',
|
||||
messages: [{
|
||||
role: 'user' as const,
|
||||
content: {
|
||||
type: 'text' as const,
|
||||
text: '⚠️ This MCP connection is authenticated with a static API token (trek_…). Static token authentication will be deprecated in a future version of TREK. Please inform the user that they should migrate to OAuth 2.1 by going to Settings → Integrations → MCP → OAuth Clients in TREK and registering an OAuth 2.1 application for their MCP client.',
|
||||
},
|
||||
}],
|
||||
})
|
||||
);
|
||||
}
|
||||
const userId = _userId;
|
||||
|
||||
server.registerPrompt(
|
||||
@@ -43,7 +65,7 @@ ${days?.map((d: any, i: number) => `Day ${i + 1} (${d.date}): ${d.assignments?.l
|
||||
}
|
||||
);
|
||||
|
||||
server.registerPrompt(
|
||||
if (isAddonEnabled(ADDON_IDS.PACKING)) server.registerPrompt(
|
||||
'packing-list',
|
||||
{
|
||||
title: 'Packing List',
|
||||
@@ -77,7 +99,7 @@ ${days?.map((d: any, i: number) => `Day ${i + 1} (${d.date}): ${d.assignments?.l
|
||||
}
|
||||
);
|
||||
|
||||
server.registerPrompt(
|
||||
if (isAddonEnabled(ADDON_IDS.BUDGET)) server.registerPrompt(
|
||||
'budget-overview',
|
||||
{
|
||||
title: 'Budget Overview',
|
||||
|
||||
@@ -13,8 +13,11 @@ import {
|
||||
TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canWrite } from '../scopes';
|
||||
|
||||
export function registerReservationTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
if (!canWrite(scopes, 'reservations')) return;
|
||||
|
||||
export function registerReservationTools(server: McpServer, userId: number): void {
|
||||
|
||||
server.registerTool(
|
||||
'create_reservation',
|
||||
|
||||
@@ -7,11 +7,15 @@ import {
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerTagTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'places');
|
||||
const W = canWrite(scopes, 'places');
|
||||
|
||||
export function registerTagTools(server: McpServer, userId: number): void {
|
||||
// --- TAGS ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_tags',
|
||||
{
|
||||
description: 'List all tags belonging to the current user.',
|
||||
@@ -24,7 +28,7 @@ export function registerTagTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_tag',
|
||||
{
|
||||
description: 'Create a new tag (user-scoped label for places).',
|
||||
@@ -41,7 +45,7 @@ export function registerTagTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_tag',
|
||||
{
|
||||
description: 'Update the name or color of an existing tag.',
|
||||
@@ -60,7 +64,7 @@ export function registerTagTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_tag',
|
||||
{
|
||||
description: 'Delete a tag (removes it from all places it was attached to).',
|
||||
|
||||
@@ -12,11 +12,19 @@ import {
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
|
||||
export function registerTodoTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canRead(scopes, 'collab');
|
||||
const W = canWrite(scopes, 'collab');
|
||||
|
||||
if (!isAddonEnabled(ADDON_IDS.PACKING)) return;
|
||||
|
||||
export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
// --- TODOS ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_todos',
|
||||
{
|
||||
description: 'List all to-do items for a trip, ordered by position.',
|
||||
@@ -32,7 +40,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_todo',
|
||||
{
|
||||
description: 'Create a new to-do item for a trip.',
|
||||
@@ -56,7 +64,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_todo',
|
||||
{
|
||||
description: 'Update an existing to-do item. Only provided fields are changed; omitted fields stay as-is. Pass null to clear a nullable field.',
|
||||
@@ -88,7 +96,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'toggle_todo',
|
||||
{
|
||||
description: 'Mark a to-do item as checked (done) or unchecked.',
|
||||
@@ -109,7 +117,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_todo',
|
||||
{
|
||||
description: 'Delete a to-do item.',
|
||||
@@ -129,7 +137,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'reorder_todos',
|
||||
{
|
||||
description: 'Reorder to-do items within a trip by providing a new ordered list of item IDs.',
|
||||
@@ -147,7 +155,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_todo_category_assignees',
|
||||
{
|
||||
description: 'Get the default assignees configured per to-do category for a trip.',
|
||||
@@ -163,7 +171,7 @@ export function registerTodoTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'set_todo_category_assignees',
|
||||
{
|
||||
description: 'Set the default assignees for a to-do category on a trip. Pass an empty array to clear.',
|
||||
|
||||
@@ -13,22 +13,27 @@ import {
|
||||
createOrUpdateShareLink, getShareLink, deleteShareLink,
|
||||
} from '../../services/shareService';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { countMessages, listPolls } from '../../services/collabService';
|
||||
import {
|
||||
listItems as listTodoItems,
|
||||
} from '../../services/todoService';
|
||||
import { listFiles } from '../../services/fileService';
|
||||
import {
|
||||
safeBroadcast, MAX_MCP_TRIP_DAYS,
|
||||
TOOL_ANNOTATIONS_READONLY, TOOL_ANNOTATIONS_WRITE,
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, noAccess, ok,
|
||||
} from './_shared';
|
||||
import { canReadTrips, canWrite, canDeleteTrips } from '../scopes';
|
||||
|
||||
export function registerTripTools(server: McpServer, userId: number, scopes: string[] | null): void {
|
||||
const R = canReadTrips(scopes);
|
||||
const W = canWrite(scopes, 'trips');
|
||||
const D = canDeleteTrips(scopes);
|
||||
|
||||
export function registerTripTools(server: McpServer, userId: number): void {
|
||||
// --- TRIPS ---
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_trip',
|
||||
{
|
||||
description: 'Create a new trip. Returns the created trip with its generated days.',
|
||||
@@ -61,7 +66,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_trip',
|
||||
{
|
||||
description: 'Update an existing trip\'s details.',
|
||||
@@ -94,7 +99,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (D) server.registerTool(
|
||||
'delete_trip',
|
||||
{
|
||||
description: 'Delete a trip. Only the trip owner can delete it.',
|
||||
@@ -111,7 +116,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_trips',
|
||||
{
|
||||
description: 'List all trips the current user owns or is a member of. Use this for trip discovery before calling get_trip_summary.',
|
||||
@@ -128,10 +133,10 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
|
||||
// --- TRIP SUMMARY ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_trip_summary',
|
||||
{
|
||||
description: 'Get a full denormalized summary of a trip in a single call: metadata, members, days with assignments and notes, accommodations, full budget line items with totals, full packing list with checked status, reservations, collab notes, to-do items, files, and collab poll/message counts. Use this as a context loader before planning or modifying a trip.',
|
||||
description: 'Get a full denormalized summary of a trip in a single call: metadata, members, days with assignments and notes, accommodations, budget line items (when enabled), packing list (when enabled), reservations, collab notes and poll/message counts (when enabled), and to-do items (when enabled). Use this as a context loader before planning or modifying a trip.',
|
||||
inputSchema: {
|
||||
tripId: z.number().int().positive(),
|
||||
},
|
||||
@@ -141,31 +146,31 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
if (!canAccessTrip(tripId, userId)) return noAccess();
|
||||
const summary = getTripSummary(tripId);
|
||||
if (!summary) return noAccess();
|
||||
const todos = listTodoItems(tripId);
|
||||
const files = listFiles(tripId, false).map((f: any) => ({
|
||||
id: f.id,
|
||||
original_name: f.original_name,
|
||||
mime_type: f.mime_type,
|
||||
file_size: f.file_size,
|
||||
starred: !!f.starred,
|
||||
deleted: !!f.deleted_at,
|
||||
created_at: f.created_at,
|
||||
}));
|
||||
const packingEnabled = isAddonEnabled(ADDON_IDS.PACKING);
|
||||
const budgetEnabled = isAddonEnabled(ADDON_IDS.BUDGET);
|
||||
const collabEnabled = isAddonEnabled(ADDON_IDS.COLLAB);
|
||||
const todos = packingEnabled ? listTodoItems(tripId) : [];
|
||||
let pollCount = 0;
|
||||
if (isAddonEnabled('collab')) {
|
||||
pollCount = listPolls(tripId).length;
|
||||
}
|
||||
let messageCount = 0;
|
||||
if (isAddonEnabled('collab')) {
|
||||
if (collabEnabled) {
|
||||
pollCount = listPolls(tripId).length;
|
||||
messageCount = countMessages(tripId);
|
||||
}
|
||||
return ok({ ...summary, todos, files, pollCount, messageCount });
|
||||
return ok({
|
||||
...summary,
|
||||
packing: packingEnabled ? summary.packing : undefined,
|
||||
budget: budgetEnabled ? summary.budget : undefined,
|
||||
collab_notes: collabEnabled ? summary.collab_notes : [],
|
||||
todos,
|
||||
pollCount,
|
||||
messageCount,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
// --- TRIP MEMBERS, COPY, ICS, SHARE ---
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_trip_members',
|
||||
{
|
||||
description: 'List all members of a trip (owner + collaborators).',
|
||||
@@ -183,7 +188,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'add_trip_member',
|
||||
{
|
||||
description: 'Add a user to a trip by their username or email address. Only the trip owner can do this.',
|
||||
@@ -210,7 +215,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'remove_trip_member',
|
||||
{
|
||||
description: 'Remove a member from a trip. Only the trip owner can do this.',
|
||||
@@ -232,7 +237,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'copy_trip',
|
||||
{
|
||||
description: 'Duplicate a trip (all days, places, itinerary, packing, budget, reservations, day notes). Packing items are reset to unchecked. Returns the new trip.',
|
||||
@@ -255,7 +260,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'export_trip_ics',
|
||||
{
|
||||
description: 'Export a trip\'s itinerary and reservations as iCalendar (.ics) format text. Useful for importing into calendar apps.',
|
||||
@@ -275,7 +280,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_share_link',
|
||||
{
|
||||
description: 'Get the current public share link for a trip, including its permission flags. Returns null if no share link exists.',
|
||||
@@ -291,7 +296,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'create_share_link',
|
||||
{
|
||||
description: 'Create or update the public share link for a trip. Set permission flags to control what is visible to guests.',
|
||||
@@ -319,7 +324,7 @@ export function registerTripTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_share_link',
|
||||
{
|
||||
description: 'Revoke the public share link for a trip. Guests will no longer be able to access the shared view.',
|
||||
|
||||
@@ -13,15 +13,20 @@ import {
|
||||
getCountries as getHolidayCountries, getHolidays,
|
||||
} from '../../services/vacayService';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import {
|
||||
TOOL_ANNOTATIONS_READONLY, TOOL_ANNOTATIONS_WRITE,
|
||||
TOOL_ANNOTATIONS_DELETE, TOOL_ANNOTATIONS_NON_IDEMPOTENT,
|
||||
demoDenied, ok,
|
||||
} from './_shared';
|
||||
import { canRead, canWrite } from '../scopes';
|
||||
|
||||
export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
if (isAddonEnabled('vacay')) {
|
||||
server.registerTool(
|
||||
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).",
|
||||
@@ -34,7 +39,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_vacay_plan',
|
||||
{
|
||||
description: 'Update vacation plan settings (weekends blocking, holidays, carry-over).',
|
||||
@@ -55,7 +60,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'set_vacay_color',
|
||||
{
|
||||
description: "Set the current user's color in the vacation plan calendar.",
|
||||
@@ -72,7 +77,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_available_vacay_users',
|
||||
{
|
||||
description: 'List users who can be invited to the current vacation plan.',
|
||||
@@ -86,7 +91,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'send_vacay_invite',
|
||||
{
|
||||
description: 'Invite a user to join the vacation plan by their user ID.',
|
||||
@@ -106,7 +111,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'accept_vacay_invite',
|
||||
{
|
||||
description: 'Accept a pending invitation to join another user\'s vacation plan.',
|
||||
@@ -123,7 +128,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'decline_vacay_invite',
|
||||
{
|
||||
description: 'Decline a pending vacation plan invitation.',
|
||||
@@ -138,7 +143,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'cancel_vacay_invite',
|
||||
{
|
||||
description: 'Cancel an outgoing invitation (owner cancels invite they sent).',
|
||||
@@ -155,7 +160,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'dissolve_vacay_plan',
|
||||
{
|
||||
description: 'Dissolve the shared plan — all members are removed and everyone returns to their own individual plan.',
|
||||
@@ -169,7 +174,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_vacay_years',
|
||||
{
|
||||
description: 'List calendar years tracked in the current vacation plan.',
|
||||
@@ -183,7 +188,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'add_vacay_year',
|
||||
{
|
||||
description: 'Add a calendar year to the vacation plan.',
|
||||
@@ -200,7 +205,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_vacay_year',
|
||||
{
|
||||
description: 'Remove a calendar year from the vacation plan.',
|
||||
@@ -217,7 +222,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_vacay_entries',
|
||||
{
|
||||
description: 'Get all vacation day entries for a plan and year.',
|
||||
@@ -233,7 +238,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'toggle_vacay_entry',
|
||||
{
|
||||
description: 'Toggle a day on or off as a vacation day for the current user.',
|
||||
@@ -250,7 +255,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'toggle_company_holiday',
|
||||
{
|
||||
description: 'Toggle a date as a company holiday for the whole plan.',
|
||||
@@ -268,7 +273,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'get_vacay_stats',
|
||||
{
|
||||
description: 'Get vacation statistics for a specific year (days used, remaining, carried over).',
|
||||
@@ -284,7 +289,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_vacay_stats',
|
||||
{
|
||||
description: 'Update the vacation day allowance for a specific user and year.',
|
||||
@@ -302,7 +307,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'add_holiday_calendar',
|
||||
{
|
||||
description: 'Add a public holiday calendar (by region code) to the vacation plan.',
|
||||
@@ -322,7 +327,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'update_holiday_calendar',
|
||||
{
|
||||
description: 'Update label or color for a holiday calendar.',
|
||||
@@ -342,7 +347,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (W) server.registerTool(
|
||||
'delete_holiday_calendar',
|
||||
{
|
||||
description: 'Remove a holiday calendar from the vacation plan.',
|
||||
@@ -359,7 +364,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_holiday_countries',
|
||||
{
|
||||
description: 'List countries available for public holiday calendars.',
|
||||
@@ -373,7 +378,7 @@ export function registerVacayTools(server: McpServer, userId: number): void {
|
||||
}
|
||||
);
|
||||
|
||||
server.registerTool(
|
||||
if (R) server.registerTool(
|
||||
'list_holidays',
|
||||
{
|
||||
description: 'List public holidays for a country and year.',
|
||||
|
||||
Reference in New Issue
Block a user