Add ExportShapeTool

This commit is contained in:
Dominik Jain 2025-09-27 16:09:13 +02:00 committed by Dominik Jain
parent 223d6d50b0
commit ad771ee92f
3 changed files with 92 additions and 2 deletions

View File

@ -11,6 +11,7 @@ import { createLogger } from "./logger";
import { Tool } from "./Tool";
import { HighLevelOverviewTool } from "./tools/HighLevelOverviewTool";
import { PenpotApiInfoTool } from "./tools/PenpotApiInfoTool";
import { ExportShapeTool } from "./tools/ExportShapeTool";
export class PenpotMcpServer {
private readonly logger = createLogger("PenpotMcpServer");
@ -58,6 +59,7 @@ export class PenpotMcpServer {
new ExecuteCodeTool(this),
new HighLevelOverviewTool(this),
new PenpotApiInfoTool(this),
new ExportShapeTool(this),
];
for (const tool of toolInstances) {

View File

@ -22,11 +22,27 @@ class ImageContent implements ImageItem {
public data: string,
public mimeType: string
) {}
/**
* @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array)
*/
protected static byteData(data: Uint8Array | object): Uint8Array {
if (typeof data === "object") {
// convert object (as obtained from JSON conversion of Uint8Array) back to Uint8Array
return new Uint8Array(Object.values(data) as number[]);
} else {
return data;
}
}
}
class PNGImageContent extends ImageContent {
constructor(data: Uint8Array) {
super(Buffer.from(data).toString("base64"), "image/png");
/**
* @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array)
*/
constructor(data: Uint8Array | object) {
let array = ImageContent.byteData(data);
super(Buffer.from(array).toString("base64"), "image/png");
}
}
@ -43,3 +59,12 @@ export class TextResponse extends ToolResponse {
super([new TextContent(text)]);
}
}
export class PNGResponse extends ToolResponse {
/**
* @param data - PNG image data as Uint8Array or as object (from JSON conversion of Uint8Array)
*/
constructor(data: Uint8Array | object) {
super([new PNGImageContent(data)]);
}
}

View File

@ -0,0 +1,63 @@
import { z } from "zod";
import { Tool } from "../Tool";
import { PNGResponse, ToolResponse } from "../ToolResponse";
import "reflect-metadata";
import { PenpotMcpServer } from "../PenpotMcpServer";
import { ExecuteCodePluginTask } from "../tasks/ExecuteCodePluginTask";
import { ExecuteCodeTaskParams } from "@penpot-mcp/common";
/**
* Arguments class for ExecuteCodeTool
*/
export class ExportShapeArgs {
static schema = {
shapeId: z.string().min(1, "shapeId cannot be empty"),
};
/**
* Identifier of the shape to export.
* The special identifier "selection" can be used to refer to the (first) currently selected shape.
*/
shapeId!: string;
}
/**
* Tool for executing JavaScript code in the Penpot plugin context
*/
export class ExportShapeTool extends Tool<ExportShapeArgs> {
/**
* Creates a new ExecuteCode tool instance.
*
* @param mcpServer - The MCP server instance
*/
constructor(mcpServer: PenpotMcpServer) {
super(mcpServer, ExportShapeArgs.schema);
}
public getToolName(): string {
return "export_shapes";
}
public getToolDescription(): string {
return (
"Exports a shape from the Penpot design to a PNG image, such that you can get an impression of what the shape looks like.\n" +
"Parameter `shapeId`: identifier of the shapes to export. Use the special identifier 'selection' to " +
"export the first shape currently selected by the user."
);
}
protected async executeCore(args: ExportShapeArgs): Promise<ToolResponse> {
// create code for exporting the shape
let taskParams: ExecuteCodeTaskParams;
if (args.shapeId === "selection") {
taskParams = { code: 'return penpot.selection[0].export({"type": "png"});' };
} else {
throw new Error("Identifiers other than 'selection' are not yet supported");
}
// execute the code and return the response
const task = new ExecuteCodePluginTask(taskParams);
const result = await this.mcpServer.pluginBridge.executePluginTask(task);
return new PNGResponse(result.data!.result);
}
}