Skip to main content
Glama
control-plane-config-service.ts17.6 kB
import { ConsumerConfig, Permissions, ToolExtension, ToolExtensions, ToolGroup, } from "@mcpx/shared-model"; import { indexBy, mapValues } from "@mcpx/toolkit-core/data"; import { LunarLogger } from "@mcpx/toolkit-core/logging"; import { ConfigService, ConfigSnapshot } from "../config.js"; import { AlreadyExistsError, NotFoundError } from "../errors.js"; import { Config, TargetServerAttributes } from "../model/config/config.js"; import { ToolGroupUpdate } from "@mcpx/shared-model"; import { PermissionsConfig } from "../model/config/permissions.js"; export class ControlPlaneConfigService { private configService: ConfigService; private logger: LunarLogger; constructor(configService: ConfigService, logger: LunarLogger) { this.configService = configService; this.logger = logger.child({ component: "ControlPlaneConfigService" }); } // ==================== CONFIG SERVICE DELEGATION ==================== subscribe(callback: (snapshot: ConfigSnapshot) => void): () => void { return this.configService.subscribe(callback); } getVersion(): number { return this.configService.getVersion(); } getLastModified(): Date { return this.configService.getLastModified(); } getSnapshot(): ConfigSnapshot { return this.configService.export(); } getConfig(): Config { return this.configService.getConfig(); } // ==================== TOOL GROUPS ==================== getToolGroups(): ToolGroup[] { return this.configService.getConfig().toolGroups; } getToolGroup(props: { name: string }): ToolGroup | undefined { const { name } = props; const toolGroupsByName = indexBy( this.configService.getConfig().toolGroups, (g) => g.name, ); return toolGroupsByName[name]; } async addToolGroup(props: { group: ToolGroup }): Promise<ToolGroup> { return this.configService.withLock(async () => { const { group } = props; const currentConfig = this.configService.getConfig(); const toolGroupsByName = indexBy(currentConfig.toolGroups, (g) => g.name); if (toolGroupsByName[group.name]) { throw new AlreadyExistsError( `Tool group '${group.name}' already exists`, ); } const updatedConfig = { ...currentConfig, toolGroups: [...currentConfig.toolGroups, group], }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Tool group '${group.name}' added successfully`); return group; }); } async updateToolGroup(props: { name: string; updates: ToolGroupUpdate; }): Promise<ToolGroup> { return this.configService.withLock(async () => { const { name, updates } = props; const currentConfig = this.configService.getConfig(); const toolGroupsByName = indexBy(currentConfig.toolGroups, (g) => g.name); if (!toolGroupsByName[name]) { throw new NotFoundError(`Tool group '${name}' not found`); } const effectiveName = updates.name ?? name; const isNameChange = effectiveName !== name; if (isNameChange && toolGroupsByName[updates.name!]) { throw new AlreadyExistsError( `Tool group name '${updates.name}' is already in use`, ); } const updatedGroup: ToolGroup = { ...updates, name: effectiveName }; const { [name]: _, ...rest } = toolGroupsByName; const updatedToolGroupsByName = { ...rest, [effectiveName]: updatedGroup, }; const updatedPermissions = ensureUpdatedToolGroupNamesInPermissions({ permissions: currentConfig.permissions, toolGroupNameUpdate: { from: name, to: effectiveName }, }); const updatedConfig = { ...currentConfig, permissions: updatedPermissions, toolGroups: Object.values(updatedToolGroupsByName), }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Tool group '${name}' updated successfully`, { name, updatedName: isNameChange ? effectiveName : undefined, }); return updatedGroup; }); } async deleteToolGroup(props: { name: string }): Promise<void> { return this.configService.withLock(async () => { const { name } = props; const currentConfig = this.configService.getConfig(); const toolGroupsByName = indexBy(currentConfig.toolGroups, (g) => g.name); if (!toolGroupsByName[name]) { throw new NotFoundError(`Tool group '${name}' not found`); } const { [name]: _, ...remaining } = toolGroupsByName; const updatedConfig = { ...currentConfig, toolGroups: Object.values(remaining), }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Tool group '${name}' deleted successfully`); }); } // ==================== PERMISSIONS ==================== getPermissions(): Permissions { return this.configService.getConfig().permissions; } getDefaultPermission(): ConsumerConfig { return this.configService.getConfig().permissions.default; } async updateDefaultPermission(props: { config: ConsumerConfig; }): Promise<ConsumerConfig> { return this.configService.withLock(async () => { const { config } = props; const currentConfig = this.configService.getConfig(); const updatedConfig = { ...currentConfig, permissions: { ...currentConfig.permissions, default: config, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info("Default permission updated successfully"); return config; }); } getPermissionConsumers(): Record<string, ConsumerConfig> { return this.configService.getConfig().permissions.consumers; } getPermissionConsumer(props: { name: string }): ConsumerConfig | undefined { const { name } = props; return this.configService.getConfig().permissions.consumers[name]; } async addPermissionConsumer(props: { name: string; config: ConsumerConfig; }): Promise<ConsumerConfig> { return this.configService.withLock(async () => { const { name, config } = props; const currentConfig = this.configService.getConfig(); if (currentConfig.permissions.consumers[name]) { throw new AlreadyExistsError( `Permission consumer '${name}' already exists`, ); } const updatedConfig = { ...currentConfig, permissions: { ...currentConfig.permissions, consumers: { ...currentConfig.permissions.consumers, [name]: config, }, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Permission consumer '${name}' added successfully`); return config; }); } async updatePermissionConsumer(props: { name: string; config: ConsumerConfig; }): Promise<ConsumerConfig> { return this.configService.withLock(async () => { const { name, config } = props; const currentConfig = this.configService.getConfig(); if (!currentConfig.permissions.consumers[name]) { throw new NotFoundError(`Permission consumer '${name}' not found`); } const updatedConfig = { ...currentConfig, permissions: { ...currentConfig.permissions, consumers: { ...currentConfig.permissions.consumers, [name]: config, }, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Permission consumer '${name}' updated successfully`); return config; }); } async deletePermissionConsumer(props: { name: string }): Promise<void> { return this.configService.withLock(async () => { const { name } = props; const currentConfig = this.configService.getConfig(); if (!currentConfig.permissions.consumers[name]) { throw new NotFoundError(`Permission consumer '${name}' not found`); } const { [name]: _, ...remainingConsumers } = currentConfig.permissions.consumers; const updatedConfig = { ...currentConfig, permissions: { ...currentConfig.permissions, consumers: remainingConsumers, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Permission consumer '${name}' deleted successfully`); }); } // ==================== TOOL EXTENSIONS ==================== getToolExtensions(): ToolExtensions { return this.configService.getConfig().toolExtensions; } getToolExtension(props: { serverName: string; originalToolName: string; customToolName: string; }): ToolExtension | undefined { const { serverName, originalToolName, customToolName } = props; const extensions = this.configService.getConfig().toolExtensions; const childTools = extensions.services[serverName]?.[originalToolName]?.childTools; return childTools?.find((t) => t.name === customToolName); } async addToolExtension(props: { serverName: string; originalToolName: string; extension: ToolExtension; }): Promise<ToolExtension> { return this.configService.withLock(async () => { const { serverName, originalToolName, extension } = props; const currentConfig = this.configService.getConfig(); const extensions = currentConfig.toolExtensions; const existingChildTools = extensions.services[serverName]?.[originalToolName]?.childTools ?? []; if (existingChildTools.some((t) => t.name === extension.name)) { throw new AlreadyExistsError( `Tool extension '${extension.name}' already exists for ${serverName}/${originalToolName}`, ); } const updatedConfig = { ...currentConfig, toolExtensions: { services: { ...extensions.services, [serverName]: { ...extensions.services[serverName], [originalToolName]: { childTools: [...existingChildTools, extension], }, }, }, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info( `Tool extension '${extension.name}' added to ${serverName}/${originalToolName}`, ); return extension; }); } async updateToolExtension(props: { serverName: string; originalToolName: string; customToolName: string; updates: Omit<ToolExtension, "name">; }): Promise<ToolExtension> { return this.configService.withLock(async () => { const { serverName, originalToolName, customToolName, updates } = props; const currentConfig = this.configService.getConfig(); const extensions = currentConfig.toolExtensions; const existingChildTools = extensions.services[serverName]?.[originalToolName]?.childTools; if (!existingChildTools?.some((t) => t.name === customToolName)) { throw new NotFoundError( `Tool extension '${customToolName}' not found for ${serverName}/${originalToolName}`, ); } const updatedExtension: ToolExtension = { ...updates, name: customToolName, }; const updatedChildTools = existingChildTools.map((t) => t.name === customToolName ? updatedExtension : t, ); const updatedConfig = { ...currentConfig, toolExtensions: { services: { ...extensions.services, [serverName]: { ...extensions.services[serverName], [originalToolName]: { childTools: updatedChildTools, }, }, }, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info( `Tool extension '${customToolName}' updated for ${serverName}/${originalToolName}`, ); return updatedExtension; }); } async deleteToolExtension(props: { serverName: string; originalToolName: string; customToolName: string; }): Promise<void> { return this.configService.withLock(async () => { const { serverName, originalToolName, customToolName } = props; const currentConfig = this.configService.getConfig(); const extensions = currentConfig.toolExtensions; const existingChildTools = extensions.services[serverName]?.[originalToolName]?.childTools; if (!existingChildTools?.some((t) => t.name === customToolName)) { throw new NotFoundError( `Tool extension '${customToolName}' not found for ${serverName}/${originalToolName}`, ); } const updatedChildTools = existingChildTools.filter( (t) => t.name !== customToolName, ); const updatedConfig = { ...currentConfig, toolExtensions: { services: { ...extensions.services, [serverName]: { ...extensions.services[serverName], [originalToolName]: { childTools: updatedChildTools, }, }, }, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info( `Tool extension '${customToolName}' deleted from ${serverName}/${originalToolName}`, ); }); } // ==================== SERVER ATTRIBUTES ==================== async activateTargetServer(name: string): Promise<void> { return this.configService.withLock(async () => { const normalizedName = name.trim().toLowerCase(); this.logger.info( "Received ActivateTargetServer event from Control Plane", { normalizedName }, ); const currentConfig = this.configService.getConfig(); const currentAttributes = currentConfig.targetServerAttributes[normalizedName]; const updatedAttributes = currentAttributes ? { ...currentAttributes, inactive: false } : { inactive: false }; const updatedConfig = { ...currentConfig, targetServerAttributes: { ...currentConfig.targetServerAttributes, [normalizedName]: updatedAttributes, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Target server ${name} activated successfully`); }); } async deactivateTargetServer(name: string): Promise<void> { return this.configService.withLock(async () => { const normalizedName = name.trim().toLowerCase(); this.logger.info( "Received DeactivateTargetServer event from Control Plane", { normalizedName }, ); const currentConfig = this.configService.getConfig(); const currentAttributes = currentConfig.targetServerAttributes[normalizedName]; const updatedAttributes = currentAttributes ? { ...currentAttributes, inactive: true } : { inactive: true }; const updatedConfig = { ...currentConfig, targetServerAttributes: { ...currentConfig.targetServerAttributes, [normalizedName]: updatedAttributes, }, }; await this.configService.updateConfig(updatedConfig); this.logger.info(`Target server ${name} deactivated successfully`); }); } getTargetServerAttributes(): Record<string, TargetServerAttributes> { this.logger.info( "Received GetTargetServerAttributes event from Control Plane", ); const currentConfig = this.configService.getConfig(); const snapshot = currentConfig.targetServerAttributes; this.logger.info("Target server attributes retrieved successfully", { snapshot, }); return snapshot; } async removeTargetServerAttribute(name: string): Promise<void> { return this.configService.withLock(async () => { this.logger.info( "Received RemoveTargetServerAttribute event from Control Plane", { name }, ); const currentConfig = this.configService.getConfig(); const { [name]: _, ...updatedAttributes } = currentConfig.targetServerAttributes; const updatedConfig = { ...currentConfig, targetServerAttributes: updatedAttributes, }; await this.configService.updateConfig(updatedConfig); this.logger.info( `Target server ${name} removed from attributes successfully`, ); }); } } function ensureUpdatedToolGroupNamesInPermissions(props: { permissions: PermissionsConfig; toolGroupNameUpdate: { from: string; to: string }; }): PermissionsConfig { const { permissions, toolGroupNameUpdate } = props; const { from, to } = toolGroupNameUpdate; if (from === to) { // No change return permissions; } // local helper - if itemName matches 'from', change it to 'to' const changeName = (itemName: string): string => { return itemName === from ? to : itemName; }; function updatePermissionConsumerConfig( consumerConfig: ConsumerConfig, ): ConsumerConfig { // We cannot use the `_type` discriminator here because it's optional due to UI legacy needs. Pity. if ("allow" in consumerConfig) { return { ...consumerConfig, allow: consumerConfig.allow.map(changeName), }; } return { ...consumerConfig, block: consumerConfig.block.map(changeName), }; } return { default: updatePermissionConsumerConfig(permissions.default), consumers: mapValues(permissions.consumers, updatePermissionConsumerConfig), }; }

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/TheLunarCompany/lunar'

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