Skip to main content
Glama
registered-apps.ts5.47 kB
/** * Registered app management * Manage app list through ~/.config/pabal-mcp/registered-apps.json file */ import { existsSync, readFileSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { AppError } from "@/packages/common/errors/app-error"; import { ERROR_CODES } from "@/packages/common/errors/error-codes"; import { getConfigDir } from "@/packages/configs/secrets-config/config"; // ============================================================================ // Type Definitions // ============================================================================ export interface RegisteredAppStoreInfo { bundleId: string; appId?: string; name?: string; supportedLocales?: string[]; } export interface RegisteredGooglePlayInfo { packageName: string; name?: string; supportedLocales?: string[]; } export interface RegisteredApp { /** App identifier (user-defined, must be unique) */ slug: string; /** Display name */ name: string; /** App Store information */ appStore?: RegisteredAppStoreInfo; /** Google Play information */ googlePlay?: RegisteredGooglePlayInfo; } export interface RegisteredAppsConfig { apps: RegisteredApp[]; } // ============================================================================ // File Paths // ============================================================================ function getRegisteredAppsPath(): string { return join(getConfigDir(), "registered-apps.json"); } // ============================================================================ // CRUD Functions // ============================================================================ /** * Load registered app list */ export function loadRegisteredApps(): RegisteredAppsConfig { const filePath = getRegisteredAppsPath(); console.error(`[RegisteredApps] 🔍 Loading registered apps...`); console.error(`[RegisteredApps] File path: ${filePath}`); console.error(`[RegisteredApps] File exists: ${existsSync(filePath)}`); if (!existsSync(filePath)) { console.error( `[RegisteredApps] ⚠️ File not found, returning empty list` ); return { apps: [] }; } try { const content = readFileSync(filePath, "utf-8"); const config = JSON.parse(content) as RegisteredAppsConfig; console.error( `[RegisteredApps] ✅ Loaded ${config.apps.length} apps: ${config.apps.map((a) => a.slug).join(", ")}` ); return config; } catch (error) { throw AppError.validation( ERROR_CODES.REGISTERED_APPS_READ_FAILED, `Failed to load registered apps: ${ error instanceof Error ? error.message : String(error) }`, { filePath } ); } } /** * Save registered app list */ export function saveRegisteredApps(config: RegisteredAppsConfig): void { const filePath = getRegisteredAppsPath(); try { writeFileSync(filePath, JSON.stringify(config, null, 2)); } catch (error) { throw AppError.io( ERROR_CODES.REGISTERED_APPS_WRITE_FAILED, `Failed to save registered apps: ${ error instanceof Error ? error.message : String(error) }`, { filePath } ); } } /** * Register app */ export function registerApp(app: RegisteredApp): RegisteredApp { const config = loadRegisteredApps(); // Check for duplicates const existing = config.apps.find((a) => a.slug === app.slug); if (existing) { throw AppError.conflict( ERROR_CODES.REGISTERED_APP_DUPLICATE, `App with slug "${app.slug}" already exists` ); } config.apps.push(app); saveRegisteredApps(config); return app; } /** * Find app (search by slug, bundleId, packageName) */ export function findApp(identifier: string): RegisteredApp | undefined { const config = loadRegisteredApps(); return config.apps.find( (app) => app.slug === identifier || app.appStore?.bundleId === identifier || app.googlePlay?.packageName === identifier ); } /** * Update supported locales for an app */ export function updateAppSupportedLocales({ identifier, store, locales, }: { identifier: string; store: "appStore" | "googlePlay"; locales: string[]; }): boolean { const config = loadRegisteredApps(); const appIndex = config.apps.findIndex( (app) => app.slug === identifier || app.appStore?.bundleId === identifier || app.googlePlay?.packageName === identifier ); if (appIndex === -1) { throw AppError.notFound( ERROR_CODES.REGISTERED_APP_NOT_FOUND, `App "${identifier}" not found in registered apps` ); } const app = config.apps[appIndex]; // Merge and deduplicate locales const existingLocales = store === "appStore" ? app.appStore?.supportedLocales || [] : app.googlePlay?.supportedLocales || []; const mergedLocales = Array.from( new Set([...existingLocales, ...locales]) ).sort(); // Update the app if (store === "appStore") { if (!app.appStore) { throw AppError.validation( ERROR_CODES.REGISTERED_APP_STORE_INFO_MISSING, `App "${identifier}" is missing App Store info` ); } app.appStore.supportedLocales = mergedLocales; } else { if (!app.googlePlay) { throw AppError.validation( ERROR_CODES.REGISTERED_APP_STORE_INFO_MISSING, `App "${identifier}" is missing Google Play info` ); } app.googlePlay.supportedLocales = mergedLocales; } saveRegisteredApps(config); return true; }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/quartz-labs-dev/pabal-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server