Skip to main content
Glama
plainly-videos

Plainly Videos MCP Server

renderItem.ts7.12 kB
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { z } from "zod"; import env from "../env"; import type { PlainlySdk, RenderableItemDetails } from "../sdk"; import { normalizeError, toToolResponse } from "../utils/toolResponse"; import { GeneralRenderError, InvalidRenderError, MissingParametersError, PlainlyMcpServerError, ProjectDesignNotFoundError, TemplateVariantNotFoundError, } from "./errors"; export function registerRenderItem(sdk: PlainlySdk, server: McpServer) { const Input = { isDesign: z.boolean().describe("True when the parent is a Design; false when it is a Project."), projectDesignId: z.string().describe("Parent identifier (projectId or designId)."), templateVariantId: z.string().describe("Template/variant identifier (the renderable leaf under the parent)."), parameters: z .record(z.any()) .describe( "Key-value parameters required by the chosen template/variant to customize the render. Mandatory parameters must be provided. Parameter type must be respected.", ), }; const Output = { // Successful response renderId: z.string().optional().describe("Server-assigned render job ID."), renderDetailsPageUrl: z.string().optional().describe("URL to the render details page."), projectDesignId: z.string().describe("Parent identifier (projectId or designId)."), templateVariantId: z.string().describe("Template/variant identifier (the renderable leaf under the parent)."), projectDesignName: z.string().optional().describe("Name of the project or design."), templateVariantName: z.string().optional().describe("Name of the template or variant."), // Failure response errorMessage: z.string().optional().describe("Error message, if any."), errorSolution: z.string().optional().describe("Error solution, if any."), errorDetails: z.string().optional().describe("Error details, if any."), }; server.registerTool( "render_item", { title: "Render Item", description: ` Create a render for a selected Project template or Design variant with specified parameters. How to use: - Call this after the user selects a candidate from \`get_renderable_items_details\`. - Call this only once the user approved all parameters for the chosen template/variant. Guidance: - Never submit more than one render with the same parameters, unless the user explicitly requests it. - Use parameters to customize the render. - All mandatory parameters must be provided. - Provide values for optional parameters if it makes sense. - Parameter types must be respected: - STRING: text string relevant to the parameter context. - MEDIA: URL to a media file (image, audio, or video). Ensure the URL is publicly accessible and points directly to the media file. - MEDIA (image): URL to an image file (jpg, png, etc.). - MEDIA (audio): URL to an audio file (mp3, wav, etc.). - MEDIA (video): URL to a video file (mp4, mov, etc.). - COLOR: hex color code (e.g. FF5733). - If a parameter has a default value and the user does not provide a value, the default will be used. - If the user is unsure about a parameter, ask for clarification rather than guessing. - When referencing parameters in conversation, use their \`label\` or \`description\` for clarity. Use when: - The user wants to create a video from a specific template/variant with defined parameters. `, inputSchema: Input, outputSchema: Output, }, async ({ isDesign, projectDesignId, templateVariantId, parameters }) => { // TODO: Handle object parameters "my.parameter.x" try { const projectDesignItems = await validateProjectDesignExists(sdk, isDesign, projectDesignId); const renderableItem = await validateTemplateVariantExists(projectDesignItems, templateVariantId); await validateTemplateVariantParameters(renderableItem, parameters); // If everything looks good, submit the render const render = await sdk.renderItem({ isDesign, projectDesignId, templateVariantId, parameters, }); // Check for API-level errors if (render.error) { // Specific handling for invalid renders if (render.state === "INVALID") { const invalidParams: { key?: string; errors: string[] }[] = []; render.parametrizationResults .filter((r) => r.mandatoryNotResolved || r.fatalError) .forEach((r) => { invalidParams.push({ key: r.parametrization?.value, errors: r.errorMessages ?? [], }); }); throw new InvalidRenderError(`${render.error.message || ""}`, invalidParams); } // General error throw new GeneralRenderError(`${render.error.message || ""}`, render.error); } // Successful submission return toToolResponse({ renderId: render.id, renderDetailsPageUrl: `${env.PLAINLY_APP_URL}/dashboard/renders/${render.id}`, projectDesignId: render.projectId, templateVariantId: render.templateId, projectDesignName: render.projectName, templateVariantName: render.templateName, }); } catch (err: unknown) { // Known errors with specific handling if (err instanceof PlainlyMcpServerError) { return toToolResponse( { message: err.message, solution: err.solution, details: err.details, }, true, ); } // All other errors return toToolResponse(normalizeError(err), true); } }, ); } const validateProjectDesignExists = async ( sdk: PlainlySdk, isDesign: boolean, projectDesignId: string, ): Promise<RenderableItemDetails[]> => { const projectDesignItems = await sdk.getRenderableItemsDetails(projectDesignId, isDesign); if (projectDesignItems.length === 0) { throw new ProjectDesignNotFoundError(projectDesignId); } return projectDesignItems; }; const validateTemplateVariantExists = async ( projectDesignItems: RenderableItemDetails[], templateVariantId: string, ): Promise<RenderableItemDetails> => { const renderableItem = projectDesignItems.find((item) => item.templateVariantId === templateVariantId); if (!renderableItem) { throw new TemplateVariantNotFoundError(templateVariantId, projectDesignItems[0].projectDesignId); } return renderableItem; }; const validateTemplateVariantParameters = async ( renderableItem: RenderableItemDetails, parameters: Record<string, unknown>, ): Promise<void> => { const mandatoryParams = renderableItem.parameters.filter((p) => p.mandatory); const providedParams = Object.keys(parameters); const missingParams = mandatoryParams.filter((p) => !providedParams.includes(p.key)); if (missingParams.length > 0) { throw new MissingParametersError(missingParams.map((p) => ({ key: p.key, label: p.label }))); } };

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/plainly-videos/mcp-server'

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