diff --git a/src/env.ts b/src/env.ts index 67ffde3..fbd1744 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,11 +1,12 @@ type Env = { - // auth + PLAINLY_APP_URL: string; PLAINLY_API_URL: string; PLAINLY_API_KEY: string; }; export default { - // auth + PLAINLY_APP_URL: + process.env.PLAINLY_APP_URL || "https://app.plainlyvideos.com", PLAINLY_API_URL: process.env.PLAINLY_API_URL || "https://api.plainlyvideos.com", PLAINLY_API_KEY: process.env.PLAINLY_API_KEY, diff --git a/src/sdk/api/getRenderItem.ts b/src/sdk/api/getRenderItem.ts new file mode 100644 index 0000000..2c001b4 --- /dev/null +++ b/src/sdk/api/getRenderItem.ts @@ -0,0 +1,13 @@ +import { AxiosResponse } from "axios"; +import { createAxiosInstance } from "../../axiosConfig"; +import { Render } from "../types"; + +const api = createAxiosInstance(); + +export const getRenderItem = async (renderingId: string): Promise => { + const response = await api.get, void>( + `/api/v2/renders/${renderingId}` + ); + + return response.data; +}; diff --git a/src/sdk/api/index.ts b/src/sdk/api/index.ts index a4782a2..154ac3e 100644 --- a/src/sdk/api/index.ts +++ b/src/sdk/api/index.ts @@ -1,3 +1,4 @@ export * from "./listRenderableItems"; export * from "./getRenderableItemDetails"; export * from "./renderItem"; +export * from "./getRenderItem"; diff --git a/src/server.ts b/src/server.ts index c9a7d29..80ed98e 100644 --- a/src/server.ts +++ b/src/server.ts @@ -4,6 +4,7 @@ import { registerListRenderableItems, registerGetRenderableItemDetails, registerRenderItem, + registerCheckRenderStatus, } from "./tools"; export class PlainlyMcpServer { @@ -21,6 +22,7 @@ export class PlainlyMcpServer { registerListRenderableItems(this.server); registerGetRenderableItemDetails(this.server); registerRenderItem(this.server); + registerCheckRenderStatus(this.server); } async start() { diff --git a/src/tools/checkRenderStatus.ts b/src/tools/checkRenderStatus.ts new file mode 100644 index 0000000..5484e4f --- /dev/null +++ b/src/tools/checkRenderStatus.ts @@ -0,0 +1,179 @@ +import { z } from "zod"; +import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { getRenderItem } from "../sdk"; +import env from "../env"; + +export function registerCheckRenderStatus(server: McpServer) { + const Input = { + renderId: z + .string() + .describe("The render ID returned from the `render_item` tool."), + }; + + const Output = { + // Render information + message: z.string().describe("A message describing the render status."), + renderId: z.string().describe("The 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."), + state: z + .enum([ + "PENDING", + "THROTTLED", + "QUEUED", + "IN_PROGRESS", + "DONE", + "FAILED", + "INVALID", + "CANCELLED", + ]) + .describe("The current state of the render job."), + + // Success output + output: z + .string() + .optional() + .describe("The render output URL (only available when state is DONE)."), + + // Error information + 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( + "check_render_status", + { + title: "Check Render Status", + description: ` +Check the status of a render job. + +Available states: +- PENDING: The render job has been created but not yet added to the queue. +- THROTTLED: The render job is waiting due to rate limiting. It will be started as soon as a slot opens, no need for manual retries. +- QUEUED: The render job is in the queue and will start soon. +- IN_PROGRESS: The render job is currently being processed. +- DONE: The render job has completed successfully. The output URL will be provided. +- FAILED: The render job encountered an error and did not complete successfully. Error details will be provided. +- INVALID: The render job was invalid (e.g., due to incorrect parameters). Error details will be provided. +- CANCELLED: The render job was cancelled by the user. + +Response format: +- Always include a link to the render details page. +- If the render is still in progress (PENDING, THROTTLED, QUEUED, IN_PROGRESS) tell user to check the status again later. +- If the render is DONE, return the output URL and the render page details. +- If the render FAILED or is INVALID, return the error message and details. + +Use when: +- You need to check the progress of a render job +- You want to retrieve the final render output URL +- You want to retrieve render error details if the job failed + `, + inputSchema: Input, + outputSchema: Output, + }, + async ({ renderId }) => { + try { + const render = await getRenderItem(renderId); + + // Handle successful completion + if (render.state === "DONE") { + return { + content: [], + structuredContent: { + message: "Render completed successfully.", + renderId: render.id, + renderDetailsPageUrl: `${env.PLAINLY_APP_URL}/dashboard/renders/${render.id}`, + projectDesignId: render.projectId, + templateVariantId: render.templateId, + projectDesignName: render.projectName, + templateVariantName: render.templateName, + state: render.state, + output: render.output, + }, + }; + } + + if (render.state === "CANCELLED") { + return { + content: [], + structuredContent: { + message: "Render was cancelled.", + renderId: render.id, + renderDetailsPageUrl: `${env.PLAINLY_APP_URL}/dashboard/renders/${render.id}`, + projectDesignId: render.projectId, + templateVariantId: render.templateId, + projectDesignName: render.projectName, + templateVariantName: render.templateName, + state: render.state, + }, + }; + } + + // Handle error states + if (render.state === "FAILED" || render.state === "INVALID") { + return { + content: [], + structuredContent: { + message: "Render failed.", + renderId: render.id, + renderDetailsPageUrl: `${env.PLAINLY_APP_URL}/dashboard/renders/${render.id}`, + projectDesignId: render.projectId, + templateVariantId: render.templateId, + projectDesignName: render.projectName, + templateVariantName: render.templateName, + state: render.state, + errorMessage: render.error.message, + errorDetails: JSON.stringify(render.error.details), + }, + isError: true, + }; + } + + // Processing + return { + content: [], + structuredContent: { + message: "Render is processing. Please wait and check back later.", + renderId: render.id, + renderDetailsPageUrl: `${env.PLAINLY_APP_URL}/dashboard/renders/${render.id}`, + projectDesignId: render.projectId, + templateVariantId: render.templateId, + projectDesignName: render.projectName, + templateVariantName: render.templateName, + state: render.state, + }, + }; + } catch (err: any) { + return { + content: [], + structuredContent: { + message: "Render status could not be retrieved.", + renderId, + errorMessage: err.message || "Failed to check render status", + errorDetails: err, + }, + isError: true, + }; + } + } + ); +} diff --git a/src/tools/index.ts b/src/tools/index.ts index c965331..9d1eca1 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,3 +1,4 @@ export * from "./getRenderableItemDetails"; export * from "./listRenderableItems"; export * from "./renderItem"; +export * from "./checkRenderStatus"; diff --git a/src/tools/renderItem.ts b/src/tools/renderItem.ts index 531e5aa..fa9d8ec 100644 --- a/src/tools/renderItem.ts +++ b/src/tools/renderItem.ts @@ -13,6 +13,7 @@ import { ProjectDesignNotFoundError, TemplateVariantNotFoundError, } from "./errors"; +import env from "../env"; export function registerRenderItem(server: McpServer) { const Input = { @@ -153,7 +154,7 @@ Use when: // Successful submission const output = { renderId: render.id, - renderDetailsPageUrl: `https://app.test.plainlyvideos.com/dashboard/renders/${render.id}`, + renderDetailsPageUrl: `${env.PLAINLY_APP_URL}/dashboard/renders/${render.id}`, projectDesignId: render.projectId, templateVariantId: render.templateId, projectDesignName: render.projectName,