import { McpServer } from '@modelcontextprotocol/sdk/server/mcp'; import { z } from 'zod'; import { isDemoUser } from '../../services/authService'; import { markCountryVisited, unmarkCountryVisited, createBucketItem, deleteBucketItem, getStats as getAtlasStats, listManuallyVisitedRegions, 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, 'atlas'); const W = canWrite(scopes, 'atlas'); if (!isAddonEnabled(ADDON_IDS.ATLAS)) return; // --- BUCKET LIST --- if (W) server.registerTool( 'create_bucket_list_item', { description: 'Add a destination to your personal travel bucket list.', inputSchema: { name: z.string().min(1).max(200).describe('Destination or experience name'), lat: z.number().optional(), lng: z.number().optional(), country_code: z.string().length(2).toUpperCase().optional().describe('ISO 3166-1 alpha-2 country code'), notes: z.string().max(1000).optional(), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ name, lat, lng, country_code, notes }) => { if (isDemoUser(userId)) return demoDenied(); const item = createBucketItem(userId, { name, lat, lng, country_code, notes }); return ok({ item }); } ); if (W) server.registerTool( 'delete_bucket_list_item', { description: 'Remove an item from your travel bucket list.', inputSchema: { itemId: z.number().int().positive(), }, annotations: TOOL_ANNOTATIONS_DELETE, }, async ({ itemId }) => { if (isDemoUser(userId)) return demoDenied(); const deleted = deleteBucketItem(userId, itemId); if (!deleted) return { content: [{ type: 'text' as const, text: 'Bucket list item not found.' }], isError: true }; return ok({ success: true }); } ); // --- ATLAS --- if (W) server.registerTool( 'mark_country_visited', { description: 'Mark a country as visited in your Atlas.', inputSchema: { country_code: z.string().length(2).toUpperCase().describe('ISO 3166-1 alpha-2 country code (e.g. "FR", "JP")'), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ country_code }) => { if (isDemoUser(userId)) return demoDenied(); markCountryVisited(userId, country_code.toUpperCase()); return ok({ success: true, country_code: country_code.toUpperCase() }); } ); if (W) server.registerTool( 'unmark_country_visited', { description: 'Remove a country from your visited countries in Atlas.', inputSchema: { country_code: z.string().length(2).toUpperCase().describe('ISO 3166-1 alpha-2 country code'), }, annotations: TOOL_ANNOTATIONS_DELETE, }, async ({ country_code }) => { if (isDemoUser(userId)) return demoDenied(); unmarkCountryVisited(userId, country_code.toUpperCase()); return ok({ success: true, country_code: country_code.toUpperCase() }); } ); // --- ATLAS EXPANDED --- if (R) server.registerTool( 'get_atlas_stats', { description: 'Get atlas statistics — total visited countries, region counts, continent breakdown.', inputSchema: {}, annotations: TOOL_ANNOTATIONS_READONLY, }, async () => { const stats = await getAtlasStats(userId); return ok({ stats }); } ); if (R) server.registerTool( 'list_visited_regions', { description: 'List all manually visited sub-country regions for the current user.', inputSchema: {}, annotations: TOOL_ANNOTATIONS_READONLY, }, async () => { const regions = listManuallyVisitedRegions(userId); return ok({ regions }); } ); if (W) server.registerTool( 'mark_region_visited', { description: 'Mark a sub-country region as visited.', inputSchema: { regionCode: z.string().describe('ISO region code e.g. US-CA'), regionName: z.string(), countryCode: z.string().describe('ISO 3166-1 alpha-2 country code'), }, annotations: TOOL_ANNOTATIONS_NON_IDEMPOTENT, }, async ({ regionCode, regionName, countryCode }) => { if (isDemoUser(userId)) return demoDenied(); markRegionVisited(userId, regionCode, regionName, countryCode); const row = listManuallyVisitedRegions(userId).find(r => r.region_code === regionCode); // Echo in the client-facing shape ({ code, name, ... }) rather than raw DB columns. const region = row ? { code: row.region_code, name: row.region_name, country_code: row.country_code, manuallyMarked: true } : undefined; return ok({ region }); } ); if (W) server.registerTool( 'unmark_region_visited', { description: 'Remove a region from the visited list.', inputSchema: { regionCode: z.string(), }, annotations: TOOL_ANNOTATIONS_DELETE, }, async ({ regionCode }) => { if (isDemoUser(userId)) return demoDenied(); unmarkRegionVisited(userId, regionCode); return ok({ success: true }); } ); if (R) server.registerTool( 'get_country_atlas_places', { description: 'Get places saved in the user\'s atlas for a specific country.', inputSchema: { countryCode: z.string().describe('ISO 3166-1 alpha-2 country code'), }, annotations: TOOL_ANNOTATIONS_READONLY, }, async ({ countryCode }) => { const result = getCountryPlaces(userId, countryCode); return ok(result); } ); if (W) server.registerTool( 'update_bucket_list_item', { description: 'Update a bucket list item (notes, name, target date, location).', inputSchema: { itemId: z.number().int().positive(), name: z.string().optional(), notes: z.string().optional(), lat: z.number().nullable().optional(), lng: z.number().nullable().optional(), country_code: z.string().optional(), target_date: z.string().nullable().optional(), }, annotations: TOOL_ANNOTATIONS_WRITE, }, async ({ itemId, name, notes, lat, lng, country_code, target_date }) => { if (isDemoUser(userId)) return demoDenied(); const item = updateBucketItem(userId, itemId, { name, notes, lat, lng, country_code, target_date }); if (!item) return { content: [{ type: 'text' as const, text: 'Bucket list item not found.' }], isError: true }; return ok({ item }); } ); }