Plugin: Add multi-user mode option (alt. run script, which sets constant)

User token is a dummy token for now; shall be provided by Penpot in the future
This commit is contained in:
Dominik Jain 2025-12-16 22:03:46 +01:00
parent 4c6b7844b1
commit 02da2b4b19
6 changed files with 128 additions and 12 deletions

View File

@ -5,10 +5,11 @@
"scripts": { "scripts": {
"install:all": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" \"npm --prefix common install\" \"npm --prefix mcp-server install\" \"npm --prefix penpot-plugin install\"", "install:all": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" \"npm --prefix common install\" \"npm --prefix mcp-server install\" \"npm --prefix penpot-plugin install\"",
"build:all": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" --success first \"npm --prefix common install && npm --prefix common run build\" \"npm --prefix mcp-server run build\" \"npm --prefix penpot-plugin run build\"", "build:all": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" --success first \"npm --prefix common install && npm --prefix common run build\" \"npm --prefix mcp-server run build\" \"npm --prefix penpot-plugin run build\"",
"build:all-multi-user": "concurrently --names \"COMMON,MCP-SERVER,PLUGIN\" --prefix-colors \"green,cyan,magenta\" --success first \"npm --prefix common install && npm --prefix common run build\" \"npm --prefix mcp-server run build\" \"npm --prefix penpot-plugin run build:multi-user\"",
"start:all": "concurrently --names \"MCP-SERVER,PLUGIN-SERVER\" --prefix-colors \"cyan,magenta\" --kill-others-on-fail \"npm --prefix mcp-server start\" \"npm --prefix penpot-plugin run dev\"", "start:all": "concurrently --names \"MCP-SERVER,PLUGIN-SERVER\" --prefix-colors \"cyan,magenta\" --kill-others-on-fail \"npm --prefix mcp-server start\" \"npm --prefix penpot-plugin run dev\"",
"start:all-multi-user": "concurrently --names \"MCP-SERVER,PLUGIN-SERVER\" --prefix-colors \"cyan,magenta\" --kill-others-on-fail \"npm --prefix mcp-server run start:multi-user\" \"npm --prefix penpot-plugin run dev\"", "start:all-multi-user": "concurrently --names \"MCP-SERVER,PLUGIN-SERVER\" --prefix-colors \"cyan,magenta\" --kill-others-on-fail \"npm --prefix mcp-server run start:multi-user\" \"npm --prefix penpot-plugin run dev:multi-user\"",
"bootstrap": "npm run install:all && npm run build:all && npm run start:all", "bootstrap": "npm run install:all && npm run build:all && npm run start:all",
"bootstrap:multi-user": "npm run install:all && npm run build:all && npm run start:all-multi-user", "bootstrap:multi-user": "npm run install:all && npm run build:all-multi-user && npm run start:all-multi-user",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check ." "format:check": "prettier --check ."
}, },

View File

@ -1,12 +1,12 @@
{ {
"name": "penpot-plugin-starter-template", "name": "penpot-mcp-plugin",
"version": "0.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "penpot-plugin-starter-template", "name": "penpot-mcp-plugin",
"version": "0.0.0", "version": "1.0.0",
"dependencies": { "dependencies": {
"@penpot-mcp/common": "file:../common", "@penpot-mcp/common": "file:../common",
"@penpot/plugin-styles": "1.3.2", "@penpot/plugin-styles": "1.3.2",
@ -14,6 +14,7 @@
"penpot-mcp": "file:.." "penpot-mcp": "file:.."
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.5", "vite": "^7.0.5",
"vite-live-preview": "^0.3.2" "vite-live-preview": "^0.3.2"
@ -816,6 +817,40 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/cross-env": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
"integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.1"
},
"bin": {
"cross-env": "src/bin/cross-env.js",
"cross-env-shell": "src/bin/cross-env-shell.js"
},
"engines": {
"node": ">=10.14",
"npm": ">=6",
"yarn": ">=1"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.6", "version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
@ -914,6 +949,13 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0" "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
} }
}, },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -950,6 +992,16 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/penpot-mcp": { "node_modules/penpot-mcp": {
"resolved": "..", "resolved": "..",
"link": true "link": true
@ -1039,6 +1091,29 @@
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -1184,6 +1259,22 @@
"vite": ">=5.2.13" "vite": ">=5.2.13"
} }
}, },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/ws": { "node_modules/ws": {
"version": "8.18.0", "version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",

View File

@ -1,11 +1,13 @@
{ {
"name": "penpot-plugin-starter-template", "name": "penpot-mcp-plugin",
"private": true, "private": true,
"version": "0.0.0", "version": "1.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite build --watch", "dev": "vite build --watch",
"build": "tsc && vite build" "dev:multi-user": "cross-env MULTI_USER_MODE=true vite build --watch",
"build": "tsc && vite build",
"build:multi-user": "tsc && cross-env MULTI_USER_MODE=true vite build"
}, },
"dependencies": { "dependencies": {
"@penpot-mcp/common": "file:../common", "@penpot-mcp/common": "file:../common",
@ -14,6 +16,7 @@
"penpot-mcp": "file:.." "penpot-mcp": "file:.."
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^7.0.3",
"typescript": "^5.8.3", "typescript": "^5.8.3",
"vite": "^7.0.5", "vite": "^7.0.5",
"vite-live-preview": "^0.3.2" "vite-live-preview": "^0.3.2"

View File

@ -4,6 +4,10 @@ import "./style.css";
const searchParams = new URLSearchParams(window.location.search); const searchParams = new URLSearchParams(window.location.search);
document.body.dataset.theme = searchParams.get("theme") ?? "light"; document.body.dataset.theme = searchParams.get("theme") ?? "light";
// Determine whether multi-user mode is enabled based on URL parameters
const isMultiUserMode = searchParams.get("multiUser") === "true";
console.log("Penpot MCP multi-user mode:", isMultiUserMode);
// WebSocket connection management // WebSocket connection management
let ws: WebSocket | null = null; let ws: WebSocket | null = null;
const statusElement = document.getElementById("connection-status"); const statusElement = document.getElementById("connection-status");
@ -47,7 +51,13 @@ function connectToMcpServer(): void {
} }
try { try {
ws = new WebSocket("ws://localhost:4402"); let wsUrl = "ws://localhost:4402";
if (isMultiUserMode) {
// TODO obtain proper userToken from penpot
const userToken = "dummyToken";
wsUrl += `?userToken=${encodeURIComponent(userToken)}`;
}
ws = new WebSocket(wsUrl);
updateConnectionStatus("Connecting...", false); updateConnectionStatus("Connecting...", false);
ws.onopen = () => { ws.onopen = () => {

View File

@ -6,8 +6,12 @@ import { Task, TaskHandler } from "./TaskHandler";
*/ */
const taskHandlers: TaskHandler[] = [new ExecuteCodeTaskHandler()]; const taskHandlers: TaskHandler[] = [new ExecuteCodeTaskHandler()];
// Open the plugin UI // Determine whether multi-user mode is enabled based on build-time configuration
penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}`, { width: 158, height: 200 }); declare const IS_MULTI_USER_MODE: boolean;
const isMultiUserMode = typeof IS_MULTI_USER_MODE !== "undefined" ? IS_MULTI_USER_MODE : false;
// Open the plugin UI (main.ts)
penpot.ui.open("Penpot MCP Plugin", `?theme=${penpot.theme}&multiUser=${isMultiUserMode}`, { width: 158, height: 200 });
// Handle messages // Handle messages
penpot.ui.onMessage<string | { id: string; task: string; params: any }>((message) => { penpot.ui.onMessage<string | { id: string; task: string; params: any }>((message) => {

View File

@ -1,6 +1,10 @@
import { defineConfig } from "vite"; import { defineConfig } from "vite";
import livePreview from "vite-live-preview"; import livePreview from "vite-live-preview";
// Debug: Log the environment variable
console.log("MULTI_USER_MODE env:", process.env.MULTI_USER_MODE);
console.log("Will define IS_MULTI_USER_MODE as:", JSON.stringify(process.env.MULTI_USER_MODE === "true"));
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
livePreview({ livePreview({
@ -27,4 +31,7 @@ export default defineConfig({
port: 4400, port: 4400,
cors: true, cors: true,
}, },
define: {
IS_MULTI_USER_MODE: JSON.stringify(process.env.MULTI_USER_MODE === "true"),
},
}); });