From 93e98f5e08742ce098bb9b0b7f8c45d8d905829e Mon Sep 17 00:00:00 2001 From: Dominik Jain Date: Wed, 17 Sep 2025 19:36:12 +0200 Subject: [PATCH] Refactor task handling (adding Task abstraction) --- mcp-server/src/PluginBridge.ts | 6 +- mcp-server/src/PluginTask.ts | 2 +- penpot-plugin/src/TaskHandler.ts | 58 +++++++++++-------- penpot-plugin/src/plugin.ts | 15 +++-- .../src/task-handlers/PrintTextTaskHandler.ts | 47 +++++---------- 5 files changed, 63 insertions(+), 65 deletions(-) diff --git a/mcp-server/src/PluginBridge.ts b/mcp-server/src/PluginBridge.ts index 7f40133..cc150b5 100644 --- a/mcp-server/src/PluginBridge.ts +++ b/mcp-server/src/PluginBridge.ts @@ -1,6 +1,6 @@ -import { WebSocket, WebSocketServer } from "ws"; -import { PluginTask } from "./PluginTask"; -import { PluginTaskResponse, PluginTaskResult } from "@penpot-mcp/common"; +import {WebSocket, WebSocketServer} from "ws"; +import {PluginTask} from "./PluginTask"; +import {PluginTaskResponse} from "@penpot-mcp/common"; /** * Provides the connection to the Penpot MCP Plugin via WebSocket diff --git a/mcp-server/src/PluginTask.ts b/mcp-server/src/PluginTask.ts index 139dcca..0644042 100644 --- a/mcp-server/src/PluginTask.ts +++ b/mcp-server/src/PluginTask.ts @@ -6,7 +6,7 @@ * * @template TParams - The strongly-typed parameters for this task */ -import { PluginTaskRequest, PluginTaskResult } from "@penpot-mcp/common"; +import { PluginTaskRequest, PluginTaskResult } from '@penpot-mcp/common'; import { randomUUID } from "crypto"; /** diff --git a/penpot-plugin/src/TaskHandler.ts b/penpot-plugin/src/TaskHandler.ts index 1ef8972..a7d005a 100644 --- a/penpot-plugin/src/TaskHandler.ts +++ b/penpot-plugin/src/TaskHandler.ts @@ -1,31 +1,22 @@ - /** - * Abstract base class for task handlers in the Penpot MCP plugin. - * - * @template TParams - The type of parameters this handler expects + * Represents a task received from the MCP server in the Penpot MCP plugin */ -export abstract class TaskHandler { - /** The task identifier this handler is responsible for */ - abstract readonly task: string; - +export class Task { /** - * Checks if this handler can process the given task. - * - * @param task - The task identifier to check - * @returns True if this handler applies to the given task + * @param requestId Unique identifier for the task request + * @param taskType The type of the task to execute + * @param params Task parameters/arguments */ - applies(task: string): boolean { - return this.task === task; - } + constructor(public requestId: string, public taskType: string, public params: TParams) {} /** * Sends a task response back to the MCP server. */ - public static sendTaskResponse(taskId: string, success: boolean, data: any = undefined, error: any = undefined): void { + protected sendResponse(success: boolean, data: any = undefined, error: any = undefined): void { const response = { type: "task-response", response: { - id: taskId, + id: this.requestId, success: success, data: data, error: error, @@ -37,19 +28,38 @@ export abstract class TaskHandler { console.log("Sent task response:", response); } - public static sendTaskSuccess(taskId: string, data: any = undefined): void { - this.sendTaskResponse(taskId, true, data); + public sendSuccess(data: any = undefined): void { + this.sendResponse(true, data); } - public static sendTaskError(taskId: string, error: string): void { - this.sendTaskResponse(taskId, false, undefined, error); + public sendError(error: string): void { + this.sendResponse(false, undefined, error); + } +} + +/** + * Abstract base class for task handlers in the Penpot MCP plugin. + * + * @template TParams - The type of parameters this handler expects + */ +export abstract class TaskHandler { + /** The task type this handler is responsible for */ + abstract readonly taskType: string; + + /** + * Checks if this handler can process the given task. + * + * @param task - The task identifier to check + * @returns True if this handler applies to the given task + */ + isApplicableTo(task: Task): boolean { + return this.taskType === task.taskType; } /** * Handles the task with the provided parameters. * - * @param taskId - The unique ID of the task request - * @param params - The parameters for the task + * @param task - The task to be handled */ - abstract handle(taskId: string, params: TParams): void; + abstract handle(task: Task): void; } \ No newline at end of file diff --git a/penpot-plugin/src/plugin.ts b/penpot-plugin/src/plugin.ts index 0288db8..3a560c4 100644 --- a/penpot-plugin/src/plugin.ts +++ b/penpot-plugin/src/plugin.ts @@ -1,5 +1,5 @@ import {PrintTextTaskHandler} from "./task-handlers/PrintTextTaskHandler"; -import {TaskHandler} from "./TaskHandler"; +import {Task, TaskHandler} from "./TaskHandler"; /** * Registry of all available task handlers. @@ -40,22 +40,25 @@ penpot.ui.onMessage((message */ function handlePluginTaskRequest(request: { id: string; task: string; params: any }): void { console.log("Executing plugin task:", request.task, request.params); + const task = new Task(request.id, request.task, request.params); // Find the appropriate handler - const handler = taskHandlers.find(h => h.applies(request.task)); + const handler = taskHandlers.find(h => h.isApplicableTo(task)); if (handler) { try { // Cast the params to the expected type and handle the task - handler.handle(request.id, request.params); + console.log("Processing task with handler:", handler); + handler.handle(task); + console.log("Task handled successfully:", task); } catch (error) { - console.error(`Error handling task '${request.task}':`, error); + console.error("Error creating text:", error); const errorMessage = error instanceof Error ? error.message : "Unknown error"; - TaskHandler.sendTaskError(request.id, `Error handling task: ${errorMessage}`); + task.sendError(`Error handling task: ${errorMessage}`); } } else { console.warn("Unknown plugin task:", request.task); - TaskHandler.sendTaskError(request.id, `Unknown task type: ${request.task}`); + task.sendError(`Unknown task type: ${request.task}`); } } diff --git a/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts b/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts index 220e14e..3052571 100644 --- a/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts +++ b/penpot-plugin/src/task-handlers/PrintTextTaskHandler.ts @@ -1,46 +1,31 @@ -import {TaskHandler} from "../TaskHandler"; +import {Task, TaskHandler} from "../TaskHandler"; import {PrintTextTaskParams} from "../../../common/src"; /** * Task handler for printing text to Penpot. */ export class PrintTextTaskHandler extends TaskHandler { - readonly task = "printText"; + readonly taskType = "printText"; - /** - * Handles the printText task by creating text in Penpot. - * - * @param taskId - The unique ID of the task request - * @param params - The parameters containing the text to create - */ - handle(taskId: string, params: PrintTextTaskParams): void { - if (!params.text) { - console.error("printText task requires 'text' parameter"); - TaskHandler.sendTaskError(taskId, "printText task requires 'text' parameter"); - return; + handle(task: Task): void { + if (!task.params.text) { + throw new Error("printText task requires 'text' parameter"); } - try { - const text = penpot.createText(params.text); + const text = penpot.createText(task.params.text); - if (text) { - // Center the text in the viewport - text.x = penpot.viewport.center.x; - text.y = penpot.viewport.center.y; + if (text) { + // Center the text in the viewport + text.x = penpot.viewport.center.x; + text.y = penpot.viewport.center.y; - // Select the newly created text - penpot.selection = [text]; + // Select the newly created text + penpot.selection = [text]; - console.log("Successfully created text:", params.text); - TaskHandler.sendTaskSuccess(taskId, { textId: text.id }); - } else { - console.error("Failed to create text element"); - TaskHandler.sendTaskError(taskId, "Failed to create text element"); - } - } catch (error) { - console.error("Error creating text:", error); - const errorMessage = error instanceof Error ? error.message : "Unknown error"; - TaskHandler.sendTaskError(taskId, `Error creating text: ${errorMessage}`); + console.log("Successfully created text:", task.params.text); + task.sendSuccess({ textId: text.id }); + } else { + throw new Error("Failed to create text element"); } } } \ No newline at end of file