Skip to main content
Glama

MCP Bridge Server

access.ts9.95 kB
import { AccessRule, AccessLevel, SecurityError, SecurityErrorType } from './types.js'; /** * Access control manager options */ interface AccessControlManagerOptions { rules: AccessRule[]; defaultLevel: AccessLevel; } /** * Access control manager * Handles access control rules and permissions */ export class AccessControlManager { private rules: AccessRule[]; private defaultLevel: AccessLevel; constructor(options: AccessControlManagerOptions) { this.validateRules(options.rules); this.rules = options.rules; this.defaultLevel = options.defaultLevel; } /** * Check access permission */ public checkAccess( resource: string, level: AccessLevel, context: { userId?: string; clientId?: string; machineId?: string; timestamp?: Date; ip?: string; location?: string; } ): boolean { try { // Get applicable rules const rules = this.getApplicableRules(resource, context); // If no rules match, use default level if (rules.length === 0) { return level <= this.defaultLevel; } // Check if any rule grants the required access level return rules.some(rule => { // Check basic level requirement if (level > rule.level) { return false; } // Check conditions if present if (rule.conditions) { // Time restriction if (rule.conditions.timeRestriction) { const time = context.timestamp || new Date(); const restriction = rule.conditions.timeRestriction; // Check time range if (restriction.start && restriction.end) { const start = new Date(`1970-01-01T${restriction.start}Z`); const end = new Date(`1970-01-01T${restriction.end}Z`); const current = new Date(`1970-01-01T${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}Z`); if (current < start || current > end) { return false; } } // Check days if (restriction.days && !restriction.days.includes(time.getDay())) { return false; } } // IP restriction if (rule.conditions.ipRestriction && context.ip) { if (!this.matchIpAddress(context.ip, rule.conditions.ipRestriction)) { return false; } } // Location restriction if (rule.conditions.locationRestriction && context.location) { if (!rule.conditions.locationRestriction.includes(context.location)) { return false; } } } return true; }); } catch (error) { if (error instanceof SecurityError) { throw error; } throw new SecurityError( SecurityErrorType.ACCESS_DENIED, 'Failed to check access', error ); } } /** * Add access rule */ public addRule(rule: AccessRule): void { try { // Validate rule this.validateRule(rule); // Add rule this.rules.push(rule); } catch (error) { if (error instanceof SecurityError) { throw error; } throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Failed to add rule', error ); } } /** * Remove access rule */ public removeRule(rule: AccessRule): void { this.rules = this.rules.filter(r => !(r.userId === rule.userId && r.clientId === rule.clientId && r.machineId === rule.machineId && r.level === rule.level && JSON.stringify(r.resources) === JSON.stringify(rule.resources) && JSON.stringify(r.conditions) === JSON.stringify(rule.conditions)) ); } /** * Get applicable rules for resource and context */ private getApplicableRules( resource: string, context: { userId?: string; clientId?: string; machineId?: string; } ): AccessRule[] { return this.rules.filter(rule => { // Check resource match if (rule.resources && !this.matchResource(resource, rule.resources)) { return false; } // Check identity match if (rule.userId && rule.userId !== context.userId) { return false; } if (rule.clientId && rule.clientId !== context.clientId) { return false; } if (rule.machineId && rule.machineId !== context.machineId) { return false; } return true; }); } /** * Match resource against patterns */ private matchResource(resource: string, patterns: string[]): boolean { return patterns.some(pattern => { // Convert glob pattern to regex const regex = new RegExp( '^' + pattern .replace(/\*/g, '.*') .replace(/\?/g, '.') .replace(/\[!/g, '[^') .replace(/\[/g, '[') .replace(/\]/g, ']') .replace(/\./g, '\\.') + '$' ); return regex.test(resource); }); } /** * Match IP address against patterns */ private matchIpAddress(ip: string, patterns: string[]): boolean { return patterns.some(pattern => { // Handle CIDR notation if (pattern.includes('/')) { return this.matchCidr(ip, pattern); } // Handle wildcard notation if (pattern.includes('*')) { const regex = new RegExp( '^' + pattern .replace(/\./g, '\\.') .replace(/\*/g, '\\d+') + '$' ); return regex.test(ip); } // Exact match return ip === pattern; }); } /** * Match IP address against CIDR pattern */ private matchCidr(ip: string, cidr: string): boolean { const [subnet, bits] = cidr.split('/'); const mask = parseInt(bits, 10); const ipNum = this.ipToNumber(ip); const subnetNum = this.ipToNumber(subnet); const maskNum = ~((1 << (32 - mask)) - 1); return (ipNum & maskNum) === (subnetNum & maskNum); } /** * Convert IP address to number */ private ipToNumber(ip: string): number { return ip.split('.') .reduce((num, octet) => (num << 8) + parseInt(octet, 10), 0) >>> 0; } /** * Validate access rules */ private validateRules(rules: AccessRule[]): void { if (!Array.isArray(rules)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Rules must be an array' ); } rules.forEach(rule => this.validateRule(rule)); } /** * Validate single access rule */ private validateRule(rule: AccessRule): void { // Validate level if (!Object.values(AccessLevel).includes(rule.level)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Invalid access level' ); } // Validate resources if (rule.resources) { if (!Array.isArray(rule.resources)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Resources must be an array' ); } rule.resources.forEach(resource => { if (typeof resource !== 'string') { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Resource must be a string' ); } }); } // Validate conditions if (rule.conditions) { // Time restriction if (rule.conditions.timeRestriction) { const time = rule.conditions.timeRestriction; if (time.start && !time.start.match(/^\d{2}:\d{2}:\d{2}$/)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Invalid time format for start time' ); } if (time.end && !time.end.match(/^\d{2}:\d{2}:\d{2}$/)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Invalid time format for end time' ); } if (time.days) { if (!Array.isArray(time.days)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Days must be an array' ); } time.days.forEach(day => { if (typeof day !== 'number' || day < 0 || day > 6) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Invalid day number' ); } }); } } // IP restriction if (rule.conditions.ipRestriction) { if (!Array.isArray(rule.conditions.ipRestriction)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'IP restriction must be an array' ); } rule.conditions.ipRestriction.forEach(ip => { if (typeof ip !== 'string') { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'IP must be a string' ); } // Validate IP format if (!ip.match(/^(\d{1,3}\.){3}\d{1,3}(\/\d{1,2})?$/) && !ip.match(/^(\d{1,3}|\*)(\.(\d{1,3}|\*)){3}$/)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Invalid IP format' ); } }); } // Location restriction if (rule.conditions.locationRestriction) { if (!Array.isArray(rule.conditions.locationRestriction)) { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Location restriction must be an array' ); } rule.conditions.locationRestriction.forEach(location => { if (typeof location !== 'string') { throw new SecurityError( SecurityErrorType.INVALID_CONFIG, 'Location must be a string' ); } }); } } } }

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/glassBead-tc/SubspaceDomain'

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