@bytebase/dbhub 0.11.0 → 0.11.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.
Files changed (3) hide show
  1. package/README.md +18 -0
  2. package/dist/index.js +109 -3
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -217,6 +217,24 @@ postgres://user:password@localhost:5432/dbname
217
217
 
218
218
  DBHub supports connecting to databases through SSH tunnels, enabling secure access to databases in private networks or behind firewalls.
219
219
 
220
+ #### Using SSH Config File (Recommended)
221
+
222
+ DBHub can read SSH connection settings from your `~/.ssh/config` file. Simply use the host alias from your SSH config:
223
+
224
+ ```bash
225
+ # If you have this in ~/.ssh/config:
226
+ # Host mybastion
227
+ # HostName bastion.example.com
228
+ # User ubuntu
229
+ # IdentityFile ~/.ssh/id_rsa
230
+
231
+ npx @bytebase/dbhub \
232
+ --dsn "postgres://dbuser:dbpass@database.internal:5432/mydb" \
233
+ --ssh-host mybastion
234
+ ```
235
+
236
+ DBHub will automatically use the settings from your SSH config, including hostname, user, port, and identity file. If no identity file is specified in the config, DBHub will try common default locations (`~/.ssh/id_rsa`, `~/.ssh/id_ed25519`, etc.).
237
+
220
238
  #### SSH with Password Authentication
221
239
 
222
240
  ```bash
package/dist/index.js CHANGED
@@ -1881,7 +1881,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/
1881
1881
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1882
1882
  import express from "express";
1883
1883
  import path3 from "path";
1884
- import { readFileSync as readFileSync2 } from "fs";
1884
+ import { readFileSync as readFileSync3 } from "fs";
1885
1885
  import { fileURLToPath as fileURLToPath3 } from "url";
1886
1886
 
1887
1887
  // src/utils/ssh-tunnel.ts
@@ -2031,6 +2031,102 @@ import dotenv from "dotenv";
2031
2031
  import path from "path";
2032
2032
  import fs from "fs";
2033
2033
  import { fileURLToPath } from "url";
2034
+
2035
+ // src/utils/ssh-config-parser.ts
2036
+ import { readFileSync as readFileSync2, existsSync } from "fs";
2037
+ import { homedir } from "os";
2038
+ import { join } from "path";
2039
+ import SSHConfig from "ssh-config";
2040
+ var DEFAULT_SSH_KEYS = [
2041
+ "~/.ssh/id_rsa",
2042
+ "~/.ssh/id_ed25519",
2043
+ "~/.ssh/id_ecdsa",
2044
+ "~/.ssh/id_dsa"
2045
+ ];
2046
+ function expandTilde(filePath) {
2047
+ if (filePath.startsWith("~/")) {
2048
+ return join(homedir(), filePath.substring(2));
2049
+ }
2050
+ return filePath;
2051
+ }
2052
+ function fileExists(filePath) {
2053
+ try {
2054
+ return existsSync(expandTilde(filePath));
2055
+ } catch {
2056
+ return false;
2057
+ }
2058
+ }
2059
+ function findDefaultSSHKey() {
2060
+ for (const keyPath of DEFAULT_SSH_KEYS) {
2061
+ if (fileExists(keyPath)) {
2062
+ return expandTilde(keyPath);
2063
+ }
2064
+ }
2065
+ return void 0;
2066
+ }
2067
+ function parseSSHConfig(hostAlias, configPath) {
2068
+ const sshConfigPath = configPath || join(homedir(), ".ssh", "config");
2069
+ if (!existsSync(sshConfigPath)) {
2070
+ return null;
2071
+ }
2072
+ try {
2073
+ const configContent = readFileSync2(sshConfigPath, "utf8");
2074
+ const config = SSHConfig.parse(configContent);
2075
+ const hostConfig = config.compute(hostAlias);
2076
+ if (!hostConfig || !hostConfig.HostName && !hostConfig.User) {
2077
+ return null;
2078
+ }
2079
+ const sshConfig = {};
2080
+ if (hostConfig.HostName) {
2081
+ sshConfig.host = hostConfig.HostName;
2082
+ } else {
2083
+ sshConfig.host = hostAlias;
2084
+ }
2085
+ if (hostConfig.Port) {
2086
+ sshConfig.port = parseInt(hostConfig.Port, 10);
2087
+ }
2088
+ if (hostConfig.User) {
2089
+ sshConfig.username = hostConfig.User;
2090
+ }
2091
+ if (hostConfig.IdentityFile) {
2092
+ const identityFile = Array.isArray(hostConfig.IdentityFile) ? hostConfig.IdentityFile[0] : hostConfig.IdentityFile;
2093
+ const expandedPath = expandTilde(identityFile);
2094
+ if (fileExists(expandedPath)) {
2095
+ sshConfig.privateKey = expandedPath;
2096
+ }
2097
+ }
2098
+ if (!sshConfig.privateKey) {
2099
+ const defaultKey = findDefaultSSHKey();
2100
+ if (defaultKey) {
2101
+ sshConfig.privateKey = defaultKey;
2102
+ }
2103
+ }
2104
+ if (hostConfig.ProxyJump || hostConfig.ProxyCommand) {
2105
+ console.error("Warning: ProxyJump/ProxyCommand in SSH config is not yet supported by DBHub");
2106
+ }
2107
+ if (!sshConfig.host || !sshConfig.username) {
2108
+ return null;
2109
+ }
2110
+ return sshConfig;
2111
+ } catch (error) {
2112
+ console.error(`Error parsing SSH config: ${error instanceof Error ? error.message : String(error)}`);
2113
+ return null;
2114
+ }
2115
+ }
2116
+ function looksLikeSSHAlias(host) {
2117
+ if (host.includes(".")) {
2118
+ return false;
2119
+ }
2120
+ if (/^[\d:]+$/.test(host)) {
2121
+ return false;
2122
+ }
2123
+ if (/^[0-9a-fA-F:]+$/.test(host) && host.includes(":")) {
2124
+ return false;
2125
+ }
2126
+ return true;
2127
+ }
2128
+
2129
+ // src/config/env.ts
2034
2130
  var __filename = fileURLToPath(import.meta.url);
2035
2131
  var __dirname = path.dirname(__filename);
2036
2132
  function parseCommandLineArgs() {
@@ -2151,15 +2247,25 @@ function resolveSSHConfig() {
2151
2247
  if (!hasSSHArgs) {
2152
2248
  return null;
2153
2249
  }
2154
- const config = {};
2250
+ let config = {};
2155
2251
  let sources = [];
2252
+ let sshConfigHost;
2156
2253
  if (args["ssh-host"]) {
2254
+ sshConfigHost = args["ssh-host"];
2157
2255
  config.host = args["ssh-host"];
2158
2256
  sources.push("ssh-host from command line");
2159
2257
  } else if (process.env.SSH_HOST) {
2258
+ sshConfigHost = process.env.SSH_HOST;
2160
2259
  config.host = process.env.SSH_HOST;
2161
2260
  sources.push("SSH_HOST from environment");
2162
2261
  }
2262
+ if (sshConfigHost && looksLikeSSHAlias(sshConfigHost)) {
2263
+ const sshConfigData = parseSSHConfig(sshConfigHost);
2264
+ if (sshConfigData) {
2265
+ config = { ...sshConfigData };
2266
+ sources.push(`SSH config for host '${sshConfigHost}'`);
2267
+ }
2268
+ }
2163
2269
  if (args["ssh-port"]) {
2164
2270
  config.port = parseInt(args["ssh-port"], 10);
2165
2271
  sources.push("ssh-port from command line");
@@ -3197,7 +3303,7 @@ function registerPrompts(server) {
3197
3303
  var __filename3 = fileURLToPath3(import.meta.url);
3198
3304
  var __dirname3 = path3.dirname(__filename3);
3199
3305
  var packageJsonPath = path3.join(__dirname3, "..", "package.json");
3200
- var packageJson = JSON.parse(readFileSync2(packageJsonPath, "utf8"));
3306
+ var packageJson = JSON.parse(readFileSync3(packageJsonPath, "utf8"));
3201
3307
  var SERVER_NAME = "DBHub MCP Server";
3202
3308
  var SERVER_VERSION = packageJson.version;
3203
3309
  function generateBanner(version, modes = []) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Universal Database MCP Server",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -25,6 +25,7 @@
25
25
  "mssql": "^11.0.1",
26
26
  "mysql2": "^3.13.0",
27
27
  "pg": "^8.13.3",
28
+ "ssh-config": "^5.0.3",
28
29
  "ssh2": "^1.16.0",
29
30
  "zod": "^3.24.2"
30
31
  },