mirror of
https://github.com/penpot/penpot.git
synced 2026-11-01 15:40:41 +00:00
Penpot only exposes penpotMgmt to plugins running with global scope. Without it, the MCP plugin failed on load when registering its workspacechange listener. When penpotMgmt is missing, the plugin now builds the workspace metadata from the open file and skips the listener. execute_code receives penpotMgmt as null and waits for layout updates as before. The tool description and instructions tell agents that only the open file is available when penpotMgmt is null.
82 lines
3.7 KiB
TypeScript
82 lines
3.7 KiB
TypeScript
import { z } from "zod";
|
|
import { Tool } from "../Tool";
|
|
import type { ToolResponse } from "../ToolResponse";
|
|
import { TextResponse } 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 ExecuteCodeArgs {
|
|
static schema = {
|
|
code: z
|
|
.string()
|
|
.min(1, "Code cannot be empty")
|
|
.describe("The JavaScript code to execute in the plugin context."),
|
|
sessionId: Tool.SESSION_ID_SCHEMA,
|
|
};
|
|
|
|
/**
|
|
* The JavaScript code to execute in the plugin context.
|
|
*/
|
|
code!: string;
|
|
|
|
sessionId?: string;
|
|
}
|
|
|
|
/**
|
|
* Tool for executing JavaScript code in the Penpot plugin context
|
|
*/
|
|
export class ExecuteCodeTool extends Tool<ExecuteCodeArgs> {
|
|
/**
|
|
* Creates a new ExecuteCode tool instance.
|
|
*
|
|
* @param mcpServer - The MCP server instance
|
|
*/
|
|
constructor(mcpServer: PenpotMcpServer) {
|
|
super(mcpServer, ExecuteCodeArgs.schema);
|
|
}
|
|
|
|
public getToolName(): string {
|
|
return "execute_code";
|
|
}
|
|
|
|
public getToolDescription(): string {
|
|
return (
|
|
"Executes JavaScript code in the Penpot plugin context.\n" +
|
|
"IMPORTANT: Before using this tool, make sure you have read the 'Penpot High-Level Overview' and know " +
|
|
"which Penpot API functionality is necessary and how to use it.\n" +
|
|
"You have access to `penpot` (of type `Penpot`), `penpotMgmt` (of type `PenpotMgmt`), `penpotUtils`, and `storage`.\n" +
|
|
"If `penpotMgmt` is null, file management is unavailable and only the open file can be used.\n" +
|
|
"`storage` is an object in which arbitrary data can be stored, simply by adding a new attribute; " +
|
|
"stored attributes can be referenced in future calls to this tool, so any intermediate results that " +
|
|
"could come in handy later should be stored in `storage` instead of just a fleeting variable; " +
|
|
"you can also store functions and thus build up a library).\n" +
|
|
"Think of the code being executed as the body of a function: " +
|
|
"The tool call returns whatever you return in the applicable `return` statement, if any. " +
|
|
"You can return arbitrary JS objects; no need to apply JSON.stringify.\n" +
|
|
"If an exception occurs, the exception's message will be returned to you.\n" +
|
|
"Any output that you generate via the `console` object will be returned to you separately; so you may use it " +
|
|
"to track what your code is doing, but you should *only* do so only if there is an ACTUAL NEED for this! " +
|
|
"VERY IMPORTANT: Don't use logging prematurely! NEVER log the data you are returning, as you will otherwise receive it twice!\n" +
|
|
"VERY IMPORTANT: In general, try a simple approach first, and only if it fails, try more complex code that involves " +
|
|
"handling different cases (in particular error cases) and that applies logging."
|
|
);
|
|
}
|
|
|
|
protected async executeCore(args: ExecuteCodeArgs): Promise<ToolResponse> {
|
|
const taskParams: ExecuteCodeTaskParams = { code: args.code };
|
|
const task = new ExecuteCodePluginTask(taskParams);
|
|
const result = await this.mcpServer.pluginBridge.executePluginTask(task, args.sessionId);
|
|
|
|
if (result.data !== undefined) {
|
|
return new TextResponse(JSON.stringify(result.data, null, 2));
|
|
} else {
|
|
return new TextResponse("Code executed successfully with no return value.");
|
|
}
|
|
}
|
|
}
|