@bytebase/dbhub 0.13.0 → 0.13.1

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";
@@ -2298,7 +2293,8 @@ import { z as z2 } from "zod";
2298
2293
  var searchDatabaseObjectsSchema = {
2299
2294
  object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Type of database object to search for"),
2300
2295
  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"),
2296
+ schema: z2.string().optional().describe("Filter results to a specific schema/database (exact match)"),
2297
+ table: z2.string().optional().describe("Filter to a specific table (exact match). Requires schema parameter. Only applies to columns and indexes."),
2302
2298
  detail_level: z2.enum(["names", "summary", "full"]).default("names").describe("Level of detail to return: names (minimal), summary (with metadata), full (complete structure)"),
2303
2299
  limit: z2.number().int().positive().max(1e3).default(100).describe("Maximum number of results to return (default: 100, max: 1000)")
2304
2300
  };
@@ -2421,7 +2417,7 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
2421
2417
  }
2422
2418
  return results;
2423
2419
  }
2424
- async function searchColumns(connector, pattern, schemaFilter, detailLevel, limit) {
2420
+ async function searchColumns(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
2425
2421
  const regex = likePatternToRegex(pattern);
2426
2422
  const results = [];
2427
2423
  let schemasToSearch;
@@ -2433,8 +2429,13 @@ async function searchColumns(connector, pattern, schemaFilter, detailLevel, limi
2433
2429
  for (const schemaName of schemasToSearch) {
2434
2430
  if (results.length >= limit) break;
2435
2431
  try {
2436
- const tables = await connector.getTables(schemaName);
2437
- for (const tableName of tables) {
2432
+ let tablesToSearch;
2433
+ if (tableFilter) {
2434
+ tablesToSearch = [tableFilter];
2435
+ } else {
2436
+ tablesToSearch = await connector.getTables(schemaName);
2437
+ }
2438
+ for (const tableName of tablesToSearch) {
2438
2439
  if (results.length >= limit) break;
2439
2440
  try {
2440
2441
  const columns = await connector.getTableSchema(tableName, schemaName);
@@ -2516,7 +2517,7 @@ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, l
2516
2517
  }
2517
2518
  return results;
2518
2519
  }
2519
- async function searchIndexes(connector, pattern, schemaFilter, detailLevel, limit) {
2520
+ async function searchIndexes(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
2520
2521
  const regex = likePatternToRegex(pattern);
2521
2522
  const results = [];
2522
2523
  let schemasToSearch;
@@ -2528,8 +2529,13 @@ async function searchIndexes(connector, pattern, schemaFilter, detailLevel, limi
2528
2529
  for (const schemaName of schemasToSearch) {
2529
2530
  if (results.length >= limit) break;
2530
2531
  try {
2531
- const tables = await connector.getTables(schemaName);
2532
- for (const tableName of tables) {
2532
+ let tablesToSearch;
2533
+ if (tableFilter) {
2534
+ tablesToSearch = [tableFilter];
2535
+ } else {
2536
+ tablesToSearch = await connector.getTables(schemaName);
2537
+ }
2538
+ for (const tableName of tablesToSearch) {
2533
2539
  if (results.length >= limit) break;
2534
2540
  try {
2535
2541
  const indexes = await connector.getTableIndexes(tableName, schemaName);
@@ -2569,11 +2575,26 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
2569
2575
  object_type,
2570
2576
  pattern = "%",
2571
2577
  schema,
2578
+ table,
2572
2579
  detail_level = "names",
2573
2580
  limit = 100
2574
2581
  } = args;
2575
2582
  try {
2576
2583
  const connector = ConnectorManager.getCurrentConnector(sourceId);
2584
+ if (table) {
2585
+ if (!schema) {
2586
+ return createToolErrorResponse(
2587
+ "The 'table' parameter requires 'schema' to be specified",
2588
+ "SCHEMA_REQUIRED"
2589
+ );
2590
+ }
2591
+ if (!["column", "index"].includes(object_type)) {
2592
+ return createToolErrorResponse(
2593
+ `The 'table' parameter only applies to object_type 'column' or 'index', not '${object_type}'`,
2594
+ "INVALID_TABLE_FILTER"
2595
+ );
2596
+ }
2597
+ }
2577
2598
  if (schema) {
2578
2599
  const schemas = await connector.getSchemas();
2579
2600
  if (!schemas.includes(schema)) {
@@ -2592,13 +2613,13 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
2592
2613
  results = await searchTables(connector, pattern, schema, detail_level, limit);
2593
2614
  break;
2594
2615
  case "column":
2595
- results = await searchColumns(connector, pattern, schema, detail_level, limit);
2616
+ results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
2596
2617
  break;
2597
2618
  case "procedure":
2598
2619
  results = await searchProcedures(connector, pattern, schema, detail_level, limit);
2599
2620
  break;
2600
2621
  case "index":
2601
- results = await searchIndexes(connector, pattern, schema, detail_level, limit);
2622
+ results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
2602
2623
  break;
2603
2624
  default:
2604
2625
  return createToolErrorResponse(
@@ -2610,6 +2631,7 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
2610
2631
  object_type,
2611
2632
  pattern,
2612
2633
  schema,
2634
+ table,
2613
2635
  detail_level,
2614
2636
  count: results.length,
2615
2637
  results,
@@ -2679,9 +2701,8 @@ function getExecuteSqlMetadata(sourceId) {
2679
2701
  // In readonly mode, queries are more predictable (though still not strictly idempotent due to data changes)
2680
2702
  // In write mode, queries are definitely not idempotent
2681
2703
  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
2704
+ // Database operations are always against internal/closed systems, not open-world
2705
+ openWorldHint: false
2685
2706
  };
2686
2707
  return {
2687
2708
  name: toolName,
@@ -2711,21 +2732,22 @@ function customParamsToToolParams(params) {
2711
2732
  description: param.description
2712
2733
  }));
2713
2734
  }
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;
2735
+ function buildExecuteSqlTool(sourceId) {
2720
2736
  const executeSqlMetadata = getExecuteSqlMetadata(sourceId);
2721
2737
  const executeSqlParameters = zodToParameters(executeSqlMetadata.schema);
2722
- tools.push({
2738
+ return {
2723
2739
  name: executeSqlMetadata.name,
2724
2740
  description: executeSqlMetadata.description,
2725
2741
  parameters: executeSqlParameters
2726
- });
2742
+ };
2743
+ }
2744
+ function buildSearchObjectsTool(sourceId) {
2745
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
2746
+ const dbType = sourceConfig.type;
2747
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
2748
+ const isDefault = sourceIds[0] === sourceId;
2727
2749
  const searchMetadata = getSearchObjectsMetadata(sourceId, dbType, isDefault);
2728
- tools.push({
2750
+ return {
2729
2751
  name: searchMetadata.name,
2730
2752
  description: searchMetadata.description,
2731
2753
  parameters: [
@@ -2760,19 +2782,27 @@ function getToolsForSource(sourceId) {
2760
2782
  description: "Maximum number of results to return (default: 100, max: 1000)"
2761
2783
  }
2762
2784
  ]
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
- });
2785
+ };
2786
+ }
2787
+ function buildCustomTool(toolConfig) {
2788
+ return {
2789
+ name: toolConfig.name,
2790
+ description: toolConfig.description,
2791
+ parameters: customParamsToToolParams(toolConfig.parameters)
2792
+ };
2793
+ }
2794
+ function getToolsForSource(sourceId) {
2795
+ const registry = getToolRegistry();
2796
+ const enabledToolConfigs = registry.getEnabledToolConfigs(sourceId);
2797
+ return enabledToolConfigs.map((toolConfig) => {
2798
+ if (toolConfig.name === "execute_sql") {
2799
+ return buildExecuteSqlTool(sourceId);
2800
+ } else if (toolConfig.name === "search_objects") {
2801
+ return buildSearchObjectsTool(sourceId);
2802
+ } else {
2803
+ return buildCustomTool(toolConfig);
2773
2804
  }
2774
- }
2775
- return tools;
2805
+ });
2776
2806
  }
2777
2807
 
2778
2808
  // src/tools/custom-tool-handler.ts
@@ -2894,7 +2924,7 @@ function registerTools(server) {
2894
2924
  }
2895
2925
  const registry = getToolRegistry();
2896
2926
  for (const sourceId of sourceIds) {
2897
- const enabledTools = registry.getToolsForSource(sourceId);
2927
+ const enabledTools = registry.getEnabledToolConfigs(sourceId);
2898
2928
  const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
2899
2929
  const dbType = sourceConfig.type;
2900
2930
  const isDefault = sourceIds[0] === sourceId;
@@ -2933,7 +2963,7 @@ function registerSearchObjectsTool(server, sourceId, dbType, isDefault) {
2933
2963
  readOnlyHint: true,
2934
2964
  destructiveHint: false,
2935
2965
  idempotentHint: true,
2936
- openWorldHint: true
2966
+ openWorldHint: false
2937
2967
  }
2938
2968
  },
2939
2969
  createSearchDatabaseObjectsToolHandler(sourceId)
@@ -3201,7 +3231,7 @@ function generateBanner(version, modes = []) {
3201
3231
  | |__| | |_) | | | | |_| | |_) |
3202
3232
  |_____/|____/|_| |_|\\__,_|_.__/
3203
3233
 
3204
- v${version}${modeText} - Universal Database MCP Server
3234
+ v${version}${modeText} - Minimal Database MCP Server
3205
3235
  `;
3206
3236
  }
3207
3237
  async function main() {
@@ -3241,25 +3271,12 @@ See documentation for more details on configuring database connections.
3241
3271
  console.error(` - ${source.id}: ${redactDSN(dsn)}`);
3242
3272
  }
3243
3273
  await connectorManager.connectWithSources(sources);
3244
- const { initializeToolRegistry } = await import("./registry-GJGPWR3I.js");
3274
+ const { initializeToolRegistry } = await import("./registry-AWAIN6WO.js");
3245
3275
  initializeToolRegistry({
3246
3276
  sources: sourceConfigsData.sources,
3247
3277
  tools: sourceConfigsData.tools
3248
3278
  });
3249
3279
  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
3280
  const createServer = () => {
3264
3281
  const server = new McpServer({
3265
3282
  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.1",
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
- };