Skip to main content
Glama

actors-mcp-server

Official
by apify
MIT License
7,198
465
  • Apple
build.ts5.96 kB
import { z } from 'zod'; import zodToJsonSchema from 'zod-to-json-schema'; import log from '@apify/log'; import { ApifyClient } from '../apify-client.js'; import { ACTOR_README_MAX_LENGTH, HelperTools } from '../const.js'; import type { ActorDefinitionPruned, ActorDefinitionWithDesc, InternalTool, ISchemaProperties, ToolEntry, } from '../types.js'; import { ajv } from '../utils/ajv.js'; import { filterSchemaProperties, shortenProperties } from './utils.js'; /** * Get Actor input schema by Actor name. * First, fetch the Actor details to get the default build tag and buildId. * Then, fetch the build details and return actorName, description, and input schema. * @param {string} actorIdOrName - Actor ID or Actor full name. * @param {number} limit - Truncate the README to this limit. * @param {string} apifyToken * @returns {Promise<ActorDefinitionWithDesc | null>} - The actor definition with description or null if not found. */ export async function getActorDefinition( actorIdOrName: string, apifyClient: ApifyClient, limit: number = ACTOR_README_MAX_LENGTH, ): Promise<ActorDefinitionPruned | null> { const actorClient = apifyClient.actor(actorIdOrName); try { // Fetch actor details const actor = await actorClient.get(); if (!actor) { return null; } const defaultBuildClient = await actorClient.defaultBuild(); const buildDetails = await defaultBuildClient.get(); if (buildDetails?.actorDefinition) { const actorDefinitions = buildDetails?.actorDefinition as ActorDefinitionWithDesc; // We set actorDefinition ID to Actor ID actorDefinitions.id = actor.id; actorDefinitions.readme = truncateActorReadme(actorDefinitions.readme || '', limit); actorDefinitions.description = actor.description || ''; actorDefinitions.actorFullName = `${actor.username}/${actor.name}`; actorDefinitions.defaultRunOptions = actor.defaultRunOptions; return pruneActorDefinition(actorDefinitions); } return null; } catch (error) { const errorMessage = `Failed to fetch input schema for Actor: ${actorIdOrName} with error ${error}.`; log.error(errorMessage); throw new Error(errorMessage); } } function pruneActorDefinition(response: ActorDefinitionWithDesc): ActorDefinitionPruned { return { id: response.id, actorFullName: response.actorFullName || '', buildTag: response?.buildTag || '', readme: response?.readme || '', input: response?.input && 'type' in response.input && 'properties' in response.input ? { ...response.input, type: response.input.type as string, properties: response.input.properties as Record<string, ISchemaProperties>, } : undefined, description: response.description, defaultRunOptions: response.defaultRunOptions, webServerMcpPath: 'webServerMcpPath' in response ? response.webServerMcpPath as string : undefined, }; } /** Prune Actor README if it is too long * If the README is too long * - We keep the README as it is up to the limit. * - After the limit, we keep heading only * - We add a note that the README was truncated because it was too long. */ function truncateActorReadme(readme: string, limit = ACTOR_README_MAX_LENGTH): string { if (readme.length <= limit) { return readme; } const readmeFirst = readme.slice(0, limit); const readmeRest = readme.slice(limit); const lines = readmeRest.split('\n'); const prunedReadme = lines.filter((line) => line.startsWith('#')); return `${readmeFirst}\n\nREADME was truncated because it was too long. Remaining headers:\n${prunedReadme.join(', ')}`; } const getActorDefinitionArgsSchema = z.object({ actorName: z.string() .min(1) .describe('Retrieve input, readme, and other details for Actor ID or Actor full name. ' + 'Actor name is always composed from `username/name`'), limit: z.number() .int() .default(ACTOR_README_MAX_LENGTH) .describe(`Truncate the README to this limit. Default value is ${ACTOR_README_MAX_LENGTH}.`), }); /** * https://docs.apify.com/api/v2/actor-build-get */ export const actorDefinitionTool: ToolEntry = { type: 'internal', tool: { name: HelperTools.ACTOR_GET_DETAILS, // TODO: remove actorFullName from internal tools actorFullName: HelperTools.ACTOR_GET_DETAILS, description: 'Get documentation, readme, input schema and other details about an Actor. ' + 'For example, when user says, I need to know more about web crawler Actor.' + 'Get details for an Actor with with Actor ID or Actor full name, i.e. username/name.' + `Limit the length of the README if needed.`, inputSchema: zodToJsonSchema(getActorDefinitionArgsSchema), ajvValidate: ajv.compile(zodToJsonSchema(getActorDefinitionArgsSchema)), call: async (toolArgs) => { const { args, apifyToken } = toolArgs; const parsed = getActorDefinitionArgsSchema.parse(args); const apifyClient = new ApifyClient({ token: apifyToken }); const v = await getActorDefinition(parsed.actorName, apifyClient, parsed.limit); if (!v) { return { content: [{ type: 'text', text: `Actor '${parsed.actorName}' not found.` }] }; } if (v && v.input && 'properties' in v.input && v.input) { const properties = filterSchemaProperties(v.input.properties as { [key: string]: ISchemaProperties }); v.input.properties = shortenProperties(properties); } return { content: [{ type: 'text', text: JSON.stringify(v) }] }; }, } as InternalTool, };

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