feat: add XuguDB agent support

This commit is contained in:
t8y2
2026-05-30 09:12:34 +08:00
parent 6ec8115984
commit 81e50cadb5
22 changed files with 265 additions and 194 deletions
+2 -2
View File
@@ -82,7 +82,7 @@
### 40+ Databases, One Tool
MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, CockroachDB, Access, HighGo, and more. Agent/JDBC-oriented profiles extend DBX to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections. All in a single ~15 MB app. No bundled Chromium.
MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. Agent/JDBC-oriented profiles extend DBX to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections. All in a single ~15 MB app. No bundled Chromium.
### Query Editor
@@ -290,7 +290,7 @@ DBX is 15 MB with no runtime dependencies (no Java, no Python). It includes AI a
<details>
<summary><strong>What databases are supported?</strong></summary>
MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, CockroachDB, Access, HighGo, and more. JDBC-oriented profiles extend support to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections.
MySQL, PostgreSQL, SQLite, Redis, MongoDB, DuckDB, ClickHouse, SQL Server, Oracle, Elasticsearch, MariaDB, TiDB, OceanBase, openGauss, GaussDB, KingBase, Vastbase, GoldenDB, Doris, SelectDB, StarRocks, Redshift, DM, TDengine, XuguDB, CockroachDB, Access, HighGo, and more. JDBC-oriented profiles extend support to H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, and custom JDBC connections.
</details>
<details>
+2 -2
View File
@@ -82,7 +82,7 @@
### 40+ 种数据库,一个工具搞定
MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、CockroachDB、Access、HighGo 等数据库都能直接连接。Agent/JDBC 方向的配置还可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 和自定义 JDBC。全部装进约 15 MB 的应用里,不内嵌 Chromium。
MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等数据库都能直接连接。Agent/JDBC 方向的配置还可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 和自定义 JDBC。全部装进约 15 MB 的应用里,不内嵌 Chromium。
### 查询编辑器
@@ -290,7 +290,7 @@ DBX 仅 15 MB,无需运行时依赖(无需 Java、无需 Python)。AI 和
<details>
<summary><strong>支持哪些数据库?</strong></summary>
MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、CockroachDB、Access、HighGo 等。JDBC 方向配置可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 及自定义 JDBC 连接。
MySQL、PostgreSQL、SQLite、Redis、MongoDB、DuckDB、ClickHouse、SQL Server、Oracle、Elasticsearch、MariaDB、TiDB、OceanBase、openGauss、GaussDB、KingBase、Vastbase、GoldenDB、Doris、SelectDB、StarRocks、Redshift、DM、TDengine、虚谷 XuguDB、CockroachDB、Access、HighGo 等。JDBC 方向配置可扩展到 H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB 及自定义 JDBC 连接。
</details>
<details>
Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

@@ -257,6 +257,7 @@ const driverProfiles: Record<
sundb: { type: "sundb", port: 22000, user: "root", label: "SunDB", icon: "sundb" },
jdbc: { type: "jdbc", port: 0, user: "", label: "JDBC", icon: "jdbc" },
tdengine: { type: "tdengine", port: 6041, user: "root", label: "TDengine", icon: "tdengine" },
xugu: { type: "xugu", port: 5138, user: "", label: "虚谷 XuguDB", icon: "xugu" },
iris: { type: "iris", port: 1972, user: "_SYSTEM", label: "IRIS", icon: "iris" },
custom_mysql: {
type: "mysql",
@@ -486,6 +487,7 @@ const iconTypeMap: Record<string, string> = {
redshift: "redshift",
cockroachdb: "cockroachdb",
tdengine: "tdengine",
xugu: "xugu",
dm: "dm",
h2: "h2",
snowflake: "snowflake",
@@ -554,6 +556,7 @@ const dbOptions = [
{ value: "bigquery", label: "BigQuery" },
{ value: "kylin", label: "Kylin" },
{ value: "sundb", label: "SunDB" },
{ value: "xugu", label: "虚谷 XuguDB" },
{ value: "iris", label: "IRIS" },
{ value: "jdbc", label: "JDBC" },
{ value: "custom_mysql", label: "Custom (MySQL)" },
@@ -62,6 +62,7 @@ const assetIcons: Record<string, string> = {
tdsql: "tdsql.webp",
polardb: "polardb.webp",
greatsql: "greatsql.webp",
xugu: "xugu.png",
iris: "iris.png",
};
@@ -70,6 +70,9 @@ export function connectionUrlPlaceholder(dbType: DatabaseType): string {
case "tdengine":
return "tdengine://user:password@host:6041/database";
case "xugu":
return "xugu://user:password@host:5138/database";
case "bigquery":
return "bigquery://https://www.googleapis.com/bigquery/v2:443/project-id";
+1
View File
@@ -45,6 +45,7 @@ const SCHEME_PROFILES: Record<string, ConnectionProfile> = {
opengauss: { type: "gaussdb", profile: "opengauss", label: "openGauss", defaultPort: 5432 },
tdengine: { type: "tdengine", profile: "tdengine", label: "TDengine", defaultPort: 6041 },
"taos-ws": { type: "tdengine", profile: "tdengine", label: "TDengine", defaultPort: 6041 },
xugu: { type: "xugu", profile: "xugu", label: "XuguDB", defaultPort: 5138 },
iris: { type: "iris", profile: "iris", label: "IRIS", defaultPort: 1972 },
};
@@ -25,6 +25,7 @@ export const SCHEMA_AWARE_TYPES = new Set<DatabaseType>([
"trino",
"db2",
"tdengine",
"xugu",
"iris",
"duckdb",
]);
@@ -98,6 +99,7 @@ export const DATABASE_SEARCH_SUPPORTED_TYPES = new Set<DatabaseType>([
"kylin",
"sundb",
"tdengine",
"xugu",
"iris",
]);
@@ -206,6 +208,7 @@ export const TREE_SCHEMA_TYPES = new Set<DatabaseType>([
"trino",
"h2",
"tdengine",
"xugu",
"iris",
"duckdb",
]);
@@ -243,6 +246,7 @@ export const AGENT_DRIVER_TYPES = new Set<DatabaseType>([
"sundb",
"gaussdb",
"tdengine",
"xugu",
"iris",
]);
+1
View File
@@ -41,6 +41,7 @@ export type DatabaseType =
| "kylin"
| "sundb"
| "tdengine"
| "xugu"
| "iris"
| "jdbc";
@@ -449,6 +449,17 @@
"skipTcpProbe": true,
"defaultPort": 6041
},
{
"dbType": "xugu",
"label": "虚谷 XuguDB",
"runtimeMode": "agent",
"mcpMode": "bridge",
"agentKey": "xugu",
"singleConnectionPool": false,
"metadataConnectionScoped": false,
"skipTcpProbe": true,
"defaultPort": 5138
},
{
"dbType": "jdbc",
"label": "JDBC",
+7
View File
@@ -205,6 +205,13 @@ const AGENT_CATALOG: &[AgentCatalogEntry] = &[
store_visible: true,
profiles: &[],
},
AgentCatalogEntry {
db_type: DatabaseType::Xugu,
key: "xugu",
label: "虚谷 XuguDB",
store_visible: true,
profiles: &[],
},
AgentCatalogEntry {
db_type: DatabaseType::MongoDb,
key: "mongodb",
+1
View File
@@ -300,6 +300,7 @@ impl AppState {
| DatabaseType::Kylin
| DatabaseType::Sundb
| DatabaseType::Tdengine
| DatabaseType::Xugu
| DatabaseType::Iris
| DatabaseType::Access => {
let connect_params =
+5
View File
@@ -190,6 +190,7 @@ pub enum DatabaseType {
Kylin,
Sundb,
Tdengine,
Xugu,
#[serde(rename = "iris")]
Iris,
Jdbc,
@@ -377,6 +378,7 @@ impl ConnectionConfig {
DatabaseType::Kylin => format!("kylin://{host}:{port}{db_part}"),
DatabaseType::Sundb => format!("sundb://{host}:{port}{db_part}"),
DatabaseType::Tdengine => format!("tdengine://{host}:{port}{db_part}"),
DatabaseType::Xugu => format!("xugu://{host}:{port}{db_part}"),
DatabaseType::Iris => format!("iris://{host}:{port}{db_part}"),
DatabaseType::Jdbc => "jdbc:<redacted>".to_string(),
}
@@ -538,6 +540,9 @@ impl ConnectionConfig {
DatabaseType::Tdengine => {
format!("tdengine://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Xugu => {
format!("xugu://{}:{}@{host}:{port}{db_part}", username, password)
}
DatabaseType::Iris => {
format!("iris://{}:{}@{host}:{port}{db_part}", username, password)
}
+5 -4
View File
@@ -18,7 +18,7 @@ DBX 主应用会尽量使用 Rust 原生驱动。部分数据库通过 MySQL 或
| MySQL 兼容类型 | MariaDB、TiDB、OceanBase、Doris、SelectDB、StarRocks、GoldenDB、自定义 MySQL | 复用 MySQL 风格连接能力 |
| PostgreSQL 兼容类型 | openGauss、KingBase、HighGo、Vastbase、CockroachDB、自定义 PostgreSQL | 复用 PostgreSQL 风格连接能力 |
| 文件型数据库 | SQLite、DuckDB、Microsoft Access | 选择本地数据库文件,不填写主机和端口 |
| Agent/JDBC 扩展类型 | H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine、JDBC | 功能覆盖取决于对应驱动路径 |
| Agent/JDBC 扩展类型 | H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine、虚谷 XuguDB、JDBC | 功能覆盖取决于对应驱动路径 |
## 默认端口
@@ -52,6 +52,7 @@ DBX 主应用会尽量使用 Rust 原生驱动。部分数据库通过 MySQL 或
| Kylin | 7070 |
| SunDB | 22000 |
| TDengine | 6041 |
| 虚谷 XuguDB | 5138 |
<Callout type="info">
SQLite、DuckDB、Access 和 JDBC 在不需要网络端口时,会在连接模型中保存为端口 `0`。
@@ -63,9 +64,9 @@ DBX 只会在具备足够元数据和 SQL 生成能力的数据库上启用高
| 功能 | 支持类型 |
|---|---|
| Schema 感知树 | PostgreSQL、SQL Server、Oracle、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、JDBC、H2、Snowflake、Trino、DB2、TDengine |
| Schema 感知树 | PostgreSQL、SQL Server、Oracle、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、JDBC、H2、Snowflake、Trino、DB2、TDengine、虚谷 XuguDB |
| ER 图 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、DB2 |
| 数据库搜索 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DuckDB、ClickHouse、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine |
| 数据库搜索 | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redshift、DuckDB、ClickHouse、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access、H2、Snowflake、Trino、Hive、DB2、Informix、Neo4j、Cassandra、BigQuery、Kylin、SunDB、TDengine、虚谷 XuguDB |
| 表数据导入 | MySQL、PostgreSQL、SQLite、DuckDB、ClickHouse、SQL Server、Oracle、Doris、StarRocks、Redshift、DM、GaussDB、KingBase、HighGo、Vastbase、GoldenDB、Access |
| 表结构编辑器 | MySQL、PostgreSQL、SQLite、SQL Server |
| 创建数据库 | MySQL、PostgreSQL、SQL Server、ClickHouse、Oracle、DM、GaussDB、Doris、StarRocks、Redshift |
@@ -81,7 +82,7 @@ DBX 只会在具备足够元数据和 SQL 生成能力的数据库上启用高
大多数网络数据库支持主机、端口、用户名、密码、默认数据库、URL 参数、SSL、连接颜色、SSH 隧道和代理设置。文件型数据库会改为选择本地文件。
DBX 也可以解析常见连接 URL,例如 MySQL、PostgreSQL、Redis、MongoDB、ClickHouse、SQL Server、Oracle、Elasticsearch、DM、GaussDB、openGauss、TDengine 和 Access。
DBX 也可以解析常见连接 URL,例如 MySQL、PostgreSQL、Redis、MongoDB、ClickHouse、SQL Server、Oracle、Elasticsearch、DM、GaussDB、openGauss、TDengine、虚谷 XuguDB 和 Access。
### ClickHouse TLS
+5 -4
View File
@@ -18,7 +18,7 @@ The connection dialog exposes ready-to-use profiles with default ports and drive
| MySQL-compatible profiles | MariaDB, TiDB, OceanBase, Doris, SelectDB, StarRocks, GoldenDB, custom MySQL | Reuse MySQL-style connection handling where the engine speaks a compatible protocol |
| PostgreSQL-compatible profiles | openGauss, KingBase, HighGo, Vastbase, CockroachDB, custom PostgreSQL | Reuse PostgreSQL-style connection handling where the engine speaks a compatible protocol |
| File-based engines | SQLite, DuckDB, Microsoft Access | Choose a local database file instead of host and port |
| Agent/JDBC-oriented engines | H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine, JDBC | Feature coverage depends on the driver path used by that engine |
| Agent/JDBC-oriented engines | H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine, XuguDB, JDBC | Feature coverage depends on the driver path used by that engine |
## Default Ports
@@ -52,6 +52,7 @@ The connection dialog exposes ready-to-use profiles with default ports and drive
| Kylin | 7070 |
| SunDB | 22000 |
| TDengine | 6041 |
| XuguDB | 5138 |
<Callout type="info">
SQLite, DuckDB, Access, and JDBC profiles use port `0` in the saved connection model when a network port is not part of the connection.
@@ -63,9 +64,9 @@ DBX intentionally enables advanced workflows only where the app has enough metad
| Feature | Supported Types |
|---|---|
| Schema-aware tree | PostgreSQL, SQL Server, Oracle, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, JDBC, H2, Snowflake, Trino, DB2, TDengine |
| Schema-aware tree | PostgreSQL, SQL Server, Oracle, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, JDBC, H2, Snowflake, Trino, DB2, TDengine, XuguDB |
| ER diagram | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, DB2 |
| Database search | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DuckDB, ClickHouse, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine |
| Database search | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redshift, DuckDB, ClickHouse, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access, H2, Snowflake, Trino, Hive, DB2, Informix, Neo4j, Cassandra, BigQuery, Kylin, SunDB, TDengine, XuguDB |
| Table import | MySQL, PostgreSQL, SQLite, DuckDB, ClickHouse, SQL Server, Oracle, Doris, StarRocks, Redshift, DM, GaussDB, KingBase, HighGo, Vastbase, GoldenDB, Access |
| Table structure editor | MySQL, PostgreSQL, SQLite, SQL Server |
| Create database | MySQL, PostgreSQL, SQL Server, ClickHouse, Oracle, DM, GaussDB, Doris, StarRocks, Redshift |
@@ -81,7 +82,7 @@ If a database can connect but does not appear in a feature row, that feature may
Most network databases support host, port, username, password, default database, optional URL parameters, SSL, connection color, SSH tunnel, and proxy settings. File-based engines replace host and port with a file picker.
DBX can also parse common connection URLs for engines such as MySQL, PostgreSQL, Redis, MongoDB, ClickHouse, SQL Server, Oracle, Elasticsearch, DM, GaussDB, openGauss, TDengine, and Access.
DBX can also parse common connection URLs for engines such as MySQL, PostgreSQL, Redis, MongoDB, ClickHouse, SQL Server, Oracle, Elasticsearch, DM, GaussDB, openGauss, TDengine, XuguDB, and Access.
### ClickHouse TLS
+1 -1
View File
@@ -10,7 +10,7 @@ DBX 使用混合驱动架构:常用数据库使用内置原生驱动,需要
| 驱动类型 | 工作方式 | 适用场景 |
|---|---|---|
| 原生(内置) | Rust 驱动编译进 DBX | MySQL、PostgreSQL、SQLite、SQL Server、Oracle、Redis、MongoDB、DuckDB、ClickHouse 等 |
| JDBC Agent | DBX 管理的 Java 子进程 | 提供 JDBC 驱动的数据库:GaussDB、openGauss、DM、KingBase、HighGo、Vastbase、Trino、Hive、DB2、Informix、Neo4j、TDengine、YashanDB、GoldenDB、Kylin、SunDB 等 |
| JDBC Agent | DBX 管理的 Java 子进程 | 提供 JDBC 驱动的数据库:GaussDB、openGauss、DM、KingBase、HighGo、Vastbase、Trino、Hive、DB2、Informix、Neo4j、TDengine、虚谷 XuguDB、YashanDB、GoldenDB、Kylin、SunDB 等 |
<Callout type="info">
原生驱动安装后立即可用。Agent 驱动需要一次性下载 JDBC 驱动和 JRE 设置,首次创建连接时 DBX 会自动处理。
+1 -1
View File
@@ -10,7 +10,7 @@ DBX uses a hybrid driver architecture: built-in native drivers for common databa
| Driver Type | How It Works | Best For |
|---|---|---|
| Native (built-in) | Rust drivers compiled into DBX | MySQL, PostgreSQL, SQLite, SQL Server, Oracle, Redis, MongoDB, DuckDB, ClickHouse, and more |
| JDBC Agent | Java subprocess managed by DBX | Databases that provide JDBC drivers: GaussDB, openGauss, DM, KingBase, HighGo, Vastbase, Trino, Hive, DB2, Informix, Neo4j, TDengine, YashanDB, GoldenDB, Kylin, SunDB, and more |
| JDBC Agent | Java subprocess managed by DBX | Databases that provide JDBC drivers: GaussDB, openGauss, DM, KingBase, HighGo, Vastbase, Trino, Hive, DB2, Informix, Neo4j, TDengine, XuguDB, YashanDB, GoldenDB, Kylin, SunDB, and more |
<Callout type="info">
Native drivers work immediately after installation. Agent drivers require a one-time JDBC driver download and JRE setup, which DBX handles automatically when you first create a connection.
+14
View File
@@ -71,6 +71,20 @@ test("parses TDengine WebSocket JDBC URLs", () => {
assert.equal(parsed.urlParams, "timezone=UTC");
});
test("parses XuguDB JDBC URLs", () => {
const parsed = parseConnectionUrl("jdbc:xugu://alice:secret@xugu.example.com:5138/demo?charset=utf8");
assert.equal(parsed.dbType, "xugu");
assert.equal(parsed.driverProfile, "xugu");
assert.equal(parsed.driverLabel, "XuguDB");
assert.equal(parsed.host, "xugu.example.com");
assert.equal(parsed.port, 5138);
assert.equal(parsed.username, "alice");
assert.equal(parsed.password, "secret");
assert.equal(parsed.database, "demo");
assert.equal(parsed.urlParams, "charset=utf8");
});
test("parses UCanAccess JDBC URLs as Access database files", () => {
const parsed = parseConnectionUrl("jdbc:ucanaccess:///Users/me/data/Northwind.accdb;memory=false");
@@ -20,6 +20,7 @@ const expected: Record<string, string> = {
elasticsearch: "http://user:password@host:port",
dameng: "dm://user:password@host:port",
tdengine: "tdengine://user:password@host:6041/database",
xugu: "xugu://user:password@host:5138/database",
bigquery: "bigquery://https://www.googleapis.com/bigquery/v2:443/project-id",
jdbc: "jdbc:mysql://host:3306/database",
};
@@ -35,6 +35,13 @@ test("treats TDengine databases as schema tree roots and agent driver databases"
assert.equal(supportsDriverManagement("tdengine"), true);
});
test("treats XuguDB as a schema-aware agent driver database", () => {
assert.equal(TREE_SCHEMA_TYPES.has("xugu"), true);
assert.equal(SCHEMA_AWARE_TYPES.has("xugu"), true);
assert.equal(supportsDatabaseSearch("xugu"), true);
assert.equal(supportsDriverManagement("xugu"), true);
});
test("treats Access as a local single-database agent driver", () => {
assert.equal(SCHEMA_AWARE_TYPES.has("access"), false);
assert.equal(supportsDriverManagement("access"), true);
+189 -180
View File
@@ -34,7 +34,7 @@ function withDatabase(config: ConnectionConfig, database?: string): ConnectionCo
}
export const DBX_CONNECTION_TYPE_DESCRIPTION =
"Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, gbase, h2, snowflake, trino, hive, db2, informix, neo4j, cassandra, bigquery, kylin, sundb, tdengine, jdbc, access";
"Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, yashandb, databricks, saphana, teradata, vertica, firebird, exasol, opengauss, oceanbase-oracle, gbase, h2, snowflake, trino, hive, db2, informix, neo4j, cassandra, bigquery, kylin, sundb, tdengine, xugu, jdbc, access";
export function createDbxMcpServer(backend: Backend, options: { isWebMode?: boolean } = {}): McpServer {
const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL;
@@ -44,199 +44,208 @@ export function createDbxMcpServer(backend: Backend, options: { isWebMode?: bool
});
server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
const connections = await backend.loadConnections();
if (connections.length === 0) return text("No connections configured in DBX.");
const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
return text(mdTable(["Name", "Type", "Host", "Port", "Database"], rows));
const connections = await backend.loadConnections();
if (connections.length === 0) return text("No connections configured in DBX.");
const rows = connections.map((c) => [c.name, c.db_type, c.host, String(c.port), c.database || ""]);
return text(mdTable(["Name", "Type", "Host", "Port", "Database"], rows));
});
server.tool(
"dbx_list_tables",
"List tables and views for a database connection",
{
connection_name: z.string().describe("Name of the DBX connection"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
},
async ({ connection_name, database, schema }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
const tables = await backend.listTables(withDatabase(config, database), schema);
if (tables.length === 0) return text("No tables found.");
const rows = tables.map((t) => [t.name, t.type]);
return text(mdTable(["Table", "Type"], rows));
},
);
server.tool(
"dbx_describe_table",
"Get column definitions for a table",
{
connection_name: z.string().describe("Name of the DBX connection"),
table: z.string().describe("Table name"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
},
async ({ connection_name, table, database, schema }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
const columns = await backend.describeTable(withDatabase(config, database), table, schema);
if (columns.length === 0) return text("No columns found.");
const rows = columns.map((c) => [
c.is_primary_key ? `${c.name} (PK)` : c.name,
c.data_type,
c.is_nullable ? "YES" : "NO",
c.column_default ?? "",
c.comment ?? "",
]);
return text(mdTable(["Column", "Type", "Nullable", "Default", "Comment"], rows));
},
);
server.tool(
"dbx_execute_query",
"Execute a SQL query on a database connection (max 100 rows returned)",
{
connection_name: z.string().describe("Name of the DBX connection"),
database: z.string().optional().describe("Database name"),
sql: z.string().describe("SQL query to execute"),
},
async ({ connection_name, database, sql }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
if (config.db_type !== "mongodb") {
const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
if (!safety.allowed) return text(`Query blocked: ${safety.reason}`);
}
// MongoDB shell commands don't fit the SQL safety evaluator; the backend
// (node-core executeQuery) applies command-aware read/write gating.
try {
const result = await backend.executeQuery(withDatabase(config, database), sql);
if (result.columns.length === 0) return text(`Query executed. ${result.row_count} row(s) affected.`);
const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
return text(`${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
return text(`Query error: ${msg}`);
}
},
);
server.tool(
"dbx_get_schema_context",
"Get compact table and column context for writing SQL",
{
connection_name: z.string().describe("Name of the DBX connection"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
tables: z.array(z.string()).optional().describe("Specific table names to include"),
max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
},
async ({ connection_name, database, schema, tables, max_tables }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
const context = await buildSchemaContext(backend, withDatabase(config, database), { schema, tables, maxTables: max_tables });
if (context.tables.length === 0) return text("No matching tables found.");
return text(formatSchemaContext(context));
},
);
server.tool(
"dbx_add_connection",
"Add a new database connection to DBX",
{
name: z.string().describe("Connection name"),
db_type: z
.string()
.describe(DBX_CONNECTION_TYPE_DESCRIPTION),
host: z.string().describe("Database host"),
port: z.number().optional().describe("Database port (TDengine defaults to 6041)"),
username: z.string().default("").describe("Username"),
password: z.string().default("").describe("Password"),
database: z.string().optional().describe("Default database name"),
ssl: z.boolean().default(false).describe("Enable SSL"),
},
async ({ name, db_type, host, port, username, password, database, ssl }) => {
const existing = await backend.findConnection(name);
if (existing) return text(`Connection "${name}" already exists.`);
const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
const resolvedPort = port ?? (db_type === "tdengine" ? 6041 : FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
if (resolvedPort === undefined) return text("Port is required for this database type.");
const config = await backend.addConnection({
name,
db_type,
host,
port: resolvedPort,
username,
password,
database,
ssl,
ssh_enabled: false,
} as Omit<ConnectionConfig, "id">);
await notifyReload();
return text(`Connection "${config.name}" added (id: ${config.id}).`);
},
);
server.tool(
"dbx_remove_connection",
"Remove a database connection from DBX",
{
connection_name: z.string().describe("Name of the connection to remove"),
},
async ({ connection_name }) => {
const removed = await backend.removeConnection(connection_name);
if (!removed) return text(`Connection "${connection_name}" not found.`);
await notifyReload();
return text(`Connection "${connection_name}" removed.`);
},
);
// Desktop-only tools: open table and execute-and-show require the Tauri bridge
if (!isWebMode) {
server.tool(
"dbx_open_table",
"Open a table in DBX desktop app UI. Requires DBX to be running.",
"dbx_list_tables",
"List tables and views for a database connection",
{
connection_name: z.string().describe("Name of the DBX connection"),
table: z.string().describe("Table name to open"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name"),
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
},
async ({ connection_name, database, schema }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
const tables = await backend.listTables(withDatabase(config, database), schema);
if (tables.length === 0) return text("No tables found.");
const rows = tables.map((t) => [t.name, t.type]);
return text(mdTable(["Table", "Type"], rows));
},
);
server.tool(
"dbx_describe_table",
"Get column definitions for a table",
{
connection_name: z.string().describe("Name of the DBX connection"),
table: z.string().describe("Table name"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
},
async ({ connection_name, table, database, schema }) => {
return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
const columns = await backend.describeTable(withDatabase(config, database), table, schema);
if (columns.length === 0) return text("No columns found.");
const rows = columns.map((c) => [
c.is_primary_key ? `${c.name} (PK)` : c.name,
c.data_type,
c.is_nullable ? "YES" : "NO",
c.column_default ?? "",
c.comment ?? "",
]);
return text(mdTable(["Column", "Type", "Nullable", "Default", "Comment"], rows));
},
);
server.tool(
"dbx_execute_query",
"Execute a SQL query on a database connection (max 100 rows returned)",
{
connection_name: z.string().describe("Name of the DBX connection"),
database: z.string().optional().describe("Database name"),
sql: z.string().describe("SQL query to execute"),
},
async ({ connection_name, database, sql }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
if (config.db_type !== "mongodb") {
const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
if (!safety.allowed) return text(`Query blocked: ${safety.reason}`);
}
// MongoDB shell commands don't fit the SQL safety evaluator; the backend
// (node-core executeQuery) applies command-aware read/write gating.
try {
const result = await backend.executeQuery(withDatabase(config, database), sql);
if (result.columns.length === 0) return text(`Query executed. ${result.row_count} row(s) affected.`);
const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
return text(`${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : String(e);
return text(`Query error: ${msg}`);
}
},
);
server.tool(
"dbx_get_schema_context",
"Get compact table and column context for writing SQL",
{
connection_name: z.string().describe("Name of the DBX connection"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
tables: z.array(z.string()).optional().describe("Specific table names to include"),
max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
},
async ({ connection_name, database, schema, tables, max_tables }) => {
const config = await backend.findConnection(connection_name);
if (!config) return text(`Connection "${connection_name}" not found`);
const context = await buildSchemaContext(backend, withDatabase(config, database), {
schema,
tables,
maxTables: max_tables,
});
if (context.tables.length === 0) return text("No matching tables found.");
return text(formatSchemaContext(context));
},
);
server.tool(
"dbx_add_connection",
"Add a new database connection to DBX",
{
name: z.string().describe("Connection name"),
db_type: z.string().describe(DBX_CONNECTION_TYPE_DESCRIPTION),
host: z.string().describe("Database host"),
port: z.number().optional().describe("Database port (TDengine defaults to 6041, XuguDB defaults to 5138)"),
username: z.string().default("").describe("Username"),
password: z.string().default("").describe("Password"),
database: z.string().optional().describe("Default database name"),
ssl: z.boolean().default(false).describe("Enable SSL"),
},
async ({ name, db_type, host, port, username, password, database, ssl }) => {
const existing = await backend.findConnection(name);
if (existing) return text(`Connection "${name}" already exists.`);
const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
const DEFAULT_PORTS: Record<string, number> = { tdengine: 6041, xugu: 5138 };
const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
if (resolvedPort === undefined) return text("Port is required for this database type.");
const config = await backend.addConnection({
name,
db_type,
host,
port: resolvedPort,
username,
password,
database,
ssl,
ssh_enabled: false,
} as Omit<ConnectionConfig, "id">);
await notifyReload();
return text(`Connection "${config.name}" added (id: ${config.id}).`);
},
);
server.tool(
"dbx_remove_connection",
"Remove a database connection from DBX",
{
connection_name: z.string().describe("Name of the connection to remove"),
},
async ({ connection_name }) => {
const removed = await backend.removeConnection(connection_name);
if (!removed) return text(`Connection "${connection_name}" not found.`);
await notifyReload();
return text(`Connection "${connection_name}" removed.`);
},
);
// Desktop-only tools: open table and execute-and-show require the Tauri bridge
if (!isWebMode) {
server.tool(
"dbx_open_table",
"Open a table in DBX desktop app UI. Requires DBX to be running.",
{
connection_name: z.string().describe("Name of the DBX connection"),
table: z.string().describe("Table name to open"),
database: z.string().optional().describe("Database name"),
schema: z.string().optional().describe("Schema name"),
},
async ({ connection_name, table, database, schema }) => {
return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
},
);
server.tool(
"dbx_execute_and_show",
"Execute a SQL query in DBX desktop app UI and show results there. Requires DBX to be running.",
{
connection_name: z.string().describe("Name of the DBX connection"),
sql: z.string().describe("SQL query to execute"),
database: z.string().optional().describe("Database name"),
},
async ({ connection_name, sql, database }) => {
const config = await backend.findConnection(connection_name);
const safetyOptions = sqlSafetyFromEnv();
if (config?.db_type === "mongodb") {
const aggregate = parseMongoAggregateCommand(sql);
if (aggregate) {
const safety = evaluateMongoAggregateSafety(aggregate, safetyOptions);
"dbx_execute_and_show",
"Execute a SQL query in DBX desktop app UI and show results there. Requires DBX to be running.",
{
connection_name: z.string().describe("Name of the DBX connection"),
sql: z.string().describe("SQL query to execute"),
database: z.string().optional().describe("Database name"),
},
async ({ connection_name, sql, database }) => {
const config = await backend.findConnection(connection_name);
const safetyOptions = sqlSafetyFromEnv();
if (config?.db_type === "mongodb") {
const aggregate = parseMongoAggregateCommand(sql);
if (aggregate) {
const safety = evaluateMongoAggregateSafety(aggregate, safetyOptions);
if (!safety.allowed) return text(`Query blocked: ${safety.reason}`);
}
} else {
const safety = evaluateSqlSafety(sql, safetyOptions);
if (!safety.allowed) return text(`Query blocked: ${safety.reason}`);
}
} else {
const safety = evaluateSqlSafety(sql, safetyOptions);
if (!safety.allowed) return text(`Query blocked: ${safety.reason}`);
}
// MongoDB shell commands bypass the SQL safety evaluator; pass MCP
// safety flags to the desktop executor for command-aware gating.
return bridgeRequest(
"/execute-query",
{ connection_name, sql, database, allow_writes: safetyOptions.allowWrites, allow_dangerous: safetyOptions.allowDangerous },
"Query sent to DBX",
);
},
// MongoDB shell commands bypass the SQL safety evaluator; pass MCP
// safety flags to the desktop executor for command-aware gating.
return bridgeRequest(
"/execute-query",
{
connection_name,
sql,
database,
allow_writes: safetyOptions.allowWrites,
allow_dangerous: safetyOptions.allowDangerous,
},
"Query sent to DBX",
);
},
);
}
+1
View File
@@ -38,6 +38,7 @@ export const BRIDGE_REQUIRED_TYPES = [
"bigquery",
"kylin",
"sundb",
"xugu",
"jdbc",
"access",
] as const;