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:
jubnl
2026-04-09 22:25:58 +02:00
parent 5c0d819fc1
commit 830f6c0706
32 changed files with 2589 additions and 669 deletions
+18 -13
View File
@@ -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 });
}
);
}
}