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
+24 -16
View File
@@ -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.',