Skip to main content
Glama
permissions.ts7.87 kB
import { ConfigConsumer } from "@mcpx/toolkit-core/config"; import { Logger } from "winston"; import { Config } from "../model/config/config.js"; import type { ConsumerConfig, Permission, ServiceToolGroup, } from "../model/config/permissions.js"; type ServiceLevelPermission = | { tag: "allow_all" } | { tag: "block_all" } | { tag: "allow"; value: Set<string> } | { tag: "block"; value: Set<string> }; interface ConsumerLevelPermission { default: Permission; services: Map<string, ServiceLevelPermission>; } class PermissionManagerState { consumers: Map<string, ConsumerLevelPermission> = new Map(); defaultConsumer: ConsumerLevelPermission | null = null; toolGroupByName: Record<string, Record<string, ServiceToolGroup>> = {}; private _initialized = false; get initialized(): boolean { return this._initialized; } initialize(config: Config): void { if (this._initialized) { throw new Error("PermissionManagerState is already initialized"); } // Prepare toolGroups this.toolGroupByName = config.toolGroups.reduce< Record<string, Record<string, ServiceToolGroup>> >((acc, group) => { acc[group.name] = group.services; return acc; }, {}); // Initialize default consumer this.defaultConsumer = this.buildConsumerPermissions( config.permissions.default, ); // Initialize regular consumers Object.entries(config.permissions.consumers).forEach(([name, config]) => this.consumers.set(name, this.buildConsumerPermissions(config)), ); this._initialized = true; } private buildConsumerPermissions( config: ConsumerConfig, ): ConsumerLevelPermission { const services = new Map<string, ServiceLevelPermission>(); let defaultPermission: Permission; // Determine the type based on available properties or _type field const hasAllow = "allow" in config && Array.isArray(config.allow); const hasBlock = "block" in config && Array.isArray(config.block); if (hasBlock && !hasAllow) { // Has block array but no allow array - default-allow behavior this.addServices( services, "block" in config && Array.isArray(config.block) ? config.block : [], "block", ); defaultPermission = "allow"; } else if (hasAllow && !hasBlock) { // Has allow array but no block array - default-block behavior this.addServices( services, "allow" in config && Array.isArray(config.allow) ? config.allow : [], "allow", ); defaultPermission = "block"; } else if (config._type === "default-allow") { // Explicit default-allow type this.addServices( services, "block" in config && Array.isArray(config.block) ? config.block : [], "block", ); defaultPermission = "allow"; } else { // Default to default-block behavior this.addServices( services, "allow" in config && Array.isArray(config.allow) ? config.allow : [], "allow", ); defaultPermission = "block"; } return { default: defaultPermission, services }; } private addServices( services: Map<string, ServiceLevelPermission>, profileNames: string[], type: "allow" | "block", ): void { for (const profileName of profileNames) { const toolGroup = this.toolGroupByName[profileName]; if (!toolGroup) { throw new Error( `Required ToolGroup ${profileName} not found, review config`, ); } Object.entries(toolGroup).forEach(([serviceName, serviceToolGroup]) => { const current = services.get(serviceName); if (!current) { services.set( serviceName, buildServicePermissions(type, serviceToolGroup), ); } else { services.set( serviceName, mergeServicePermissions( current, buildServicePermissions(type, serviceToolGroup), ), ); } }); } } } function buildServicePermissions( type: "allow" | "block", serviceToolGroup: ServiceToolGroup, ): ServiceLevelPermission { if (typeof serviceToolGroup === "string" && serviceToolGroup === "*") { if (type === "allow") { return { tag: "allow_all" }; } else { return { tag: "block_all" }; } } else { if (type === "allow") { return { tag: "allow", value: new Set(serviceToolGroup) }; } else { return { tag: "block", value: new Set(serviceToolGroup) }; } } } export class PermissionManager implements ConfigConsumer<Config> { readonly name = "PermissionManager"; private currentState = new PermissionManagerState(); private nextState: PermissionManagerState | null = null; private logger: Logger; constructor(logger: Logger) { this.logger = logger.child({ component: "PermissionManager" }); } prepareConfig(newConfig: Config): Promise<void> { try { const nextState = new PermissionManagerState(); nextState.initialize(newConfig); // Set next state this.nextState = nextState; return Promise.resolve(); } catch (e: unknown) { return Promise.reject(e); } } async commitConfig(): Promise<void> { if (!this.nextState) { return Promise.reject(new Error("No next state to commit")); } this.currentState = this.nextState; this.nextState = null; } rollbackConfig(): void { this.logger.info("rolling back config"); this.nextState = null; } hasPermission(props: { serviceName: string; toolName: string; consumerTag?: string; }): boolean { if (!this.currentState.initialized) { throw new Error("PermissionManager is not initialized"); } const { consumerTag, serviceName, toolName } = props; const consumer = consumerTag ? this.currentState.consumers.get(consumerTag) : null; if (!consumer) { return this.getAnonymousConsumerPermission(serviceName, toolName); } const { default: consumerDefault, services } = consumer; const service = services.get(serviceName); if (!service) { return consumerDefault === "allow"; } switch (service.tag) { case "allow_all": return true; case "block_all": return false; case "allow": return service.value.has(toolName); case "block": return !service.value.has(toolName); } } private getAnonymousConsumerPermission( serviceName: string, toolName: string, ): boolean { if (!this.currentState.defaultConsumer) { throw new Error("Anonymous consumer not initialized"); } const service = this.currentState.defaultConsumer.services.get(serviceName); if (!service) { return this.currentState.defaultConsumer.default === "allow"; } switch (service.tag) { case "allow_all": return true; case "block_all": return false; case "allow": return service.value.has(toolName); case "block": return !service.value.has(toolName); } } } function mergeServicePermissions( existing: ServiceLevelPermission, current: ServiceLevelPermission, ): ServiceLevelPermission { // If either is *_all, it takes precedence if (existing.tag === "allow_all" || current.tag === "allow_all") { return { tag: "allow_all" }; } if (existing.tag === "block_all" || current.tag === "block_all") { return { tag: "block_all" }; } // Otherwise merge the sets (they must be the same type) if (existing.tag === "allow" && current.tag === "allow") { return { tag: "allow", value: new Set([...existing.value, ...current.value]), }; } // Must be both "block" return { tag: "block", value: new Set([...existing.value, ...current.value]), }; }

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