Skip to main content
Glama
OAuthResourceServer.ts4.23 kB
/** * db-mcp - OAuth Protected Resource Server (RFC 9728) * * Implements the OAuth 2.0 Protected Resource Metadata endpoint * as specified in RFC 9728. * * @see https://datatracker.ietf.org/doc/html/rfc9728 */ import type { RequestHandler } from 'express'; import type { ProtectedResourceMetadata, ResourceServerConfig } from './types.js'; import { SUPPORTED_SCOPES } from './scopes.js'; import { createModuleLogger } from '../utils/logger.js'; const logger = createModuleLogger('AUTH'); // ============================================================================= // OAuth Resource Server // ============================================================================= /** * OAuth 2.0 Protected Resource Server * * Provides Protected Resource Metadata (RFC 9728) for MCP authorization. */ export class OAuthResourceServer { private readonly config: Required<ResourceServerConfig>; private metadata: ProtectedResourceMetadata | null = null; constructor(config: ResourceServerConfig) { this.config = { resource: config.resource, authorizationServers: config.authorizationServers, scopesSupported: config.scopesSupported.length > 0 ? config.scopesSupported : [...SUPPORTED_SCOPES], bearerMethodsSupported: config.bearerMethodsSupported ?? ['header'] }; logger.info('INIT', `OAuth Resource Server initialized for: ${this.config.resource}`); } /** * Get the Protected Resource Metadata document * * @returns RFC 9728 compliant metadata */ getMetadata(): ProtectedResourceMetadata { this.metadata ??= this.buildMetadata(); return this.metadata; } /** * Build the Protected Resource Metadata document */ private buildMetadata(): ProtectedResourceMetadata { return { resource: this.config.resource, authorization_servers: this.config.authorizationServers, scopes_supported: this.config.scopesSupported, bearer_methods_supported: this.config.bearerMethodsSupported }; } /** * Get Express request handler for the metadata endpoint * * Serves: GET /.well-known/oauth-protected-resource */ getMetadataHandler(): RequestHandler { return (_req, res) => { const metadata = this.getMetadata(); res.setHeader('Content-Type', 'application/json'); res.setHeader('Cache-Control', 'public, max-age=3600'); res.json(metadata); logger.info('METADATA_SERVED', 'Protected Resource Metadata served'); }; } /** * Generate WWW-Authenticate header for 401 responses * * @param error - Error type for the header * @param errorDescription - Human-readable error description * @returns WWW-Authenticate header value */ getWWWAuthenticateHeader(error?: string, errorDescription?: string): string { const parts = [`Bearer realm="${this.config.resource}"`]; if (error) { parts.push(`error="${error}"`); } if (errorDescription) { parts.push(`error_description="${errorDescription}"`); } return parts.join(', '); } /** * Get the resource URI */ getResourceUri(): string { return this.config.resource; } /** * Get the authorization servers */ getAuthorizationServers(): string[] { return [...this.config.authorizationServers]; } /** * Get supported scopes */ getSupportedScopes(): string[] { return [...this.config.scopesSupported]; } /** * Clear cached metadata (useful when configuration changes) */ clearCache(): void { this.metadata = null; } } // ============================================================================= // Factory Function // ============================================================================= /** * Create an OAuth Resource Server instance */ export function createOAuthResourceServer(config: ResourceServerConfig): OAuthResourceServer { return new OAuthResourceServer(config); }

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