From bec1dcd741b274e376c386fdadbbab7cb80690cf Mon Sep 17 00:00:00 2001 From: Tim Pearson Date: Tue, 12 May 2026 23:51:52 -0400 Subject: [PATCH] feat: build mcpb bundle and attach to github releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a one-click install path for Claude Desktop. The Release workflow now produces gitlab-mcp-community-${VERSION}.mcpb alongside the existing npm and GHCR artifacts and attaches it to each GitHub Release. - mcpb/manifest.template.json describes the bundle: name gitlab-mcp-community, display name "GitLab (community, read-only)" to disambiguate from GitLab Inc.'s first-party MCP server. Exposes GITLAB_URL and GITLAB_READ_TOKEN as user_config; the read token is marked sensitive so OS keychain storage applies. Writes are not exposed in the bundle — users needing write access install via npm or GHCR. - scripts/build-mcpb.mjs runs npm pack, installs the tarball into dist-mcpb/ with --omit=dev --ignore-scripts, substitutes the version in the manifest, and zips manifest.json + node_modules into the final .mcpb. Invoked as `npm run build:mcpb`. - release.yml: new "Build MCPB bundle" step plus an asset arg on gh release create, so the bundle ships with every tag push. - README: documents the drag-and-drop install option in the Claude Desktop section and points to the latest release. - .gitignore: ignores dist-mcpb/ and *.mcpb so local builds stay out of the repo. --- .claude-plugin/plugin.json | 2 +- .github/workflows/release.yml | 11 ++++++-- .gitignore | 2 ++ CHANGELOG.md | 15 +++++++++++ README.md | 10 +++++++- mcpb/manifest.template.json | 48 +++++++++++++++++++++++++++++++++++ package-lock.json | 4 +-- package.json | 3 ++- scripts/build-mcpb.mjs | 47 ++++++++++++++++++++++++++++++++++ 9 files changed, 135 insertions(+), 7 deletions(-) create mode 100644 mcpb/manifest.template.json create mode 100755 scripts/build-mcpb.mjs diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 01f2f39..ca3e6b5 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,7 +1,7 @@ { "name": "gitlab-mcp", "description": "GitLab MCP server with GraphQL discovery and team activity tools", - "version": "1.15.1", + "version": "1.15.2", "icon": "assets/logo.svg", "author": { "name": "Tim Pearson" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9107edf..8236360 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -31,10 +31,17 @@ jobs: # for a short-lived registry token at publish time. run: npm publish --access public --provenance - - name: Create GitHub Release + - name: Build MCPB bundle + run: node scripts/build-mcpb.mjs + + - name: Create GitHub Release with MCPB asset env: GH_TOKEN: ${{ github.token }} - run: gh release create ${{ github.ref_name }} --generate-notes + run: | + VERSION="${GITHUB_REF_NAME#v}" + gh release create "${GITHUB_REF_NAME}" \ + --generate-notes \ + "gitlab-mcp-community-${VERSION}.mcpb#GitLab (community, read-only) — Claude Desktop / MCPB bundle" docker: name: Build and push container image diff --git a/.gitignore b/.gitignore index 1f3f247..b37eac6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ node_modules/ dist/ +dist-mcpb/ +*.mcpb docs/superpowers/ .env .env.local diff --git a/CHANGELOG.md b/CHANGELOG.md index b5b84c7..4a2359b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [1.15.2] - 2026-05-07 + +### Added + +- MCPB (Claude Desktop / MCP Bundle) build. `npm run build:mcpb` + produces `gitlab-mcp-community-${VERSION}.mcpb` — a self-contained + zip with `manifest.json` + pruned `node_modules/`. The Release + workflow now attaches the bundle to each GitHub Release alongside + the existing npm and GHCR artifacts, so users can install via + one-click drag-and-drop into Claude Desktop. The bundle ships as + **read-only** (`GITLAB_READ_TOKEN` only) and is labeled + "GitLab (community, read-only)" to disambiguate from GitLab Inc.'s + first-party MCP server. Users who need write access should install + via npm or GHCR. + ## [1.15.1] - 2026-05-07 ### Fixed diff --git a/README.md b/README.md index f5cabe9..ef948ed 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,15 @@ npm install -g @ttpears/gitlab-mcp-server ### Claude Desktop -Add to `claude_desktop_config.json`: +**Option A — MCPB one-click install (read-only):** download +`gitlab-mcp-community-${VERSION}.mcpb` from the +[latest GitHub Release](https://github.com/ttpears/gitlab-mcp/releases/latest) +and drag it onto the Claude Desktop window. Fill in your GitLab URL and a +read-only PAT (`read_api` scope) when prompted; the token is stored in your +OS keychain. This bundle is intentionally read-only — use Option B if you need +to create/update issues or MRs. + +**Option B — manual config (read or write):** add to `claude_desktop_config.json`: ```json { diff --git a/mcpb/manifest.template.json b/mcpb/manifest.template.json new file mode 100644 index 0000000..f83e177 --- /dev/null +++ b/mcpb/manifest.template.json @@ -0,0 +1,48 @@ +{ + "manifest_version": "0.3", + "name": "gitlab-mcp-community", + "display_name": "GitLab (community, read-only)", + "version": "__VERSION__", + "description": "Community GitLab MCP server with GraphQL discovery and team-activity analytics. Read-only build. Not affiliated with GitLab Inc.'s first-party MCP server.", + "author": { + "name": "Tim Pearson", + "url": "https://github.com/ttpears/gitlab-mcp" + }, + "homepage": "https://github.com/ttpears/gitlab-mcp", + "repository": { + "type": "git", + "url": "https://github.com/ttpears/gitlab-mcp" + }, + "license": "MIT", + "keywords": ["mcp", "gitlab", "graphql", "community"], + "server": { + "type": "node", + "entry_point": "node_modules/@ttpears/gitlab-mcp-server/dist/index.js", + "mcp_config": { + "command": "node", + "args": [ + "${__dirname}/node_modules/@ttpears/gitlab-mcp-server/dist/index.js" + ], + "env": { + "GITLAB_URL": "${user_config.gitlab_url}", + "GITLAB_READ_TOKEN": "${user_config.gitlab_read_token}" + } + } + }, + "user_config": { + "gitlab_url": { + "type": "string", + "title": "GitLab URL", + "description": "Base URL of your GitLab instance (e.g. https://gitlab.com or your self-hosted host).", + "default": "https://gitlab.com", + "required": true + }, + "gitlab_read_token": { + "type": "string", + "title": "GitLab Read-Only PAT", + "description": "Personal access token with read_api scope. Stored in your OS keychain. Writes (create_issue, create_merge_request, etc.) will be rejected by this bundle — install via npm/GHCR if you need write access.", + "sensitive": true, + "required": true + } + } +} diff --git a/package-lock.json b/package-lock.json index 1fc5437..f07650d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@ttpears/gitlab-mcp-server", - "version": "1.15.1", + "version": "1.15.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@ttpears/gitlab-mcp-server", - "version": "1.15.1", + "version": "1.15.2", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.26.0", diff --git a/package.json b/package.json index aa44246..67146bf 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,13 @@ { "name": "@ttpears/gitlab-mcp-server", - "version": "1.15.1", + "version": "1.15.2", "description": "GitLab MCP Server with GraphQL discovery", "main": "dist/index.js", "module": "./src/index.ts", "type": "module", "scripts": { "build": "tsc && chmod +x dist/index.js", + "build:mcpb": "npm run build && node scripts/build-mcpb.mjs", "dev": "tsx src/index.ts", "start": "node dist/index.js", "test": "jest", diff --git a/scripts/build-mcpb.mjs b/scripts/build-mcpb.mjs new file mode 100755 index 0000000..610f7bd --- /dev/null +++ b/scripts/build-mcpb.mjs @@ -0,0 +1,47 @@ +#!/usr/bin/env node +import { execSync } from 'node:child_process'; +import { mkdirSync, rmSync, readFileSync, writeFileSync, existsSync, unlinkSync } from 'node:fs'; +import { resolve, dirname } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); +const pkg = JSON.parse(readFileSync(resolve(repoRoot, 'package.json'), 'utf8')); +const version = pkg.version; +const stageDir = resolve(repoRoot, 'dist-mcpb'); +const bundleName = `gitlab-mcp-community-${version}.mcpb`; +const bundlePath = resolve(repoRoot, bundleName); + +const run = (cmd, opts = {}) => + execSync(cmd, { cwd: repoRoot, stdio: 'inherit', ...opts }); + +rmSync(stageDir, { recursive: true, force: true }); +if (existsSync(bundlePath)) unlinkSync(bundlePath); +mkdirSync(stageDir, { recursive: true }); + +const tarball = execSync('npm pack --silent', { cwd: repoRoot, encoding: 'utf8' }).trim(); +const tarballPath = resolve(repoRoot, tarball); + +try { + run( + `npm install --prefix "${stageDir}" --omit=dev --no-audit --no-fund --no-package-lock --ignore-scripts "${tarballPath}"` + ); +} finally { + if (existsSync(tarballPath)) unlinkSync(tarballPath); +} + +// Drop the install scaffold (package.json/lock) — we only ship manifest + node_modules. +for (const f of ['package.json', 'package-lock.json']) { + const p = resolve(stageDir, f); + if (existsSync(p)) unlinkSync(p); +} + +const manifestTemplate = JSON.parse( + readFileSync(resolve(repoRoot, 'mcpb/manifest.template.json'), 'utf8') +); +manifestTemplate.version = version; +writeFileSync(resolve(stageDir, 'manifest.json'), JSON.stringify(manifestTemplate, null, 2) + '\n'); + +run(`zip -qr "${bundlePath}" manifest.json node_modules`, { cwd: stageDir }); + +const sizeMb = (execSync(`stat -c %s "${bundlePath}"`, { encoding: 'utf8' }).trim() / 1024 / 1024).toFixed(2); +console.log(`Built ${bundleName} (${sizeMb} MB)`);