# TREK Addons System Architecture: How Feature Modules Are Implemented

> Explore the TREK addons system architecture. Learn how feature modules are implemented using a centralized registry, database state, and runtime feature gating to manage MCP, AirTrail, and more.

- Repository: [Maurice/TREK](https://github.com/mauriceboe/TREK)
- Tags: architecture
- Published: 2026-07-04

---

**The TREK addons system uses a centralized identifier registry, database-backed state management, and runtime feature gating to enable or disable optional modules like MCP, AirTrail, and Journey thumbnails from a single admin panel.**

The `mauriceboe/TREK` repository implements a modular addons architecture that allows administrators to toggle optional features without code changes. This system separates core functionality from extended capabilities through a consistent pattern of identifier constants, database persistence, and conditional execution checks. Understanding the TREK addons system architecture reveals how the application maintains a lightweight core while supporting extensible features such as Model Context Protocol (MCP) integration, AI parsing, and collaboration tools.

## Core Components of the Addons Architecture

### Add-on Identifiers

All supported addons are enumerated in a central constant object. The [`server/src/addons.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/addons.ts) file exports `ADDON_IDS`, which serves as the single source of truth for addon references throughout the codebase. Every addon—whether MCP, AIRTRAIL, JOURNEY, LLM_PARSING, COLLAB, or PACKING—is defined here with a stable string identifier.

Using constants prevents typos and ensures type safety when checking addon status across different services. Any code that needs to verify if a feature is active imports `ADDON_IDS` from this file rather than using raw strings.

### Database Schema

The system persists addon state in two primary tables: `addons` for core modules and `photo_providers` for photo-provider specific addons. Each row contains three critical columns: `id` (matching the `ADDON_IDS` constant), `enabled` (boolean flag), and `config` (JSON column for feature-specific settings).

During application startup, [`server/src/db/seeds.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/db/seeds.ts) populates the `addons` table with rows for every key defined in `ADDON_IDS`. This seeding ensures that all potential features exist in the database with default disabled states, ready for activation through the admin interface.

### Admin Service API

The [`server/src/services/adminService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/adminService.ts) file provides the central API for addon management through three key functions:

- **`isAddonEnabled(id)`**: Returns a boolean by querying the database (lines 66-68)
- **`listAddons()`**: Retrieves the complete addon list including configuration objects (lines 71-84)
- **`updateAddon(id, data)`**: Toggles the `enabled` flag or updates the JSON `config` column (lines 33-51)

All runtime feature checks route through `isAddonEnabled`, creating a single bottleneck for permission validation. When updating the LLM_PARSING addon, `updateAddon` additionally handles encryption for stored API keys before persistence.

## Runtime Feature Gating Implementation

Feature gating occurs at multiple architectural layers. Services, controllers, and MCP tools check addon status before executing protected logic.

**HTTP Route Protection**: In [`server/src/nest/platform/platform.routes.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/nest/platform/platform.routes.ts), MCP API endpoints check `isAddonEnabled(ADDON_IDS.MCP)` before registration (lines 123, 169, 191). If disabled, the routes return 404 or skip mounting entirely.

**Service-Level Guards**: The AirTrail synchronization service in [`server/src/services/airtrail/airtrailSync.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/airtrail/airtrailSync.ts) validates `ADDON_IDS.AIRTRAIL` at line 21 before proceeding with sync operations. Similarly, the thumbnail generation service in [`server/src/services/memories/thumbnailService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/memories/thumbnailService.ts) checks `ADDON_IDS.JOURNEY` at line 15.

**MCP Tool Registration**: Individual MCP tools in `server/src/mcp/tools/*.ts` verify addon status before registering their handlers. The collab tools check `ADDON_IDS.COLLAB` (line 11), while packing tools validate `ADDON_IDS.PACKING` (lines 22-24).

## MCP Integration and Conditional Registration

The Model Context Protocol layer implements dynamic resource registration based on addon state. During bootstrap, [`server/src/mcp/index.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/mcp/index.ts) iterates through available addons, registering resources and prompts only for those marked enabled.

For example, when `ADDON_IDS.PACKING` is active, the system registers packing list prompts and budget resources. If disabled, these MCP capabilities remain unavailable to connected clients. This conditional registration prevents exposing tools for features that lack backend support.

## Admin Control and Configuration Flow

The architecture follows a predictable lifecycle from startup to runtime toggling:

1. **Database Seeding**: Application initialization runs [`seeds.ts`](https://github.com/mauriceboe/TREK/blob/main/seeds.ts), inserting rows for every `ADDON_IDS` value with `enabled: false` by default
2. **Runtime Validation**: Incoming requests trigger `isAddonEnabled` checks, which query the current database state
3. **Conditional Execution**: Enabled addons proceed with normal logic; disabled addons return early or throw 404 errors
4. **Admin Toggling**: Administrators use the PATCH `/admin/addons/:id` endpoint, which routes to `updateAddon` to modify the `enabled` flag or update JSON configuration
5. **UI Synchronization**: The frontend fetches the current state via `listAddons()` to conditionally render navigation tabs and feature panels

## Practical Implementation Examples

### Checking Addon Status in a Service

When implementing a feature that depends on an optional addon, import the identifiers and admin service:

```typescript
import { ADDON_IDS } from '../../addons';
import { isAddonEnabled } from '../services/adminService';

export async function syncAirTrail() {
  if (!isAddonEnabled(ADDON_IDS.AIRTRAIL)) {
    // Feature disabled - exit early
    return false;
  }
  // Proceed with AirTrail synchronization logic
  const data = await fetchAirTrailData();
  return processData(data);
}

```

*Reference*: [`server/src/services/airtrail/airtrailSync.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/airtrail/airtrailSync.ts) (line 21)

### Enabling Addons Programmatically

To toggle an addon from administrative code or tests:

```typescript
import { ADDON_IDS } from '../addons';
import { updateAddon } from '../services/adminService';

// Enable the packing addon
await updateAddon(ADDON_IDS.PACKING, { enabled: true });

// Configure with specific settings
await updateAddon(ADDON_IDS.LLM_PARSING, { 
  enabled: true, 
  config: { apiKey: 'encrypted_value' } 
});

```

*Reference*: [`server/src/services/adminService.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/adminService.ts) (lines 33-51)

### Conditional MCP Resource Registration

MCP servers register capabilities only when their corresponding addon is active:

```typescript
import { ADDON_IDS } from '../../addons';
import { isAddonEnabled } from '../services/adminService';
import { mcpServer } from './server';

if (isAddonEnabled(ADDON_IDS.PACKING)) {
  mcpServer.registerResource('packing-lists', {
    // Resource definition
  });
}

if (isAddonEnabled(ADDON_IDS.BUDGET)) {
  mcpServer.registerResource('budget-tracking', {
    // Resource definition
  });
}

```

*Reference*: [`server/src/mcp/resources.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/mcp/resources.ts) (line 110 and related sections)

### Frontend Conditional Rendering

The client-side application queries the addon state to determine UI visibility:

```typescript
// React component example
const [addons, setAddons] = useState([]);

useEffect(() => {
  fetch('/api/admin/addons')
    .then(r => r.json())
    .then(setAddons);
}, []);

const hasBudget = addons.find(a => 
  a.id === ADDON_IDS.BUDGET && a.enabled
);

return (
  <nav>
    {hasBudget && <Tab label="Budget" to="/budget" />}
  </nav>
);

```

The endpoint returns the array constructed by `listAddons()` from [`adminService.ts`](https://github.com/mauriceboe/TREK/blob/main/adminService.ts).

## Summary

- **Centralized Registry**: [`server/src/addons.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/addons.ts) maintains `ADDON_IDS` as the single source of truth for all optional features
- **Database Persistence**: The `addons` table stores `enabled` flags and JSON configuration, seeded automatically at startup
- **Uniform API**: [`adminService.ts`](https://github.com/mauriceboe/TREK/blob/main/adminService.ts) provides `isAddonEnabled`, `listAddons`, and `updateAddon` for all state management needs
- **Layered Gating**: Features are guarded at the HTTP route, service method, and MCP registration levels using consistent identifier checks
- **Dynamic MCP Exposure**: Model Context Protocol resources register conditionally based on realtime addon state
- **Admin Control**: The PATCH `/admin/addons/:id` endpoint enables runtime toggling without application restarts

## Frequently Asked Questions

### How does TREK handle addon configuration storage?

The `addons` table includes a JSON `config` column that stores feature-specific settings. When `updateAddon` is called with configuration data, it merges the new values into this JSON field. For sensitive data like API keys in the LLM_PARSING addon, the service applies encryption before saving to the database.

### What happens when an addon is disabled at runtime?

When `isAddonEnabled` returns `false`, the calling code typically returns early from functions or throws 404 errors for HTTP routes. For MCP integrations, disabled addons simply skip resource registration during server initialization. The frontend removes corresponding navigation elements by filtering the list returned from `listAddons()`.

### Can third-party developers create custom addons for TREK?

The current architecture in `mauriceboe/TREK` supports the predefined set of addons listed in `ADDON_IDS`. While the database schema and [`adminService.ts`](https://github.com/mauriceboe/TREK/blob/main/adminService.ts) could theoretically support dynamic addon IDs, the codebase currently references specific `ADDON_IDS` constants throughout the feature gating logic. Extending the system would require adding new identifiers to [`server/src/addons.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/addons.ts) and implementing corresponding guard checks in the relevant services.

### Where is the addon state checked most frequently?

The most critical checks occur in [`server/src/nest/platform/platform.routes.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/nest/platform/platform.routes.ts) for MCP API exposure, [`server/src/services/airtrail/airtrailSync.ts`](https://github.com/mauriceboe/TREK/blob/main/server/src/services/airtrail/airtrailSync.ts) for external integrations, and `server/src/mcp/tools/*.ts` for AI tool availability. Every check imports `ADDON_IDS` from the central registry and queries the database through `isAddonEnabled` to ensure consistent behavior across the application.