Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 116 additions & 0 deletions .github/workflows/mcpb-release.yml
Original file line number Diff line number Diff line change
@@ -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<version>, 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"

Comment thread
tianzhou marked this conversation as resolved.
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}"
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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-<version>.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
Expand Down
2 changes: 1 addition & 1 deletion docs/docs.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"groups": [
{
"group": "Getting Started",
"pages": ["index", "installation", "quickstart"]
"pages": ["index", "installation", "quickstart", "mcpb"]
},
{
"group": "MCP Tools",
Expand Down
8 changes: 8 additions & 0 deletions docs/installation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -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.
</Note>

## 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

<Tabs>
Expand Down Expand Up @@ -261,6 +265,10 @@ Verify DBHub is loaded:

![Claude Desktop with DBHub](https://raw.githubusercontent.com/bytebase/dbhub/main/docs/images/claude-desktop.webp)

<Note>
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.
</Note>

<Tabs>
<Tab title="MacOS/Linux">
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
Expand Down
40 changes: 40 additions & 0 deletions docs/mcpb.mdx
Original file line number Diff line number Diff line change
@@ -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-<version>.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.

<Note>
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.
</Note>

## 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-<version>.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.

<Warning>
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.
</Warning>
28 changes: 28 additions & 0 deletions mcpb/dbhub.toml
Original file line number Diff line number Diff line change
@@ -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"
Binary file added mcpb/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
74 changes: 74 additions & 0 deletions mcpb/manifest.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
}
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
Loading
Loading