From 7b5602654cb663d38e3a7ef9c3bc9b99f3713ba0 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sat, 11 Jul 2026 00:01:22 +0800 Subject: [PATCH 1/4] feat: package DBHub as a Claude Desktop MCP Bundle (.mcpb) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add one-click Claude Desktop distribution via the MCP Bundle format: double-click install, DSN prompted from user settings (stored in the OS keychain), running locally over stdio. The bundle is read-only by design (readonly = true, max_rows = 1000 on execute_sql) — the curated path for giving non-technical users database access without a remote HTTP endpoint or OAuth setup (#146). No server code changes: the bundle ships a static dbhub.toml that uses the existing ${ENV_VAR} interpolation (dsn = "${DBHUB_DSN}"), with DBHUB_DSN injected by the client from the manifest's user_config. - mcpb/: manifest (MCPB v0.3), read-only TOML config, icon - scripts/build-mcpb.mjs: stages dist/ + runtime deps + drivers (derived from optionalDependencies, cloud-auth packages excluded), packs and validates with @anthropic-ai/mcpb; `pnpm run build:mcpb` - scripts/smoke-test-mcpb.mjs: unpacks the archive outside the repo (so module resolution can't lean on the project's node_modules) and drives it over stdio with the MCP SDK client; `pnpm run test:mcpb` - .github/workflows/mcpb-release.yml: on version bumps, builds, smoke tests, and attaches the bundle to GitHub release v - docs: new dbhub.ai/mcpb page + pointers from README/installation Co-Authored-By: Claude Fable 5 --- .github/workflows/mcpb-release.yml | 103 +++++++++ .gitignore | 5 +- CLAUDE.md | 1 + README.md | 4 + docs/docs.json | 2 +- docs/installation.mdx | 8 + docs/mcpb.mdx | 40 ++++ mcpb/dbhub.toml | 28 +++ mcpb/icon.png | Bin 0 -> 55698 bytes mcpb/manifest.json | 74 +++++++ package.json | 3 + pnpm-lock.yaml | 342 +++++++++++++++++++++++++++++ scripts/build-mcpb.mjs | 114 ++++++++++ scripts/mcpb-common.mjs | 19 ++ scripts/smoke-test-mcpb.mjs | 127 +++++++++++ 15 files changed, 868 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/mcpb-release.yml create mode 100644 docs/mcpb.mdx create mode 100644 mcpb/dbhub.toml create mode 100644 mcpb/icon.png create mode 100644 mcpb/manifest.json create mode 100644 scripts/build-mcpb.mjs create mode 100644 scripts/mcpb-common.mjs create mode 100644 scripts/smoke-test-mcpb.mjs diff --git a/.github/workflows/mcpb-release.yml b/.github/workflows/mcpb-release.yml new file mode 100644 index 00000000..d62f6df5 --- /dev/null +++ b/.github/workflows/mcpb-release.yml @@ -0,0 +1,103 @@ +# Workflow for publishing the DBHub MCP Bundle (.mcpb) as a GitHub release asset. +# +# The .mcpb bundle is a self-contained package (server + database drivers + +# read-only TOML config) that installs into Claude Desktop with a double click, +# running locally over stdio — no remote HTTP endpoint or OAuth setup needed. +# +# Trigger modes (mirrors npm-publish.yml): +# 1. Automatic: on push to main that modifies package.json — builds and attaches +# the bundle to the GitHub release v, creating the release if needed. +# Skips if that release already has the asset. +# 2. Manual (workflow_dispatch): force a rebuild and re-upload of the asset for +# the current package.json version. + +name: Publish MCP Bundle + +on: + workflow_dispatch: + + push: + branches: + - main + paths: + - "package.json" + +jobs: + build-and-release: + runs-on: ubuntu-latest + permissions: + contents: write # create releases and upload assets + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Decide before installing anything — a skipped run costs only this step + # (jq and gh are preinstalled on ubuntu-latest). + - name: Determine version and whether to publish + env: + GH_TOKEN: ${{ github.token }} + run: | + VERSION=$(jq -r '.version' package.json) + ASSET="dbhub-${VERSION}.mcpb" + + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + # Manual trigger: always rebuild and re-upload + SHOULD_PUBLISH="true" + echo "Manual trigger: publishing ${ASSET}" + elif gh release view "v${VERSION}" --json assets --jq '.assets[].name' 2>/dev/null | grep -qx "${ASSET}"; then + echo "Release v${VERSION} already has ${ASSET}. Skipping." + SHOULD_PUBLISH="false" + else + echo "Publishing ${ASSET} to release v${VERSION}" + SHOULD_PUBLISH="true" + fi + + echo "VERSION=${VERSION}" >> $GITHUB_ENV + echo "ASSET=${ASSET}" >> $GITHUB_ENV + echo "SHOULD_PUBLISH=${SHOULD_PUBLISH}" >> $GITHUB_ENV + + # pnpm must be on PATH before setup-node can use the pnpm store cache + - name: Install pnpm + if: env.SHOULD_PUBLISH == 'true' + uses: pnpm/action-setup@v3 + with: + version: 10.17.1 + + - name: Setup Node.js + if: env.SHOULD_PUBLISH == 'true' + uses: actions/setup-node@v4 + with: + node-version: "22" + cache: "pnpm" + + - name: Install dependencies + if: env.SHOULD_PUBLISH == 'true' + run: pnpm install --frozen-lockfile + + - name: Build MCP Bundle + if: env.SHOULD_PUBLISH == 'true' + run: pnpm run build:mcpb + + - name: Smoke test MCP Bundle + if: env.SHOULD_PUBLISH == 'true' + run: pnpm run test:mcpb + + - name: Create release and upload asset + if: env.SHOULD_PUBLISH == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: | + # Create the release if it doesn't exist yet + if ! gh release view "v${VERSION}" > /dev/null 2>&1; then + PRERELEASE_FLAG="" + case "${VERSION}" in + *-*) PRERELEASE_FLAG="--prerelease" ;; + esac + gh release create "v${VERSION}" \ + --title "v${VERSION}" \ + --generate-notes \ + ${PRERELEASE_FLAG} + fi + + gh release upload "v${VERSION}" "dist-mcpb/${ASSET}" --clobber + echo "✅ Uploaded ${ASSET} to release v${VERSION}" diff --git a/.gitignore b/.gitignore index c4f80375..6ed09e0c 100644 --- a/.gitignore +++ b/.gitignore @@ -10,12 +10,15 @@ bower_components/ # Build outputs build/ dist/ +dist-mcpb/ out/ *.min.js *.min.css # Environment & config -dbhub.toml +# Anchored to the repo root: only the local runtime config is ignored, +# checked-in templates like mcpb/dbhub.toml stay tracked. +/dbhub.toml docs/plans/ docs/testing/ .env diff --git a/CLAUDE.md b/CLAUDE.md index cacc03c6..00d6a131 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,7 @@ DBHub is a zero-dependency, token efficient database MCP server implementing the - Test: `pnpm test` - Run all tests - Test Watch: `pnpm test:watch` - Run tests in watch mode - Integration Tests: `pnpm test:integration` - Run database integration tests (requires Docker) +- MCP Bundle: `pnpm run build:mcpb` - Package DBHub as a Claude Desktop `.mcpb` bundle (see `mcpb/` and `scripts/build-mcpb.mjs`); `pnpm run test:mcpb` smoke-tests the packed bundle over stdio. Published to GitHub releases by `.github/workflows/mcpb-release.yml`. The bundle is read-only by design (`mcpb/dbhub.toml`), with the DSN supplied via the `DBHUB_DSN` env var declared in `mcpb/manifest.json` (TOML `${ENV_VAR}` interpolation). ## Architecture Overview diff --git a/README.md b/README.md index b33cbfd7..53e0eb23 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,10 @@ docker run --rm --init \ npx @bytebase/dbhub@latest --transport http --port 8080 --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable" ``` +**Claude Desktop (MCP Bundle):** + +Download `dbhub-.mcpb` from the [latest release](https://github.com/bytebase/dbhub/releases/latest), double-click it (or drag it into Claude Desktop's Settings → Extensions), and enter your database connection string. The bundle runs locally over stdio, is **read-only by design** (writes are rejected and the database session is set to read-only at the engine level), and needs no remote endpoint or OAuth setup — ideal for giving non-technical teammates curated, read-only database access. Pair it with a least-privilege, read-only database account. See the [MCP Bundle guide](https://dbhub.ai/mcpb) for details and for packaging your own bundle. + **Demo Mode:** ```bash diff --git a/docs/docs.json b/docs/docs.json index 0eac0678..a927b7fc 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -18,7 +18,7 @@ "groups": [ { "group": "Getting Started", - "pages": ["index", "installation", "quickstart"] + "pages": ["index", "installation", "quickstart", "mcpb"] }, { "group": "MCP Tools", diff --git a/docs/installation.mdx b/docs/installation.mdx index 0943074f..8c144b80 100644 --- a/docs/installation.mdx +++ b/docs/installation.mdx @@ -62,6 +62,10 @@ SQLite uses the built-in `node:sqlite` module (Node.js 22.5+), so it requires no When a driver is not installed or a required dependency is missing (e.g., a transitive dependency like `@azure/core-client` for the SQL Server driver), DBHub will skip that connector at startup and log a message. All other connectors will work normally. SQLite (including `--demo` mode) needs no extra driver — it relies on the built-in `node:sqlite` module. +## MCP Bundle (.mcpb) + +For Claude Desktop, the [MCP Bundle](/mcpb) is a one-click install: download the `.mcpb` from the [latest release](https://github.com/bytebase/dbhub/releases/latest), double-click, enter your connection string. Read-only by default, no Node.js or JSON editing required — the recommended path for non-technical users. The guide also covers packaging your own bundle. + ## Docker @@ -261,6 +265,10 @@ Verify DBHub is loaded: ![Claude Desktop with DBHub](https://raw.githubusercontent.com/bytebase/dbhub/main/docs/images/claude-desktop.webp) + +Prefer a one-click, read-only install with no JSON editing? Use the [MCP Bundle](/mcpb). The manual config below gives full control over flags and transports. + + Edit `~/Library/Application Support/Claude/claude_desktop_config.json`: diff --git a/docs/mcpb.mdx b/docs/mcpb.mdx new file mode 100644 index 00000000..b8b2fa30 --- /dev/null +++ b/docs/mcpb.mdx @@ -0,0 +1,40 @@ +--- +title: "MCP Bundle" +description: "One-click, read-only Claude Desktop install via .mcpb" +--- + +The [MCP Bundle](https://github.com/anthropics/mcpb) (`.mcpb`) is a one-click installation path for Claude Desktop — no Node.js install, no JSON config editing, and no remote HTTP endpoint or OAuth setup. It is the recommended way to give **non-technical users** curated database access. + +## Install + +1. Download `dbhub-.mcpb` from the [latest GitHub release](https://github.com/bytebase/dbhub/releases/latest). +2. Double-click the file (or drag it into Claude Desktop's **Settings → Extensions**). +3. Enter your database connection string when prompted, e.g. `postgres://user:password@host:5432/dbname`. It is stored in the OS keychain, not in a config file. + +The bundle includes drivers for PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite, and runs locally over stdio. + +## Read-only by design + +The bundled configuration sets `readonly = true` and `max_rows = 1000` on `execute_sql`: mutating statements are rejected, and the database session is additionally set to read-only at the engine level. Still, connect with a least-privilege, read-only database account — defense in depth beats configuration alone. + + +The machine running Claude Desktop connects **directly** to the database, so the database must be reachable from it (VPN or network allow-list). AWS IAM and Azure AD authentication are not included in the bundle; repackage with those drivers added if you need them. + + +## Packaging your own bundle + +To ship a different policy — multiple sources, custom tools, a baked-in DSN so users have nothing to configure — edit `mcpb/dbhub.toml` (and `mcpb/manifest.json` if you change `user_config`) and rebuild: + +```bash +git clone https://github.com/bytebase/dbhub.git && cd dbhub +pnpm install +# Edit mcpb/dbhub.toml (tool policy) and mcpb/manifest.json (user settings) +pnpm run build:mcpb # produces dist-mcpb/dbhub-.mcpb +pnpm run test:mcpb # smoke-tests the packed bundle over stdio +``` + +Anything in a `[[sources]]` or `[[tools]]` section of the [TOML configuration](/config/toml) works in the bundle, including `${ENV_VAR}` interpolation — the stock bundle uses `dsn = "${DBHUB_DSN}"`, with `DBHUB_DSN` supplied by Claude Desktop from the user's extension settings. + + +If you bake a DSN into your own bundle, remember that anyone can unzip an `.mcpb` and read it. Only embed credentials for a curated, read-only, least-privilege account. + diff --git a/mcpb/dbhub.toml b/mcpb/dbhub.toml new file mode 100644 index 00000000..d2403ab0 --- /dev/null +++ b/mcpb/dbhub.toml @@ -0,0 +1,28 @@ +# DBHub configuration for the MCP Bundle (.mcpb) distribution. +# +# The connection string is supplied by the MCP client (e.g. Claude Desktop) +# from the user's extension settings — stored in the OS keychain — and passed +# to the server via the DBHUB_DSN environment variable declared in +# manifest.json. It is interpolated into the `dsn` field below at load time. +# +# This bundle is read-only by design: execute_sql rejects mutating statements +# and the database session is additionally set to read-only at the engine +# level. Pair it with a least-privilege, read-only database account. +# +# To ship a different policy (multiple sources, custom tools, write access), +# repackage the bundle with your own dbhub.toml — see +# https://dbhub.ai/mcpb for the recipe. + +[[sources]] +id = "default" +dsn = "${DBHUB_DSN}" + +[[tools]] +name = "execute_sql" +source = "default" +readonly = true +max_rows = 1000 + +[[tools]] +name = "search_objects" +source = "default" diff --git a/mcpb/icon.png b/mcpb/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e724ce0ae2024eb51496eaa2e8cf361655364141 GIT binary patch literal 55698 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.5.0" + } + } +} diff --git a/package.json b/package.json index 1014ad2a..d21614eb 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "build": "pnpm run generate:api-types && tsup && cd frontend && pnpm run build", "build:backend": "pnpm run generate:api-types && tsup", "build:frontend": "cd frontend && pnpm run build", + "build:mcpb": "pnpm run build:backend && node scripts/build-mcpb.mjs", + "test:mcpb": "node scripts/smoke-test-mcpb.mjs", "start": "node dist/index.js", "dev": "concurrently --kill-others \"pnpm run dev:backend\" \"pnpm run dev:frontend\"", "dev:backend": "NODE_ENV=development tsx src/index.ts --transport=http", @@ -58,6 +60,7 @@ "pg": "^8.13.3" }, "devDependencies": { + "@anthropic-ai/mcpb": "^2.1.2", "@testcontainers/mariadb": "^11.0.3", "@testcontainers/mssqlserver": "^11.0.3", "@testcontainers/mysql": "^11.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 27ca345d..0d1a023c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: specifier: ^3.24.2 version: 3.25.64 devDependencies: + '@anthropic-ai/mcpb': + specifier: ^2.1.2 + version: 2.1.2 '@testcontainers/mariadb': specifier: ^11.0.3 version: 11.0.3 @@ -179,6 +182,10 @@ importers: packages: + '@anthropic-ai/mcpb@2.1.2': + resolution: {integrity: sha512-goRbBC8ySo7SWb7tRzr+tL6FxDc4JPTRCdgfD2omba7freofvjq5rom1lBnYHZHo6Mizs1jAHJeN53aZbDoy8A==} + hasBin: true + '@aws-crypto/sha256-browser@5.2.0': resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==} @@ -698,6 +705,62 @@ packages: '@iarna/toml@2.2.5': resolution: {integrity: sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg==} + '@inquirer/checkbox@3.0.1': + resolution: {integrity: sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==} + engines: {node: '>=18'} + + '@inquirer/confirm@4.0.1': + resolution: {integrity: sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==} + engines: {node: '>=18'} + + '@inquirer/core@9.2.1': + resolution: {integrity: sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==} + engines: {node: '>=18'} + + '@inquirer/editor@3.0.1': + resolution: {integrity: sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==} + engines: {node: '>=18'} + + '@inquirer/expand@3.0.1': + resolution: {integrity: sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==} + engines: {node: '>=18'} + + '@inquirer/figures@1.0.15': + resolution: {integrity: sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==} + engines: {node: '>=18'} + + '@inquirer/input@3.0.1': + resolution: {integrity: sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==} + engines: {node: '>=18'} + + '@inquirer/number@2.0.1': + resolution: {integrity: sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==} + engines: {node: '>=18'} + + '@inquirer/password@3.0.1': + resolution: {integrity: sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==} + engines: {node: '>=18'} + + '@inquirer/prompts@6.0.1': + resolution: {integrity: sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==} + engines: {node: '>=18'} + + '@inquirer/rawlist@3.0.1': + resolution: {integrity: sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==} + engines: {node: '>=18'} + + '@inquirer/search@2.0.1': + resolution: {integrity: sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==} + engines: {node: '>=18'} + + '@inquirer/select@3.0.1': + resolution: {integrity: sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==} + engines: {node: '>=18'} + + '@inquirer/type@2.0.0': + resolution: {integrity: sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==} + engines: {node: '>=18'} + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -1265,6 +1328,9 @@ packages: '@types/mssql@9.1.7': resolution: {integrity: sha512-eIOEe78nuSW5KctDHImDhLZ9a+jV/z/Xs5RBhcG/jrk+YWqhdNmzBmHVWV7aWQ5fW+jbIGtX6Ph+bbVqfhzafg==} + '@types/mute-stream@0.0.4': + resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} + '@types/node@18.19.111': resolution: {integrity: sha512-90sGdgA+QLJr1F9X79tQuEut0gEYIfkX9pydI4XGRgvFo9g2JWswefI+WUSUHPYVBHYSEfTEqBxA5hQvAZB3Mw==} @@ -1309,6 +1375,9 @@ packages: '@types/ssh2@1.15.5': resolution: {integrity: sha512-N1ASjp/nXH3ovBHddRJpli4ozpk6UdDYIX4RJWFa9L1YKnzdhTlVmiGHm4DZnj/jLbqZpes4aeR30EFGQtvhQQ==} + '@types/wrap-ansi@3.0.0': + resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@typespec/ts-http-runtime@0.2.3': resolution: {integrity: sha512-oRhjSzcVjX8ExyaF8hC0zzTqxlVuRlgMHL/Bh4w3xB9+wjbm0FpXylVU/lBrn+kgphwYTrOk3tp+AVShGmlYCg==} engines: {node: '>=18.0.0'} @@ -1388,6 +1457,10 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1573,6 +1646,9 @@ packages: change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + chardet@0.7.0: + resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} + chokidar@4.0.3: resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} engines: {node: '>= 14.16.0'} @@ -1583,6 +1659,10 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -1608,6 +1688,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@13.1.0: + resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} + engines: {node: '>=18'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -1879,6 +1963,10 @@ packages: resolution: {integrity: sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==} engines: {node: '>= 18'} + external-editor@3.1.0: + resolution: {integrity: sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==} + engines: {node: '>=4'} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -1912,6 +2000,9 @@ packages: picomatch: optional: true + fflate@0.8.3: + resolution: {integrity: sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==} + finalhandler@1.3.1: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} @@ -1923,6 +2014,10 @@ packages: fix-dts-default-cjs-exports@1.0.1: resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==} + flora-colossus@2.0.0: + resolution: {integrity: sha512-dz4HxH6pOvbUzZpZ/yXhafjbR2I8cenK5xL0KtBFb7U2ADsR+OwXifnxZjij/pZWF775uSCMzWVd+jDik2H2IA==} + engines: {node: '>= 12'} + foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} engines: {node: '>=14'} @@ -1942,6 +2037,10 @@ packages: fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -1950,6 +2049,10 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + galactus@1.0.0: + resolution: {integrity: sha512-R1fam6D4CyKQGNlvJne4dkNF+PvUUl7TAJInvTGa9fti9qAv95quQz29GXapA4d8Ec266mJJxFVh82M4GIIGDQ==} + engines: {node: '>= 12'} + generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -2027,6 +2130,10 @@ packages: ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + engines: {node: '>= 4'} + index-to-position@1.2.0: resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} engines: {node: '>=18'} @@ -2116,6 +2223,9 @@ packages: engines: {node: '>=6'} hasBin: true + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + jsonwebtoken@9.0.2: resolution: {integrity: sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==} engines: {node: '>=12', npm: '>=6'} @@ -2358,6 +2468,10 @@ packages: engines: {node: '>=18'} hasBin: true + mute-stream@1.0.0: + resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mysql2@3.14.1: resolution: {integrity: sha512-7ytuPQJjQB8TNAYX/H2yhL+iQOnIBjAMam361R7UAL0lOVXWjtdrmoL9HYKqKoLp/8UUTRcvo1QPvK9KL7wA8w==} engines: {node: '>= 8.0'} @@ -2388,6 +2502,10 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} + node-forge@1.4.0: + resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} + engines: {node: '>= 6.13.0'} + node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -2420,6 +2538,10 @@ packages: peerDependencies: typescript: ^5.x + os-tmpdir@1.0.2: + resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} + engines: {node: '>=0.10.0'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} @@ -2552,6 +2674,10 @@ packages: engines: {node: '>=14'} hasBin: true + pretty-bytes@5.6.0: + resolution: {integrity: sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==} + engines: {node: '>=6'} + process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -2934,6 +3060,10 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} + tmp@0.0.33: + resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} + engines: {node: '>=0.6.0'} + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} @@ -2999,6 +3129,10 @@ packages: tweetnacl@0.14.5: resolution: {integrity: sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + type-fest@4.41.0: resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} engines: {node: '>=16'} @@ -3034,6 +3168,10 @@ packages: resolution: {integrity: sha512-u5otvFBOBZvmdjWLVW+5DAc9Nkq8f24g0O9oY7qw2JVIF1VocIFoyz9JFkuVOS2j41AufeO0xnlweJ2RLT8nGw==} engines: {node: '>=20.18.1'} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} @@ -3164,6 +3302,10 @@ packages: engines: {node: '>=8'} hasBin: true + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3206,6 +3348,10 @@ packages: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} + yoctocolors-cjs@2.1.3: + resolution: {integrity: sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==} + engines: {node: '>=18'} + zip-stream@6.0.1: resolution: {integrity: sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==} engines: {node: '>= 14'} @@ -3218,8 +3364,25 @@ packages: zod@3.25.64: resolution: {integrity: sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==} + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + snapshots: + '@anthropic-ai/mcpb@2.1.2': + dependencies: + '@inquirer/prompts': 6.0.1 + commander: 13.1.0 + fflate: 0.8.3 + galactus: 1.0.0 + ignore: 7.0.6 + node-forge: 1.4.0 + pretty-bytes: 5.6.0 + zod: 3.25.76 + zod-to-json-schema: 3.25.0(zod@3.25.76) + transitivePeerDependencies: + - supports-color + '@aws-crypto/sha256-browser@5.2.0': dependencies: '@aws-crypto/sha256-js': 5.2.0 @@ -4090,6 +4253,102 @@ snapshots: '@iarna/toml@2.2.5': {} + '@inquirer/checkbox@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 2.0.0 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.3 + + '@inquirer/confirm@4.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + + '@inquirer/core@9.2.1': + dependencies: + '@inquirer/figures': 1.0.15 + '@inquirer/type': 2.0.0 + '@types/mute-stream': 0.0.4 + '@types/node': 22.15.31 + '@types/wrap-ansi': 3.0.0 + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 1.0.0 + signal-exit: 4.1.0 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/editor@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + external-editor: 3.1.0 + + '@inquirer/expand@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/figures@1.0.15': {} + + '@inquirer/input@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + + '@inquirer/number@2.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + + '@inquirer/password@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + ansi-escapes: 4.3.2 + + '@inquirer/prompts@6.0.1': + dependencies: + '@inquirer/checkbox': 3.0.1 + '@inquirer/confirm': 4.0.1 + '@inquirer/editor': 3.0.1 + '@inquirer/expand': 3.0.1 + '@inquirer/input': 3.0.1 + '@inquirer/number': 2.0.1 + '@inquirer/password': 3.0.1 + '@inquirer/rawlist': 3.0.1 + '@inquirer/search': 2.0.1 + '@inquirer/select': 3.0.1 + + '@inquirer/rawlist@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/type': 2.0.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/search@2.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 2.0.0 + yoctocolors-cjs: 2.1.3 + + '@inquirer/select@3.0.1': + dependencies: + '@inquirer/core': 9.2.1 + '@inquirer/figures': 1.0.15 + '@inquirer/type': 2.0.0 + ansi-escapes: 4.3.2 + yoctocolors-cjs: 2.1.3 + + '@inquirer/type@2.0.0': + dependencies: + mute-stream: 1.0.0 + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -4802,6 +5061,10 @@ snapshots: transitivePeerDependencies: - supports-color + '@types/mute-stream@0.0.4': + dependencies: + '@types/node': 22.15.31 + '@types/node@18.19.111': dependencies: undici-types: 5.26.5 @@ -4859,6 +5122,8 @@ snapshots: dependencies: '@types/node': 18.19.111 + '@types/wrap-ansi@3.0.0': {} + '@typespec/ts-http-runtime@0.2.3': dependencies: http-proxy-agent: 7.0.2 @@ -4953,6 +5218,10 @@ snapshots: ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -5153,6 +5422,8 @@ snapshots: change-case@5.4.4: {} + chardet@0.7.0: {} + chokidar@4.0.3: dependencies: readdirp: 4.1.2 @@ -5163,6 +5434,8 @@ snapshots: dependencies: clsx: 2.1.1 + cli-width@4.1.0: {} + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -5192,6 +5465,8 @@ snapshots: commander@11.1.0: optional: true + commander@13.1.0: {} + commander@4.1.1: {} compress-commons@6.0.2: @@ -5499,6 +5774,12 @@ snapshots: transitivePeerDependencies: - supports-color + external-editor@3.1.0: + dependencies: + chardet: 0.7.0 + iconv-lite: 0.4.24 + tmp: 0.0.33 + fast-deep-equal@3.1.3: {} fast-fifo@1.3.2: {} @@ -5522,6 +5803,8 @@ snapshots: optionalDependencies: picomatch: 4.0.3 + fflate@0.8.3: {} + finalhandler@1.3.1: dependencies: debug: 2.6.9 @@ -5551,6 +5834,13 @@ snapshots: mlly: 1.7.4 rollup: 4.43.0 + flora-colossus@2.0.0: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + fs-extra: 10.1.0 + transitivePeerDependencies: + - supports-color + foreground-child@3.3.1: dependencies: cross-spawn: 7.0.6 @@ -5564,11 +5854,25 @@ snapshots: fs-constants@1.0.0: {} + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + fsevents@2.3.3: optional: true function-bind@1.1.2: {} + galactus@1.0.0: + dependencies: + debug: 4.4.3(supports-color@10.2.2) + flora-colossus: 2.0.0 + fs-extra: 10.1.0 + transitivePeerDependencies: + - supports-color + generate-function@2.3.1: dependencies: is-property: 1.0.2 @@ -5657,6 +5961,8 @@ snapshots: ieee754@1.2.1: {} + ignore@7.0.6: {} + index-to-position@1.2.0: {} inherits@2.0.4: {} @@ -5716,6 +6022,12 @@ snapshots: json5@2.2.3: {} + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsonwebtoken@9.0.2: dependencies: jws: 3.2.2 @@ -5923,6 +6235,8 @@ snapshots: - supports-color optional: true + mute-stream@1.0.0: {} + mysql2@3.14.1: dependencies: aws-ssl-profiles: 1.1.2 @@ -5958,6 +6272,8 @@ snapshots: negotiator@1.0.0: {} + node-forge@1.4.0: {} + node-releases@2.0.27: {} normalize-path@3.0.0: {} @@ -5991,6 +6307,8 @@ snapshots: typescript: 5.8.3 yargs-parser: 21.1.1 + os-tmpdir@1.0.2: {} + package-json-from-dist@1.0.1: {} parse-json@8.3.0: @@ -6098,6 +6416,8 @@ snapshots: prettier@3.5.3: {} + pretty-bytes@5.6.0: {} + process-nextick-args@2.0.1: {} process@0.11.10: {} @@ -6595,6 +6915,10 @@ snapshots: tinyrainbow@3.0.3: {} + tmp@0.0.33: + dependencies: + os-tmpdir: 1.0.2 + tmp@0.2.3: {} toidentifier@1.0.1: {} @@ -6666,6 +6990,8 @@ snapshots: tweetnacl@0.14.5: {} + type-fest@0.21.3: {} + type-fest@4.41.0: {} type-is@1.6.18: @@ -6691,6 +7017,8 @@ snapshots: undici@7.10.0: {} + universalify@2.0.1: {} + unpipe@1.0.0: {} update-browserslist-db@1.1.4(browserslist@4.28.0): @@ -6788,6 +7116,12 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -6826,6 +7160,8 @@ snapshots: yn@3.1.1: {} + yoctocolors-cjs@2.1.3: {} + zip-stream@6.0.1: dependencies: archiver-utils: 5.0.2 @@ -6836,4 +7172,10 @@ snapshots: dependencies: zod: 3.25.64 + zod-to-json-schema@3.25.0(zod@3.25.76): + dependencies: + zod: 3.25.76 + zod@3.25.64: {} + + zod@3.25.76: {} diff --git a/scripts/build-mcpb.mjs b/scripts/build-mcpb.mjs new file mode 100644 index 00000000..5b8d3d8d --- /dev/null +++ b/scripts/build-mcpb.mjs @@ -0,0 +1,114 @@ +#!/usr/bin/env node + +// Package DBHub as an MCP Bundle (.mcpb) for one-click installation in +// Claude Desktop and other MCPB-compatible clients. +// +// Layout of the staged bundle (dist-mcpb/bundle): +// manifest.json — mcpb/manifest.json with the version stamped from package.json +// dbhub.toml — read-only tool config; DSN interpolated from the DBHUB_DSN +// env var that the client sets from user settings (keychain) +// icon.png — extension icon +// LICENSE +// server/ — the tsup backend build (dist/), minus the HTTP-only +// workbench frontend, the --demo seed data (the bundle +// always connects via DBHUB_DSN), and type declarations +// node_modules/ — runtime dependencies (tsup externalizes package.json +// `dependencies`) plus the database driver packages +// +// Run: pnpm run build:mcpb (builds the backend first, then this script) + +import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs"; +import { spawnSync } from "node:child_process"; +import { join } from "node:path"; +import { root, rootPkg, bundleVersion, mcpbFile, localBin } from "./mcpb-common.mjs"; + +const distDir = join(root, "dist"); +const bundleDir = join(root, "dist-mcpb", "bundle"); + +// Every optional dependency is a bundled database driver, except the cloud +// auth packages: AWS IAM / Azure AD auth requires local cloud credential +// setup that does not fit the bundle's zero-setup, read-only use case, and +// they would triple the bundle size. Repackage with them added if needed. +const CLOUD_AUTH_PACKAGES = new Set(["@aws-sdk/rds-signer", "@azure/identity"]); +const drivers = Object.keys(rootPkg.optionalDependencies ?? {}).filter( + (pkg) => !CLOUD_AUTH_PACKAGES.has(pkg) +); + +function run(command, args, opts = {}) { + const result = spawnSync(command, args, { stdio: "inherit", ...opts }); + if (result.error) { + throw result.error; + } + if (result.status !== 0) { + throw new Error(`${command} ${args.join(" ")} exited with status ${result.status}`); + } +} + +if (!existsSync(join(distDir, "index.js"))) { + console.error("dist/index.js not found — run `pnpm run build:backend` first."); + process.exit(1); +} + +console.log(`Staging MCP Bundle for dbhub@${bundleVersion} ...`); +rmSync(join(root, "dist-mcpb"), { recursive: true, force: true }); +mkdirSync(bundleDir, { recursive: true }); + +// 1. Server code, minus what the stdio bundle never uses +const excludedDirs = [join(distDir, "public"), join(distDir, "demo")]; +cpSync(distDir, join(bundleDir, "server"), { + recursive: true, + filter: (src) => + !excludedDirs.some((dir) => src.startsWith(dir)) && + !src.endsWith(".d.ts") && + !src.endsWith(".DS_Store"), +}); + +// 2. Static bundle assets +cpSync(join(root, "mcpb", "dbhub.toml"), join(bundleDir, "dbhub.toml")); +cpSync(join(root, "mcpb", "icon.png"), join(bundleDir, "icon.png")); +cpSync(join(root, "LICENSE"), join(bundleDir, "LICENSE")); + +// 3. Manifest with the version stamped from package.json +const manifest = JSON.parse(readFileSync(join(root, "mcpb", "manifest.json"), "utf-8")); +manifest.version = bundleVersion; +writeFileSync(join(bundleDir, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n"); + +// 4. node_modules. npm (not pnpm) so the result is a plain, symlink-free tree +// that survives zipping. +const dependencies = { ...rootPkg.dependencies }; +for (const driver of drivers) { + dependencies[driver] = rootPkg.optionalDependencies[driver]; +} +writeFileSync( + join(bundleDir, "package.json"), + JSON.stringify( + { + name: "dbhub-mcpb", + version: bundleVersion, + private: true, + type: "module", + description: "Staged MCP Bundle contents for DBHub (generated by scripts/build-mcpb.mjs)", + dependencies, + }, + null, + 2 + ) + "\n" +); +console.log(`Installing runtime dependencies: ${Object.keys(dependencies).join(", ")} ...`); +run( + "npm", + ["install", "--omit=dev", "--no-audit", "--no-fund", "--no-package-lock", "--loglevel=error"], + { cwd: bundleDir } +); + +// 5. Validate the manifest and pack the bundle +const mcpb = localBin("mcpb"); +if (!existsSync(mcpb)) { + console.error("mcpb CLI not found — run `pnpm install` (it is a devDependency)."); + process.exit(1); +} +run(mcpb, ["validate", join(bundleDir, "manifest.json")]); +run(mcpb, ["pack", bundleDir, mcpbFile]); + +const sizeMB = (statSync(mcpbFile).size / 1024 / 1024).toFixed(1); +console.log(`\nDone: ${mcpbFile} (${sizeMB} MB)`); diff --git a/scripts/mcpb-common.mjs b/scripts/mcpb-common.mjs new file mode 100644 index 00000000..1ebdbcd8 --- /dev/null +++ b/scripts/mcpb-common.mjs @@ -0,0 +1,19 @@ +// Shared constants for the MCP Bundle scripts (build-mcpb.mjs, smoke-test-mcpb.mjs). + +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +export const root = join(import.meta.dirname, ".."); + +export const rootPkg = JSON.parse(readFileSync(join(root, "package.json"), "utf-8")); + +// The bundle version, overridable for CI re-releases +export const bundleVersion = process.env.MCPB_VERSION || rootPkg.version; + +// Where build-mcpb.mjs writes the packed bundle and smoke-test-mcpb.mjs reads it +export const mcpbFile = join(root, "dist-mcpb", `dbhub-${bundleVersion}.mcpb`); + +// Path to a locally installed package bin (cross-platform) +export function localBin(name) { + return join(root, "node_modules", ".bin", process.platform === "win32" ? `${name}.cmd` : name); +} diff --git a/scripts/smoke-test-mcpb.mjs b/scripts/smoke-test-mcpb.mjs new file mode 100644 index 00000000..a4b0139c --- /dev/null +++ b/scripts/smoke-test-mcpb.mjs @@ -0,0 +1,127 @@ +#!/usr/bin/env node + +// Smoke test for the packed MCP Bundle (dist-mcpb/dbhub-.mcpb). +// +// The bundle is unpacked into an OS temp directory OUTSIDE the repository — +// this is essential: inside the repo, Node module resolution would walk up +// into the project's own node_modules and mask missing packages in the +// bundle. The unpacked server is then driven over stdio with the MCP SDK +// client, exactly as an MCPB client (e.g. Claude Desktop) would: +// - launches `node server/index.js --transport stdio --config dbhub.toml` +// with the DSN injected via the DBHUB_DSN env var (in-memory SQLite) +// - all five connectors must load (drivers resolve from the bundle) +// - tools/list must expose exactly execute_sql and search_objects +// - execute_sql SELECT succeeds +// - execute_sql CREATE TABLE is rejected (the bundle config is read-only) +// +// Run after `pnpm run build:mcpb`: pnpm run test:mcpb + +import { spawnSync } from "node:child_process"; +import { existsSync, mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { Client } from "@modelcontextprotocol/sdk/client/index.js"; +import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; +import { mcpbFile, localBin } from "./mcpb-common.mjs"; + +if (!existsSync(mcpbFile)) { + console.error(`Packed bundle ${mcpbFile} not found — run \`pnpm run build:mcpb\` first.`); + process.exit(1); +} + +const bundleDir = mkdtempSync(join(tmpdir(), "dbhub-mcpb-smoke-")); +process.on("exit", () => rmSync(bundleDir, { recursive: true, force: true })); + +const unpack = spawnSync(localBin("mcpb"), ["unpack", mcpbFile, bundleDir], { stdio: "inherit" }); +if (unpack.status !== 0) { + console.error("mcpb unpack failed"); + process.exit(1); +} + +let failures = 0; +function check(name, condition, detail) { + if (condition) { + console.log(` OK ${name}`); + } else { + console.error(` FAIL ${name}${detail ? `: ${detail}` : ""}`); + failures++; + } +} + +function resultText(result) { + return (result.content ?? []).map((item) => item.text ?? "").join("\n"); +} + +const client = new Client({ name: "mcpb-smoke-test", version: "0.0.0" }); +const transport = new StdioClientTransport({ + command: process.execPath, + args: [ + join(bundleDir, "server", "index.js"), + "--transport", + "stdio", + "--config", + join(bundleDir, "dbhub.toml"), + ], + cwd: bundleDir, + env: { ...process.env, DBHUB_DSN: "sqlite:///:memory:" }, + stderr: "pipe", +}); + +let stderr = ""; +try { + await client.connect(transport); + // Safe to attach after connect: the stream stays paused (buffering) until a + // listener is added, so startup output is not lost. + transport.stderr?.on("data", (chunk) => { + stderr += chunk; + }); + check("initialize", client.getServerVersion()?.name !== undefined); + + const { tools } = await client.listTools(); + const toolNames = tools.map((tool) => tool.name).sort(); + check( + "tools/list exposes exactly execute_sql + search_objects", + JSON.stringify(toolNames) === JSON.stringify(["execute_sql", "search_objects"]), + `got: ${toolNames.join(", ")}` + ); + + const select = await client.callTool({ + name: "execute_sql", + arguments: { sql: "SELECT 1 AS one" }, + }); + check( + "execute_sql SELECT succeeds", + !select.isError && resultText(select).includes("one"), + resultText(select) + ); + + const create = await client.callTool({ + name: "execute_sql", + arguments: { sql: "CREATE TABLE smoke_test (id INTEGER)" }, + }); + check( + "execute_sql CREATE is rejected (read-only)", + create.isError === true && /read[\s-]?only/i.test(resultText(create)), + resultText(create) + ); +} catch (err) { + console.error(` FAIL ${err.message}`); + console.error(stderr); + failures++; +} finally { + await client.close(); +} + +// The server logs "Skipping connector: ..." when a driver package is +// missing from the bundle's node_modules — that means the packaging is broken. +check( + "all connectors loaded (no drivers skipped)", + !stderr.includes("Skipping"), + stderr.split("\n").filter((line) => line.includes("Skipping")).join("; ") +); + +if (failures > 0) { + console.error(`\n${failures} smoke test check(s) failed`); + process.exit(1); +} +console.log("\nMCP Bundle smoke test passed"); From 70bbadfcc1ebac87e90cf031cfdcad970c9c7b60 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sat, 11 Jul 2026 00:12:21 +0800 Subject: [PATCH 2/4] fix: harden mcpb smoke test error paths per review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Attach the stderr listener before connect() — the SDK exposes the PassThrough before spawning, so a startup crash now surfaces its stderr instead of logging an empty string - Convert stderr chunks to text explicitly - Handle non-Error throws in the catch path; make client.close() best-effort so a close failure can't mask the real outcome - Include the spawn error message when mcpb unpack fails to start Co-Authored-By: Claude Fable 5 --- scripts/smoke-test-mcpb.mjs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/scripts/smoke-test-mcpb.mjs b/scripts/smoke-test-mcpb.mjs index a4b0139c..abbfc1d5 100644 --- a/scripts/smoke-test-mcpb.mjs +++ b/scripts/smoke-test-mcpb.mjs @@ -34,7 +34,7 @@ process.on("exit", () => rmSync(bundleDir, { recursive: true, force: true })); const unpack = spawnSync(localBin("mcpb"), ["unpack", mcpbFile, bundleDir], { stdio: "inherit" }); if (unpack.status !== 0) { - console.error("mcpb unpack failed"); + console.error(`mcpb unpack failed${unpack.error ? `: ${unpack.error.message}` : ""}`); process.exit(1); } @@ -67,14 +67,16 @@ const transport = new StdioClientTransport({ stderr: "pipe", }); +// With stderr: "pipe" the transport exposes a PassThrough stream before the +// process is spawned — attach before connect() so startup output (including +// a startup crash) is always captured. let stderr = ""; +transport.stderr?.on("data", (chunk) => { + stderr += chunk.toString(); +}); + try { await client.connect(transport); - // Safe to attach after connect: the stream stays paused (buffering) until a - // listener is added, so startup output is not lost. - transport.stderr?.on("data", (chunk) => { - stderr += chunk; - }); check("initialize", client.getServerVersion()?.name !== undefined); const { tools } = await client.listTools(); @@ -105,11 +107,12 @@ try { resultText(create) ); } catch (err) { - console.error(` FAIL ${err.message}`); + console.error(` FAIL ${err instanceof Error ? err.message : String(err)}`); console.error(stderr); failures++; } finally { - await client.close(); + // Best-effort: a close failure must not mask the real test outcome + await client.close().catch(() => {}); } // The server logs "Skipping connector: ..." when a driver package is From fe5bce06f11576c7dc8fdcf6183303623bce183e Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sat, 11 Jul 2026 00:26:52 +0800 Subject: [PATCH 3/4] docs: describe .mcpb as an open, multi-client format, not Claude Desktop-only MCPB is governed by the Model Context Protocol project and installs directly in Claude Desktop, Claude Code, and MCP for Windows; other clients can run the unpacked bundle as a plain stdio server. Reword README, docs, manifest, and comments accordingly, keeping Claude Desktop as the concrete install walkthrough. Co-Authored-By: Claude Fable 5 --- .github/workflows/mcpb-release.yml | 5 +++-- CLAUDE.md | 2 +- README.md | 4 ++-- docs/installation.mdx | 2 +- docs/mcpb.mdx | 12 ++++++------ mcpb/manifest.json | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) diff --git a/.github/workflows/mcpb-release.yml b/.github/workflows/mcpb-release.yml index d62f6df5..dae2f9bb 100644 --- a/.github/workflows/mcpb-release.yml +++ b/.github/workflows/mcpb-release.yml @@ -1,8 +1,9 @@ # Workflow for publishing the DBHub MCP Bundle (.mcpb) as a GitHub release asset. # # The .mcpb bundle is a self-contained package (server + database drivers + -# read-only TOML config) that installs into Claude Desktop with a double click, -# running locally over stdio — no remote HTTP endpoint or OAuth setup needed. +# read-only TOML config) that installs with one click into MCPB-compatible +# clients (Claude Desktop, Claude Code, MCP for Windows), running locally over +# stdio — no remote HTTP endpoint or OAuth setup needed. # # Trigger modes (mirrors npm-publish.yml): # 1. Automatic: on push to main that modifies package.json — builds and attaches diff --git a/CLAUDE.md b/CLAUDE.md index 00d6a131..a6f8ab7f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ DBHub is a zero-dependency, token efficient database MCP server implementing the - Test: `pnpm test` - Run all tests - Test Watch: `pnpm test:watch` - Run tests in watch mode - Integration Tests: `pnpm test:integration` - Run database integration tests (requires Docker) -- MCP Bundle: `pnpm run build:mcpb` - Package DBHub as a Claude Desktop `.mcpb` bundle (see `mcpb/` and `scripts/build-mcpb.mjs`); `pnpm run test:mcpb` smoke-tests the packed bundle over stdio. Published to GitHub releases by `.github/workflows/mcpb-release.yml`. The bundle is read-only by design (`mcpb/dbhub.toml`), with the DSN supplied via the `DBHUB_DSN` env var declared in `mcpb/manifest.json` (TOML `${ENV_VAR}` interpolation). +- MCP Bundle: `pnpm run build:mcpb` - Package DBHub as an `.mcpb` bundle for MCPB-compatible clients (Claude Desktop, Claude Code, MCP for Windows; see `mcpb/` and `scripts/build-mcpb.mjs`); `pnpm run test:mcpb` smoke-tests the packed bundle over stdio. Published to GitHub releases by `.github/workflows/mcpb-release.yml`. The bundle is read-only by design (`mcpb/dbhub.toml`), with the DSN supplied via the `DBHUB_DSN` env var declared in `mcpb/manifest.json` (TOML `${ENV_VAR}` interpolation). ## Architecture Overview diff --git a/README.md b/README.md index 53e0eb23..eb8b57e1 100644 --- a/README.md +++ b/README.md @@ -80,9 +80,9 @@ docker run --rm --init \ npx @bytebase/dbhub@latest --transport http --port 8080 --dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable" ``` -**Claude Desktop (MCP Bundle):** +**MCP Bundle (one-click install):** -Download `dbhub-.mcpb` from the [latest release](https://github.com/bytebase/dbhub/releases/latest), double-click it (or drag it into Claude Desktop's Settings → Extensions), and enter your database connection string. The bundle runs locally over stdio, is **read-only by design** (writes are rejected and the database session is set to read-only at the engine level), and needs no remote endpoint or OAuth setup — ideal for giving non-technical teammates curated, read-only database access. Pair it with a least-privilege, read-only database account. See the [MCP Bundle guide](https://dbhub.ai/mcpb) for details and for packaging your own bundle. +Download `dbhub-.mcpb` from the [latest release](https://github.com/bytebase/dbhub/releases/latest) and install it in any [MCPB-compatible client](https://github.com/modelcontextprotocol/mcpb) — Claude Desktop (double-click, or drag into Settings → Extensions), Claude Code, or MCP for Windows — then enter your database connection string. The bundle runs locally over stdio, is **read-only by design** (writes are rejected and the database session is set to read-only at the engine level), and needs no remote endpoint or OAuth setup — ideal for giving non-technical teammates curated, read-only database access. Pair it with a least-privilege, read-only database account. See the [MCP Bundle guide](https://dbhub.ai/mcpb) for details and for packaging your own bundle. **Demo Mode:** diff --git a/docs/installation.mdx b/docs/installation.mdx index 8c144b80..651d974d 100644 --- a/docs/installation.mdx +++ b/docs/installation.mdx @@ -64,7 +64,7 @@ When a driver is not installed or a required dependency is missing (e.g., a tran ## MCP Bundle (.mcpb) -For Claude Desktop, the [MCP Bundle](/mcpb) is a one-click install: download the `.mcpb` from the [latest release](https://github.com/bytebase/dbhub/releases/latest), double-click, enter your connection string. Read-only by default, no Node.js or JSON editing required — the recommended path for non-technical users. The guide also covers packaging your own bundle. +The [MCP Bundle](/mcpb) is a one-click install for MCPB-compatible clients — Claude Desktop, Claude Code, MCP for Windows: download the `.mcpb` from the [latest release](https://github.com/bytebase/dbhub/releases/latest), install, enter your connection string. Read-only by default, no Node.js or JSON editing required — the recommended path for non-technical users. The guide also covers packaging your own bundle. ## Docker diff --git a/docs/mcpb.mdx b/docs/mcpb.mdx index b8b2fa30..eb6da890 100644 --- a/docs/mcpb.mdx +++ b/docs/mcpb.mdx @@ -1,24 +1,24 @@ --- title: "MCP Bundle" -description: "One-click, read-only Claude Desktop install via .mcpb" +description: "One-click, read-only install via .mcpb for Claude Desktop and other MCPB clients" --- -The [MCP Bundle](https://github.com/anthropics/mcpb) (`.mcpb`) is a one-click installation path for Claude Desktop — no Node.js install, no JSON config editing, and no remote HTTP endpoint or OAuth setup. It is the recommended way to give **non-technical users** curated database access. +The [MCP Bundle](https://github.com/modelcontextprotocol/mcpb) (`.mcpb`) is a one-click installation path — no Node.js install, no JSON config editing, and no remote HTTP endpoint or OAuth setup. It is an open format governed by the Model Context Protocol project; clients that install it directly include **Claude Desktop**, **Claude Code**, and **MCP for Windows**. It is the recommended way to give **non-technical users** curated database access. ## Install 1. Download `dbhub-.mcpb` from the [latest GitHub release](https://github.com/bytebase/dbhub/releases/latest). -2. Double-click the file (or drag it into Claude Desktop's **Settings → Extensions**). +2. Install it in your client — in Claude Desktop, double-click the file (or drag it into **Settings → Extensions**). 3. Enter your database connection string when prompted, e.g. `postgres://user:password@host:5432/dbname`. It is stored in the OS keychain, not in a config file. -The bundle includes drivers for PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite, and runs locally over stdio. +The bundle includes drivers for PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite, and runs locally over stdio. Clients without native `.mcpb` support (Cursor, VS Code, ...) can still run the unpacked bundle as a plain stdio MCP server: `node server/index.js --transport stdio --config dbhub.toml` with `DBHUB_DSN` set. ## Read-only by design The bundled configuration sets `readonly = true` and `max_rows = 1000` on `execute_sql`: mutating statements are rejected, and the database session is additionally set to read-only at the engine level. Still, connect with a least-privilege, read-only database account — defense in depth beats configuration alone. -The machine running Claude Desktop connects **directly** to the database, so the database must be reachable from it (VPN or network allow-list). AWS IAM and Azure AD authentication are not included in the bundle; repackage with those drivers added if you need them. +The machine running the MCP client connects **directly** to the database, so the database must be reachable from it (VPN or network allow-list). AWS IAM and Azure AD authentication are not included in the bundle; repackage with those drivers added if you need them. ## Packaging your own bundle @@ -33,7 +33,7 @@ pnpm run build:mcpb # produces dist-mcpb/dbhub-.mcpb pnpm run test:mcpb # smoke-tests the packed bundle over stdio ``` -Anything in a `[[sources]]` or `[[tools]]` section of the [TOML configuration](/config/toml) works in the bundle, including `${ENV_VAR}` interpolation — the stock bundle uses `dsn = "${DBHUB_DSN}"`, with `DBHUB_DSN` supplied by Claude Desktop from the user's extension settings. +Anything in a `[[sources]]` or `[[tools]]` section of the [TOML configuration](/config/toml) works in the bundle, including `${ENV_VAR}` interpolation — the stock bundle uses `dsn = "${DBHUB_DSN}"`, with `DBHUB_DSN` supplied by the client from the user's extension settings. If you bake a DSN into your own bundle, remember that anyone can unzip an `.mcpb` and read it. Only embed credentials for a curated, read-only, least-privilege account. diff --git a/mcpb/manifest.json b/mcpb/manifest.json index 62918d7d..24bff884 100644 --- a/mcpb/manifest.json +++ b/mcpb/manifest.json @@ -4,7 +4,7 @@ "display_name": "DBHub", "version": "0.0.0", "description": "Minimal, token-efficient database MCP server. Read-only access to PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite.", - "long_description": "DBHub bridges Claude with your database through just two tools: `execute_sql` and `search_objects`. This bundle is configured read-only by design — writes are rejected by the SQL classifier and the database session is additionally set to read-only at the engine level — making it safe to hand to non-technical users for data exploration, reporting, and debugging. Connect it to PostgreSQL, MySQL, MariaDB, SQL Server, or SQLite by entering a single connection string.", + "long_description": "DBHub bridges your AI assistant with your database through just two tools: `execute_sql` and `search_objects`. This bundle is configured read-only by design — writes are rejected by the SQL classifier and the database session is additionally set to read-only at the engine level — making it safe to hand to non-technical users for data exploration, reporting, and debugging. Connect it to PostgreSQL, MySQL, MariaDB, SQL Server, or SQLite by entering a single connection string.", "author": { "name": "Bytebase", "url": "https://www.bytebase.com" From 62d21725034ae2d116155acfcb0951bf71e13548 Mon Sep 17 00:00:00 2001 From: Tianzhou Date: Sat, 11 Jul 2026 00:40:24 +0800 Subject: [PATCH 4/4] fix: bootstrap npm upgrade in mcpb-release workflow build-mcpb.mjs stages the bundle's node_modules via npm install, and Node 22.22.2's bundled npm is broken (missing promise-retry). Apply the same bootstrap-without-npm upgrade step npm-publish.yml already uses. Co-Authored-By: Claude Fable 5 --- .github/workflows/mcpb-release.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/.github/workflows/mcpb-release.yml b/.github/workflows/mcpb-release.yml index dae2f9bb..d57280fa 100644 --- a/.github/workflows/mcpb-release.yml +++ b/.github/workflows/mcpb-release.yml @@ -71,6 +71,18 @@ jobs: node-version: "22" cache: "pnpm" + # build-mcpb.mjs stages the bundle's node_modules with `npm install`, and + # Node 22.22.2 ships with broken npm (missing promise-retry) — bootstrap a + # working npm without invoking the broken one, same as npm-publish.yml. + - name: Upgrade npm + if: env.SHOULD_PUBLISH == 'true' + run: | + npm_tarball=$(curl -fsSL https://registry.npmjs.org/npm/latest | node -p "JSON.parse(require('fs').readFileSync(0,'utf8')).dist.tarball") + curl -fsSL "$npm_tarball" | tar xz -C /tmp + node /tmp/package/bin/npm-cli.js install -g npm@latest + rm -rf /tmp/package + echo "npm version: $(npm --version)" + - name: Install dependencies if: env.SHOULD_PUBLISH == 'true' run: pnpm install --frozen-lockfile