Skip to main content
Glama
scopes.ts10.9 kB
/** * db-mcp - OAuth Scopes * * Scope definitions and enforcement utilities for * granular access control. * * Scope Patterns: * - read : Read-only access to all databases * - write : Read and write access to all databases * - admin : Full administrative access * - db:{name} : Access to specific database only * - table:{db}:{table} : Access to specific table only */ import type { ToolGroup } from '../types/index.js'; import { TOOL_GROUPS } from '../filtering/ToolFilter.js'; // ============================================================================= // Scope Constants // ============================================================================= /** * Base scopes supported by the server */ export const BASE_SCOPES = ['read', 'write', 'admin'] as const; /** * Scope patterns (regex patterns for validation) */ export const SCOPE_PATTERNS = { /** Read-only access */ READ: 'read', /** Read and write access */ WRITE: 'write', /** Full admin access */ ADMIN: 'admin', /** Database-specific access pattern */ DATABASE: /^db:([a-zA-Z0-9_-]+)$/, /** Table-specific access pattern */ TABLE: /^table:([a-zA-Z0-9_-]+):([a-zA-Z0-9_-]+)$/ } as const; /** * All supported scope patterns for metadata */ export const SUPPORTED_SCOPES = [ 'read', 'write', 'admin', 'db:{database}', 'table:{database}:{table}' ] as const; // ============================================================================= // Scope to Tool Group Mapping // ============================================================================= /** * Tool groups accessible with read scope (read-only operations) */ export const READ_SCOPE_GROUPS: ToolGroup[] = [ 'core', // read_query, list_tables, describe_table, etc. 'monitoring' // health_check, connection_status, etc. ]; /** * Tool groups accessible with write scope (read + write operations) */ export const WRITE_SCOPE_GROUPS: ToolGroup[] = [ ...READ_SCOPE_GROUPS, 'json', // JSON operations 'text', // Text processing 'stats', // Statistical analysis 'performance', // Performance optimization 'vector', // Vector operations 'geo' // Geospatial operations ]; /** * Tool groups accessible with admin scope (all operations) */ export const ADMIN_SCOPE_GROUPS: ToolGroup[] = [ ...WRITE_SCOPE_GROUPS, 'backup', // Backup & recovery 'admin' // Administration ]; /** * Read-only tools within the core group * (used when scope is 'read' to filter write operations) */ export const READ_ONLY_TOOLS = new Set([ 'execute_query', // If used with SELECT only 'read_query', 'list_tables', 'describe_table', 'list_schemas', 'get_schema', 'health_check', 'connection_status', 'database_stats', 'active_queries', 'resource_usage', 'analyze_query', 'explain_query', 'query_plan' ]); /** * Write tools that require 'write' scope */ export const WRITE_TOOLS = new Set([ 'write_query', 'create_table', 'drop_table', 'json_insert', 'json_replace', 'json_remove', 'json_set', 'create_fts_index', 'create_vector_index', 'create_spatial_index', 'create_index', 'drop_index', 'reindex' ]); /** * Admin tools that require 'admin' scope */ export const ADMIN_TOOLS = new Set([ 'vacuum_database', 'analyze_tables', 'pragma_get', 'pragma_set', 'extension_list', 'extension_install', 'optimize', 'backup_database', 'restore_database', 'backup_table', 'export_data' ]); // ============================================================================= // Scope Parsing // ============================================================================= /** * Parse a scope string (space-delimited) into an array */ export function parseScopes(scopeString: string): string[] { return scopeString .split(/\s+/) .map(s => s.trim()) .filter(s => s.length > 0); } /** * Parse a database-specific scope * @returns The database name or null if not a database scope */ export function parseDatabaseScope(scope: string): string | null { const match = SCOPE_PATTERNS.DATABASE.exec(scope); return match?.[1] ?? null; } /** * Parse a table-specific scope * @returns Object with database and table names, or null if not a table scope */ export function parseTableScope(scope: string): { database: string; table: string } | null { const match = SCOPE_PATTERNS.TABLE.exec(scope); const database = match?.[1]; const table = match?.[2]; if (database !== undefined && table !== undefined) { return { database, table }; } return null; } // ============================================================================= // Scope Validation // ============================================================================= /** * Check if a scope is valid (matches known patterns) */ export function isValidScope(scope: string): boolean { // Check base scopes if ((BASE_SCOPES as readonly string[]).includes(scope)) { return true; } // Check database pattern if (SCOPE_PATTERNS.DATABASE.test(scope)) { return true; } // Check table pattern if (SCOPE_PATTERNS.TABLE.test(scope)) { return true; } return false; } /** * Check if scopes include admin access */ export function hasAdminScope(scopes: string[]): boolean { return scopes.includes('admin'); } /** * Check if scopes include write access */ export function hasWriteScope(scopes: string[]): boolean { return scopes.includes('write') || hasAdminScope(scopes); } /** * Check if scopes include read access */ export function hasReadScope(scopes: string[]): boolean { return scopes.includes('read') || hasWriteScope(scopes); } // ============================================================================= // Scope Enforcement // ============================================================================= /** * Check if a scope grants access to a specific tool */ export function scopeGrantsToolAccess(scope: string, toolName: string): boolean { // Admin scope grants access to all tools if (scope === 'admin') { return true; } // Write scope grants access to write tools and below if (scope === 'write') { if (ADMIN_TOOLS.has(toolName)) { return false; } return true; } // Read scope only grants read-only tools if (scope === 'read') { return READ_ONLY_TOOLS.has(toolName); } // Database/table scopes don't directly affect tool access // They are used for filtering data, not tools return false; } /** * Check if any of the scopes grants access to a tool */ export function scopesGrantToolAccess(scopes: string[], toolName: string): boolean { return scopes.some(scope => scopeGrantsToolAccess(scope, toolName)); } /** * Check if a scope grants access to a specific database */ export function scopeGrantsDatabaseAccess(scope: string, databaseName: string): boolean { // Admin and write scopes grant access to all databases if (scope === 'admin' || scope === 'write' || scope === 'read') { return true; } // Check database-specific scope const dbName = parseDatabaseScope(scope); if (dbName && dbName === databaseName) { return true; } // Check table scope (grants access to the database of the table) const tableScope = parseTableScope(scope); if (tableScope?.database === databaseName) { return true; } return false; } /** * Check if any of the scopes grants access to a database */ export function scopesGrantDatabaseAccess(scopes: string[], databaseName: string): boolean { return scopes.some(scope => scopeGrantsDatabaseAccess(scope, databaseName)); } /** * Check if a scope grants access to a specific table */ export function scopeGrantsTableAccess( scope: string, databaseName: string, tableName: string ): boolean { // Admin and write scopes grant access to all tables if (scope === 'admin' || scope === 'write' || scope === 'read') { return true; } // Database scope grants access to all tables in that database const dbName = parseDatabaseScope(scope); if (dbName && dbName === databaseName) { return true; } // Check table-specific scope const tableScope = parseTableScope(scope); if (tableScope?.database === databaseName && tableScope.table === tableName) { return true; } return false; } /** * Check if any of the scopes grants access to a table */ export function scopesGrantTableAccess( scopes: string[], databaseName: string, tableName: string ): boolean { return scopes.some(scope => scopeGrantsTableAccess(scope, databaseName, tableName)); } // ============================================================================= // Tool Group Utilities // ============================================================================= /** * Get the required minimum scope for a tool group */ export function getRequiredScopeForGroup(group: ToolGroup): string { if (ADMIN_SCOPE_GROUPS.includes(group) && !WRITE_SCOPE_GROUPS.includes(group)) { return 'admin'; } if (WRITE_SCOPE_GROUPS.includes(group) && !READ_SCOPE_GROUPS.includes(group)) { return 'write'; } return 'read'; } /** * Get the required minimum scope for a tool */ export function getRequiredScopeForTool(toolName: string): string { if (ADMIN_TOOLS.has(toolName)) { return 'admin'; } if (WRITE_TOOLS.has(toolName)) { return 'write'; } return 'read'; } /** * Get tool groups accessible with given scopes */ export function getAccessibleToolGroups(scopes: string[]): ToolGroup[] { if (hasAdminScope(scopes)) { return [...ADMIN_SCOPE_GROUPS]; } if (hasWriteScope(scopes)) { return [...WRITE_SCOPE_GROUPS]; } if (hasReadScope(scopes)) { return [...READ_SCOPE_GROUPS]; } return []; } /** * Get all tools accessible with given scopes */ export function getAccessibleTools(scopes: string[]): string[] { const groups = getAccessibleToolGroups(scopes); const allTools: string[] = []; for (const group of groups) { const groupTools = TOOL_GROUPS[group] ?? []; for (const tool of groupTools) { // For read scope, only include read-only tools if (hasReadScope(scopes) && !hasWriteScope(scopes)) { if (READ_ONLY_TOOLS.has(tool)) { allTools.push(tool); } } else { allTools.push(tool); } } } return [...new Set(allTools)]; }

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/neverinfamous/db-mcp'

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