Skip to main content
Glama

actors-mcp-server

Official
by apify
actor-card.ts7.65 kB
import type { Actor } from 'apify-client'; import { APIFY_STORE_URL } from '../const.js'; import type { ExtendedActorStoreList, ExtendedPricingInfo, StructuredActorCard } from '../types.js'; import { getCurrentPricingInfo, pricingInfoToString, pricingInfoToStructured } from './pricing-info.js'; // Helper function to format categories from uppercase with underscores to proper case function formatCategories(categories?: string[]): string[] { if (!categories) return []; return categories.map((category) => { const formatted = category .toLowerCase() .split('_') .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); // Special case for MCP server, AI, and SEO tools return formatted.replace('Mcp Server', 'MCP Server').replace('Ai', 'AI').replace('Seo', 'SEO'); }); } /** * Formats Actor details into an Actor card (Actor information in markdown). * @param actor - Actor information from the API * @returns Formatted actor card */ export function formatActorToActorCard( actor: Actor | ExtendedActorStoreList, ): string { // Format categories for display const formattedCategories = formatCategories('categories' in actor ? actor.categories : undefined); // Get pricing info let pricingInfo: string; if ('currentPricingInfo' in actor) { // ActorStoreList has currentPricingInfo pricingInfo = pricingInfoToString(actor.currentPricingInfo as ExtendedPricingInfo); } else { // Actor has pricingInfos array const currentPricingInfo = getCurrentPricingInfo(actor.pricingInfos || [], new Date()); pricingInfo = pricingInfoToString(currentPricingInfo as (ExtendedPricingInfo | null)); } const actorFullName = `${actor.username}/${actor.name}`; const actorUrl = `${APIFY_STORE_URL}/${actorFullName}`; // Build the markdown lines const markdownLines = [ `## [${actor.title}](${actorUrl}) (\`${actorFullName}\`)`, `- **URL:** ${actorUrl}`, `- **Developed by:** [${actor.username}](${APIFY_STORE_URL}/${actor.username}) ${actor.username === 'apify' ? '(Apify)' : '(community)'}`, `- **Description:** ${actor.description || 'No description provided.'}`, `- **Categories:** ${formattedCategories.length ? formattedCategories.join(', ') : 'Uncategorized'}`, `- **[Pricing](${actorUrl}/pricing):** ${pricingInfo}`, ]; // Add stats - handle different stat structures if ('stats' in actor) { const { stats } = actor; const statsParts = []; if ('totalUsers' in stats && 'totalUsers30Days' in stats) { // Both Actor and ActorStoreList have the same stats structure statsParts.push(`${stats.totalUsers.toLocaleString()} total users, ${stats.totalUsers30Days.toLocaleString()} monthly users`); } // Add success rate for last 30 days if available if ('publicActorRunStats30Days' in stats && stats.publicActorRunStats30Days) { const runStats = stats.publicActorRunStats30Days as { SUCCEEDED: number; TOTAL: number; }; if (runStats.TOTAL > 0) { const successRate = ((runStats.SUCCEEDED / runStats.TOTAL) * 100).toFixed(1); statsParts.push(`Runs succeeded: ${successRate}%`); } } // Add bookmark count if available (ActorStoreList only) if ('bookmarkCount' in actor && actor.bookmarkCount) { statsParts.push(`${actor.bookmarkCount} bookmarks`); } if (statsParts.length > 0) { markdownLines.push(`- **Stats:** ${statsParts.join(', ')}`); } } // Add rating if available (ActorStoreList only) if ('actorReviewRating' in actor && actor.actorReviewRating) { markdownLines.push(`- **Rating:** ${actor.actorReviewRating.toFixed(2)} out of 5`); } // Add modification date if available if ('modifiedAt' in actor) { markdownLines.push(`- **Last modified:** ${actor.modifiedAt.toISOString()}`); } // Add deprecation warning if applicable if ('isDeprecated' in actor && actor.isDeprecated) { markdownLines.push('\n>This Actor is deprecated and may not be maintained anymore.'); } return markdownLines.join('\n'); } /** * Extracts structured data from Actor information. * @param actor - Actor information from the API * @returns Structured actor card data for programmatic use */ export function formatActorToStructuredCard( actor: Actor | ExtendedActorStoreList, ): StructuredActorCard { // Format categories for display const formattedCategories = formatCategories('categories' in actor ? actor.categories : undefined); // Get pricing info - try to extract from the appropriate source let pricingInfo: ExtendedPricingInfo | null = null; if ('currentPricingInfo' in actor) { // ActorStoreList has currentPricingInfo pricingInfo = actor.currentPricingInfo as unknown as ExtendedPricingInfo; } else if ('pricingInfos' in actor && actor.pricingInfos && actor.pricingInfos.length > 0) { // Actor has pricingInfos array - get the current one pricingInfo = getCurrentPricingInfo(actor.pricingInfos, new Date()) as unknown as ExtendedPricingInfo; } // If pricingInfo is still null, it means the actor is free (no pricing info means free) const pricing = pricingInfoToStructured(pricingInfo); const actorFullName = `${actor.username}/${actor.name}`; const actorUrl = `${APIFY_STORE_URL}/${actorFullName}`; // Build structured data const structuredData: StructuredActorCard = { title: actor.title, url: actorUrl, fullName: actorFullName, developer: { username: actor.username, isOfficialApify: actor.username === 'apify', url: `${APIFY_STORE_URL}/${actor.username}`, }, description: actor.description || 'No description provided.', categories: formattedCategories, pricing, isDeprecated: ('isDeprecated' in actor && actor.isDeprecated) || false, }; // Add stats if available if ('stats' in actor) { const { stats } = actor; if ('totalUsers' in stats && 'totalUsers30Days' in stats) { structuredData.stats = { totalUsers: stats.totalUsers, monthlyUsers: stats.totalUsers30Days, }; // Add success rate for last 30 days if available if ('publicActorRunStats30Days' in stats && stats.publicActorRunStats30Days) { const runStats = stats.publicActorRunStats30Days as { SUCCEEDED: number; TOTAL: number; }; if (runStats.TOTAL > 0) { structuredData.stats.successRate = Number(((runStats.SUCCEEDED / runStats.TOTAL) * 100).toFixed(1)); } } // Add bookmark count if available (ActorStoreList only) if ('bookmarkCount' in actor && actor.bookmarkCount) { structuredData.stats.bookmarks = actor.bookmarkCount; } } } // Add rating if available (ActorStoreList only) if ('actorReviewRating' in actor && actor.actorReviewRating) { structuredData.rating = actor.actorReviewRating; } // Add modification date if available if ('modifiedAt' in actor && actor.modifiedAt) { structuredData.modifiedAt = actor.modifiedAt.toISOString(); } return structuredData; }

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/apify/actors-mcp-server'

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