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
JavaScript
/**
* 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