Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 3 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import env from "./env";

export const PLAINLY_APP_URL = env.PLAINLY_API_URL.replace("api.", "app.");
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.

come on we could also have this in the .env file or?

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
146 changes: 146 additions & 0 deletions src/tools/checkRenderStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { z } from "zod";
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { getRenderItem } from "../sdk";
import { PLAINLY_APP_URL } from "../constants";

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?

// Status information
message: z
.string()
.describe("A human-readable message for the render status."),
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.

why is this better, than the state description in the tool?

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 am even thinking of getting state out of the output, because this message should be sufficient and combine some states in case of "processing"

renderId: z.string().describe("The render job ID."),
renderDetailsPageUrl: z
.string()
.optional()
.describe("URL to the render details page."),
state: z
.enum([
"PENDING",
"THROTTLED",
"QUEUED",
"IN_PROGRESS",
"DONE",
"FAILED",
"INVALID",
"CANCELLED",
])
.describe("The current state of the render job."),
projectName: z
.string()
.optional()
.describe("Name of the project or design."),
templateName: z
.string()
.optional()
.describe("Name of the template or variant."),

// 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.any().optional().describe("Error details, if any."),
};

server.registerTool(
"check_render_status",
{
title: "Check Render Status",
description: `
Check the status of a render job and optionally wait for completion.

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: `${PLAINLY_APP_URL}/dashboard/renders/${render.id}`,
state: render.state,
projectName: render.projectName,
templateName: render.templateName,
output: render.output,
},
};
}

if (render.state === "CANCELLED") {
return {
content: [],
structuredContent: {
message: "Render was cancelled.",
renderId: render.id,
state: render.state,
projectName: render.projectName,
templateName: render.templateName,
},
};
}

// Handle error states
if (render.state === "FAILED" || render.state === "INVALID") {
return {
content: [],
structuredContent: {
message: "Render failed.",
renderId: render.id,
state: render.state,
projectName: render.projectName,
templateName: render.templateName,
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,
state: render.state,
projectName: render.projectName,
templateName: render.templateName,
},
};
} 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 { PLAINLY_APP_URL } from "../constants";

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: `${PLAINLY_APP_URL}/dashboard/renders/${render.id}`,
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.

why did not you add this as the resource link also?

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.

same reason as above, resource links are always pointing to local file with download icon.. so I guess it's used for a different usecase. Also, you can ask for render page url and you will get a nice link in the response, so not sure what are benefits of resources

projectDesignId: render.projectId,
templateVariantId: render.templateId,
projectDesignName: render.projectName,
Expand Down