@bytebase/dbhub 0.15.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
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 zero-dependency,
|
|
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
|
|
|
@@ -1451,7 +1451,7 @@ function buildDSNFromSource(source) {
|
|
|
1451
1451
|
// src/connectors/manager.ts
|
|
1452
1452
|
var managerInstance = null;
|
|
1453
1453
|
var ConnectorManager = class {
|
|
1454
|
-
//
|
|
1454
|
+
// Prevent race conditions
|
|
1455
1455
|
constructor() {
|
|
1456
1456
|
// Maps for multi-source support
|
|
1457
1457
|
this.connectors = /* @__PURE__ */ new Map();
|
|
@@ -1459,6 +1459,11 @@ var ConnectorManager = class {
|
|
|
1459
1459
|
this.sourceConfigs = /* @__PURE__ */ new Map();
|
|
1460
1460
|
// Store original source configs
|
|
1461
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();
|
|
1462
1467
|
if (!managerInstance) {
|
|
1463
1468
|
managerInstance = this;
|
|
1464
1469
|
}
|
|
@@ -1471,10 +1476,73 @@ var ConnectorManager = class {
|
|
|
1471
1476
|
if (sources.length === 0) {
|
|
1472
1477
|
throw new Error("No sources provided");
|
|
1473
1478
|
}
|
|
1474
|
-
|
|
1475
|
-
|
|
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) {
|
|
1476
1485
|
await this.connectSource(source);
|
|
1477
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);
|
|
1478
1546
|
}
|
|
1479
1547
|
/**
|
|
1480
1548
|
* Connect to a single source (helper for connectWithSources)
|
|
@@ -1540,7 +1608,9 @@ var ConnectorManager = class {
|
|
|
1540
1608
|
}
|
|
1541
1609
|
await connector.connect(actualDSN, source.init_script, config);
|
|
1542
1610
|
this.connectors.set(sourceId, connector);
|
|
1543
|
-
this.sourceIds.
|
|
1611
|
+
if (!this.sourceIds.includes(sourceId)) {
|
|
1612
|
+
this.sourceIds.push(sourceId);
|
|
1613
|
+
}
|
|
1544
1614
|
this.sourceConfigs.set(sourceId, source);
|
|
1545
1615
|
}
|
|
1546
1616
|
/**
|
|
@@ -1565,6 +1635,8 @@ var ConnectorManager = class {
|
|
|
1565
1635
|
this.connectors.clear();
|
|
1566
1636
|
this.sshTunnels.clear();
|
|
1567
1637
|
this.sourceConfigs.clear();
|
|
1638
|
+
this.lazySources.clear();
|
|
1639
|
+
this.pendingConnections.clear();
|
|
1568
1640
|
this.sourceIds = [];
|
|
1569
1641
|
}
|
|
1570
1642
|
/**
|
|
@@ -1626,7 +1698,7 @@ var ConnectorManager = class {
|
|
|
1626
1698
|
* @param sourceId - Source ID. If not provided, returns default (first) source config
|
|
1627
1699
|
*/
|
|
1628
1700
|
getSourceConfig(sourceId) {
|
|
1629
|
-
if (this.
|
|
1701
|
+
if (this.sourceIds.length === 0) {
|
|
1630
1702
|
return null;
|
|
1631
1703
|
}
|
|
1632
1704
|
const id = sourceId || this.sourceIds[0];
|
package/dist/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
resolveSourceConfigs,
|
|
17
17
|
resolveTransport,
|
|
18
18
|
stripCommentsAndStrings
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-IBBG4PSO.js";
|
|
20
20
|
|
|
21
21
|
// src/connectors/postgres/index.ts
|
|
22
22
|
import pg from "pg";
|
|
@@ -2344,6 +2344,7 @@ function createExecuteSqlToolHandler(sourceId) {
|
|
|
2344
2344
|
let errorMessage;
|
|
2345
2345
|
let result;
|
|
2346
2346
|
try {
|
|
2347
|
+
await ConnectorManager.ensureConnected(sourceId);
|
|
2347
2348
|
const connector = ConnectorManager.getCurrentConnector(sourceId);
|
|
2348
2349
|
const actualSourceId = connector.getId();
|
|
2349
2350
|
const registry = getToolRegistry();
|
|
@@ -2681,6 +2682,7 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
|
|
|
2681
2682
|
let success = true;
|
|
2682
2683
|
let errorMessage;
|
|
2683
2684
|
try {
|
|
2685
|
+
await ConnectorManager.ensureConnected(sourceId);
|
|
2684
2686
|
const connector = ConnectorManager.getCurrentConnector(sourceId);
|
|
2685
2687
|
if (table) {
|
|
2686
2688
|
if (!schema) {
|
|
@@ -2993,6 +2995,7 @@ function createCustomToolHandler(toolConfig) {
|
|
|
2993
2995
|
let paramValues = [];
|
|
2994
2996
|
try {
|
|
2995
2997
|
const validatedArgs = zodSchema.parse(args);
|
|
2998
|
+
await ConnectorManager.ensureConnected(toolConfig.source);
|
|
2996
2999
|
const connector = ConnectorManager.getCurrentConnector(toolConfig.source);
|
|
2997
3000
|
const executeOptions = {
|
|
2998
3001
|
readonly: toolConfig.readonly,
|
|
@@ -3135,6 +3138,9 @@ function transformSourceConfig(source) {
|
|
|
3135
3138
|
id: source.id,
|
|
3136
3139
|
type: source.type
|
|
3137
3140
|
};
|
|
3141
|
+
if (source.description) {
|
|
3142
|
+
dataSource.description = source.description;
|
|
3143
|
+
}
|
|
3138
3144
|
if (source.host) {
|
|
3139
3145
|
dataSource.host = source.host;
|
|
3140
3146
|
}
|
|
@@ -3386,7 +3392,7 @@ See documentation for more details on configuring database connections.
|
|
|
3386
3392
|
const sources = sourceConfigsData.sources;
|
|
3387
3393
|
console.error(`Configuration source: ${sourceConfigsData.source}`);
|
|
3388
3394
|
await connectorManager.connectWithSources(sources);
|
|
3389
|
-
const { initializeToolRegistry } = await import("./registry-
|
|
3395
|
+
const { initializeToolRegistry } = await import("./registry-D77Y4CIA.js");
|
|
3390
3396
|
initializeToolRegistry({
|
|
3391
3397
|
sources: sourceConfigsData.sources,
|
|
3392
3398
|
tools: sourceConfigsData.tools
|
package/package.json
CHANGED