@mickdarling/dollhousemcp
Version:
DollhouseMCP - A Model Context Protocol (MCP) server that enables dynamic AI persona management from markdown files, allowing Claude and other compatible AI assistants to activate and switch between different behavioral personas.
98 lines • 13.6 kB
JavaScript
/**
* GitHub API client for marketplace integration
*/
import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js';
import { SECURITY_LIMITS } from '../security/constants.js';
import { SecureTokenManager, TokenScope } from '../security/tokenManager.js';
export class GitHubClient {
apiCache;
rateLimitTracker;
constructor(apiCache, rateLimitTracker) {
this.apiCache = apiCache;
this.rateLimitTracker = rateLimitTracker;
}
/**
* Check rate limit for API calls
*/
checkRateLimit(key = 'default') {
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) {
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 = {
'Accept': 'application/vnd.github.v3+json',
'User-Agent': 'DollhouseMCP/1.0'
};
// Use SecureTokenManager for token handling if token is available
if (process.env.GITHUB_TOKEN) {
try {
const token = await SecureTokenManager.getSecureGitHubToken(TokenScope.READ);
headers['Authorization'] = `Bearer ${token}`;
}
catch (tokenError) {
// Log error but continue without token
console.log('GitHub token validation failed, proceeding without authentication');
}
}
// 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) {
throw new Error('GitHub API rate limit exceeded. Consider setting GITHUB_TOKEN environment variable.');
}
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) {
// Preserve original error information with proper error chaining
const errorMessage = error instanceof Error ? error.message : String(error);
const errorDetails = {
originalMessage: errorMessage,
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: ${errorMessage}`, errorDetails);
// Also preserve original error for debugging
mcpError.cause = error;
throw mcpError;
}
}
}
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiR2l0SHViQ2xpZW50LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL21hcmtldHBsYWNlL0dpdEh1YkNsaWVudC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQTs7R0FFRztBQUVILE9BQU8sRUFBRSxRQUFRLEVBQUUsU0FBUyxFQUFFLE1BQU0sb0NBQW9DLENBQUM7QUFFekUsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQzNELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxVQUFVLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUU3RSxNQUFNLE9BQU8sWUFBWTtJQUNmLFFBQVEsQ0FBVztJQUNuQixnQkFBZ0IsQ0FBd0I7SUFFaEQsWUFBWSxRQUFrQixFQUFFLGdCQUF1QztRQUNyRSxJQUFJLENBQUMsUUFBUSxHQUFHLFFBQVEsQ0FBQztRQUN6QixJQUFJLENBQUMsZ0JBQWdCLEdBQUcsZ0JBQWdCLENBQUM7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ0ssY0FBYyxDQUFDLE1BQWMsU0FBUztRQUM1QyxNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7UUFDdkIsTUFBTSxRQUFRLEdBQUcsSUFBSSxDQUFDLGdCQUFnQixDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLENBQUM7UUFFdEQscUNBQXFDO1FBQ3JDLE1BQU0sYUFBYSxHQUFHLFFBQVEsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxHQUFHLEdBQUcsSUFBSSxHQUFHLGVBQWUsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBRWpHLElBQUksYUFBYSxDQUFDLE1BQU0sSUFBSSxlQUFlLENBQUMsbUJBQW1CLEVBQUUsQ0FBQztZQUNoRSxNQUFNLElBQUksS0FBSyxDQUFDLDRCQUE0QixlQUFlLENBQUMsbUJBQW1CLHVCQUF1QixDQUFDLENBQUM7UUFDMUcsQ0FBQztRQUVELGFBQWEsQ0FBQyxJQUFJ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LDBDQUEwQztnQkFDMUMsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLFlBQVksRUFBRSxDQUFDO29CQUNoQyxZQUFZLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQztnQkFDOUIsQ0FBQztZQUNILENBQUM7WUFFRCxNQUFNLFFBQVEsR0FBRyxJQUFJLFFBQVEsQ0FDM0IsU0FBUyxDQUFDLGFBQWEsRUFDdkIsZ0NBQWdDLFlBQVksRUFBRSxFQUM5QyxZQUFZLENBQ2IsQ0FBQztZQUVGLDZDQUE2QztZQUM1QyxRQUFnQixDQUFDLEtBQUssR0FBRyxLQUFLLENBQUM7WUFFaEMsTUFBTSxRQUFRLENBQUM7UUFDakIsQ0FBQztJQUNILENBQUM7Q0FDRiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogR2l0SHViIEFQSSBjbGllbnQgZm9yIG1hcmtldHBsYWNlIGludGVncmF0aW9uXG4gKi9cblxuaW1wb3J0IHsgTWNwRXJyb3IsIEVycm9yQ29kZSB9IGZyb20gJ0Btb2RlbGNvbnRleHRwcm90b2NvbC9zZGsvdHlwZXMuanMnO1xuaW1wb3J0IHsgQVBJQ2FjaGUgfSBmcm9tICcuLi9jYWNoZS9BUElDYWNoZS5qcyc7XG5pbXBvcnQgeyBTRUNVUklUWV9MSU1JVFMgfSBmcm9tICcuLi9zZWN1cml0eS9jb25zdGFudHMuanMnO1xuaW1wb3J0IHsgU2VjdXJlVG9rZW5NYW5hZ2VyLCBUb2tlblNjb3BlIH0gZnJvbSAnLi4vc2VjdXJpdHkvdG9rZW5NYW5hZ2VyLmpzJztcblxuZXhwb3J0IGNsYXNzIEdpdEh1YkNsaWVudCB7XG4gIHByaXZhdGUgYXBpQ2FjaGU6IEFQSUNhY2hlO1xuICBwcml2YXRlIHJhdGVMaW1pdFRyYWNrZXI6IE1hcDxzdHJpbmcsIG51bWJlcltdPjtcbiAgXG4gIGNvbnN0cnVjdG9yKGFwaUNhY2hlOiBBUElDYWNoZSwgcmF0ZUxpbWl0VHJhY2tlcjogTWFwPHN0cmluZywgbnVtYmVyW10+KSB7XG4gICAgdGhpcy5hcGlDYWNoZSA9IGFwaUNhY2hlO1xuICAgIHRoaXMucmF0ZUxpbWl0VHJhY2tlciA9IHJhdGVMaW1pdFRyYWNrZXI7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBDaGVjayByYXRlIGxpbWl0IGZvciBBUEkgY2FsbHNcbiAgICovXG4gIHByaXZhdGUgY2hlY2tSYXRlTGltaXQoa2V5OiBzdHJpbmcgPSAnZGVmYXVsdCcpOiB2b2lkIHtcbiAgICBjb25zdCBub3cgPSBEYXRlLm5vdygpO1xuICAgIGNvbnN0IHJlcXVlc3RzID0gdGhpcy5yYXRlTGltaXRUcmFja2VyLmdldChrZXkpIHx8IFtdO1xuICAgIFxuICAgIC8vIFJlbW92ZSByZXF1ZXN0cyBvdXRzaWRlIHRoZSB3aW5kb3dcbiAgICBjb25zdCB2YWxpZFJlcXVlc3RzID0gcmVxdWVzdHMuZmlsdGVyKHRpbWUgPT4gbm93IC0gdGltZSA8IFNFQ1VSSVRZX0xJTUlUUy5SQVRFX0xJTUlUX1dJTkRPV19NUyk7XG4gICAgXG4gICAgaWYgKHZhbGlkUmVxdWVzdHMubGVuZ3RoID49IFNFQ1VSSVRZX0xJTUlUUy5SQVRFX0xJTUlUX1JFUVVFU1RTKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoYFJhdGUgbGltaXQgZXhjZWVkZWQuIE1heCAke1NFQ1VSSVRZX0xJTUlUUy5SQVRFX0xJTUlUX1JFUVVFU1RTfSByZXF1ZXN0cyBwZXIgbWludXRlLmApO1xuICAgIH1cbiAgICBcbiAgICB2YWxpZFJlcXVlc3RzLnB1c2gobm93KTtcbiAgICB0aGlzLnJhdGVMaW1pdFRyYWNrZXIuc2V0KGtleSwgdmFsaWRSZXF1ZXN0cyk7XG4gIH1cbiAgXG4gIC8qKlxuICAgKiBGZXRjaCBkYXRhIGZyb20gR2l0SHViIEFQSSB3aXRoIGNhY2hpbmcgYW5kIHJhdGUgbGltaXRpbmdcbiAgICovXG4gIGFzeW5jIGZldGNoRnJvbUdpdEh1Yih1cmw6IHN0cmluZyk6IFByb21pc2U8YW55PiB7XG4gICAgdHJ5IHtcbiAgICAgIC8vIENoZWNrIHJhdGUgbGltaXRcbiAgICAgIHRoaXMuY2hlY2tSYXRlTGltaXQoJ2dpdGh1Yl9hcGknKTtcbiAgICAgIFxuICAgICAgLy8gQ2hlY2sgY2FjaGUgZmlyc3RcbiAgICAgIGNvbnN0IGNhY2hlZCA9IHRoaXMuYXBpQ2FjaGUuZ2V0KHVybCk7XG4gICAgICBpZiAoY2FjaGVkKSB7XG4gICAgICAgIHJldHVybiBjYWNoZWQ7XG4gICAgICB9XG4gICAgICBcbiAgICAgIC8vIEFkZCBHaXRIdWIgdG9rZW4gaWYgYXZhaWxhYmxlIGZvciBoaWdoZXIgcmF0ZSBsaW1pdHNcbiAgICAgIGNvbnN0IGhlYWRlcnM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4gPSB7XG4gICAgICAgICdBY2NlcHQnOiAnYXBwbGljYXRpb24vdm5kLmdpdGh1Yi52Mytqc29uJyxcbiAgICAgICAgJ1VzZXItQWdlbnQnOiAnRG9sbGhvdXNlTUNQLzEuMCdcbiAgICAgIH07XG4gICAgICBcbiAgICAgIC8vIFVzZSBTZWN1cmVUb2tlbk1hbmFnZXIgZm9yIHRva2VuIGhhbmRsaW5nIGlmIHRva2VuIGlzIGF2YWlsYWJsZVxuICAgICAgaWYgKHByb2Nlc3MuZW52LkdJVEhVQl9UT0tFTikge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IHRva2VuID0gYXdhaXQgU2VjdXJlVG9rZW5NYW5hZ2VyLmdldFNlY3VyZUdpdEh1YlRva2VuKFRva2VuU2NvcGUuUkVBRCk7XG4gICAgICAgICAgaGVhZGVyc1snQXV0aG9yaXphdGlvbiddID0gYEJlYXJlciAke3Rva2VufWA7XG4gICAgICAgIH0gY2F0Y2ggKHRva2VuRXJyb3IpIHtcbiAgICAgICAgICAvLyBMb2cgZXJyb3IgYnV0IGNvbnRpbnVlIHdpdGhvdXQgdG9rZW5cbiAgICAgICAgICBjb25zb2xlLmxvZygnR2l0SHViIHRva2VuIHZhbGlkYXRpb24gZmFpbGVkLCBwcm9jZWVkaW5nIHdpdGhvdXQgYXV0aGVudGljYXRpb24nKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgXG4gICAgICAvLyBDcmVhdGUgZmV0Y2ggd2l0aCB0aW1lb3V0XG4gICAgICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICAgICAgY29uc3QgdGltZW91dElkID0gc2V0VGltZW91dCgoKSA9PiBjb250cm9sbGVyLmFib3J0KCksIDEwMDAwKTsgLy8gMTAgc2Vjb25kIHRpbWVvdXRcbiAgICAgIFxuICAgICAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBmZXRjaCh1cmwsIHtcbiAgICAgICAgaGVhZGVycyxcbiAgICAgICAgc2lnbmFsOiBjb250cm9sbGVyLnNpZ25hbFxuICAgICAgfSk7XG4gICAgICBcbiAgICAgIGNsZWFyVGltZW91dCh0aW1lb3V0SWQpO1xuICAgICAgXG4gICAgICBpZiAoIXJlc3BvbnNlLm9rKSB7XG4gICAgICAgIGlmIChyZXNwb25zZS5zdGF0dXMgPT09IDQwMykge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcignR2l0SHViIEFQSSByYXRlIGxpbWl0IGV4Y2VlZGVkLiBDb25zaWRlciBzZXR0aW5nIEdJVEhVQl9UT0tFTiBlbnZpcm9ubWVudCB2YXJpYWJsZS4nKTtcbiAgICAgICAgfVxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoYEdpdEh1YiBBUEkgZXJyb3I6ICR7cmVzcG9uc2Uuc3RhdHVzfSAke3Jlc3BvbnNlLnN0YXR1c1RleHR9YCk7XG4gICAgICB9XG4gICAgICBcbiAgICAgIGNvbnN0IGRhdGEgPSBhd2FpdCByZXNwb25zZS5qc29uKCk7XG4gICAgICBcbiAgICAgIC8vIENhY2hlIHRoZSBzdWNjZXNzZnVsIHJlc3BvbnNlXG4gICAgICB0aGlzLmFwaUNhY2hlLnNldCh1cmwsIGRhdGEpO1xuICAgICAgXG4gICAgICByZXR1cm4gZGF0YTtcbiAgICB9IGNhdGNoIChlcnJvcikge1xuICAgICAgLy8gUHJlc2VydmUgb3JpZ2luYWwgZXJyb3IgaW5mb3JtYXRpb24gd2l0aCBwcm9wZXIgZXJyb3IgY2hhaW5pbmdcbiAgICAgIGNvbnN0IGVycm9yTWVzc2FnZSA9IGVycm9yIGluc3RhbmNlb2YgRXJyb3IgPyBlcnJvci5tZXNzYWdlIDogU3RyaW5nKGVycm9yKTtcbiAgICAgIGNvbnN0IGVycm9yRGV0YWlsczogYW55ID0ge1xuICAgICAgICBvcmlnaW5hbE1lc3NhZ2U6IGVycm9yTWVzc2FnZSxcbiAgICAgICAgdXJsXG4gICAgICB9O1xuICAgICAgXG4gICAgICAvLyBQcmVzZXJ2ZSBzdGFjayB0cmFjZSBhbmQgZXJyb3IgdHlwZSBpbmZvcm1hdGlvblxuICAgICAgaWYgKGVycm9yIGluc3RhbmNlb2YgRXJyb3IpIHtcbiAgICAgICAgZXJyb3JEZXRhaWxzLmVycm9yVHlwZSA9IGVycm9yLmNvbnN0cnVjdG9yLm5hbWU7XG4gICAgICAgIGVycm9yRGV0YWlscy5zdGFjayA9IGVycm9yLnN0YWNrO1xuICAgICAgICBcbiAgICAgICAgLy8gU3BlY2lhbCBoYW5kbGluZyBmb3IgY29tbW9uIGVycm9yIHR5cGVzXG4gICAgICAgIGlmIChlcnJvci5uYW1lID09PSAnQWJvcnRFcnJvcicpIHtcbiAgICAgICAgICBlcnJvckRldGFpbHMudGltZW91dCA9IHRydWU7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIFxuICAgICAgY29uc3QgbWNwRXJyb3IgPSBuZXcgTWNwRXJyb3IoXG4gICAgICAgIEVycm9yQ29kZS5JbnRlcm5hbEVycm9yLFxuICAgICAgICBgRmFpbGVkIHRvIGZldGNoIGZyb20gR2l0SHViOiAke2Vycm9yTWVzc2FnZX1gLFxuICAgICAgICBlcnJvckRldGFpbHNcbiAgICAgICk7XG4gICAgICBcbiAgICAgIC8vIEFsc28gcHJlc2VydmUgb3JpZ2luYWwgZXJyb3IgZm9yIGRlYnVnZ2luZ1xuICAgICAgKG1jcEVycm9yIGFzIGFueSkuY2F1c2UgPSBlcnJvcjtcbiAgICAgIFxuICAgICAgdGhyb3cgbWNwRXJyb3I7XG4gICAgfVxuICB9XG59Il19