@bytebase/dbhub 0.13.0 → 0.13.2

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
@@ -30,11 +30,12 @@
30
30
  MCP Clients MCP Server Databases
31
31
  ```
32
32
 
33
- DBHub is a Universal Database MCP Server implementing the Model Context Protocol (MCP) server interface. This gateway allows MCP-compatible clients to connect to and explore different databases:
33
+ DBHub is a Minimal Database 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
- - **Universal Gateway**: Single interface for PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite
35
+ - **Minimal Design**: Just two general MCP tools (execute_sql, search_objects) for token-efficient operations, plus support for custom tools
36
+ - **Multi-Database Support**: Single interface for PostgreSQL, MySQL, MariaDB, SQL Server, and SQLite
36
37
  - **Secure Access**: Read-only mode, SSH tunneling, and SSL/TLS encryption support
37
- - **Multi-Database**: Connect to multiple databases simultaneously with TOML configuration
38
+ - **Multiple Connections**: Connect to multiple databases simultaneously with TOML configuration
38
39
  - **Production-Ready**: Row limiting, lock timeout control, and connection pooling
39
40
  - **MCP Native**: Full implementation of Model Context Protocol with comprehensive tools
40
41
 
@@ -1,7 +1,10 @@
1
- import {
2
- BUILTIN_TOOLS,
3
- BUILTIN_TOOL_EXECUTE_SQL
4
- } from "./chunk-D23WQQY7.js";
1
+ // src/tools/builtin-tools.ts
2
+ var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
3
+ var BUILTIN_TOOL_SEARCH_OBJECTS = "search_objects";
4
+ var BUILTIN_TOOLS = [
5
+ BUILTIN_TOOL_EXECUTE_SQL,
6
+ BUILTIN_TOOL_SEARCH_OBJECTS
7
+ ];
5
8
 
6
9
  // src/connectors/interface.ts
7
10
  var _ConnectorRegistry = class _ConnectorRegistry {
@@ -1588,37 +1591,64 @@ function mapArgumentsToArray(parameters, args) {
1588
1591
  });
1589
1592
  }
1590
1593
 
1591
- // src/tools/custom-tool-registry.ts
1592
- var CustomToolRegistry = class {
1593
- constructor() {
1594
- this.tools = [];
1595
- this.initialized = false;
1594
+ // src/tools/registry.ts
1595
+ var ToolRegistry = class {
1596
+ constructor(config) {
1597
+ this.toolsBySource = this.buildRegistry(config);
1596
1598
  }
1597
1599
  /**
1598
- * Initialize the registry with tool definitions from TOML
1599
- * @param toolConfigs Tool definitions from TOML config
1600
- * @throws Error if validation fails
1600
+ * Check if a tool name is a built-in tool
1601
1601
  */
1602
- initialize(toolConfigs) {
1603
- if (this.initialized) {
1604
- throw new Error("CustomToolRegistry already initialized");
1602
+ isBuiltinTool(toolName) {
1603
+ return BUILTIN_TOOLS.includes(toolName);
1604
+ }
1605
+ /**
1606
+ * Validate a custom tool parameter definition
1607
+ */
1608
+ validateParameter(toolName, param) {
1609
+ if (!param.name || param.name.trim() === "") {
1610
+ throw new Error(`Tool '${toolName}' has parameter missing 'name' field`);
1605
1611
  }
1606
- this.tools = [];
1607
- if (!toolConfigs || toolConfigs.length === 0) {
1608
- this.initialized = true;
1609
- return;
1612
+ if (!param.type) {
1613
+ throw new Error(
1614
+ `Tool '${toolName}', parameter '${param.name}' missing 'type' field`
1615
+ );
1610
1616
  }
1611
- for (const toolConfig of toolConfigs) {
1612
- this.validateAndRegister(toolConfig);
1617
+ const validTypes = ["string", "integer", "float", "boolean", "array"];
1618
+ if (!validTypes.includes(param.type)) {
1619
+ throw new Error(
1620
+ `Tool '${toolName}', parameter '${param.name}' has invalid type '${param.type}'. Valid types: ${validTypes.join(", ")}`
1621
+ );
1622
+ }
1623
+ if (!param.description || param.description.trim() === "") {
1624
+ throw new Error(
1625
+ `Tool '${toolName}', parameter '${param.name}' missing 'description' field`
1626
+ );
1627
+ }
1628
+ if (param.allowed_values) {
1629
+ if (!Array.isArray(param.allowed_values)) {
1630
+ throw new Error(
1631
+ `Tool '${toolName}', parameter '${param.name}': allowed_values must be an array`
1632
+ );
1633
+ }
1634
+ if (param.allowed_values.length === 0) {
1635
+ throw new Error(
1636
+ `Tool '${toolName}', parameter '${param.name}': allowed_values cannot be empty`
1637
+ );
1638
+ }
1639
+ }
1640
+ if (param.default !== void 0 && param.allowed_values) {
1641
+ if (!param.allowed_values.includes(param.default)) {
1642
+ throw new Error(
1643
+ `Tool '${toolName}', parameter '${param.name}': default value '${param.default}' is not in allowed_values: ${param.allowed_values.join(", ")}`
1644
+ );
1645
+ }
1613
1646
  }
1614
- this.initialized = true;
1615
1647
  }
1616
1648
  /**
1617
- * Validate a tool configuration and add it to the registry
1618
- * @param toolConfig Tool configuration to validate
1619
- * @throws Error if validation fails
1649
+ * Validate a custom tool configuration
1620
1650
  */
1621
- validateAndRegister(toolConfig) {
1651
+ validateCustomTool(toolConfig, availableSources) {
1622
1652
  if (!toolConfig.name || toolConfig.name.trim() === "") {
1623
1653
  throw new Error("Tool definition missing required field: name");
1624
1654
  }
@@ -1637,7 +1667,6 @@ var CustomToolRegistry = class {
1637
1667
  `Tool '${toolConfig.name}' missing required field: statement`
1638
1668
  );
1639
1669
  }
1640
- const availableSources = ConnectorManager.getAvailableSourceIds();
1641
1670
  if (!availableSources.includes(toolConfig.source)) {
1642
1671
  throw new Error(
1643
1672
  `Tool '${toolConfig.name}' references unknown source '${toolConfig.source}'. Available sources: ${availableSources.join(", ")}`
@@ -1650,11 +1679,6 @@ var CustomToolRegistry = class {
1650
1679
  );
1651
1680
  }
1652
1681
  }
1653
- if (this.tools.some((t) => t.name === toolConfig.name)) {
1654
- throw new Error(
1655
- `Duplicate tool name '${toolConfig.name}'. Tool names must be unique.`
1656
- );
1657
- }
1658
1682
  const sourceConfig = ConnectorManager.getSourceConfig(toolConfig.source);
1659
1683
  const connectorType = sourceConfig.type;
1660
1684
  try {
@@ -1673,85 +1697,109 @@ var CustomToolRegistry = class {
1673
1697
  this.validateParameter(toolConfig.name, param);
1674
1698
  }
1675
1699
  }
1676
- this.tools.push(toolConfig);
1677
1700
  }
1678
1701
  /**
1679
- * Validate a parameter definition
1680
- * @param toolName Name of the tool (for error messages)
1681
- * @param param Parameter configuration to validate
1682
- * @throws Error if validation fails
1702
+ * Build the internal registry mapping sources to their enabled tools
1683
1703
  */
1684
- validateParameter(toolName, param) {
1685
- if (!param.name || param.name.trim() === "") {
1686
- throw new Error(`Tool '${toolName}' has parameter missing 'name' field`);
1687
- }
1688
- if (!param.type) {
1689
- throw new Error(
1690
- `Tool '${toolName}', parameter '${param.name}' missing 'type' field`
1691
- );
1692
- }
1693
- const validTypes = ["string", "integer", "float", "boolean", "array"];
1694
- if (!validTypes.includes(param.type)) {
1695
- throw new Error(
1696
- `Tool '${toolName}', parameter '${param.name}' has invalid type '${param.type}'. Valid types: ${validTypes.join(", ")}`
1697
- );
1698
- }
1699
- if (!param.description || param.description.trim() === "") {
1700
- throw new Error(
1701
- `Tool '${toolName}', parameter '${param.name}' missing 'description' field`
1702
- );
1703
- }
1704
- if (param.allowed_values) {
1705
- if (!Array.isArray(param.allowed_values)) {
1706
- throw new Error(
1707
- `Tool '${toolName}', parameter '${param.name}': allowed_values must be an array`
1708
- );
1709
- }
1710
- if (param.allowed_values.length === 0) {
1711
- throw new Error(
1712
- `Tool '${toolName}', parameter '${param.name}': allowed_values cannot be empty`
1713
- );
1704
+ buildRegistry(config) {
1705
+ const registry = /* @__PURE__ */ new Map();
1706
+ const availableSources = config.sources.map((s) => s.id);
1707
+ const customToolNames = /* @__PURE__ */ new Set();
1708
+ for (const tool of config.tools || []) {
1709
+ if (!this.isBuiltinTool(tool.name)) {
1710
+ this.validateCustomTool(tool, availableSources);
1711
+ if (customToolNames.has(tool.name)) {
1712
+ throw new Error(
1713
+ `Duplicate tool name '${tool.name}'. Tool names must be unique.`
1714
+ );
1715
+ }
1716
+ customToolNames.add(tool.name);
1714
1717
  }
1715
- }
1716
- if (param.default !== void 0 && param.allowed_values) {
1717
- if (!param.allowed_values.includes(param.default)) {
1718
- throw new Error(
1719
- `Tool '${toolName}', parameter '${param.name}': default value '${param.default}' is not in allowed_values: ${param.allowed_values.join(", ")}`
1720
- );
1718
+ const existing = registry.get(tool.source) || [];
1719
+ existing.push(tool);
1720
+ registry.set(tool.source, existing);
1721
+ }
1722
+ for (const source of config.sources) {
1723
+ if (!registry.has(source.id)) {
1724
+ const defaultTools = BUILTIN_TOOLS.map((name) => {
1725
+ if (name === "execute_sql") {
1726
+ return { name: "execute_sql", source: source.id };
1727
+ } else {
1728
+ return { name: "search_objects", source: source.id };
1729
+ }
1730
+ });
1731
+ registry.set(source.id, defaultTools);
1721
1732
  }
1722
1733
  }
1734
+ return registry;
1723
1735
  }
1724
1736
  /**
1725
- * Get all registered custom tools
1726
- * @returns Array of tool configurations
1737
+ * Get all enabled tool configs for a specific source
1727
1738
  */
1728
- getTools() {
1729
- return [...this.tools];
1739
+ getEnabledToolConfigs(sourceId) {
1740
+ return this.toolsBySource.get(sourceId) || [];
1730
1741
  }
1731
1742
  /**
1732
- * Get a specific tool by name
1733
- * @param name Tool name
1734
- * @returns Tool configuration or undefined if not found
1743
+ * Get built-in tool configuration for a specific source
1744
+ * Returns undefined if tool is not enabled or not a built-in
1735
1745
  */
1736
- getTool(name) {
1737
- return this.tools.find((t) => t.name === name);
1746
+ getBuiltinToolConfig(toolName, sourceId) {
1747
+ if (!this.isBuiltinTool(toolName)) {
1748
+ return void 0;
1749
+ }
1750
+ const tools = this.getEnabledToolConfigs(sourceId);
1751
+ return tools.find((t) => t.name === toolName);
1738
1752
  }
1739
1753
  /**
1740
- * Check if the registry has been initialized
1741
- * @returns True if initialized
1754
+ * Get all unique tools across all sources (for tools/list response)
1755
+ * Returns the union of all enabled tools
1742
1756
  */
1743
- isInitialized() {
1744
- return this.initialized;
1757
+ getAllTools() {
1758
+ const seen = /* @__PURE__ */ new Set();
1759
+ const result = [];
1760
+ for (const tools of this.toolsBySource.values()) {
1761
+ for (const tool of tools) {
1762
+ if (!seen.has(tool.name)) {
1763
+ seen.add(tool.name);
1764
+ result.push(tool);
1765
+ }
1766
+ }
1767
+ }
1768
+ return result;
1769
+ }
1770
+ /**
1771
+ * Get all custom tools (non-builtin) across all sources
1772
+ */
1773
+ getCustomTools() {
1774
+ return this.getAllTools().filter((tool) => !this.isBuiltinTool(tool.name));
1745
1775
  }
1746
1776
  /**
1747
- * Reset the registry (primarily for testing)
1777
+ * Get all built-in tool names that are enabled across any source
1748
1778
  */
1749
- reset() {
1750
- this.tools = [];
1751
- this.initialized = false;
1779
+ getEnabledBuiltinToolNames() {
1780
+ const enabledBuiltins = /* @__PURE__ */ new Set();
1781
+ for (const tools of this.toolsBySource.values()) {
1782
+ for (const tool of tools) {
1783
+ if (this.isBuiltinTool(tool.name)) {
1784
+ enabledBuiltins.add(tool.name);
1785
+ }
1786
+ }
1787
+ }
1788
+ return Array.from(enabledBuiltins);
1752
1789
  }
1753
1790
  };
1754
- var customToolRegistry = new CustomToolRegistry();
1791
+ var globalRegistry = null;
1792
+ function initializeToolRegistry(config) {
1793
+ globalRegistry = new ToolRegistry(config);
1794
+ }
1795
+ function getToolRegistry() {
1796
+ if (!globalRegistry) {
1797
+ throw new Error(
1798
+ "Tool registry not initialized. Call initializeToolRegistry first."
1799
+ );
1800
+ }
1801
+ return globalRegistry;
1802
+ }
1755
1803
 
1756
1804
  export {
1757
1805
  ConnectorRegistry,
@@ -1766,8 +1814,12 @@ export {
1766
1814
  resolvePort,
1767
1815
  redactDSN,
1768
1816
  resolveSourceConfigs,
1817
+ BUILTIN_TOOL_EXECUTE_SQL,
1818
+ BUILTIN_TOOL_SEARCH_OBJECTS,
1769
1819
  buildDSNFromSource,
1770
1820
  ConnectorManager,
1771
1821
  mapArgumentsToArray,
1772
- customToolRegistry
1822
+ ToolRegistry,
1823
+ initializeToolRegistry,
1824
+ getToolRegistry
1773
1825
  };
package/dist/index.js CHANGED
@@ -1,15 +1,14 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- getToolRegistry
4
- } from "./chunk-VWZF5OAJ.js";
5
- import {
3
+ BUILTIN_TOOL_EXECUTE_SQL,
4
+ BUILTIN_TOOL_SEARCH_OBJECTS,
6
5
  ConnectorManager,
7
6
  ConnectorRegistry,
8
7
  SafeURL,
9
8
  buildDSNFromSource,
10
- customToolRegistry,
11
9
  getDatabaseTypeFromDSN,
12
10
  getDefaultPortForType,
11
+ getToolRegistry,
13
12
  isDemoMode,
14
13
  mapArgumentsToArray,
15
14
  obfuscateDSNPassword,
@@ -19,11 +18,7 @@ import {
19
18
  resolveSourceConfigs,
20
19
  resolveTransport,
21
20
  stripCommentsAndStrings
22
- } from "./chunk-WSLDVMBA.js";
23
- import {
24
- BUILTIN_TOOL_EXECUTE_SQL,
25
- BUILTIN_TOOL_SEARCH_OBJECTS
26
- } from "./chunk-D23WQQY7.js";
21
+ } from "./chunk-KBVJEDZF.js";
27
22
 
28
23
  // src/connectors/postgres/index.ts
29
24
  import pg from "pg";
@@ -1332,6 +1327,16 @@ Expected: ${expectedFormat}`
1332
1327
  if (connectionTimeoutSeconds !== void 0) {
1333
1328
  config2.connectTimeout = connectionTimeoutSeconds * 1e3;
1334
1329
  }
1330
+ if (url.password && url.password.includes("X-Amz-Credential")) {
1331
+ config2.authPlugins = {
1332
+ mysql_clear_password: () => () => {
1333
+ return Buffer.from(url.password + "\0");
1334
+ }
1335
+ };
1336
+ if (config2.ssl === void 0) {
1337
+ config2.ssl = { rejectUnauthorized: false };
1338
+ }
1339
+ }
1335
1340
  return config2;
1336
1341
  } catch (error) {
1337
1342
  throw new Error(
@@ -1735,6 +1740,11 @@ Expected: ${expectedFormat}`
1735
1740
  }
1736
1741
  }
1737
1742
  });
1743
+ if (url.password && url.password.includes("X-Amz-Credential")) {
1744
+ if (config2.ssl === void 0) {
1745
+ config2.ssl = { rejectUnauthorized: false };
1746
+ }
1747
+ }
1738
1748
  return config2;
1739
1749
  } catch (error) {
1740
1750
  throw new Error(
@@ -2298,7 +2308,8 @@ import { z as z2 } from "zod";
2298
2308
  var searchDatabaseObjectsSchema = {
2299
2309
  object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Type of database object to search for"),
2300
2310
  pattern: z2.string().optional().default("%").describe("Search pattern (SQL LIKE syntax: % for wildcard, _ for single char). Case-insensitive. Defaults to '%' (match all)."),
2301
- schema: z2.string().optional().describe("Filter results to a specific schema/database"),
2311
+ schema: z2.string().optional().describe("Filter results to a specific schema/database (exact match)"),
2312
+ table: z2.string().optional().describe("Filter to a specific table (exact match). Requires schema parameter. Only applies to columns and indexes."),
2302
2313
  detail_level: z2.enum(["names", "summary", "full"]).default("names").describe("Level of detail to return: names (minimal), summary (with metadata), full (complete structure)"),
2303
2314
  limit: z2.number().int().positive().max(1e3).default(100).describe("Maximum number of results to return (default: 100, max: 1000)")
2304
2315
  };
@@ -2421,7 +2432,7 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
2421
2432
  }
2422
2433
  return results;
2423
2434
  }
2424
- async function searchColumns(connector, pattern, schemaFilter, detailLevel, limit) {
2435
+ async function searchColumns(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
2425
2436
  const regex = likePatternToRegex(pattern);
2426
2437
  const results = [];
2427
2438
  let schemasToSearch;
@@ -2433,8 +2444,13 @@ async function searchColumns(connector, pattern, schemaFilter, detailLevel, limi
2433
2444
  for (const schemaName of schemasToSearch) {
2434
2445
  if (results.length >= limit) break;
2435
2446
  try {
2436
- const tables = await connector.getTables(schemaName);
2437
- for (const tableName of tables) {
2447
+ let tablesToSearch;
2448
+ if (tableFilter) {
2449
+ tablesToSearch = [tableFilter];
2450
+ } else {
2451
+ tablesToSearch = await connector.getTables(schemaName);
2452
+ }
2453
+ for (const tableName of tablesToSearch) {
2438
2454
  if (results.length >= limit) break;
2439
2455
  try {
2440
2456
  const columns = await connector.getTableSchema(tableName, schemaName);
@@ -2516,7 +2532,7 @@ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, l
2516
2532
  }
2517
2533
  return results;
2518
2534
  }
2519
- async function searchIndexes(connector, pattern, schemaFilter, detailLevel, limit) {
2535
+ async function searchIndexes(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
2520
2536
  const regex = likePatternToRegex(pattern);
2521
2537
  const results = [];
2522
2538
  let schemasToSearch;
@@ -2528,8 +2544,13 @@ async function searchIndexes(connector, pattern, schemaFilter, detailLevel, limi
2528
2544
  for (const schemaName of schemasToSearch) {
2529
2545
  if (results.length >= limit) break;
2530
2546
  try {
2531
- const tables = await connector.getTables(schemaName);
2532
- for (const tableName of tables) {
2547
+ let tablesToSearch;
2548
+ if (tableFilter) {
2549
+ tablesToSearch = [tableFilter];
2550
+ } else {
2551
+ tablesToSearch = await connector.getTables(schemaName);
2552
+ }
2553
+ for (const tableName of tablesToSearch) {
2533
2554
  if (results.length >= limit) break;
2534
2555
  try {
2535
2556
  const indexes = await connector.getTableIndexes(tableName, schemaName);
@@ -2569,11 +2590,26 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
2569
2590
  object_type,
2570
2591
  pattern = "%",
2571
2592
  schema,
2593
+ table,
2572
2594
  detail_level = "names",
2573
2595
  limit = 100
2574
2596
  } = args;
2575
2597
  try {
2576
2598
  const connector = ConnectorManager.getCurrentConnector(sourceId);
2599
+ if (table) {
2600
+ if (!schema) {
2601
+ return createToolErrorResponse(
2602
+ "The 'table' parameter requires 'schema' to be specified",
2603
+ "SCHEMA_REQUIRED"
2604
+ );
2605
+ }
2606
+ if (!["column", "index"].includes(object_type)) {
2607
+ return createToolErrorResponse(
2608
+ `The 'table' parameter only applies to object_type 'column' or 'index', not '${object_type}'`,
2609
+ "INVALID_TABLE_FILTER"
2610
+ );
2611
+ }
2612
+ }
2577
2613
  if (schema) {
2578
2614
  const schemas = await connector.getSchemas();
2579
2615
  if (!schemas.includes(schema)) {
@@ -2592,13 +2628,13 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
2592
2628
  results = await searchTables(connector, pattern, schema, detail_level, limit);
2593
2629
  break;
2594
2630
  case "column":
2595
- results = await searchColumns(connector, pattern, schema, detail_level, limit);
2631
+ results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
2596
2632
  break;
2597
2633
  case "procedure":
2598
2634
  results = await searchProcedures(connector, pattern, schema, detail_level, limit);
2599
2635
  break;
2600
2636
  case "index":
2601
- results = await searchIndexes(connector, pattern, schema, detail_level, limit);
2637
+ results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
2602
2638
  break;
2603
2639
  default:
2604
2640
  return createToolErrorResponse(
@@ -2610,6 +2646,7 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
2610
2646
  object_type,
2611
2647
  pattern,
2612
2648
  schema,
2649
+ table,
2613
2650
  detail_level,
2614
2651
  count: results.length,
2615
2652
  results,
@@ -2679,9 +2716,8 @@ function getExecuteSqlMetadata(sourceId) {
2679
2716
  // In readonly mode, queries are more predictable (though still not strictly idempotent due to data changes)
2680
2717
  // In write mode, queries are definitely not idempotent
2681
2718
  idempotentHint: false,
2682
- // In readonly mode, it's safer to operate on arbitrary tables (just reading)
2683
- // In write mode, operating on arbitrary tables is more dangerous
2684
- openWorldHint: isReadonly
2719
+ // Database operations are always against internal/closed systems, not open-world
2720
+ openWorldHint: false
2685
2721
  };
2686
2722
  return {
2687
2723
  name: toolName,
@@ -2711,21 +2747,22 @@ function customParamsToToolParams(params) {
2711
2747
  description: param.description
2712
2748
  }));
2713
2749
  }
2714
- function getToolsForSource(sourceId) {
2715
- const tools = [];
2716
- const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
2717
- const dbType = sourceConfig.type;
2718
- const sourceIds = ConnectorManager.getAvailableSourceIds();
2719
- const isDefault = sourceIds[0] === sourceId;
2750
+ function buildExecuteSqlTool(sourceId) {
2720
2751
  const executeSqlMetadata = getExecuteSqlMetadata(sourceId);
2721
2752
  const executeSqlParameters = zodToParameters(executeSqlMetadata.schema);
2722
- tools.push({
2753
+ return {
2723
2754
  name: executeSqlMetadata.name,
2724
2755
  description: executeSqlMetadata.description,
2725
2756
  parameters: executeSqlParameters
2726
- });
2757
+ };
2758
+ }
2759
+ function buildSearchObjectsTool(sourceId) {
2760
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
2761
+ const dbType = sourceConfig.type;
2762
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
2763
+ const isDefault = sourceIds[0] === sourceId;
2727
2764
  const searchMetadata = getSearchObjectsMetadata(sourceId, dbType, isDefault);
2728
- tools.push({
2765
+ return {
2729
2766
  name: searchMetadata.name,
2730
2767
  description: searchMetadata.description,
2731
2768
  parameters: [
@@ -2760,19 +2797,27 @@ function getToolsForSource(sourceId) {
2760
2797
  description: "Maximum number of results to return (default: 100, max: 1000)"
2761
2798
  }
2762
2799
  ]
2763
- });
2764
- if (customToolRegistry.isInitialized()) {
2765
- const customTools = customToolRegistry.getTools();
2766
- const sourceCustomTools = customTools.filter((tool) => tool.source === sourceId);
2767
- for (const customTool of sourceCustomTools) {
2768
- tools.push({
2769
- name: customTool.name,
2770
- description: customTool.description,
2771
- parameters: customParamsToToolParams(customTool.parameters)
2772
- });
2800
+ };
2801
+ }
2802
+ function buildCustomTool(toolConfig) {
2803
+ return {
2804
+ name: toolConfig.name,
2805
+ description: toolConfig.description,
2806
+ parameters: customParamsToToolParams(toolConfig.parameters)
2807
+ };
2808
+ }
2809
+ function getToolsForSource(sourceId) {
2810
+ const registry = getToolRegistry();
2811
+ const enabledToolConfigs = registry.getEnabledToolConfigs(sourceId);
2812
+ return enabledToolConfigs.map((toolConfig) => {
2813
+ if (toolConfig.name === "execute_sql") {
2814
+ return buildExecuteSqlTool(sourceId);
2815
+ } else if (toolConfig.name === "search_objects") {
2816
+ return buildSearchObjectsTool(sourceId);
2817
+ } else {
2818
+ return buildCustomTool(toolConfig);
2773
2819
  }
2774
- }
2775
- return tools;
2820
+ });
2776
2821
  }
2777
2822
 
2778
2823
  // src/tools/custom-tool-handler.ts
@@ -2894,7 +2939,7 @@ function registerTools(server) {
2894
2939
  }
2895
2940
  const registry = getToolRegistry();
2896
2941
  for (const sourceId of sourceIds) {
2897
- const enabledTools = registry.getToolsForSource(sourceId);
2942
+ const enabledTools = registry.getEnabledToolConfigs(sourceId);
2898
2943
  const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
2899
2944
  const dbType = sourceConfig.type;
2900
2945
  const isDefault = sourceIds[0] === sourceId;
@@ -2933,7 +2978,7 @@ function registerSearchObjectsTool(server, sourceId, dbType, isDefault) {
2933
2978
  readOnlyHint: true,
2934
2979
  destructiveHint: false,
2935
2980
  idempotentHint: true,
2936
- openWorldHint: true
2981
+ openWorldHint: false
2937
2982
  }
2938
2983
  },
2939
2984
  createSearchDatabaseObjectsToolHandler(sourceId)
@@ -3201,7 +3246,7 @@ function generateBanner(version, modes = []) {
3201
3246
  | |__| | |_) | | | | |_| | |_) |
3202
3247
  |_____/|____/|_| |_|\\__,_|_.__/
3203
3248
 
3204
- v${version}${modeText} - Universal Database MCP Server
3249
+ v${version}${modeText} - Minimal Database MCP Server
3205
3250
  `;
3206
3251
  }
3207
3252
  async function main() {
@@ -3241,25 +3286,12 @@ See documentation for more details on configuring database connections.
3241
3286
  console.error(` - ${source.id}: ${redactDSN(dsn)}`);
3242
3287
  }
3243
3288
  await connectorManager.connectWithSources(sources);
3244
- const { initializeToolRegistry } = await import("./registry-GJGPWR3I.js");
3289
+ const { initializeToolRegistry } = await import("./registry-AWAIN6WO.js");
3245
3290
  initializeToolRegistry({
3246
3291
  sources: sourceConfigsData.sources,
3247
3292
  tools: sourceConfigsData.tools
3248
3293
  });
3249
3294
  console.error("Tool registry initialized");
3250
- if (sourceConfigsData.tools && sourceConfigsData.tools.length > 0) {
3251
- const { customToolRegistry: customToolRegistry2 } = await import("./custom-tool-registry-EW3KOBGC.js");
3252
- const { BUILTIN_TOOLS } = await import("./builtin-tools-SVENYBIA.js");
3253
- if (!customToolRegistry2.isInitialized()) {
3254
- const customTools = sourceConfigsData.tools.filter(
3255
- (tool) => !BUILTIN_TOOLS.includes(tool.name)
3256
- );
3257
- if (customTools.length > 0) {
3258
- customToolRegistry2.initialize(customTools);
3259
- console.error(`Custom tool registry initialized with ${customTools.length} tool(s)`);
3260
- }
3261
- }
3262
- }
3263
3295
  const createServer = () => {
3264
3296
  const server = new McpServer({
3265
3297
  name: SERVER_NAME,
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
- <title>DBHub - Universal Database MCP Server</title>
7
+ <title>DBHub - Minimal Database MCP Server</title>
8
8
  <script type="module" crossorigin src="/assets/index-hd88eD9m.js"></script>
9
9
  <link rel="stylesheet" crossorigin href="/assets/index-gVrYRID4.css">
10
10
  </head>
@@ -2,8 +2,7 @@ import {
2
2
  ToolRegistry,
3
3
  getToolRegistry,
4
4
  initializeToolRegistry
5
- } from "./chunk-VWZF5OAJ.js";
6
- import "./chunk-D23WQQY7.js";
5
+ } from "./chunk-KBVJEDZF.js";
7
6
  export {
8
7
  ToolRegistry,
9
8
  getToolRegistry,
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "0.13.0",
4
- "description": "Universal Database MCP Server",
3
+ "version": "0.13.2",
4
+ "mcpName": "io.github.bytebase/dbhub",
5
+ "description": "Minimal Database MCP Server for PostgreSQL, MySQL, SQL Server, SQLite, MariaDB",
5
6
  "repository": {
6
7
  "type": "git",
7
8
  "url": "https://github.com/bytebase/dbhub.git"
@@ -16,6 +17,21 @@
16
17
  "LICENSE",
17
18
  "README.md"
18
19
  ],
20
+ "scripts": {
21
+ "build": "pnpm run generate:api-types && tsup && cd frontend && pnpm run build",
22
+ "build:backend": "pnpm run generate:api-types && tsup",
23
+ "build:frontend": "cd frontend && pnpm run build",
24
+ "start": "node dist/index.js",
25
+ "dev": "concurrently --kill-others \"pnpm run dev:backend\" \"pnpm run dev:frontend\"",
26
+ "dev:backend": "NODE_ENV=development tsx src/index.ts --transport=http",
27
+ "dev:frontend": "cd frontend && pnpm run dev",
28
+ "crossdev": "cross-env NODE_ENV=development tsx src/index.ts",
29
+ "generate:api-types": "openapi-typescript src/api/openapi.yaml -o src/api/openapi.d.ts",
30
+ "test": "vitest run",
31
+ "test:unit": "vitest run --project unit",
32
+ "test:watch": "vitest",
33
+ "test:integration": "vitest run --project integration"
34
+ },
19
35
  "keywords": [],
20
36
  "author": "",
21
37
  "license": "MIT",
@@ -67,20 +83,5 @@
67
83
  },
68
84
  "include": [
69
85
  "src/**/*"
70
- ],
71
- "scripts": {
72
- "build": "pnpm run generate:api-types && tsup && cd frontend && pnpm run build",
73
- "build:backend": "pnpm run generate:api-types && tsup",
74
- "build:frontend": "cd frontend && pnpm run build",
75
- "start": "node dist/index.js",
76
- "dev": "concurrently --kill-others \"pnpm run dev:backend\" \"pnpm run dev:frontend\"",
77
- "dev:backend": "NODE_ENV=development tsx src/index.ts --transport=http",
78
- "dev:frontend": "cd frontend && pnpm run dev",
79
- "crossdev": "cross-env NODE_ENV=development tsx src/index.ts",
80
- "generate:api-types": "openapi-typescript src/api/openapi.yaml -o src/api/openapi.d.ts",
81
- "test": "vitest run",
82
- "test:unit": "vitest run --project unit",
83
- "test:watch": "vitest",
84
- "test:integration": "vitest run --project integration"
85
- }
86
- }
86
+ ]
87
+ }
@@ -1,10 +0,0 @@
1
- import {
2
- BUILTIN_TOOLS,
3
- BUILTIN_TOOL_EXECUTE_SQL,
4
- BUILTIN_TOOL_SEARCH_OBJECTS
5
- } from "./chunk-D23WQQY7.js";
6
- export {
7
- BUILTIN_TOOLS,
8
- BUILTIN_TOOL_EXECUTE_SQL,
9
- BUILTIN_TOOL_SEARCH_OBJECTS
10
- };
@@ -1,13 +0,0 @@
1
- // src/tools/builtin-tools.ts
2
- var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
3
- var BUILTIN_TOOL_SEARCH_OBJECTS = "search_objects";
4
- var BUILTIN_TOOLS = [
5
- BUILTIN_TOOL_EXECUTE_SQL,
6
- BUILTIN_TOOL_SEARCH_OBJECTS
7
- ];
8
-
9
- export {
10
- BUILTIN_TOOL_EXECUTE_SQL,
11
- BUILTIN_TOOL_SEARCH_OBJECTS,
12
- BUILTIN_TOOLS
13
- };
@@ -1,112 +0,0 @@
1
- import {
2
- BUILTIN_TOOLS
3
- } from "./chunk-D23WQQY7.js";
4
-
5
- // src/tools/registry.ts
6
- var ToolRegistry = class {
7
- constructor(config) {
8
- this.toolsBySource = this.buildRegistry(config);
9
- }
10
- /**
11
- * Check if a tool name is a built-in tool
12
- */
13
- isBuiltinTool(toolName) {
14
- return BUILTIN_TOOLS.includes(toolName);
15
- }
16
- /**
17
- * Build the internal registry mapping sources to their enabled tools
18
- */
19
- buildRegistry(config) {
20
- const registry = /* @__PURE__ */ new Map();
21
- for (const tool of config.tools || []) {
22
- const existing = registry.get(tool.source) || [];
23
- existing.push(tool);
24
- registry.set(tool.source, existing);
25
- }
26
- for (const source of config.sources) {
27
- if (!registry.has(source.id)) {
28
- const defaultTools = BUILTIN_TOOLS.map((name) => {
29
- if (name === "execute_sql") {
30
- return { name: "execute_sql", source: source.id };
31
- } else {
32
- return { name: "search_objects", source: source.id };
33
- }
34
- });
35
- registry.set(source.id, defaultTools);
36
- }
37
- }
38
- return registry;
39
- }
40
- /**
41
- * Get all tools enabled for a specific source
42
- */
43
- getToolsForSource(sourceId) {
44
- return this.toolsBySource.get(sourceId) || [];
45
- }
46
- /**
47
- * Get built-in tool configuration for a specific source
48
- * Returns undefined if tool is not enabled or not a built-in
49
- */
50
- getBuiltinToolConfig(toolName, sourceId) {
51
- if (!this.isBuiltinTool(toolName)) {
52
- return void 0;
53
- }
54
- const tools = this.getToolsForSource(sourceId);
55
- return tools.find((t) => t.name === toolName);
56
- }
57
- /**
58
- * Get all unique tools across all sources (for tools/list response)
59
- * Returns the union of all enabled tools
60
- */
61
- getAllTools() {
62
- const seen = /* @__PURE__ */ new Set();
63
- const result = [];
64
- for (const tools of this.toolsBySource.values()) {
65
- for (const tool of tools) {
66
- if (!seen.has(tool.name)) {
67
- seen.add(tool.name);
68
- result.push(tool);
69
- }
70
- }
71
- }
72
- return result;
73
- }
74
- /**
75
- * Get all custom tools (non-builtin) across all sources
76
- */
77
- getCustomTools() {
78
- return this.getAllTools().filter((tool) => !this.isBuiltinTool(tool.name));
79
- }
80
- /**
81
- * Get all built-in tool names that are enabled across any source
82
- */
83
- getEnabledBuiltinToolNames() {
84
- const enabledBuiltins = /* @__PURE__ */ new Set();
85
- for (const tools of this.toolsBySource.values()) {
86
- for (const tool of tools) {
87
- if (this.isBuiltinTool(tool.name)) {
88
- enabledBuiltins.add(tool.name);
89
- }
90
- }
91
- }
92
- return Array.from(enabledBuiltins);
93
- }
94
- };
95
- var globalRegistry = null;
96
- function initializeToolRegistry(config) {
97
- globalRegistry = new ToolRegistry(config);
98
- }
99
- function getToolRegistry() {
100
- if (!globalRegistry) {
101
- throw new Error(
102
- "Tool registry not initialized. Call initializeToolRegistry first."
103
- );
104
- }
105
- return globalRegistry;
106
- }
107
-
108
- export {
109
- ToolRegistry,
110
- initializeToolRegistry,
111
- getToolRegistry
112
- };
@@ -1,7 +0,0 @@
1
- import {
2
- customToolRegistry
3
- } from "./chunk-WSLDVMBA.js";
4
- import "./chunk-D23WQQY7.js";
5
- export {
6
- customToolRegistry
7
- };