rapidapi-mcp-server
Version:
MCP server for discovering and assessing APIs from RapidAPI marketplace
230 lines (229 loc) • 10.4 kB
JavaScript
export class SearchResultsExtractor {
async extractSearchResults(page, maxResults = 20) {
try {
// Use the actual selectors found on the page
await page.waitForSelector('div[class*="group/card"], div[class*="card"], div[class*="api"]', { timeout: 10000 });
}
catch (error) {
console.error('No search results found or page structure changed');
return [];
}
const results = await page.evaluate((max) => {
const searchResults = [];
// Use the actual selectors from RapidAPI search page
const cardSelectors = [
'div[class*="group/card"]', // Main card selector found
'div[class*="card"]', // General card selector
'div[class*="api"]' // API-specific divs
];
let apiCards = [];
for (const selector of cardSelectors) {
apiCards = Array.from(document.querySelectorAll(selector));
if (apiCards.length > 0)
break;
}
for (let i = 0; i < Math.min(apiCards.length, max); i++) {
const card = apiCards[i];
try {
const result = {
name: '',
description: '',
provider: '',
url: ''
};
// Extract API name
const nameEl = card.querySelector('h3, h4, [class*="title"], [class*="name"], a[href*="/api/"]');
if (nameEl) {
result.name = nameEl.textContent?.trim() || '';
}
// Extract description
const descEl = card.querySelector('p, [class*="description"], [class*="summary"]');
if (descEl) {
result.description = descEl.textContent?.trim() || '';
}
// Extract provider/author
const providerEl = card.querySelector('[class*="provider"], [class*="author"], [class*="by"]');
if (providerEl) {
result.provider = providerEl.textContent?.replace(/^by\s+/i, '').trim() || '';
}
// Extract URL
const linkEl = card.querySelector('a[href*="/api/"], a[href*="rapidapi.com"]');
if (linkEl) {
result.url = linkEl.href;
}
// Extract rating if available
const ratingEl = card.querySelector('[class*="rating"], [class*="score"], [title*="star"]');
if (ratingEl) {
const ratingText = ratingEl.textContent?.trim();
const ratingMatch = ratingText?.match(/(\d+(?:\.\d+)?)/);
if (ratingMatch) {
result.rating = parseFloat(ratingMatch[1]);
}
}
// Only add if we have essential information
if (result.name && result.url) {
searchResults.push(result);
}
}
catch (error) {
console.error('Error extracting individual search result:', error);
}
}
return searchResults;
}, maxResults);
return results;
}
}
export class APIDetailExtractor {
async extractAPIDetails(page) {
// Wait for page content to load
await page.waitForSelector('h1, [class*="title"]', { timeout: 10000 });
const apiDetails = await page.evaluate(() => {
const assessment = {
pricing: {},
endpoints: []
};
// Extract API name
const nameEl = document.querySelector('h1, [class*="api-title"], [class*="title"]');
if (nameEl) {
assessment.name = nameEl.textContent?.trim() || '';
}
// Extract description
const descriptionSelectors = [
'[class*="description"]',
'[class*="overview"]',
'p:not([class*="metric"]):not([class*="stat"])'
];
for (const selector of descriptionSelectors) {
const descEl = document.querySelector(selector);
if (descEl && descEl.textContent && descEl.textContent.length > 50) {
assessment.description = descEl.textContent.trim();
break;
}
}
// Extract provider
const providerEl = document.querySelector('[class*="provider"], [class*="author"], [href*="provider"]');
if (providerEl) {
assessment.provider = providerEl.textContent?.replace(/^by\s+/i, '').trim() || '';
}
// Extract current URL
assessment.url = window.location.href;
// Extract rating
const ratingEl = document.querySelector('[class*="rating"], [title*="star"]');
if (ratingEl) {
const ratingText = ratingEl.textContent?.trim();
const ratingMatch = ratingText?.match(/(\d+(?:\.\d+)?)/);
if (ratingMatch) {
assessment.rating = parseFloat(ratingMatch[1]);
}
}
// Extract metrics (popularity, service level, latency)
const metricSelectors = [
'[class*="popularity"]',
'[class*="service"]',
'[class*="latency"]',
'[title*="Popularity"]',
'[title*="Service"]',
'[title*="Latency"]'
];
metricSelectors.forEach(selector => {
const metricEl = document.querySelector(selector);
if (metricEl) {
const text = metricEl.textContent?.trim();
if (text) {
if (selector.includes('popularity') || selector.includes('Popularity')) {
assessment.popularity = text;
}
else if (selector.includes('service') || selector.includes('Service')) {
assessment.serviceLevel = text;
}
else if (selector.includes('latency') || selector.includes('Latency')) {
assessment.latency = text;
}
}
}
});
return assessment;
});
// Extract pricing information
apiDetails.pricing = await this.extractPricing(page);
// Extract endpoints
apiDetails.endpoints = await this.extractEndpoints(page);
return apiDetails;
}
async extractPricing(page) {
return await page.evaluate(() => {
const pricing = {};
// Look for pricing sections
const pricingSelectors = [
'[class*="pricing"]',
'[class*="plan"]',
'[class*="tier"]'
];
pricingSelectors.forEach(selector => {
const pricingElements = document.querySelectorAll(selector);
pricingElements.forEach(element => {
const tierName = element.querySelector('[class*="name"], h3, h4')?.textContent?.trim().toLowerCase();
const priceEl = element.querySelector('[class*="price"], [class*="cost"]');
if (tierName && priceEl) {
const price = priceEl.textContent?.trim() || '';
pricing[tierName] = {
name: tierName,
price: price
};
}
});
});
// Look for specific tier information displayed on page
const basicEl = document.querySelector('[class*="BASIC"]');
const megaEl = document.querySelector('[class*="MEGA"]');
const proEl = document.querySelector('[class*="PRO"]');
if (basicEl) {
const price = basicEl.closest('[class*="pricing"]')?.querySelector('[class*="price"]')?.textContent?.trim() || '$0.00';
pricing.basic = { name: 'Basic', price };
}
if (megaEl) {
const price = megaEl.closest('[class*="pricing"]')?.querySelector('[class*="price"]')?.textContent?.trim() || 'Unknown';
pricing.pro = { name: 'Pro', price };
}
return pricing;
});
}
async extractEndpoints(page) {
return await page.evaluate(() => {
const endpoints = [];
// Look for endpoints in sidebar or documentation
const endpointSelectors = [
'[class*="endpoint"]',
'[class*="method"]',
'nav a[href*="endpoint"]',
'aside a',
'[class*="sidebar"] a'
];
endpointSelectors.forEach(selector => {
const endpointElements = document.querySelectorAll(selector);
endpointElements.forEach(element => {
const text = element.textContent?.trim();
if (text && text.includes('API') && !text.includes('Overview')) {
// Extract HTTP method if present
const methodMatch = text.match(/^(GET|POST|PUT|DELETE|PATCH)\s*(.+)/i);
endpoints.push({
name: methodMatch ? methodMatch[2] : text,
method: methodMatch ? methodMatch[1].toUpperCase() : 'GET',
description: text
});
}
});
});
// Remove duplicates
const seen = new Set();
return endpoints.filter(endpoint => {
const key = `${endpoint.method}:${endpoint.name}`;
if (seen.has(key))
return false;
seen.add(key);
return true;
});
});
}
}