Simplify MCP client setup, adding client-setup command (#10604)

Add a `client-setup` command to the published `@penpot/mcp` package.
When invoked as `penpot-mcp client-setup`, the bin delegates to the `add-mcp`
CLI against the local MCP server URL (from `PENPOT_MCP_SERVER_PORT`, default
4401), run via npx.

Update docs on MCP client configuration.
Recommend calling `add-mcp` directly for now, since the `client-setup` command
only becomes available once a new `@penpot/mcp` release is published to npm.

AI-assisted-by: Claude

Co-authored-by: Michael Panchenko <michael.panchenko@oraios-ai.de>
This commit is contained in:
Dr. Dominik Jain 2026-07-20 13:08:51 +02:00 committed by GitHub
parent 4c21923c4a
commit 51c0129485
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 151 additions and 136 deletions

View File

@ -129,17 +129,18 @@ If you just want to try Penpot AI workflows quickly through the MCP, follow this
![MCP Server in Penpot Integrations, copy server url](/img/mcp/mcp-server-url.webp)
4. #### Add the server to your MCP client
In your MCP-aware IDE/agent (Cursor, Claude Code, etc.), add a new server pointing to that URL.
**Example (generic JSON config):**
```json
{
"mcpServers": {
"penpot": {
"url": "https://<your-penpot-domain>/mcp/stream?userToken=YOUR_MCP_KEY"
}
}
}
We recommend using the [add-mcp](https://github.com/neon-solutions/add-mcp) project for connecting your MCP client to the Penpot MCP server.
With `npx` available, call
```shell
npx -y add-mcp -g -n penpot <URL>
```
and follow the interactive setup. Alternatively, follow your client's instructions for connecting
to a remote MCP server. For Claude Desktop, we recommend adding the Penpot MCP Server as a
custom [connector](http://claude.ai/customize/connectors).
See the section **Connect your MCP client** for more details on how to connect.
5. #### Open a Penpot file and connect MCP
In Penpot, open a design file and use **File → MCP Server → Connect** to connect the plugin to your current file.
@ -216,84 +217,21 @@ You can use Penpot MCP server in two main ways:
## Connect your MCP client
Use the same client setup flow for both modes. What changes is the server URL and authentication method.
### Connection values by mode
* **Remote MCP**
* URL: `https://<your-penpot-domain>/mcp/stream?userToken=YOUR_MCP_KEY`
* Auth: MCP key in `userToken`
* URL (copy from your Penpot account overview): `https://<your-penpot-domain>/mcp/stream?userToken=YOUR_MCP_KEY`
* Configure your MCP client with `npx -y add-mcp -g -n penpot <URL>` or by following your client's instructions for connecting to a remote MCP server.
For Claude Desktop, the server can be added as a custom [connector](http://claude.ai/customize/connectors).
* **Local MCP**
* URL: `http://localhost:4401/mcp`
* Auth: none (uses your active Penpot browser session)
* Configure your MCP client with `npx -y add-mcp -g -n penpot http://localhost:4401/mcp` (adjust the port if you changed `PENPOT_MCP_SERVER_PORT`) or by following your client's instructions for connecting to an HTTP MCP server.
### Cursor
1. Open Cursor MCP/tool configuration.
2. Add a Penpot MCP server entry:
Note: For clients that do not support HTTP servers directly (like Claude Desktop), the local MCP server can be connected via [mcp-remote](https://github.com/geelen/mcp-remote) as follows:
```json
{
"mcpServers": {
"penpot": {
"url": "REMOTE_OR_LOCAL_URL",
"type": "http"
}
}
}
```
Replace `REMOTE_OR_LOCAL_URL` with the URL for your mode.
### Claude Code
1. Open MCP configuration in Claude Code.
2. Add a Penpot server with `http` transport and the URL for your mode.
3. Restart Claude Code or reload tools.
```json
{
"mcpServers": {
"penpot": {
"transport": "http",
"url": "REMOTE_OR_LOCAL_URL"
}
}
}
```
### VS Code / Copilot
1. Open external MCP server configuration in your extension/settings.
2. Add Penpot with the URL for your mode.
3. Save and reload tools.
```json
{
"mcp.servers": {
"penpot": {
"transport": "http",
"url": "REMOTE_OR_LOCAL_URL"
}
}
}
```
### Codex / OpenCode etc
1. Use your client's "Add MCP server" flow.
2. Set the URL for your mode.
3. Reload tools and verify Penpot tools are available.
```json
{
"servers": {
"penpot": {
"url": "REMOTE_OR_LOCAL_URL",
"transport": {
"type": "http"
}
"command": "npx",
"args": ["-y", "mcp-remote", "<URL>", "--allow-http"]
}
}
}
@ -326,9 +264,8 @@ Remote MCP is the easiest way to start using AI agents with Penpot. It's hosted
<a id="connect-remote"></a>
### Connect
For client-specific setup, use the shared section **Connect your MCP client**.
For remote mode, use the URL shown in **Your account → Integrations → MCP Server**, which includes your `userToken`.
See the section **Connect your MCP client** for details on how to connect.
### Setup videos
@ -464,9 +401,7 @@ For advanced or repository-based workflows, see the [MCP README](https://github.
<a id="connect-local"></a>
### Connect
For client-specific setup, use the shared section **Connect your MCP client**.
For local mode, use `http://localhost:4401/mcp` with HTTP transport (no MCP key; authentication uses your active Penpot browser session).
See the section **Connect your MCP client** for details on how to connect.
<a id="use-local"></a>
### Use

View File

@ -59,14 +59,10 @@ The project requires [Node.js](https://nodejs.org/) (tested with v22.x).
The easiest way to launch the servers is to use `npx` to run the appropriate
version that matches your Penpot version.
* If you are using the latest Penpot release, e.g. as served on [design.penpot.app](https://design.penpot.app), run:
```shell
npx -y @penpot/mcp@latest
```
* If you are participating in the MCP beta-test, which uses [test-mcp.penpot.dev](https://test-mcp.penpot.dev), run:
```shell
npx -y @penpot/mcp@beta
```
If you are using the latest Penpot release, e.g. as served on [design.penpot.app](https://design.penpot.app), run:
```shell
npx -y @penpot/mcp@latest
```
Once the servers are running, continue with step 2.
@ -78,24 +74,13 @@ On Windows, use the Git Bash terminal to ensure compatibility with the provided
##### Clone the Appropriate Branch of the Repository
> [!IMPORTANT]
> The branches are subject to change in the future.
> Be sure to check the instructions for the latest information on which branch to use.
Clone the Penpot repository, using the proper branch/tag depending on the
version of Penpot you want to use the MCP server with.
For instance, to target the latest development version, use the `develop` branch:
Clone the Penpot repository, using the proper branch depending on the
version of Penpot you want to use the MCP server with.
* For the current Penpot release 2.14, use the `mcp-prod-2.14.1` branch:
```shell
git clone https://github.com/penpot/penpot.git --branch mcp-prod-2.14.1 --depth 1
```
* For the MCP beta-test, use the `staging` branch:
```shell
git clone https://github.com/penpot/penpot.git --branch staging --depth 1
```
```shell
git clone https://github.com/penpot/penpot.git --branch develop --depth 1
```
Then change into the `mcp` directory:
@ -180,16 +165,27 @@ By default, the server runs on port 4401 and provides:
- **Modern Streamable HTTP endpoint**: `http://localhost:4401/mcp`
- **Legacy SSE endpoint**: `http://localhost:4401/sse`
These endpoints can be used directly by MCP clients that support them.
You can change the port by setting the `PENPOT_MCP_SERVER_PORT` environment variable
before starting the server. These endpoints can be used directly by MCP clients that support them.
Simply configure the client to connect the MCP server by providing the respective URL.
When using a client that only supports stdio transport,
a proxy like `mcp-remote` is required.
#### Configuring your client
You can configure your client with the [add-mcp](https://github.com/neon-solutions/add-mcp) helper.
Simply call
npx -y add-mcp -g -n penpot http://localhost:4401/mcp
and follow the interactive dialogue to configure the clients of your choice.
The config entry name is `penpot` (override it with `-n <name>`) and the URL points to the local http
endpoint (adjust the port if you changed `PENPOT_MCP_SERVER_PORT`).
When using a client that only supports stdio transport like **Claude Desktop**,
a proxy like [mcp-remote](https://github.com/geelen/mcp-remote) is required.
More information on connecting your client follows below.
#### Using a Proxy for stdio Transport
NOTE: only relevant if you are executing this outside of devenv
The `mcp-remote` package can proxy stdio transport to HTTP/SSE,
allowing clients that support only stdio to connect to the MCP server indirectly.
Use it to provide the launch command for your MCP client as follows:
@ -232,12 +228,6 @@ After updating the configuration file, restart Claude Desktop completely for the
After the restart, you should see the MCP server listed when clicking on the "Search and tools" icon at the bottom
of the prompt input area.
#### Example: Claude Code
To add the Penpot MCP server to a Claude Code project, issue the command
claude mcp add penpot -t http http://localhost:4401/mcp
## Repository Structure
This repository is a monorepo containing four main components:
@ -296,9 +286,9 @@ The Penpot MCP server can be configured using environment variables.
## Beyond Local Execution
The above instructions describe how to run the MCP server and plugin server locally.
We are working on enabling remote deployments of the MCP server, particularly
in [multi-user mode](docs/multi-user-mode.md), where multiple Penpot users will
be able to connect to the same MCP server instance.
The Penpot MCP server can also support multiple remote users simultaneously
in [multi-user mode](docs/multi-user-mode.md).
To run the server remotely (even for a single user),
you may set the following environment variables to configure the two servers

70
mcp/bin/client-setup.js Normal file
View File

@ -0,0 +1,70 @@
#!/usr/bin/env node
// Client-setup entry point of the published @penpot/mcp package.
// Configures an MCP client to talk to the local Penpot MCP server by delegating
// to the add-mcp CLI. This launcher is deliberately independent of the server
// build/bootstrap: it only needs npx to fetch add-mcp on demand, so it does not
// require a source checkout, a prior install, or a running server.
const { spawnSync } = require("child_process");
/**
* Runs the add-mcp CLI against the local Penpot MCP server URL.
*
* The server port is read from PENPOT_MCP_SERVER_PORT (default 4401), matching
* the server's own default. add-mcp is fetched via npx using @latest on
* purpose: MCP client config formats change frequently, so we always want the
* newest add-mcp rather than a pinned version.
*
* forwardedArgs are passed straight through to add-mcp so callers can pick
* agents, set headers, override the name or URL, etc. Two defaults are applied
* conditionally so caller args always win: "penpot" as the server name (dropped
* when the caller passes -n/--name) and the local server URL as the positional
* target (dropped when the caller passes their own URL). Unlike a repeated
* option, the URL is a positional, so add-mcp cannot "last-wins" two of them --
* we must omit ours rather than append it. A caller URL is detected as an arg
* that starts with a scheme (e.g. http://, https://); matching only at the start
* avoids false positives from URLs embedded in --header/--env values.
*
* @param {string[]} forwardedArgs - additional arguments passed through to add-mcp
*/
function runClientSetup(forwardedArgs) {
const port = process.env.PENPOT_MCP_SERVER_PORT || "4401";
const defaultUrl = `http://localhost:${port}/mcp`;
const hasName = forwardedArgs.some(
(arg) => arg === "-n" || arg === "--name" || arg.startsWith("--name="),
);
const nameArgs = hasName ? [] : ["-n", "penpot"];
const isUrl = (arg) => /^[a-zA-Z][\w+.-]*:\/\//.test(arg);
const hasUrl = forwardedArgs.some(isUrl);
const urlArgs = hasUrl ? [] : [defaultUrl];
const args = ["-y", "add-mcp@latest", "-g", ...nameArgs, ...forwardedArgs, ...urlArgs];
const result = spawnSync("npx", args, {
stdio: "inherit",
shell: process.platform === "win32",
});
if (result.error) {
throw result.error;
}
if (typeof result.status === "number" && result.status !== 0) {
const error = new Error(`add-mcp exited with code ${result.status}`);
error.status = result.status;
throw error;
}
}
module.exports = { runClientSetup };
// run directly, e.g. via `pnpm run client-setup` or `node bin/client-setup.js`
if (require.main === module) {
try {
runClientSetup(process.argv.slice(2));
} catch (error) {
process.exit(error.status ?? 1);
}
}

View File

@ -54,18 +54,37 @@ function prepareRuntimeRoot() {
return runtimeRoot;
}
const root = prepareRuntimeRoot();
// pnpm-lock.yaml is hard-excluded by npm pack; it is shipped as pnpm-lock.dist.yaml
// and restored here before bootstrap runs.
const distLock = path.join(root, "pnpm-lock.dist.yaml");
const lock = path.join(root, "pnpm-lock.yaml");
if (fs.existsSync(distLock)) {
fs.copyFileSync(distLock, lock);
// check arguments
const cliArgs = process.argv.slice(2);
if (cliArgs[0] === "client-setup") {
// when invoked as `penpot-mcp client-setup [...]`, configure an MCP client
// instead of bootstrapping/starting the server. This must run before the
// runtime-directory copy and bootstrap, neither of which client setup needs.
try {
require("./client-setup.js").runClientSetup(cliArgs.slice(1));
process.exit(0);
} catch (error) {
process.exit(error.status ?? 1);
}
}
else {
// regular case: run bootstrap, building and starting the servers
try {
execSync(`npx -y pnpm@${pnpmVersion()} run bootstrap`, { cwd: root, stdio: "inherit" });
} catch (error) {
process.exit(error.status ?? 1);
// get runtime root
const root = prepareRuntimeRoot();
// pnpm-lock.yaml is hard-excluded by npm pack; it is shipped as pnpm-lock.dist.yaml
// and restored here before bootstrap runs.
const distLock = path.join(root, "pnpm-lock.dist.yaml");
const lock = path.join(root, "pnpm-lock.yaml");
if (fs.existsSync(distLock)) {
fs.copyFileSync(distLock, lock);
}
// run the bootstrap command
try {
execSync(`npx -y pnpm@${pnpmVersion()} run bootstrap`, { cwd: root, stdio: "inherit" });
} catch (error) {
process.exit(error.status ?? 1);
}
}

View File

@ -14,6 +14,7 @@
"start:multi-user": "pnpm -r --parallel run start:multi-user",
"bootstrap": "pnpm -r install && pnpm run build && pnpm run start",
"bootstrap:multi-user": "pnpm -r install && pnpm run build && pnpm run start:multi-user",
"client-setup": "node bin/client-setup.js",
"fmt": "prettier --write packages/",
"fmt:check": "prettier --check packages/",
"test": "pnpm -r run test"