@bytebase/dbhub 0.11.3 → 0.11.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +194 -22
  2. package/dist/index.js +100 -6
  3. package/package.json +5 -1
package/README.md CHANGED
@@ -43,12 +43,6 @@ DBHub is a universal database gateway implementing the Model Context Protocol (M
43
43
  MCP Clients MCP Server Databases
44
44
  ```
45
45
 
46
- ## Demo HTTP Endpoint
47
-
48
- https://demo.dbhub.ai/message connects a [sample employee database](https://github.com/bytebase/employee-sample-database). You can point Cursor or MCP Inspector to it to see it in action.
49
-
50
- ![mcp-inspector](https://raw.githubusercontent.com/bytebase/dbhub/main/resources/images/mcp-inspector.webp)
51
-
52
46
  ## Supported Matrix
53
47
 
54
48
  ### Database Resources
@@ -101,6 +95,29 @@ docker run --rm --init \
101
95
  --demo
102
96
  ```
103
97
 
98
+ **Docker Compose Setup:**
99
+
100
+ If you're using Docker Compose for development, add DBHub to your `docker-compose.yml`:
101
+
102
+ ```yaml
103
+ dbhub:
104
+ image: bytebase/dbhub:latest
105
+ container_name: dbhub
106
+ ports:
107
+ - "8080:8080"
108
+ environment:
109
+ - DBHUB_LOG_LEVEL=info
110
+ command:
111
+ - --transport
112
+ - http
113
+ - --port
114
+ - "8080"
115
+ - --dsn
116
+ - "postgres://user:password@database:5432/dbname"
117
+ depends_on:
118
+ - database
119
+ ```
120
+
104
121
  ### NPM
105
122
 
106
123
  ```bash
@@ -181,6 +198,74 @@ cursor://anysphere.cursor-deeplink/mcp/install?name=dbhub&config=eyJjb21tYW5kIjo
181
198
  - Cursor supports both `stdio` and `http`.
182
199
  - Follow [Cursor MCP guide](https://docs.cursor.com/context/model-context-protocol) and make sure to use [Agent](https://docs.cursor.com/chat/agent) mode.
183
200
 
201
+ ### VSCode + Copilot
202
+
203
+ Check https://code.visualstudio.com/docs/copilot/customization/mcp-servers
204
+
205
+ VSCode with GitHub Copilot can connect to DBHub via both `stdio` and `http` transports. This enables AI agents to interact with your development database through a secure interface.
206
+
207
+ - VSCode supports both `stdio` and `http` transports
208
+ - Configure MCP server in `.vscode/mcp.json`:
209
+
210
+ **Stdio Transport:**
211
+
212
+ ```json
213
+ {
214
+ "servers": {
215
+ "dbhub": {
216
+ "command": "npx",
217
+ "args": [
218
+ "-y",
219
+ "@bytebase/dbhub",
220
+ "--transport",
221
+ "stdio",
222
+ "--dsn",
223
+ "postgres://user:password@localhost:5432/dbname"
224
+ ]
225
+ }
226
+ },
227
+ "inputs": []
228
+ }
229
+ ```
230
+
231
+ **HTTP Transport:**
232
+
233
+ ```json
234
+ {
235
+ "servers": {
236
+ "dbhub": {
237
+ "url": "http://localhost:8080/message",
238
+ "type": "http"
239
+ }
240
+ },
241
+ "inputs": []
242
+ }
243
+ ```
244
+
245
+ **Copilot Instructions:**
246
+
247
+ You can provide Copilot with context by creating `.github/copilot-instructions.md`:
248
+
249
+ ```markdown
250
+ ## Database Access
251
+
252
+ This project provides an MCP server (DBHub) for secure SQL access to the development database.
253
+
254
+ AI agents can execute SQL queries. In read-only mode (recommended for production):
255
+
256
+ - `SELECT * FROM users LIMIT 5;`
257
+ - `SHOW TABLES;`
258
+ - `DESCRIBE table_name;`
259
+
260
+ In read-write mode (development/testing):
261
+
262
+ - `INSERT INTO users (name, email) VALUES ('John', 'john@example.com');`
263
+ - `UPDATE users SET status = 'active' WHERE id = 1;`
264
+ - `CREATE TABLE test_table (id INT PRIMARY KEY);`
265
+
266
+ Use `--readonly` flag to restrict to read-only operations for safety.
267
+ ```
268
+
184
269
  ## Usage
185
270
 
186
271
  ### Read-only Mode
@@ -196,6 +281,40 @@ In read-only mode, only [readonly SQL operations](https://github.com/bytebase/db
196
281
 
197
282
  This provides an additional layer of security when connecting to production databases.
198
283
 
284
+ ### Suffix Tool Names with ID
285
+
286
+ You can suffix tool names with a custom ID using the `--id` flag or `ID` environment variable. This is useful when running multiple DBHub instances (e.g., in Cursor) to allow the client to route queries to the correct database.
287
+
288
+ **Example configuration for multiple databases in Cursor:**
289
+
290
+ ```json
291
+ {
292
+ "mcpServers": {
293
+ "dbhub-prod": {
294
+ "command": "npx",
295
+ "args": ["-y", "@bytebase/dbhub"],
296
+ "env": {
297
+ "ID": "prod",
298
+ "DSN": "postgres://user:password@prod-host:5432/dbname"
299
+ }
300
+ },
301
+ "dbhub-staging": {
302
+ "command": "npx",
303
+ "args": ["-y", "@bytebase/dbhub"],
304
+ "env": {
305
+ "ID": "staging",
306
+ "DSN": "mysql://user:password@staging-host:3306/dbname"
307
+ }
308
+ }
309
+ }
310
+ }
311
+ ```
312
+
313
+ With this configuration:
314
+
315
+ - Production database tools: `execute_sql_prod`
316
+ - Staging database tools: `execute_sql_staging`
317
+
199
318
  ### Row Limiting
200
319
 
201
320
  You can limit the number of rows returned from SELECT queries using the `--max-rows` parameter. This helps prevent accidentally retrieving too much data from large tables:
@@ -314,9 +433,14 @@ npx @bytebase/dbhub --demo
314
433
  ```
315
434
 
316
435
  > [!WARNING]
317
- > If your user/password contains special characters, you need to escape them first. (e.g. `pass#word` should be escaped as `pass%23word`)
436
+ > If your user/password contains special characters, you have two options:
437
+ >
438
+ > 1. Escape them in the DSN (e.g. `pass#word` should be escaped as `pass%23word`)
439
+ > 2. Use the individual database parameters method below (recommended)
440
+
441
+ For real databases, you can configure the database connection in two ways:
318
442
 
319
- For real databases, a Database Source Name (DSN) is required. You can provide this in several ways:
443
+ #### Method 1: Database Source Name (DSN)
320
444
 
321
445
  - **Command line argument** (highest priority):
322
446
 
@@ -338,6 +462,45 @@ For real databases, a Database Source Name (DSN) is required. You can provide th
338
462
  DSN=postgres://user:password@localhost:5432/dbname?sslmode=disable
339
463
  ```
340
464
 
465
+ #### Method 2: Individual Database Parameters
466
+
467
+ If your password contains special characters that would break URL parsing, use individual environment variables instead:
468
+
469
+ - **Environment variables**:
470
+
471
+ ```bash
472
+ export DB_TYPE=postgres
473
+ export DB_HOST=localhost
474
+ export DB_PORT=5432
475
+ export DB_USER=myuser
476
+ export DB_PASSWORD='my@complex:password/with#special&chars'
477
+ export DB_NAME=mydatabase
478
+ npx @bytebase/dbhub
479
+ ```
480
+
481
+ - **Environment file**:
482
+ ```
483
+ DB_TYPE=postgres
484
+ DB_HOST=localhost
485
+ DB_PORT=5432
486
+ DB_USER=myuser
487
+ DB_PASSWORD=my@complex:password/with#special&chars
488
+ DB_NAME=mydatabase
489
+ ```
490
+
491
+ **Supported DB_TYPE values**: `postgres`, `mysql`, `mariadb`, `sqlserver`, `sqlite`
492
+
493
+ **Default ports** (when DB_PORT is omitted):
494
+
495
+ - PostgreSQL: `5432`
496
+ - MySQL/MariaDB: `3306`
497
+ - SQL Server: `1433`
498
+
499
+ **For SQLite**: Only `DB_TYPE=sqlite` and `DB_NAME=/path/to/database.db` are required.
500
+
501
+ > [!TIP]
502
+ > Use the individual parameter method when your password contains special characters like `@`, `:`, `/`, `#`, `&`, `=` that would break DSN parsing.
503
+
341
504
  > [!WARNING]
342
505
  > When running in Docker, use `host.docker.internal` instead of `localhost` to connect to databases running on your host machine. For example: `mysql://user:password@host.docker.internal:3306/dbname`
343
506
 
@@ -374,20 +537,27 @@ Extra query parameters:
374
537
 
375
538
  ### Command line options
376
539
 
377
- | Option | Environment Variable | Description | Default |
378
- | -------------- | -------------------- | ---------------------------------------------------------------- | ---------------------------- |
379
- | dsn | `DSN` | Database connection string | Required if not in demo mode |
380
- | transport | `TRANSPORT` | Transport mode: `stdio` or `http` | `stdio` |
381
- | port | `PORT` | HTTP server port (only applicable when using `--transport=http`) | `8080` |
382
- | readonly | `READONLY` | Restrict SQL execution to read-only operations | `false` |
383
- | max-rows | N/A | Limit the number of rows returned from SELECT queries | No limit |
384
- | demo | N/A | Run in demo mode with sample employee database | `false` |
385
- | ssh-host | `SSH_HOST` | SSH server hostname for tunnel connection | N/A |
386
- | ssh-port | `SSH_PORT` | SSH server port | `22` |
387
- | ssh-user | `SSH_USER` | SSH username | N/A |
388
- | ssh-password | `SSH_PASSWORD` | SSH password (for password authentication) | N/A |
389
- | ssh-key | `SSH_KEY` | Path to SSH private key file | N/A |
390
- | ssh-passphrase | `SSH_PASSPHRASE` | Passphrase for SSH private key | N/A |
540
+ | Option | Environment Variable | Description | Default |
541
+ | -------------- | -------------------- | --------------------------------------------------------------------- | ---------------------------- |
542
+ | dsn | `DSN` | Database connection string | Required if not in demo mode |
543
+ | N/A | `DB_TYPE` | Database type: `postgres`, `mysql`, `mariadb`, `sqlserver`, `sqlite` | N/A |
544
+ | N/A | `DB_HOST` | Database server hostname (not needed for SQLite) | N/A |
545
+ | N/A | `DB_PORT` | Database server port (uses default if omitted, not needed for SQLite) | N/A |
546
+ | N/A | `DB_USER` | Database username (not needed for SQLite) | N/A |
547
+ | N/A | `DB_PASSWORD` | Database password (not needed for SQLite) | N/A |
548
+ | N/A | `DB_NAME` | Database name or SQLite file path | N/A |
549
+ | transport | `TRANSPORT` | Transport mode: `stdio` or `http` | `stdio` |
550
+ | port | `PORT` | HTTP server port (only applicable when using `--transport=http`) | `8080` |
551
+ | readonly | `READONLY` | Restrict SQL execution to read-only operations | `false` |
552
+ | max-rows | N/A | Limit the number of rows returned from SELECT queries | No limit |
553
+ | demo | N/A | Run in demo mode with sample employee database | `false` |
554
+ | id | `ID` | Instance identifier to suffix tool names (for multi-instance) | N/A |
555
+ | ssh-host | `SSH_HOST` | SSH server hostname for tunnel connection | N/A |
556
+ | ssh-port | `SSH_PORT` | SSH server port | `22` |
557
+ | ssh-user | `SSH_USER` | SSH username | N/A |
558
+ | ssh-password | `SSH_PASSWORD` | SSH password (for password authentication) | N/A |
559
+ | ssh-key | `SSH_KEY` | Path to SSH private key file | N/A |
560
+ | ssh-passphrase | `SSH_PASSPHRASE` | Passphrase for SSH private key | N/A |
391
561
 
392
562
  The demo mode uses an in-memory SQLite database loaded with the [sample employee database](https://github.com/bytebase/dbhub/tree/main/resources/employee-sqlite) that includes tables for employees, departments, titles, salaries, department employees, and department managers. The sample database includes SQL scripts for table creation, data loading, and testing.
393
563
 
@@ -511,6 +681,8 @@ The project includes pre-commit hooks to run tests automatically before each com
511
681
 
512
682
  ### Debug with [MCP Inspector](https://github.com/modelcontextprotocol/inspector)
513
683
 
684
+ ![mcp-inspector](https://raw.githubusercontent.com/bytebase/dbhub/main/resources/images/mcp-inspector.webp)
685
+
514
686
  #### stdio
515
687
 
516
688
  ```bash
package/dist/index.js CHANGED
@@ -1046,10 +1046,19 @@ var SQLiteConnector = class {
1046
1046
  async disconnect() {
1047
1047
  if (this.db) {
1048
1048
  try {
1049
- this.db.close();
1049
+ if (!this.db.inTransaction) {
1050
+ this.db.close();
1051
+ } else {
1052
+ try {
1053
+ this.db.exec("ROLLBACK");
1054
+ } catch (rollbackError) {
1055
+ }
1056
+ this.db.close();
1057
+ }
1050
1058
  this.db = null;
1051
1059
  } catch (error) {
1052
- throw error;
1060
+ console.error("Error during SQLite disconnect:", error);
1061
+ this.db = null;
1053
1062
  }
1054
1063
  }
1055
1064
  return Promise.resolve();
@@ -2310,6 +2319,62 @@ function isReadOnlyMode() {
2310
2319
  }
2311
2320
  return false;
2312
2321
  }
2322
+ function buildDSNFromEnvParams() {
2323
+ const dbType = process.env.DB_TYPE;
2324
+ const dbHost = process.env.DB_HOST;
2325
+ const dbUser = process.env.DB_USER;
2326
+ const dbPassword = process.env.DB_PASSWORD;
2327
+ const dbName = process.env.DB_NAME;
2328
+ const dbPort = process.env.DB_PORT;
2329
+ if (dbType?.toLowerCase() === "sqlite") {
2330
+ if (!dbName) {
2331
+ return null;
2332
+ }
2333
+ } else {
2334
+ if (!dbType || !dbHost || !dbUser || !dbPassword || !dbName) {
2335
+ return null;
2336
+ }
2337
+ }
2338
+ const supportedTypes = ["postgres", "postgresql", "mysql", "mariadb", "sqlserver", "sqlite"];
2339
+ if (!supportedTypes.includes(dbType.toLowerCase())) {
2340
+ throw new Error(`Unsupported DB_TYPE: ${dbType}. Supported types: ${supportedTypes.join(", ")}`);
2341
+ }
2342
+ let port = dbPort;
2343
+ if (!port) {
2344
+ switch (dbType.toLowerCase()) {
2345
+ case "postgres":
2346
+ case "postgresql":
2347
+ port = "5432";
2348
+ break;
2349
+ case "mysql":
2350
+ case "mariadb":
2351
+ port = "3306";
2352
+ break;
2353
+ case "sqlserver":
2354
+ port = "1433";
2355
+ break;
2356
+ case "sqlite":
2357
+ return {
2358
+ dsn: `sqlite:///${dbName}`,
2359
+ source: "individual environment variables"
2360
+ };
2361
+ default:
2362
+ throw new Error(`Unknown database type for port determination: ${dbType}`);
2363
+ }
2364
+ }
2365
+ const user = dbUser;
2366
+ const password = dbPassword;
2367
+ const dbNameStr = dbName;
2368
+ const encodedUser = encodeURIComponent(user);
2369
+ const encodedPassword = encodeURIComponent(password);
2370
+ const encodedDbName = encodeURIComponent(dbNameStr);
2371
+ const protocol = dbType.toLowerCase() === "postgresql" ? "postgres" : dbType.toLowerCase();
2372
+ const dsn = `${protocol}://${encodedUser}:${encodedPassword}@${dbHost}:${port}/${encodedDbName}`;
2373
+ return {
2374
+ dsn,
2375
+ source: "individual environment variables"
2376
+ };
2377
+ }
2313
2378
  function resolveDSN() {
2314
2379
  const args = parseCommandLineArgs();
2315
2380
  if (isDemoMode()) {
@@ -2325,10 +2390,23 @@ function resolveDSN() {
2325
2390
  if (process.env.DSN) {
2326
2391
  return { dsn: process.env.DSN, source: "environment variable" };
2327
2392
  }
2393
+ const envParamsResult = buildDSNFromEnvParams();
2394
+ if (envParamsResult) {
2395
+ return envParamsResult;
2396
+ }
2328
2397
  const loadedEnvFile = loadEnvFiles();
2329
2398
  if (loadedEnvFile && process.env.DSN) {
2330
2399
  return { dsn: process.env.DSN, source: `${loadedEnvFile} file` };
2331
2400
  }
2401
+ if (loadedEnvFile) {
2402
+ const envFileParamsResult = buildDSNFromEnvParams();
2403
+ if (envFileParamsResult) {
2404
+ return {
2405
+ dsn: envFileParamsResult.dsn,
2406
+ source: `${loadedEnvFile} file (individual parameters)`
2407
+ };
2408
+ }
2409
+ }
2332
2410
  return null;
2333
2411
  }
2334
2412
  function resolveTransport() {
@@ -2377,6 +2455,16 @@ function redactDSN(dsn) {
2377
2455
  return dsn.replace(/\/\/([^:]+):([^@]+)@/, "//$1:***@");
2378
2456
  }
2379
2457
  }
2458
+ function resolveId() {
2459
+ const args = parseCommandLineArgs();
2460
+ if (args.id) {
2461
+ return { id: args.id, source: "command line argument" };
2462
+ }
2463
+ if (process.env.ID) {
2464
+ return { id: process.env.ID, source: "environment variable" };
2465
+ }
2466
+ return null;
2467
+ }
2380
2468
  function resolveSSHConfig() {
2381
2469
  const args = parseCommandLineArgs();
2382
2470
  const hasSSHArgs = args["ssh-host"] || process.env.SSH_HOST;
@@ -3051,9 +3139,10 @@ async function executeSqlToolHandler({ sql: sql2 }, _extra) {
3051
3139
  }
3052
3140
 
3053
3141
  // src/tools/index.ts
3054
- function registerTools(server) {
3142
+ function registerTools(server, id) {
3143
+ const toolName = id ? `execute_sql_${id}` : "execute_sql";
3055
3144
  server.tool(
3056
- "execute_sql",
3145
+ toolName,
3057
3146
  "Execute a SQL query on the current database",
3058
3147
  executeSqlSchema,
3059
3148
  executeSqlToolHandler
@@ -3497,10 +3586,12 @@ v${version}${modeText} - Universal Database MCP Server
3497
3586
  }
3498
3587
  async function main() {
3499
3588
  try {
3589
+ const idData = resolveId();
3590
+ const id = idData?.id;
3500
3591
  const dsnData = resolveDSN();
3501
3592
  if (!dsnData) {
3502
3593
  const samples = ConnectorRegistry.getAllSampleDSNs();
3503
- const sampleFormats = Object.entries(samples).map(([id, dsn]) => ` - ${id}: ${dsn}`).join("\n");
3594
+ const sampleFormats = Object.entries(samples).map(([id2, dsn]) => ` - ${id2}: ${dsn}`).join("\n");
3504
3595
  console.error(`
3505
3596
  ERROR: Database connection string (DSN) is required.
3506
3597
  Please provide the DSN in one of these ways (in order of priority):
@@ -3523,13 +3614,16 @@ See documentation for more details on configuring database connections.
3523
3614
  version: SERVER_VERSION
3524
3615
  });
3525
3616
  registerResources(server);
3526
- registerTools(server);
3617
+ registerTools(server, id);
3527
3618
  registerPrompts(server);
3528
3619
  return server;
3529
3620
  };
3530
3621
  const connectorManager = new ConnectorManager();
3531
3622
  console.error(`Connecting with DSN: ${redactDSN(dsnData.dsn)}`);
3532
3623
  console.error(`DSN source: ${dsnData.source}`);
3624
+ if (idData) {
3625
+ console.error(`ID: ${idData.id} (from ${idData.source})`);
3626
+ }
3533
3627
  if (dsnData.isDemo) {
3534
3628
  const initScript = getSqliteInMemorySetupSql();
3535
3629
  await connectorManager.connectWithDSN(dsnData.dsn, initScript);
package/package.json CHANGED
@@ -1,7 +1,11 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "0.11.3",
3
+ "version": "0.11.5",
4
4
  "description": "Universal Database MCP Server",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/bytebase/dbhub.git"
8
+ },
5
9
  "main": "dist/index.js",
6
10
  "type": "module",
7
11
  "bin": {