diff --git a/.github/workflows/mcpb-release.yml b/.github/workflows/mcpb-release.yml new file mode 100644 index 00000000..d57280fa --- /dev/null +++ b/.github/workflows/mcpb-release.yml @@ -0,0 +1,116 @@ +# 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 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 +# 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" + + # 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 + + - 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..a6f8ab7f 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 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 b33cbfd7..eb8b57e1 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" ``` +**MCP Bundle (one-click install):** + +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:** ```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..651d974d 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) + +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 @@ -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..eb6da890 --- /dev/null +++ b/docs/mcpb.mdx @@ -0,0 +1,40 @@ +--- +title: "MCP Bundle" +description: "One-click, read-only install via .mcpb for Claude Desktop and other MCPB clients" +--- + +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. 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. 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 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 + +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 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/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 00000000..e724ce0a Binary files /dev/null and b/mcpb/icon.png differ diff --git a/mcpb/manifest.json b/mcpb/manifest.json new file mode 100644 index 00000000..24bff884 --- /dev/null +++ b/mcpb/manifest.json @@ -0,0 +1,74 @@ +{ + "manifest_version": "0.3", + "name": "dbhub", + "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 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" + }, + "repository": { + "type": "git", + "url": "https://github.com/bytebase/dbhub" + }, + "homepage": "https://dbhub.ai", + "documentation": "https://dbhub.ai/mcpb", + "support": "https://github.com/bytebase/dbhub/issues", + "icon": "icon.png", + "license": "MIT", + "keywords": [ + "database", + "sql", + "postgres", + "postgresql", + "mysql", + "mariadb", + "sqlserver", + "sqlite", + "read-only" + ], + "server": { + "type": "node", + "entry_point": "server/index.js", + "mcp_config": { + "command": "node", + "args": [ + "${__dirname}/server/index.js", + "--transport", + "stdio", + "--config", + "${__dirname}/dbhub.toml" + ], + "env": { + "DBHUB_DSN": "${user_config.dsn}" + } + } + }, + "user_config": { + "dsn": { + "type": "string", + "title": "Database connection string (DSN)", + "description": "Examples: postgres://user:password@localhost:5432/dbname | mysql://user:password@localhost:3306/dbname | mariadb://user:password@localhost:3306/dbname | sqlserver://user:password@localhost:1433/dbname | sqlite:///path/to/database.db. Append ?sslmode=require for SSL. Use a least-privilege, read-only database account.", + "sensitive": true, + "required": true + } + }, + "tools": [ + { + "name": "execute_sql", + "description": "Execute SQL against the connected database (read-only: SELECT and other non-mutating statements only)" + }, + { + "name": "search_objects", + "description": "Search and list database objects (schemas, tables, columns, indexes, procedures) with progressive detail levels" + } + ], + "compatibility": { + "platforms": ["darwin", "win32", "linux"], + "runtimes": { + "node": ">=22.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..abbfc1d5 --- /dev/null +++ b/scripts/smoke-test-mcpb.mjs @@ -0,0 +1,130 @@ +#!/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${unpack.error ? `: ${unpack.error.message}` : ""}`); + 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", +}); + +// 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); + 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 instanceof Error ? err.message : String(err)}`); + console.error(stderr); + failures++; +} finally { + // 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 +// 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");