penpot/mcp/bin/client-setup.js
Dr. Dominik Jain 51c0129485
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>
2026-07-20 13:08:51 +02:00

71 lines
2.7 KiB
JavaScript

#!/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);
}
}