mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-21 22:31:46 +00:00
refactor(mcp): extract all MCP tools into dedicated modules and add shared helpers
This commit is contained in:
+172
-4
@@ -3,13 +3,18 @@ import { canAccessTrip } from '../db/database';
|
||||
import { listTrips, getTrip, getTripOwner, listMembers } from '../services/tripService';
|
||||
import { listDays, listAccommodations } from '../services/dayService';
|
||||
import { listPlaces } from '../services/placeService';
|
||||
import { listBudgetItems } from '../services/budgetService';
|
||||
import { listItems as listPackingItems } from '../services/packingService';
|
||||
import { listBudgetItems, getPerPersonSummary, calculateSettlement } from '../services/budgetService';
|
||||
import { listItems as listPackingItems, listBags } from '../services/packingService';
|
||||
import { listReservations } from '../services/reservationService';
|
||||
import { listNotes as listDayNotes } from '../services/dayNoteService';
|
||||
import { listNotes as listCollabNotes } from '../services/collabService';
|
||||
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 } from '../services/atlasService';
|
||||
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';
|
||||
|
||||
function parseId(value: string | string[]): number | null {
|
||||
const n = Number(Array.isArray(value) ? value[0] : value);
|
||||
@@ -183,6 +188,32 @@ 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(
|
||||
'trip-todos',
|
||||
new ResourceTemplate('trek://trips/{tripId}/todos', { list: undefined }),
|
||||
{ description: 'To-do items for a trip, ordered by position', mimeType: 'application/json' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const items = listTodoItems(id);
|
||||
return jsonContent(uri.href, items);
|
||||
}
|
||||
);
|
||||
|
||||
// All place categories (global, no trip filter)
|
||||
server.registerResource(
|
||||
'categories',
|
||||
@@ -215,4 +246,141 @@ export function registerResources(server: McpServer, userId: number): void {
|
||||
return jsonContent(uri.href, countries);
|
||||
}
|
||||
);
|
||||
|
||||
// Budget per-person summary
|
||||
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' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const summary = getPerPersonSummary(id);
|
||||
return jsonContent(uri.href, summary);
|
||||
}
|
||||
);
|
||||
|
||||
// Budget settlement
|
||||
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' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const settlement = calculateSettlement(id);
|
||||
return jsonContent(uri.href, settlement);
|
||||
}
|
||||
);
|
||||
|
||||
// Packing bags
|
||||
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' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const bags = listBags(id);
|
||||
return jsonContent(uri.href, bags);
|
||||
}
|
||||
);
|
||||
|
||||
// In-app notifications
|
||||
server.registerResource(
|
||||
'notifications-in-app',
|
||||
'trek://notifications/in-app',
|
||||
{ description: "The current user's in-app notifications (most recent 50, unread first)", mimeType: 'application/json' },
|
||||
async (uri) => {
|
||||
const result = getNotifications(userId, { limit: 50 });
|
||||
return jsonContent(uri.href, result);
|
||||
}
|
||||
);
|
||||
|
||||
// Atlas stats and regions (addon-gated)
|
||||
if (isAddonEnabled('atlas')) {
|
||||
server.registerResource(
|
||||
'atlas-stats',
|
||||
'trek://atlas/stats',
|
||||
{ description: "User's atlas statistics — visited country counts and breakdown", mimeType: 'application/json' },
|
||||
async (uri) => {
|
||||
const stats = await getAtlasStats(userId);
|
||||
return jsonContent(uri.href, stats);
|
||||
}
|
||||
);
|
||||
|
||||
server.registerResource(
|
||||
'atlas-regions',
|
||||
'trek://atlas/regions',
|
||||
{ description: 'List of manually visited regions for the current user', mimeType: 'application/json' },
|
||||
async (uri) => {
|
||||
const regions = listManuallyVisitedRegions(userId);
|
||||
return jsonContent(uri.href, regions);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Collab polls & messages (addon-gated)
|
||||
if (isAddonEnabled('collab')) {
|
||||
server.registerResource(
|
||||
'trip-collab-polls',
|
||||
new ResourceTemplate('trek://trips/{tripId}/collab/polls', { list: undefined }),
|
||||
{ description: 'All polls for a trip with vote counts per option', mimeType: 'application/json' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const polls = listPolls(id);
|
||||
return jsonContent(uri.href, polls);
|
||||
}
|
||||
);
|
||||
|
||||
server.registerResource(
|
||||
'trip-collab-messages',
|
||||
new ResourceTemplate('trek://trips/{tripId}/collab/messages', { list: undefined }),
|
||||
{ description: 'Most recent 100 chat messages for a trip', mimeType: 'application/json' },
|
||||
async (uri, { tripId }) => {
|
||||
const id = parseId(tripId);
|
||||
if (id === null || !canAccessTrip(id, userId)) return accessDenied(uri.href);
|
||||
const messages = listMessages(id);
|
||||
return jsonContent(uri.href, messages);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Vacay resources (addon-gated)
|
||||
if (isAddonEnabled('vacay')) {
|
||||
server.registerResource(
|
||||
'vacay-plan',
|
||||
'trek://vacay/plan',
|
||||
{ description: "Full snapshot of the user's active vacation plan (members, years, settings)", mimeType: 'application/json' },
|
||||
async (uri) => {
|
||||
const plan = getPlanData(userId);
|
||||
return jsonContent(uri.href, plan);
|
||||
}
|
||||
);
|
||||
|
||||
server.registerResource(
|
||||
'vacay-entries',
|
||||
new ResourceTemplate('trek://vacay/entries/{year}', { list: undefined }),
|
||||
{ description: 'All vacation entries for the active plan and a specific year', mimeType: 'application/json' },
|
||||
async (uri, { year }) => {
|
||||
const planId = getActivePlanId(userId);
|
||||
const entries = getVacayEntries(planId, Array.isArray(year) ? year[0] : year);
|
||||
return jsonContent(uri.href, entries);
|
||||
}
|
||||
);
|
||||
|
||||
server.registerResource(
|
||||
'vacay-holidays',
|
||||
new ResourceTemplate('trek://vacay/holidays/{year}', { list: undefined }),
|
||||
{ description: "Cached public holidays for the plan's configured region and year", mimeType: 'application/json' },
|
||||
async (uri, { year }) => {
|
||||
const plan = getActivePlan(userId);
|
||||
if (!plan.holidays_enabled || !plan.holidays_region) return jsonContent(uri.href, []);
|
||||
const yearStr = Array.isArray(year) ? year[0] : year;
|
||||
const result = await getHolidays(yearStr, plan.holidays_region);
|
||||
return jsonContent(uri.href, result.data ?? []);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user