diff --git a/mcp/README.md b/mcp/README.md index 4c206d9e35..632abf4254 100644 --- a/mcp/README.md +++ b/mcp/README.md @@ -298,6 +298,7 @@ The Penpot MCP server can be configured using environment variables. | `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 in local single-user mode. 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_MAX_BODY_SIZE` | Maximum size of a JSON request body accepted by the MCP endpoint, in any format supported by Express (e.g. `10mb`) | `10mb` | | `PENPOT_MCP_EXPORT_SHAPE_MAX_PARALLEL_REQUESTS` | Maximum number of parallel export shape requests (multi-user mode only). | `0` (no limit) | | `PENPOT_MCP_AUTH_URI` | Public base URL of the MCP authorization service, e.g. `https://penpot.example/api/mcp/oauth`. Set to enable OAuth; requires `--multi-user`. Use HTTPS (HTTP is allowed on loopback for development), without credentials, query parameters, or a fragment. | (unset; OAuth disabled) | | `PENPOT_MCP_REDIS_URI` | Redis connection URI (e.g. `redis://host:6379`) enabling multi-instance horizontal scaling via Redis pub/sub task routing (multi-user mode only). When unset, the server runs in single-instance mode, requiring the plugin and MCP client to connect to the same instance. | (unset) | diff --git a/mcp/packages/server/src/PenpotMcpServer.http.test.ts b/mcp/packages/server/src/PenpotMcpServer.http.test.ts index 693e3197ab..ad2e75d446 100644 --- a/mcp/packages/server/src/PenpotMcpServer.http.test.ts +++ b/mcp/packages/server/src/PenpotMcpServer.http.test.ts @@ -126,6 +126,13 @@ test("passes an explicit Penpot session ID from the tool call to plugin dispatch assert.match(JSON.stringify(body), /chosen-tab/); }); +test("accepts tool calls with request bodies larger than 100 KiB", async (t) => { + t.mock.method(server.pluginBridge, "executePluginTask", async () => ({ data: "ok" })); + const code = `return "${"x".repeat(200_000)}".length;`; + const response = await modernRequest("tools/call", { name: "execute_code", arguments: { code } }); + assert.equal(response.status, 200); +}); + test("supports older Streamable HTTP clients without allocating a session", async () => { const client = new Client({ name: "legacy-test", version: "1" }); const transport = new StreamableHTTPClientTransport(new URL(`${baseUrl}/mcp`)); diff --git a/mcp/packages/server/src/PenpotMcpServer.ts b/mcp/packages/server/src/PenpotMcpServer.ts index 445fb4ee4e..56f7ac5b2c 100644 --- a/mcp/packages/server/src/PenpotMcpServer.ts +++ b/mcp/packages/server/src/PenpotMcpServer.ts @@ -147,6 +147,14 @@ export class PenpotMcpServer { */ private readonly tenant: string; + /** + * Maximum size of a JSON request body, read from the `PENPOT_MCP_MAX_BODY_SIZE` environment variable. + * + * Accepts any value supported by Express' `json` body parser (e.g. `"10mb"`). Defaults to `"10mb"`, + * since tool calls such as `execute_code` can carry large payloads. + */ + private readonly maxBodySize: string; + constructor(private isMultiUser: boolean = false) { this.authentication = new McpAuthentication(process.env.PENPOT_MCP_AUTH_URI, isMultiUser); // read port configuration from environment variables @@ -156,6 +164,7 @@ export class PenpotMcpServer { this.replHost = process.env.PENPOT_MCP_REPL_HOST ?? "localhost"; this.replPort = parseInt(process.env.PENPOT_MCP_REPL_PORT ?? "4403", 10); this.tenant = process.env.PENPOT_TENANT ?? "default"; + this.maxBodySize = process.env.PENPOT_MCP_MAX_BODY_SIZE ?? "10mb"; const toolTimeoutSecs = parseInt(process.env.PENPOT_MCP_TOOL_TIMEOUT_S ?? "120", 10); this.configLoader = new ConfigurationLoader(process.cwd()); @@ -323,7 +332,7 @@ export class PenpotMcpServer { async start(): Promise { const { default: express } = await import("express"); this.app = express(); - this.app.use(express.json()); + this.app.use(express.json({ limit: this.maxBodySize })); this.setupHttpEndpoints();