/**
* postgres-mcp - OAuth Scopes
*
* Scope definitions and utilities for PostgreSQL MCP OAuth 2.0.
*/
import type { ToolGroup } from '../types/index.js';
// =============================================================================
// Scope Definitions
// =============================================================================
/**
* Standard OAuth scopes for postgres-mcp
*/
export const SCOPES = {
/** Read-only access to all databases */
READ: 'read',
/** Read and write access to all databases */
WRITE: 'write',
/** Administrative access (VACUUM, ANALYZE, etc.) */
ADMIN: 'admin',
/** Full access to all operations */
FULL: 'full'
} as const;
export type StandardScope = typeof SCOPES[keyof typeof SCOPES];
/**
* All supported scopes including patterns
*/
export const ALL_SCOPES = [
SCOPES.READ,
SCOPES.WRITE,
SCOPES.ADMIN,
SCOPES.FULL,
// Pattern scopes: db:{name}, schema:{name}, table:{schema}:{table}
] as const;
// =============================================================================
// Tool Group to Scope Mapping
// =============================================================================
/**
* Map PostgreSQL tool groups to required minimum scopes
*/
export const TOOL_GROUP_SCOPES: Record<ToolGroup, StandardScope> = {
// Core database operations
core: SCOPES.READ,
transactions: SCOPES.WRITE,
jsonb: SCOPES.READ,
text: SCOPES.READ,
stats: SCOPES.READ,
// Performance and monitoring
performance: SCOPES.READ,
monitoring: SCOPES.READ,
// Administrative operations
admin: SCOPES.ADMIN,
backup: SCOPES.ADMIN,
schema: SCOPES.READ,
partitioning: SCOPES.ADMIN,
// Extensions
vector: SCOPES.READ,
postgis: SCOPES.READ,
cron: SCOPES.ADMIN,
partman: SCOPES.ADMIN,
kcache: SCOPES.READ,
citext: SCOPES.READ,
ltree: SCOPES.READ,
pgcrypto: SCOPES.READ,
// Code Mode (requires admin - can execute arbitrary operations)
codemode: SCOPES.ADMIN
};
// =============================================================================
// Scope Utilities
// =============================================================================
/**
* Parse scope string into array
*/
export function parseScopes(scopeString: string | undefined): string[] {
if (!scopeString) return [];
return scopeString.split(' ').filter(s => s.length > 0);
}
/**
* Check if granted scopes include the required scope
*/
export function hasScope(grantedScopes: string[], requiredScope: string): boolean {
// Full scope grants everything
if (grantedScopes.includes(SCOPES.FULL)) {
return true;
}
// Direct match
if (grantedScopes.includes(requiredScope)) {
return true;
}
// Admin scope includes write and read
if (requiredScope === SCOPES.READ || requiredScope === SCOPES.WRITE) {
if (grantedScopes.includes(SCOPES.ADMIN)) {
return true;
}
}
// Write scope includes read
if (requiredScope === SCOPES.READ) {
if (grantedScopes.includes(SCOPES.WRITE)) {
return true;
}
}
return false;
}
/**
* Check if granted scopes include any of the required scopes
*/
export function hasAnyScope(grantedScopes: string[], requiredScopes: string[]): boolean {
return requiredScopes.some(scope => hasScope(grantedScopes, scope));
}
/**
* Check if granted scopes include all of the required scopes
*/
export function hasAllScopes(grantedScopes: string[], requiredScopes: string[]): boolean {
return requiredScopes.every(scope => hasScope(grantedScopes, scope));
}
/**
* Get the required scope for a tool group
*/
export function getScopeForToolGroup(group: ToolGroup): StandardScope {
return TOOL_GROUP_SCOPES[group] ?? SCOPES.READ;
}
/**
* Check if database-specific scope matches
*/
export function hasDatabaseScope(grantedScopes: string[], database: string): boolean {
// Full or admin grants all databases
if (grantedScopes.includes(SCOPES.FULL) || grantedScopes.includes(SCOPES.ADMIN)) {
return true;
}
// Check for db:{name} pattern
const dbScope = `db:${database}`;
return grantedScopes.includes(dbScope);
}
/**
* Check if schema-specific scope matches
*/
export function hasSchemaScope(grantedScopes: string[], schema: string): boolean {
// Full or admin grants all schemas
if (grantedScopes.includes(SCOPES.FULL) || grantedScopes.includes(SCOPES.ADMIN)) {
return true;
}
// Check for schema:{name} pattern
const schemaScope = `schema:${schema}`;
return grantedScopes.includes(schemaScope);
}
/**
* Check if table-specific scope matches
*/
export function hasTableScope(grantedScopes: string[], schema: string, table: string): boolean {
// Full or admin grants all tables
if (grantedScopes.includes(SCOPES.FULL) || grantedScopes.includes(SCOPES.ADMIN)) {
return true;
}
// Check for schema:{name} scope (grants access to all tables in schema)
if (hasSchemaScope(grantedScopes, schema)) {
return true;
}
// Check for table:{schema}:{table} pattern
const tableScope = `table:${schema}:${table}`;
return grantedScopes.includes(tableScope);
}
/**
* Get scope display name
*/
export function getScopeDisplayName(scope: string): string {
switch (scope) {
case SCOPES.READ: return 'Read Only';
case SCOPES.WRITE: return 'Read/Write';
case SCOPES.ADMIN: return 'Administrative';
case SCOPES.FULL: return 'Full Access';
default:
if (scope.startsWith('db:')) {
return `Database: ${scope.slice(3)}`;
}
if (scope.startsWith('schema:')) {
return `Schema: ${scope.slice(7)}`;
}
if (scope.startsWith('table:')) {
return `Table: ${scope.slice(6)}`;
}
return scope;
}
}