mirror of
https://github.com/penpot/penpot-mcp.git
synced 2026-04-25 11:18:37 +00:00
Add code execution tool
This commit is contained in:
parent
9fb3ccc2e2
commit
5ffaabd728
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,7 @@
|
||||
.idea
|
||||
node_modules
|
||||
dist
|
||||
*.bak
|
||||
*.orig
|
||||
temp
|
||||
*.tsbuildinfo
|
||||
|
||||
@ -68,3 +68,13 @@ export interface PrintTextTaskParams {
|
||||
*/
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parameters for the executeCode task.
|
||||
*/
|
||||
export interface ExecuteCodeTaskParams {
|
||||
/**
|
||||
* The JavaScript code to be executed.
|
||||
*/
|
||||
code: string;
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@ import { CallToolRequestSchema, CallToolResult, ListToolsRequestSchema } from "@
|
||||
import { ToolInterface } from "./Tool";
|
||||
import { HelloWorldTool } from "./tools/HelloWorldTool";
|
||||
import { PrintTextTool } from "./tools/PrintTextTool";
|
||||
import { ExecuteCodeTool } from "./tools/ExecuteCodeTool";
|
||||
import { PluginBridge } from "./PluginBridge";
|
||||
import { createLogger } from "./logger";
|
||||
|
||||
@ -58,7 +59,7 @@ export class PenpotMcpServer {
|
||||
* the internal registry for later execution.
|
||||
*/
|
||||
private registerTools(): void {
|
||||
const toolInstances: ToolInterface[] = [new HelloWorldTool(this), new PrintTextTool(this)];
|
||||
const toolInstances: ToolInterface[] = [new HelloWorldTool(this), new PrintTextTool(this), new ExecuteCodeTool(this)];
|
||||
|
||||
for (const tool of toolInstances) {
|
||||
this.tools.set(tool.definition.name, tool);
|
||||
|
||||
19
mcp-server/src/tasks/ExecuteCodePluginTask.ts
Normal file
19
mcp-server/src/tasks/ExecuteCodePluginTask.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { PluginTask } from "../PluginTask";
|
||||
import { ExecuteCodeTaskParams, PluginTaskResult } from "@penpot-mcp/common";
|
||||
|
||||
/**
|
||||
* Task for executing JavaScript code in the plugin context.
|
||||
*
|
||||
* This task instructs the plugin to execute arbitrary JavaScript code
|
||||
* and return the result of execution.
|
||||
*/
|
||||
export class ExecuteCodePluginTask extends PluginTask<ExecuteCodeTaskParams, PluginTaskResult<any>> {
|
||||
/**
|
||||
* Creates a new execute code task.
|
||||
*
|
||||
* @param params - The parameters containing the code to execute
|
||||
*/
|
||||
constructor(params: ExecuteCodeTaskParams) {
|
||||
super("executeCode", params);
|
||||
}
|
||||
}
|
||||
61
mcp-server/src/tools/ExecuteCodeTool.ts
Normal file
61
mcp-server/src/tools/ExecuteCodeTool.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { IsNotEmpty, IsString } from "class-validator";
|
||||
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 {
|
||||
/**
|
||||
* The JavaScript code to execute in the plugin context.
|
||||
*/
|
||||
@IsString({ message: "Code must be a string" })
|
||||
@IsNotEmpty({ message: "Code cannot be empty" })
|
||||
code!: 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);
|
||||
}
|
||||
|
||||
protected getToolName(): string {
|
||||
return "execute_code";
|
||||
}
|
||||
|
||||
protected getToolDescription(): string {
|
||||
return (
|
||||
"Executes JavaScript code in the Penpot plugin context. " +
|
||||
"Two objects are available: `penpot` (the Penpot API) and `storage` (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).\n" +
|
||||
"The tool call returns the value of the concluding return statement, if any."
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (result.data !== undefined) {
|
||||
return new TextResponse(`Code executed successfully. Result: ${JSON.stringify(result.data, null, 2)}`);
|
||||
} else {
|
||||
return new TextResponse("Code executed successfully with no return value.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
import {PrintTextTaskHandler} from "./task-handlers/PrintTextTaskHandler";
|
||||
import {ExecuteCodeTaskHandler} from "./task-handlers/ExecuteCodeTaskHandler";
|
||||
import {Task, TaskHandler} from "./TaskHandler";
|
||||
|
||||
/**
|
||||
@ -6,6 +7,7 @@ import {Task, TaskHandler} from "./TaskHandler";
|
||||
*/
|
||||
const taskHandlers: TaskHandler[] = [
|
||||
new PrintTextTaskHandler(),
|
||||
new ExecuteCodeTaskHandler(),
|
||||
];
|
||||
|
||||
penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`);
|
||||
@ -52,12 +54,12 @@ function handlePluginTaskRequest(request: { id: string; task: string; params: an
|
||||
handler.handle(task);
|
||||
console.log("Task handled successfully:", task);
|
||||
} catch (error) {
|
||||
console.error("Error creating text:", error);
|
||||
console.error("Error handling task:", error);
|
||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||
task.sendError(`Error handling task: ${errorMessage}`);
|
||||
}
|
||||
} else {
|
||||
console.warn("Unknown plugin task:", request.task);
|
||||
console.error("Unknown plugin task:", request.task);
|
||||
task.sendError(`Unknown task type: ${request.task}`);
|
||||
}
|
||||
}
|
||||
|
||||
44
penpot-plugin/src/task-handlers/ExecuteCodeTaskHandler.ts
Normal file
44
penpot-plugin/src/task-handlers/ExecuteCodeTaskHandler.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import {Task, TaskHandler} from "../TaskHandler";
|
||||
import {ExecuteCodeTaskParams} from "../../../common/src";
|
||||
|
||||
/**
|
||||
* Task handler for executing JavaScript code in the plugin context.
|
||||
*
|
||||
* Maintains a persistent context object that preserves state between code executions.
|
||||
*/
|
||||
export class ExecuteCodeTaskHandler extends TaskHandler<ExecuteCodeTaskParams> {
|
||||
readonly taskType = "executeCode";
|
||||
|
||||
/**
|
||||
* Persistent context object that maintains state between code executions.
|
||||
* Contains the penpot API and any variables defined in executed code.
|
||||
*/
|
||||
private readonly context: any;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
// initialize context, making penpot object available
|
||||
this.context = {
|
||||
penpot: penpot,
|
||||
storage: {}
|
||||
};
|
||||
}
|
||||
|
||||
handle(task: Task<ExecuteCodeTaskParams>): void {
|
||||
if (!task.params.code) {
|
||||
task.sendError("executeCode task requires 'code' parameter");
|
||||
return;
|
||||
}
|
||||
|
||||
const context = this.context;
|
||||
const code = task.params.code;
|
||||
|
||||
const result = (function (ctx) {
|
||||
return Function(...Object.keys(ctx), code)(...Object.values(ctx));
|
||||
})(context);
|
||||
|
||||
console.log("Code execution result:", result);
|
||||
task.sendSuccess(result);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user