Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/env.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
13 changes: 13 additions & 0 deletions src/sdk/api/getRenderItem.ts
Original file line number Diff line number Diff line change
@@ -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<Render> => {
const response = await api.get<Render, AxiosResponse<Render>, void>(
`/api/v2/renders/${renderingId}`
);

return response.data;
};
1 change: 1 addition & 0 deletions src/sdk/api/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./listRenderableItems";
export * from "./getRenderableItemDetails";
export * from "./renderItem";
export * from "./getRenderItem";
2 changes: 2 additions & 0 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
registerListRenderableItems,
registerGetRenderableItemDetails,
registerRenderItem,
registerCheckRenderStatus,
} from "./tools";

export class PlainlyMcpServer {
Expand All @@ -21,6 +22,7 @@ export class PlainlyMcpServer {
registerListRenderableItems(this.server);
registerGetRenderableItemDetails(this.server);
registerRenderItem(this.server);
registerCheckRenderStatus(this.server);
}

async start() {
Expand Down
179 changes: 179 additions & 0 deletions src/tools/checkRenderStatus.ts
Original file line number Diff line number Diff line change
@@ -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 = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be same output as the render item? should be unifed imo?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idk, more or less it is the same, why you think it should be exactly the same?

// 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.
Comment thread
danixeee marked this conversation as resolved.
Outdated
- 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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

keep text, would also add that 👍

  • uri to the video output
  • name can be resolved from filename render stuff
  • mime type known by content type (also available)
  • description: link to a final video output
  • no annotations
{
  "type": "resource_link",
  "uri": "file:///project/src/main.rs",
  "name": "main.rs",
  "description": "Primary application entry point",
  "mimeType": "text/x-rust",
  "annotations": {
    "audience": ["assistant"],
    "priority": 0.9
  }
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hardcoded these, but it always point to local file, see here
#3 (comment)

content: [],
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should remove text content everywhere, because we use the same data as in structuredContent, just stringyfied.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's explicitly said this is needed for backward compatibility on the spec:

image

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know but I didn't notice any difference.. Do you think that depends on lcp clients or what?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sure it depends on the client

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.",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe a time to wait before checking back. If throttled longest, queued & in-progress shorter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Client always needs a user input to be able to invoke tool, so you need to ask him if the render is finished, that's why I added render page URL so you can navigate and see the details there

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,
};
}
}
);
}
1 change: 1 addition & 0 deletions src/tools/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./getRenderableItemDetails";
export * from "./listRenderableItems";
export * from "./renderItem";
export * from "./checkRenderStatus";
3 changes: 2 additions & 1 deletion src/tools/renderItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
ProjectDesignNotFoundError,
TemplateVariantNotFoundError,
} from "./errors";
import env from "../env";

export function registerRenderItem(server: McpServer) {
const Input = {
Expand Down Expand Up @@ -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,
Expand Down