mirror of
https://github.com/penpot/penpot.git
synced 2026-08-28 23:59:00 +00:00
✨ Add PENPOT_MCP_REPL_ENABLE env var for explicit REPL control
Allow the REPL server to be enabled independently of the devenv setting via a new PENPOT_MCP_REPL_ENABLE environment variable. When set to "true", the REPL server starts regardless of PENPOT_MCP_DEVENV; when set to any other value, it is disabled. When unset, the previous isDevEnv fallback applies. Addresses review feedback on PR #11282. AI-assisted-by: mimo-v2.5
This commit is contained in:
parent
eb21d4e899
commit
bd83ab076a
@ -265,6 +265,7 @@ The Penpot MCP server can be configured using environment variables.
|
|||||||
| `PENPOT_MCP_SERVER_PORT` | Port for the HTTP/SSE server | `4401` |
|
| `PENPOT_MCP_SERVER_PORT` | Port for the HTTP/SSE server | `4401` |
|
||||||
| `PENPOT_MCP_WEBSOCKET_PORT` | Port for the WebSocket server (plugin connection) | `4402` |
|
| `PENPOT_MCP_WEBSOCKET_PORT` | Port for the WebSocket server (plugin connection) | `4402` |
|
||||||
| `PENPOT_MCP_REPL_PORT` | Port for the REPL server (development/debugging) | `4403` |
|
| `PENPOT_MCP_REPL_PORT` | Port for the REPL server (development/debugging) | `4403` |
|
||||||
|
| `PENPOT_MCP_REPL_ENABLE` | Explicitly enable/disable the REPL server. Set to `true` to enable. When unset, defaults to the value of `PENPOT_MCP_DEVENV`. | (unset) |
|
||||||
| `PENPOT_MCP_REMOTE_MODE` | Enable remote mode (disables file system access). Set to `true` to enable. | `false` |
|
| `PENPOT_MCP_REMOTE_MODE` | Enable remote mode (disables file system access). Set to `true` to enable. | `false` |
|
||||||
| `PENPOT_MCP_DEVENV` | Enable Penpot development environment tools. Set to `true` to enable. | `false` |
|
| `PENPOT_MCP_DEVENV` | Enable Penpot development environment tools. Set to `true` to enable. | `false` |
|
||||||
| `PENPOT_MCP_TOOL_TIMEOUT_S` | Timeout, in seconds, for tool calls dispatched to the Penpot plugin | `120` |
|
| `PENPOT_MCP_TOOL_TIMEOUT_S` | Timeout, in seconds, for tool calls dispatched to the Penpot plugin | `120` |
|
||||||
|
|||||||
@ -16,6 +16,28 @@ test("isDevEnvEnabled returns true when PENPOT_MCP_DEVENV is 'true'", () => {
|
|||||||
assert.equal(PenpotMcpServer.isDevEnvEnabled({ PENPOT_MCP_DEVENV: "true" }), true);
|
assert.equal(PenpotMcpServer.isDevEnvEnabled({ PENPOT_MCP_DEVENV: "true" }), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// ── Pure function tests: isReplEnabled ──────────────────────────
|
||||||
|
|
||||||
|
test("isReplEnabled returns false when neither env var is set", () => {
|
||||||
|
assert.equal(PenpotMcpServer.isReplEnabled({}), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isReplEnabled returns true when PENPOT_MCP_DEVENV is 'true' (fallback)", () => {
|
||||||
|
assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_DEVENV: "true" }), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isReplEnabled returns true when PENPOT_MCP_REPL_ENABLE is 'true'", () => {
|
||||||
|
assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_REPL_ENABLE: "true" }), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isReplEnabled returns false when PENPOT_MCP_REPL_ENABLE is 'false' even if DEVENV is true", () => {
|
||||||
|
assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_REPL_ENABLE: "false", PENPOT_MCP_DEVENV: "true" }), false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("isReplEnabled returns true when PENPOT_MCP_REPL_ENABLE is 'true' regardless of DEVENV", () => {
|
||||||
|
assert.equal(PenpotMcpServer.isReplEnabled({ PENPOT_MCP_REPL_ENABLE: "true" }), true);
|
||||||
|
});
|
||||||
|
|
||||||
// ── Integration tests: constructor gating ──────────────────────
|
// ── Integration tests: constructor gating ──────────────────────
|
||||||
//
|
//
|
||||||
// Each test uses unique ports to avoid conflicts when tests run
|
// Each test uses unique ports to avoid conflicts when tests run
|
||||||
@ -57,6 +79,40 @@ test("constructor creates ReplServer when PENPOT_MCP_DEVENV is 'true'", async ()
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("constructor creates ReplServer when PENPOT_MCP_REPL_ENABLE is 'true' without DEVENV", async () => {
|
||||||
|
const prevDevEnv = process.env.PENPOT_MCP_DEVENV;
|
||||||
|
const prevReplEnable = process.env.PENPOT_MCP_REPL_ENABLE;
|
||||||
|
const prevPorts = setUniqueEnv();
|
||||||
|
delete process.env.PENPOT_MCP_DEVENV;
|
||||||
|
process.env.PENPOT_MCP_REPL_ENABLE = "true";
|
||||||
|
let server: PenpotMcpServer | undefined;
|
||||||
|
try {
|
||||||
|
server = new PenpotMcpServer(false);
|
||||||
|
assert.equal(server.hasReplServer(), true);
|
||||||
|
} finally {
|
||||||
|
await server?.stop();
|
||||||
|
restoreEnv(prevDevEnv, prevPorts);
|
||||||
|
restoreOrDelete("PENPOT_MCP_REPL_ENABLE", prevReplEnable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test("constructor does not create ReplServer when PENPOT_MCP_REPL_ENABLE is 'false' even with DEVENV", async () => {
|
||||||
|
const prevDevEnv = process.env.PENPOT_MCP_DEVENV;
|
||||||
|
const prevReplEnable = process.env.PENPOT_MCP_REPL_ENABLE;
|
||||||
|
const prevPorts = setUniqueEnv();
|
||||||
|
process.env.PENPOT_MCP_DEVENV = "true";
|
||||||
|
process.env.PENPOT_MCP_REPL_ENABLE = "false";
|
||||||
|
let server: PenpotMcpServer | undefined;
|
||||||
|
try {
|
||||||
|
server = new PenpotMcpServer(false);
|
||||||
|
assert.equal(server.hasReplServer(), false);
|
||||||
|
} finally {
|
||||||
|
await server?.stop();
|
||||||
|
restoreEnv(prevDevEnv, prevPorts);
|
||||||
|
restoreOrDelete("PENPOT_MCP_REPL_ENABLE", prevReplEnable);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// ── Helpers ────────────────────────────────────────────────────
|
// ── Helpers ────────────────────────────────────────────────────
|
||||||
|
|
||||||
function setUniqueEnv() {
|
function setUniqueEnv() {
|
||||||
|
|||||||
@ -66,6 +66,20 @@ export class PenpotMcpServer {
|
|||||||
return env.PENPOT_MCP_DEVENV === "true";
|
return env.PENPOT_MCP_DEVENV === "true";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines whether the REPL server should be enabled.
|
||||||
|
*
|
||||||
|
* If ``PENPOT_MCP_REPL_ENABLE`` is set, its value controls the result
|
||||||
|
* (``"true"`` enables, any other value disables). When the variable is
|
||||||
|
* not set, the result falls back to {@link isDevEnvEnabled}.
|
||||||
|
*/
|
||||||
|
public static isReplEnabled(env: Record<string, string | undefined>): boolean {
|
||||||
|
if (env.PENPOT_MCP_REPL_ENABLE !== undefined) {
|
||||||
|
return env.PENPOT_MCP_REPL_ENABLE === "true";
|
||||||
|
}
|
||||||
|
return PenpotMcpServer.isDevEnvEnabled(env);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a short, non-reversible fingerprint of a user token, suitable for
|
* Returns a short, non-reversible fingerprint of a user token, suitable for
|
||||||
* correlating log lines without exposing the full credential.
|
* correlating log lines without exposing the full credential.
|
||||||
@ -160,7 +174,7 @@ export class PenpotMcpServer {
|
|||||||
|
|
||||||
this.pluginBridge = new PluginBridge(this, this.webSocketPort, toolTimeoutSecs, this.redisBridge);
|
this.pluginBridge = new PluginBridge(this, this.webSocketPort, toolTimeoutSecs, this.redisBridge);
|
||||||
|
|
||||||
if (PenpotMcpServer.isDevEnvEnabled(process.env)) {
|
if (PenpotMcpServer.isReplEnabled(process.env)) {
|
||||||
this.replServer = new ReplServer(this.pluginBridge, this.replPort, this.host);
|
this.replServer = new ReplServer(this.pluginBridge, this.replPort, this.host);
|
||||||
} else {
|
} else {
|
||||||
this.replServer = null;
|
this.replServer = null;
|
||||||
@ -211,7 +225,9 @@ export class PenpotMcpServer {
|
|||||||
/**
|
/**
|
||||||
* Indicates whether the REPL server was created.
|
* Indicates whether the REPL server was created.
|
||||||
*
|
*
|
||||||
* The REPL server is created only in development environment mode.
|
* The REPL server is created when {@link isReplEnabled} returns true,
|
||||||
|
* which means either ``PENPOT_MCP_REPL_ENABLE=true`` or, when that
|
||||||
|
* variable is unset, ``PENPOT_MCP_DEVENV=true``.
|
||||||
*/
|
*/
|
||||||
public hasReplServer(): boolean {
|
public hasReplServer(): boolean {
|
||||||
return this.replServer !== null;
|
return this.replServer !== null;
|
||||||
@ -449,7 +465,9 @@ export class PenpotMcpServer {
|
|||||||
if (this.replServer) {
|
if (this.replServer) {
|
||||||
await this.replServer.start();
|
await this.replServer.start();
|
||||||
} else {
|
} else {
|
||||||
this.logger.info("REPL server disabled (set PENPOT_MCP_DEVENV=true to enable)");
|
this.logger.info(
|
||||||
|
"REPL server disabled (set PENPOT_MCP_REPL_ENABLE=true or PENPOT_MCP_DEVENV=true to enable)"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
this.startSessionTimeoutChecker();
|
this.startSessionTimeoutChecker();
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user