UNPKG

bottlenecks-mcp-server

Version:

Model Context Protocol server for Bottlenecks database - enables AI agents like Claude to interact with bottleneck data

170 lines 5.65 kB
/** * OAuth Client for MCP Authentication * Handles API key validation and HTTP requests to Vercel-deployed APIs */ export class MCPOAuthClient { config; apiKey; constructor(config, apiKey) { this.config = config; this.apiKey = apiKey; } /** * Set API key for authenticated requests */ setApiKey(apiKey) { this.apiKey = apiKey; } /** * Get the API base URL for constructing links */ getApiBaseUrl() { return this.config.apiBaseUrl; } /** * Make authenticated HTTP request to API */ async request(endpoint, options = {}) { const { method = 'GET', body, headers = {}, requireAuth = true } = options; // Build URL const url = `${this.config.apiBaseUrl}${endpoint}`; // Prepare headers const requestHeaders = { 'Content-Type': 'application/json', 'User-Agent': 'Bottlenecks-MCP/1.0.0', ...headers, }; // Add authentication if required if (requireAuth) { if (!this.apiKey) { return { success: false, error: 'API key required for authenticated requests. Use OAuth flow to obtain an API key.', data: null, }; } requestHeaders['Authorization'] = `Bearer ${this.apiKey}`; } try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), this.config.defaultTimeout || 30000); const response = await fetch(url, { method, headers: requestHeaders, body: body ? JSON.stringify(body) : undefined, signal: controller.signal, }); clearTimeout(timeoutId); let data; try { const responseText = await response.text(); if (responseText.trim()) { data = JSON.parse(responseText); } else { data = {}; } } catch (parseError) { return { success: false, error: `Invalid JSON response: ${parseError instanceof Error ? parseError.message : 'Unknown parsing error'}`, data: null, }; } if (!response.ok) { return { success: false, error: data?.error || `HTTP ${response.status}: ${response.statusText}`, data: null, }; } return { success: true, data: data, error: null, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Unknown error occurred', data: null, }; } } /** * Validate API key with the server */ async validateApiKey() { if (!this.apiKey) { return false; } try { const response = await this.request('/api/auth/mcp/keys', { method: 'GET', requireAuth: true, }); return response.success; } catch { return false; } } /** * Get user information from API key */ async getUserInfo() { return this.request('/api/auth/session', { method: 'GET', requireAuth: true, }); } /** * Check if user has required permissions * * Instead of trying to query the keys endpoint (which requires session auth), * we'll just skip permission checking and let the actual API calls handle validation. * This is more reliable since the API endpoints already have proper scope validation. */ async checkPermissions(scopes) { // For MCP API key authentication, we trust that the API endpoints // will properly validate scopes when the actual requests are made. // This avoids the chicken-and-egg problem of needing session auth // to check API key permissions. return true; } /** * Handle rate limiting and retries */ async requestWithRetry(endpoint, options = {}, maxRetries = 3) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await this.request(endpoint, options); // If rate limited, wait and retry if (!response.success && response.error?.includes('rate limit')) { const waitTime = Math.pow(2, attempt) * 1000; // Exponential backoff await new Promise((resolve) => setTimeout(resolve, waitTime)); continue; } return response; } catch (error) { lastError = error; if (attempt === maxRetries) { break; } // Wait before retry const waitTime = Math.pow(2, attempt) * 1000; await new Promise((resolve) => setTimeout(resolve, waitTime)); } } return { success: false, error: `Request failed after ${maxRetries} attempts: ${lastError?.message || 'Unknown error'}`, data: null, }; } } //# sourceMappingURL=oauth-client.js.map