@bytebase/dbhub 0.14.0 → 0.16.0
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.
- package/README.md +15 -9
- package/dist/{chunk-WGDSRFBW.js → chunk-IBBG4PSO.js} +92 -39
- package/dist/index.js +160 -82
- package/dist/public/assets/index-BJ-1UrcV.css +1 -0
- package/dist/public/assets/index-DBYlgGks.js +147 -0
- package/dist/public/index.html +2 -2
- package/dist/{registry-FVGT25UH.js → registry-D77Y4CIA.js} +1 -1
- package/package.json +1 -1
- package/dist/public/assets/index-gVrYRID4.css +0 -1
- package/dist/public/assets/index-hd88eD9m.js +0 -51
package/README.md
CHANGED
|
@@ -23,20 +23,20 @@
|
|
|
23
23
|
| | | | | |
|
|
24
24
|
| VS Code +--->+ +--->+ MySQL |
|
|
25
25
|
| | | | | |
|
|
26
|
-
|
|
|
26
|
+
| Copilot CLI +--->+ +--->+ MariaDB |
|
|
27
27
|
| | | | | |
|
|
28
28
|
| | | | | |
|
|
29
29
|
+------------------+ +--------------+ +------------------+
|
|
30
30
|
MCP Clients MCP Server Databases
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
DBHub is a
|
|
33
|
+
DBHub is a zero-dependency, token efficient MCP server implementing the Model Context Protocol (MCP) server interface. This lightweight gateway allows MCP-compatible clients to connect to and explore different databases:
|
|
34
34
|
|
|
35
|
-
- **
|
|
36
|
-
- **Multi-Database**:
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
39
|
-
- **
|
|
35
|
+
- **Local Development First**: Zero dependency, token efficient with just two MCP tools to maximize context window
|
|
36
|
+
- **Multi-Database**: PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite through a single interface
|
|
37
|
+
- **Multi-Connection**: Connect to multiple databases simultaneously with TOML configuration
|
|
38
|
+
- **Guardrails**: Read-only mode, row limiting, and query timeout to prevent runaway operations
|
|
39
|
+
- **Secure Access**: SSH tunneling and SSL/TLS encryption
|
|
40
40
|
|
|
41
41
|
## Supported Databases
|
|
42
42
|
|
|
@@ -50,6 +50,12 @@ DBHub implements MCP tools for database operations:
|
|
|
50
50
|
- **[search_objects](https://dbhub.ai/tools/search-objects)**: Search and explore database schemas, tables, columns, indexes, and procedures with progressive disclosure
|
|
51
51
|
- **[Custom Tools](https://dbhub.ai/tools/custom-tools)**: Define reusable, parameterized SQL operations in your `dbhub.toml` configuration file
|
|
52
52
|
|
|
53
|
+
## Workbench
|
|
54
|
+
|
|
55
|
+
DBHub includes a [built-in web interface](https://dbhub.ai/workbench/overview) for interacting with your database tools. It provides a visual way to execute queries, run custom tools, and view request traces without requiring an MCP client.
|
|
56
|
+
|
|
57
|
+

|
|
58
|
+
|
|
53
59
|
## Installation
|
|
54
60
|
|
|
55
61
|
See the full [Installation Guide](https://dbhub.ai/installation) for detailed instructions.
|
|
@@ -80,13 +86,13 @@ npx @bytebase/dbhub@latest --transport http --port 8080 --dsn "postgres://user:p
|
|
|
80
86
|
npx @bytebase/dbhub@latest --transport http --port 8080 --demo
|
|
81
87
|
```
|
|
82
88
|
|
|
83
|
-
See [
|
|
89
|
+
See [Command-Line Options](https://dbhub.ai/config/command-line) for all available parameters.
|
|
84
90
|
|
|
85
91
|
### Multi-Database Setup
|
|
86
92
|
|
|
87
93
|
Connect to multiple databases simultaneously using TOML configuration files. Perfect for managing production, staging, and development databases from a single DBHub instance.
|
|
88
94
|
|
|
89
|
-
See [Multi-Database Configuration](https://dbhub.ai/config/
|
|
95
|
+
See [Multi-Database Configuration](https://dbhub.ai/config/toml) for complete setup instructions.
|
|
90
96
|
|
|
91
97
|
## Development
|
|
92
98
|
|
|
@@ -1237,11 +1237,6 @@ function validateToolsConfig(tools, sources, configPath) {
|
|
|
1237
1237
|
`Configuration file ${configPath}: custom tool '${tool.name}' must have 'description' and 'statement' fields`
|
|
1238
1238
|
);
|
|
1239
1239
|
}
|
|
1240
|
-
if (tool.readonly !== void 0 || tool.max_rows !== void 0) {
|
|
1241
|
-
throw new Error(
|
|
1242
|
-
`Configuration file ${configPath}: custom tool '${tool.name}' cannot have readonly or max_rows fields (these are only valid for ${BUILTIN_TOOL_EXECUTE_SQL} tool)`
|
|
1243
|
-
);
|
|
1244
|
-
}
|
|
1245
1240
|
}
|
|
1246
1241
|
if (tool.max_rows !== void 0) {
|
|
1247
1242
|
if (typeof tool.max_rows !== "number" || tool.max_rows <= 0) {
|
|
@@ -1250,6 +1245,11 @@ function validateToolsConfig(tools, sources, configPath) {
|
|
|
1250
1245
|
);
|
|
1251
1246
|
}
|
|
1252
1247
|
}
|
|
1248
|
+
if (tool.readonly !== void 0 && typeof tool.readonly !== "boolean") {
|
|
1249
|
+
throw new Error(
|
|
1250
|
+
`Configuration file ${configPath}: tool '${tool.name}' has invalid readonly. Must be a boolean (true or false).`
|
|
1251
|
+
);
|
|
1252
|
+
}
|
|
1253
1253
|
}
|
|
1254
1254
|
}
|
|
1255
1255
|
function validateSourceConfig(source, configPath) {
|
|
@@ -1339,6 +1339,16 @@ function validateSourceConfig(source, configPath) {
|
|
|
1339
1339
|
);
|
|
1340
1340
|
}
|
|
1341
1341
|
}
|
|
1342
|
+
if (source.readonly !== void 0) {
|
|
1343
|
+
throw new Error(
|
|
1344
|
+
`Configuration file ${configPath}: source '${source.id}' has 'readonly' field, but readonly must be configured per-tool, not per-source. Move 'readonly' to [[tools]] configuration instead.`
|
|
1345
|
+
);
|
|
1346
|
+
}
|
|
1347
|
+
if (source.max_rows !== void 0) {
|
|
1348
|
+
throw new Error(
|
|
1349
|
+
`Configuration file ${configPath}: source '${source.id}' has 'max_rows' field, but max_rows must be configured per-tool, not per-source. Move 'max_rows' to [[tools]] configuration instead.`
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1342
1352
|
}
|
|
1343
1353
|
function processSourceConfigs(sources, configPath) {
|
|
1344
1354
|
return sources.map((source) => {
|
|
@@ -1441,15 +1451,19 @@ function buildDSNFromSource(source) {
|
|
|
1441
1451
|
// src/connectors/manager.ts
|
|
1442
1452
|
var managerInstance = null;
|
|
1443
1453
|
var ConnectorManager = class {
|
|
1444
|
-
//
|
|
1454
|
+
// Prevent race conditions
|
|
1445
1455
|
constructor() {
|
|
1446
1456
|
// Maps for multi-source support
|
|
1447
1457
|
this.connectors = /* @__PURE__ */ new Map();
|
|
1448
1458
|
this.sshTunnels = /* @__PURE__ */ new Map();
|
|
1449
|
-
this.executeOptions = /* @__PURE__ */ new Map();
|
|
1450
1459
|
this.sourceConfigs = /* @__PURE__ */ new Map();
|
|
1451
1460
|
// Store original source configs
|
|
1452
1461
|
this.sourceIds = [];
|
|
1462
|
+
// Ordered list of source IDs (first is default)
|
|
1463
|
+
// Lazy connection support
|
|
1464
|
+
this.lazySources = /* @__PURE__ */ new Map();
|
|
1465
|
+
// Sources pending lazy connection
|
|
1466
|
+
this.pendingConnections = /* @__PURE__ */ new Map();
|
|
1453
1467
|
if (!managerInstance) {
|
|
1454
1468
|
managerInstance = this;
|
|
1455
1469
|
}
|
|
@@ -1462,10 +1476,73 @@ var ConnectorManager = class {
|
|
|
1462
1476
|
if (sources.length === 0) {
|
|
1463
1477
|
throw new Error("No sources provided");
|
|
1464
1478
|
}
|
|
1465
|
-
|
|
1466
|
-
|
|
1479
|
+
const eagerSources = sources.filter((s) => !s.lazy);
|
|
1480
|
+
const lazySources = sources.filter((s) => s.lazy);
|
|
1481
|
+
if (eagerSources.length > 0) {
|
|
1482
|
+
console.error(`Connecting to ${eagerSources.length} database source(s)...`);
|
|
1483
|
+
}
|
|
1484
|
+
for (const source of eagerSources) {
|
|
1467
1485
|
await this.connectSource(source);
|
|
1468
1486
|
}
|
|
1487
|
+
for (const source of lazySources) {
|
|
1488
|
+
this.registerLazySource(source);
|
|
1489
|
+
}
|
|
1490
|
+
}
|
|
1491
|
+
/**
|
|
1492
|
+
* Register a lazy source without establishing connection
|
|
1493
|
+
* Connection will be established on first use via ensureConnected()
|
|
1494
|
+
*/
|
|
1495
|
+
registerLazySource(source) {
|
|
1496
|
+
const sourceId = source.id;
|
|
1497
|
+
const dsn = buildDSNFromSource(source);
|
|
1498
|
+
console.error(` - ${sourceId}: ${redactDSN(dsn)} (lazy, will connect on first use)`);
|
|
1499
|
+
this.lazySources.set(sourceId, source);
|
|
1500
|
+
this.sourceConfigs.set(sourceId, source);
|
|
1501
|
+
this.sourceIds.push(sourceId);
|
|
1502
|
+
}
|
|
1503
|
+
/**
|
|
1504
|
+
* Ensure a source is connected (handles lazy connection on demand)
|
|
1505
|
+
* Safe to call multiple times - uses promise-based deduplication so concurrent calls share the same connection attempt
|
|
1506
|
+
*/
|
|
1507
|
+
async ensureConnected(sourceId) {
|
|
1508
|
+
const id = sourceId || this.sourceIds[0];
|
|
1509
|
+
if (this.connectors.has(id)) {
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
const lazySource = this.lazySources.get(id);
|
|
1513
|
+
if (!lazySource) {
|
|
1514
|
+
if (sourceId) {
|
|
1515
|
+
throw new Error(
|
|
1516
|
+
`Source '${sourceId}' not found. Available sources: ${this.sourceIds.join(", ")}`
|
|
1517
|
+
);
|
|
1518
|
+
} else {
|
|
1519
|
+
throw new Error("No sources configured. Call connectWithSources() first.");
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
const pending = this.pendingConnections.get(id);
|
|
1523
|
+
if (pending) {
|
|
1524
|
+
return pending;
|
|
1525
|
+
}
|
|
1526
|
+
const connectionPromise = (async () => {
|
|
1527
|
+
try {
|
|
1528
|
+
console.error(`Lazy connecting to source '${id}'...`);
|
|
1529
|
+
await this.connectSource(lazySource);
|
|
1530
|
+
this.lazySources.delete(id);
|
|
1531
|
+
} finally {
|
|
1532
|
+
this.pendingConnections.delete(id);
|
|
1533
|
+
}
|
|
1534
|
+
})();
|
|
1535
|
+
this.pendingConnections.set(id, connectionPromise);
|
|
1536
|
+
return connectionPromise;
|
|
1537
|
+
}
|
|
1538
|
+
/**
|
|
1539
|
+
* Static method to ensure a source is connected (for tool handlers)
|
|
1540
|
+
*/
|
|
1541
|
+
static async ensureConnected(sourceId) {
|
|
1542
|
+
if (!managerInstance) {
|
|
1543
|
+
throw new Error("ConnectorManager not initialized");
|
|
1544
|
+
}
|
|
1545
|
+
return managerInstance.ensureConnected(sourceId);
|
|
1469
1546
|
}
|
|
1470
1547
|
/**
|
|
1471
1548
|
* Connect to a single source (helper for connectWithSources)
|
|
@@ -1531,16 +1608,10 @@ var ConnectorManager = class {
|
|
|
1531
1608
|
}
|
|
1532
1609
|
await connector.connect(actualDSN, source.init_script, config);
|
|
1533
1610
|
this.connectors.set(sourceId, connector);
|
|
1534
|
-
this.sourceIds.
|
|
1535
|
-
|
|
1536
|
-
const options = {};
|
|
1537
|
-
if (source.max_rows !== void 0) {
|
|
1538
|
-
options.maxRows = source.max_rows;
|
|
1539
|
-
}
|
|
1540
|
-
if (source.readonly !== void 0) {
|
|
1541
|
-
options.readonly = source.readonly;
|
|
1611
|
+
if (!this.sourceIds.includes(sourceId)) {
|
|
1612
|
+
this.sourceIds.push(sourceId);
|
|
1542
1613
|
}
|
|
1543
|
-
this.
|
|
1614
|
+
this.sourceConfigs.set(sourceId, source);
|
|
1544
1615
|
}
|
|
1545
1616
|
/**
|
|
1546
1617
|
* Close all database connections
|
|
@@ -1563,8 +1634,9 @@ var ConnectorManager = class {
|
|
|
1563
1634
|
}
|
|
1564
1635
|
this.connectors.clear();
|
|
1565
1636
|
this.sshTunnels.clear();
|
|
1566
|
-
this.executeOptions.clear();
|
|
1567
1637
|
this.sourceConfigs.clear();
|
|
1638
|
+
this.lazySources.clear();
|
|
1639
|
+
this.pendingConnections.clear();
|
|
1568
1640
|
this.sourceIds = [];
|
|
1569
1641
|
}
|
|
1570
1642
|
/**
|
|
@@ -1608,25 +1680,6 @@ var ConnectorManager = class {
|
|
|
1608
1680
|
}
|
|
1609
1681
|
return managerInstance.getConnector(sourceId);
|
|
1610
1682
|
}
|
|
1611
|
-
/**
|
|
1612
|
-
* Get execute options for SQL execution
|
|
1613
|
-
* @param sourceId - Optional source ID. If not provided, returns default options
|
|
1614
|
-
*/
|
|
1615
|
-
getExecuteOptions(sourceId) {
|
|
1616
|
-
const id = sourceId || this.sourceIds[0];
|
|
1617
|
-
return this.executeOptions.get(id) || {};
|
|
1618
|
-
}
|
|
1619
|
-
/**
|
|
1620
|
-
* Get the current execute options
|
|
1621
|
-
* This is used by tool handlers
|
|
1622
|
-
* @param sourceId - Optional source ID. If not provided, returns default options
|
|
1623
|
-
*/
|
|
1624
|
-
static getCurrentExecuteOptions(sourceId) {
|
|
1625
|
-
if (!managerInstance) {
|
|
1626
|
-
throw new Error("ConnectorManager not initialized");
|
|
1627
|
-
}
|
|
1628
|
-
return managerInstance.getExecuteOptions(sourceId);
|
|
1629
|
-
}
|
|
1630
1683
|
/**
|
|
1631
1684
|
* Get all available source IDs
|
|
1632
1685
|
*/
|
|
@@ -1645,7 +1698,7 @@ var ConnectorManager = class {
|
|
|
1645
1698
|
* @param sourceId - Source ID. If not provided, returns default (first) source config
|
|
1646
1699
|
*/
|
|
1647
1700
|
getSourceConfig(sourceId) {
|
|
1648
|
-
if (this.
|
|
1701
|
+
if (this.sourceIds.length === 0) {
|
|
1649
1702
|
return null;
|
|
1650
1703
|
}
|
|
1651
1704
|
const id = sourceId || this.sourceIds[0];
|