Files
penpot/mcp/packages/server/src/ReplServer.ts
2026-02-09 17:38:40 +01:00

111 lines
3.6 KiB
TypeScript

import express from "express";
import path from "path";
import { fileURLToPath } from "url";
import { PluginBridge } from "./PluginBridge";
import { ExecuteCodePluginTask } from "./tasks/ExecuteCodePluginTask";
import { createLogger } from "./logger";
/**
* Web-based REPL server for executing code through the PluginBridge.
*
* Provides a REPL-style HTML interface that allows users to input
* JavaScript code and execute it via ExecuteCodePluginTask instances.
* The interface maintains command history, displays logs in <pre> tags,
* and shows results in visually separated blocks.
*/
export class ReplServer {
private readonly logger = createLogger("ReplServer");
private readonly app: express.Application;
private readonly port: number;
private server: any;
constructor(
private readonly pluginBridge: PluginBridge,
port: number = 4403
) {
this.port = port;
this.app = express();
this.setupMiddleware();
this.setupRoutes();
}
/**
* Sets up Express middleware for request parsing and static content.
*/
private setupMiddleware(): void {
this.app.use(express.json());
}
/**
* Sets up HTTP routes for the REPL interface and API endpoints.
*/
private setupRoutes(): void {
// serve the main REPL interface
this.app.get("/", (req, res) => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const htmlPath = path.join(__dirname, "static", "repl.html");
res.sendFile(htmlPath);
});
// API endpoint for executing code
this.app.post("/execute", async (req, res) => {
try {
const { code } = req.body;
if (!code || typeof code !== "string") {
return res.status(400).json({
error: "Code parameter is required and must be a string",
});
}
const task = new ExecuteCodePluginTask({ code });
const result = await this.pluginBridge.executePluginTask(task);
// extract the result member from ExecuteCodeTaskResultData
const executeResult = result.data?.result;
res.json({
success: true,
result: executeResult,
log: result.data?.log || "",
});
} catch (error) {
this.logger.error(error, "Failed to execute code in REPL");
res.status(500).json({
error: error instanceof Error ? error.message : "Unknown error occurred",
});
}
});
}
/**
* Starts the REPL web server.
*
* Begins listening on the configured port and logs server startup information.
*/
public async start(): Promise<void> {
return new Promise((resolve) => {
this.server = this.app.listen(this.port, () => {
this.logger.info(`REPL server started on port ${this.port}`);
this.logger.info(`REPL interface URL: http://${this.pluginBridge.mcpServer.host}:${this.port}`);
resolve();
});
});
}
/**
* Stops the REPL web server.
*/
public async stop(): Promise<void> {
if (this.server) {
return new Promise((resolve) => {
this.server.close(() => {
this.logger.info("REPL server stopped");
resolve();
});
});
}
}
}