Skip to main content
Glama

DollhouseMCP

by DollhouseMCP
GitHubClient.tsโ€ข5.54 kB
/** * GitHub API client for collection integration */ import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { APICache } from '../cache/APICache.js'; import { SECURITY_LIMITS } from '../security/constants.js'; import { TokenManager, TokenScopes } from '../security/tokenManager.js'; import { logger } from '../utils/logger.js'; export class GitHubClient { private apiCache: APICache; private rateLimitTracker: Map<string, number[]>; constructor(apiCache: APICache, rateLimitTracker: Map<string, number[]>) { this.apiCache = apiCache; this.rateLimitTracker = rateLimitTracker; } /** * Check rate limit for API calls */ private checkRateLimit(key: string = 'default'): void { const now = Date.now(); const requests = this.rateLimitTracker.get(key) || []; // Remove requests outside the window const validRequests = requests.filter(time => now - time < SECURITY_LIMITS.RATE_LIMIT_WINDOW_MS); if (validRequests.length >= SECURITY_LIMITS.RATE_LIMIT_REQUESTS) { throw new Error(`Rate limit exceeded. Max ${SECURITY_LIMITS.RATE_LIMIT_REQUESTS} requests per minute.`); } validRequests.push(now); this.rateLimitTracker.set(key, validRequests); } /** * Fetch data from GitHub API with caching and rate limiting */ async fetchFromGitHub(url: string, requireAuth: boolean = false): Promise<any> { try { // Check rate limit this.checkRateLimit('github_api'); // Check cache first const cached = this.apiCache.get(url); if (cached) { return cached; } // Add GitHub token if available for higher rate limits const headers: Record<string, string> = { 'Accept': 'application/vnd.github.v3+json', 'User-Agent': 'DollhouseMCP/1.0' }; // Use TokenManager for secure token handling // CRITICAL FIX #471: Must use async method to retrieve OAuth tokens from secure storage // The OAuth flow (setup_github_auth) stores tokens in secure OS keychain/credential store // which requires async access. The sync method only checks environment variables, // missing tokens stored by the OAuth authentication flow. const token = await TokenManager.getGitHubTokenAsync(); if (token) { headers['Authorization'] = `Bearer ${token}`; } else if (requireAuth) { throw new Error('GitHub authentication required but no valid token available. Please use setup_github_auth or set GITHUB_TOKEN environment variable.'); } // Create fetch with timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout const response = await fetch(url, { headers, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { if (response.status === 403) { const errorMsg = token ? 'GitHub API rate limit exceeded or token lacks required permissions.' : 'GitHub API rate limit exceeded. Consider using setup_github_auth or setting GITHUB_TOKEN environment variable.'; throw new Error(errorMsg); } if (response.status === 401) { throw new Error('GitHub API authentication failed. Please check your GITHUB_TOKEN.'); } // Provide helpful error messages based on status code if (response.status === 404) { throw new Error(`File not found in collection. Try using search to get the correct path: search_collection_enhanced "your-search-term"`); } throw new Error(`GitHub API error: ${response.status} ${response.statusText}`); } const data = await response.json(); // Cache the successful response this.apiCache.set(url, data); return data; } catch (error) { // Use TokenManager for safe error handling const errorMessage = error instanceof Error ? error.message : String(error); const safeMessage = TokenManager.createSafeErrorMessage(errorMessage); const errorDetails: any = { originalMessage: safeMessage, url }; // Preserve stack trace and error type information if (error instanceof Error) { errorDetails.errorType = error.constructor.name; errorDetails.stack = error.stack; // Special handling for common error types if (error.name === 'AbortError') { errorDetails.timeout = true; } } const mcpError = new McpError( ErrorCode.InternalError, `Failed to fetch from GitHub: ${safeMessage}`, errorDetails ); // Also preserve original error for debugging (mcpError as any).cause = error; throw mcpError; } } /** * Validate token permissions for collection operations */ async validateCollectionPermissions(): Promise<void> { // NOTE: Using 'marketplace' scope for backward compatibility with TokenManager. // This is an internal implementation detail that doesn't affect functionality. (PR #280) const validation = await TokenManager.ensureTokenPermissions('marketplace'); if (!validation.isValid) { const safeMessage = TokenManager.createSafeErrorMessage(validation.error || 'Unknown validation error'); throw new Error(`GitHub token validation failed: ${safeMessage}`); } } }

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/DollhouseMCP/DollhouseMCP'

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