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
|
.idea
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
|
*.bak
|
||||||
|
*.orig
|
||||||
temp
|
temp
|
||||||
|
*.tsbuildinfo
|
||||||
|
|||||||
@ -68,3 +68,13 @@ export interface PrintTextTaskParams {
|
|||||||
*/
|
*/
|
||||||
text: string;
|
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 { ToolInterface } from "./Tool";
|
||||||
import { HelloWorldTool } from "./tools/HelloWorldTool";
|
import { HelloWorldTool } from "./tools/HelloWorldTool";
|
||||||
import { PrintTextTool } from "./tools/PrintTextTool";
|
import { PrintTextTool } from "./tools/PrintTextTool";
|
||||||
|
import { ExecuteCodeTool } from "./tools/ExecuteCodeTool";
|
||||||
import { PluginBridge } from "./PluginBridge";
|
import { PluginBridge } from "./PluginBridge";
|
||||||
import { createLogger } from "./logger";
|
import { createLogger } from "./logger";
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ export class PenpotMcpServer {
|
|||||||
* the internal registry for later execution.
|
* the internal registry for later execution.
|
||||||
*/
|
*/
|
||||||
private registerTools(): void {
|
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) {
|
for (const tool of toolInstances) {
|
||||||
this.tools.set(tool.definition.name, tool);
|
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 {PrintTextTaskHandler} from "./task-handlers/PrintTextTaskHandler";
|
||||||
|
import {ExecuteCodeTaskHandler} from "./task-handlers/ExecuteCodeTaskHandler";
|
||||||
import {Task, TaskHandler} from "./TaskHandler";
|
import {Task, TaskHandler} from "./TaskHandler";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -6,6 +7,7 @@ import {Task, TaskHandler} from "./TaskHandler";
|
|||||||
*/
|
*/
|
||||||
const taskHandlers: TaskHandler[] = [
|
const taskHandlers: TaskHandler[] = [
|
||||||
new PrintTextTaskHandler(),
|
new PrintTextTaskHandler(),
|
||||||
|
new ExecuteCodeTaskHandler(),
|
||||||
];
|
];
|
||||||
|
|
||||||
penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`);
|
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);
|
handler.handle(task);
|
||||||
console.log("Task handled successfully:", task);
|
console.log("Task handled successfully:", task);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error creating text:", error);
|
console.error("Error handling task:", error);
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
task.sendError(`Error handling task: ${errorMessage}`);
|
task.sendError(`Error handling task: ${errorMessage}`);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.warn("Unknown plugin task:", request.task);
|
console.error("Unknown plugin task:", request.task);
|
||||||
task.sendError(`Unknown task type: ${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