Skip to main content
Glama
test-rate-limits.js8.49 kB
#!/usr/bin/env node /** * NCBI API Rate Limit Testing Script * * This script tests the NCBI E-utilities rate limits: * - Without API key: 3 requests per second maximum * - With API key: 10 requests per second maximum * * The script uses the MCP tools directly to make requests and measure response times. */ // Set up environment const TEST_WITHOUT_API_KEY = true; const TEST_WITH_API_KEY = true; // NCBI API key for testing (set this to your actual API key) const NCBI_API_KEY = process.env.NCBI_API_KEY || null; class NCBIRateLimitTester { constructor() { this.baseUrl = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/"; this.defaultEmail = "entrez-mcp-server@example.com"; this.defaultTool = "entrez-mcp-server"; this.results = []; } /** * Build URL with optional API key */ buildUrl(endpoint, params, includeApiKey) { const cleanParams = new URLSearchParams(); params.forEach((value, key) => { if (value && value.trim() !== "") { cleanParams.append(key, value.trim()); } }); if (includeApiKey && NCBI_API_KEY) { cleanParams.append("api_key", NCBI_API_KEY); } return `${this.baseUrl}${endpoint}?${cleanParams}`; } /** * Make a single ESearch request */ async makeRequest(query, includeApiKey = false) { const params = new URLSearchParams({ db: "pubmed", term: query, retmax: "1", tool: this.defaultTool, email: this.defaultEmail, retmode: "json", }); const url = this.buildUrl("esearch.fcgi", params, includeApiKey); const startTime = Date.now(); try { const response = await fetch(url); const endTime = Date.now(); const responseTime = endTime - startTime; const data = await response.text(); // Check for rate limiting errors const isRateLimited = response.status === 429 || data.includes("too many requests") || data.includes("rate limit") || data.includes("Too Many Requests") || data.includes("Retry-After"); return { status: response.status, responseTime, isRateLimited, success: response.ok && !isRateLimited, timestamp: startTime, hasApiKey: includeApiKey && !!NCBI_API_KEY, }; } catch (error) { const endTime = Date.now(); return { status: "ERROR", responseTime: endTime - startTime, isRateLimited: false, success: false, error: error.message, timestamp: startTime, hasApiKey: includeApiKey && !!NCBI_API_KEY, }; } } /** * Test rate limiting at a specific rate */ async testRate(requestsPerSecond, duration, includeApiKey = false) { const intervalMs = 1000 / requestsPerSecond; const totalRequests = Math.floor(duration * requestsPerSecond); console.log( `\n🧪 Testing ${requestsPerSecond} requests/second for ${duration} seconds`, ); console.log( ` API Key: ${includeApiKey && NCBI_API_KEY ? "✅ Enabled" : "❌ Disabled"}`, ); console.log(` Interval: ${intervalMs}ms between requests`); console.log(` Total requests: ${totalRequests}`); const results = []; const testQueries = [ "covid-19", "diabetes", "cancer research", "alzheimer", "heart disease", "machine learning", "artificial intelligence", "genomics", "proteomics", "bioinformatics", ]; for (let i = 0; i < totalRequests; i++) { const query = testQueries[i % testQueries.length] + ` test ${i}`; const requestPromise = this.makeRequest(query, includeApiKey); results.push(requestPromise); // Progress indicator if (i > 0 && i % 5 === 0) { process.stdout.write(`📡 ${i}/${totalRequests} requests sent...\r`); } // Wait for the interval (except for the last request) if (i < totalRequests - 1) { await new Promise((resolve) => setTimeout(resolve, intervalMs)); } } console.log(`\n⏳ Waiting for all ${totalRequests} responses...`); const responses = await Promise.all(results); // Analyze results const successful = responses.filter((r) => r.success).length; const rateLimited = responses.filter((r) => r.isRateLimited).length; const errors = responses.filter((r) => r.status === "ERROR").length; const avgResponseTime = responses.reduce((sum, r) => sum + r.responseTime, 0) / responses.length; const testResult = { requestsPerSecond, duration, totalRequests, successful, rateLimited, errors, successRate: (successful / totalRequests) * 100, avgResponseTime: Math.round(avgResponseTime), includeApiKey: includeApiKey && !!NCBI_API_KEY, responses, }; this.results.push(testResult); console.log(`\n📊 Results:`); console.log( ` ✅ Successful: ${successful}/${totalRequests} (${testResult.successRate.toFixed(1)}%)`, ); console.log(` 🚫 Rate Limited: ${rateLimited}`); console.log(` ❌ Errors: ${errors}`); console.log(` ⏱️ Avg Response Time: ${testResult.avgResponseTime}ms`); return testResult; } /** * Run comprehensive rate limit tests */ async runTests() { console.log("🚀 Starting NCBI API Rate Limit Tests"); console.log("====================================="); if (!NCBI_API_KEY) { console.log("⚠️ No NCBI_API_KEY environment variable found."); console.log(" Only testing unauthenticated rate limits."); console.log( " To test authenticated limits, set NCBI_API_KEY environment variable.", ); } else { console.log(`✅ NCBI API Key found: ${NCBI_API_KEY.substring(0, 8)}...`); } try { // Test 1: Unauthenticated rate limit (should work at 3 req/sec) if (TEST_WITHOUT_API_KEY) { console.log( "\n🔍 Test 1: Unauthenticated Rate Limit (3 req/sec - should succeed)", ); await this.testRate(3, 5, false); // Test slightly above unauthenticated limit console.log( "\n🔍 Test 2: Above Unauthenticated Limit (5 req/sec - may get rate limited)", ); await this.testRate(5, 3, false); } // Test 2: Authenticated rate limit (should work at 10 req/sec if API key is valid) if (TEST_WITH_API_KEY && NCBI_API_KEY) { console.log( "\n🔍 Test 3: Authenticated Rate Limit (10 req/sec - should succeed with valid API key)", ); await this.testRate(10, 5, true); // Test above authenticated limit console.log( "\n🔍 Test 4: Above Authenticated Limit (15 req/sec - may get rate limited)", ); await this.testRate(15, 3, true); } // Print summary this.printSummary(); } catch (error) { console.error("❌ Error during testing:", error); } } /** * Print test summary */ printSummary() { console.log("\n\n📈 Test Summary"); console.log("==============="); this.results.forEach((result, index) => { const apiKeyStatus = result.includeApiKey ? "✅ With API Key" : "❌ Without API Key"; const status = result.successRate >= 90 ? "✅ PASS" : result.successRate >= 70 ? "⚠️ PARTIAL" : "❌ FAIL"; console.log( `\nTest ${index + 1}: ${result.requestsPerSecond} req/sec (${apiKeyStatus})`, ); console.log(` Status: ${status}`); console.log(` Success Rate: ${result.successRate.toFixed(1)}%`); console.log( ` Rate Limited: ${result.rateLimited}/${result.totalRequests}`, ); console.log(` Avg Response: ${result.avgResponseTime}ms`); }); // Recommendations console.log("\n💡 Recommendations:"); const noApiKeyResults = this.results.filter((r) => !r.includeApiKey); const withApiKeyResults = this.results.filter((r) => r.includeApiKey); if (noApiKeyResults.length > 0) { const bestNoApiKey = Math.max( ...noApiKeyResults .filter((r) => r.successRate >= 90) .map((r) => r.requestsPerSecond), ); if (bestNoApiKey > 0) { console.log( ` • Without API key: Keep requests ≤ ${bestNoApiKey}/second`, ); } else { console.log( ` • Without API key: All tested rates had issues - try lower rates`, ); } } if (withApiKeyResults.length > 0) { const bestWithApiKey = Math.max( ...withApiKeyResults .filter((r) => r.successRate >= 90) .map((r) => r.requestsPerSecond), ); if (bestWithApiKey > 0) { console.log( ` • With API key: Can safely use up to ${bestWithApiKey}/second`, ); } else { console.log(` • With API key: Verify your API key is valid`); } } } } // Run the tests if (require.main === module) { const tester = new NCBIRateLimitTester(); tester.runTests().catch(console.error); } module.exports = NCBIRateLimitTester;

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/QuentinCody/entrez-mcp-server'

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