🎉 Prepare npm package for MCP server (#8473)

* 🎉 Prepare npm package for MCP server

* 🐛 Re-establish Windows compatibility of MCP server build script

Use node instead of cp to copy files

*  Set version for MCP npm tarball based on git tag

* Add scripts/set-version to set the version in package.json
  based on git describe information
* Add scripts/pack to perform the packaging
This commit is contained in:
Dominik Jain 2026-03-04 08:41:28 +01:00 committed by GitHub
parent eb5b3a3fe5
commit c9d9e493e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 99 additions and 4 deletions

1
mcp/.gitignore vendored
View File

@ -1,6 +1,7 @@
.idea
node_modules
dist
*.tgz
*.bak
*.orig
temp

View File

@ -282,3 +282,5 @@ you may set the following environment variables to configure the two servers
* The [contribution guidelines for Penpot](../CONTRIBUTING.md) apply
* Auto-formatting: Use `pnpm run fmt`
* Generating API type data: See [types-generator/README.md](types-generator/README.md)
* Packaging and publishing:
- Create npm package: `bash scripts/pack` (sets version and then calls `npm pack`)

22
mcp/bin/mcp-local.js Normal file
View File

@ -0,0 +1,22 @@
#!/usr/bin/env node
const { execSync } = require("child_process");
const path = require("path");
const root = path.resolve(__dirname, "..");
function run(command) {
execSync(command, { cwd: root, stdio: "inherit" });
}
try {
run("corepack pnpm run bootstrap");
} catch (error) {
if (error.code === "ENOENT") {
console.error(
"corepack is required but was not found. It ships with Node.js >= 16."
);
process.exit(1);
}
process.exit(error.status ?? 1);
}

View File

@ -1,7 +1,10 @@
{
"name": "mcp-meta",
"name": "@penpot/mcp",
"version": "1.0.0",
"description": "",
"description": "MCP server for Penpot integration",
"bin": {
"penpot-mcp": "./bin/mcp-local.js"
},
"scripts": {
"build": "pnpm -r run build",
"build:multi-user": "pnpm -r run build:multi-user",
@ -18,7 +21,6 @@
"url": "https://github.com/penpot/penpot.git"
},
"packageManager": "pnpm@10.28.2+sha512.41872f037ad22f7348e3b1debbaf7e867cfd448f2726d9cf74c08f19507c31d2c8e7a11525b983febc2df640b5438dee6023ebb1f84ed43cc2d654d2bc326264",
"private": true,
"devDependencies": {
"concurrently": "^9.2.1",
"prettier": "^3.0.0"

View File

@ -6,7 +6,7 @@
"main": "dist/index.js",
"scripts": {
"build:server": "esbuild src/index.ts --bundle --platform=node --target=node18 --format=esm --outfile=dist/index.js --external:@modelcontextprotocol/* --external:ws --external:express --external:class-transformer --external:class-validator --external:reflect-metadata --external:pino --external:pino-pretty --external:js-yaml --external:sharp",
"build": "pnpm run build:server && cp -r src/static dist/static && cp -r data dist/data",
"build": "pnpm run build:server && node scripts/copy-resources.js",
"build:multi-user": "pnpm run build",
"build:types": "tsc --emitDeclarationOnly --outDir dist",
"start": "node dist/index.js",

View File

@ -0,0 +1,5 @@
import { cpSync } from "fs";
// copy static assets and data to dist
cpSync("src/static", "dist/static", { recursive: true });
cpSync("data", "dist/data", { recursive: true });

12
mcp/scripts/pack Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env bash
#
# Sets the version from Git tags, then produces the npm tarball.
# Must be invoked directly (not via npm/pnpm) so that the version
# is written to package.json before npm reads it.
set -euo pipefail
cd "$(dirname "$0")/.."
bash ./scripts/set-version
npm pack

51
mcp/scripts/set-version Normal file
View File

@ -0,0 +1,51 @@
#!/usr/bin/env bash
#
# Derives a valid npm semver version from the Git tag produced by
# `git describe` and writes it into the root package.json.
#
# Examples of the conversion:
# 2.14.0 -> 2.14.0
# 2.14.0-RC1 -> 2.14.0-rc.1
# 2.14.0-RC1-140-g9f2ca9965 -> 2.14.0-rc.1.140
# 2.14.0-140-g9f2ca9965 -> 2.14.1-dev.140
#
# The last case (commits after a release tag) bumps the patch level so
# the resulting semver sorts higher than the release.
set -euo pipefail
raw=$(git describe --tags --match "*.*.*")
# Parse: <major>.<minor>.<patch>[-<label><n>][-<commits>-g<hash>]
if [[ "$raw" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-([A-Za-z]+)([0-9]*))?(-([0-9]+)-g[0-9a-f]+)?$ ]]; then
base="${BASH_REMATCH[1]}"
label="${BASH_REMATCH[3]}" # e.g. RC
label_num="${BASH_REMATCH[4]}" # e.g. 1
commits="${BASH_REMATCH[6]}" # e.g. 140
# build dot-separated pre-release identifiers
pre=""
if [[ -n "$label" ]]; then
pre="${label,,}" # use lower-case label
[[ -n "$label_num" ]] && pre="${pre}.${label_num}"
elif [[ -n "$commits" ]]; then
# commits after a release tag: bump patch, mark as dev
IFS='.' read -r major minor patch <<< "$base"
base="${major}.${minor}.$((patch + 1))"
pre="dev"
fi
[[ -n "$commits" && -n "$pre" ]] && pre="${pre}.${commits}"
if [[ -n "$pre" ]]; then
version="${base}-${pre}"
else
version="$base"
fi
else
echo "ERROR: Cannot parse version from git describe output: $raw" >&2
exit 1
fi
cd "$(dirname "$0")/.."
npm pkg set "version=${version}"
echo "$version"