@dbx-app/mcp-server 0.4.3 → 0.4.4

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
@@ -127,7 +127,7 @@ PostgreSQL, MySQL, SQLite, Doris, StarRocks, and Redshift queries run directly f
127
127
  ## Requirements
128
128
 
129
129
  - [DBX](https://github.com/t8y2/dbx) installed with at least one connection configured
130
- - Node.js 18+
130
+ - Node.js 22.13.0 或更高版本
131
131
 
132
132
  ## License
133
133
 
@@ -242,4 +242,4 @@ PostgreSQL、MySQL、SQLite、Doris、StarRocks、Redshift 查询可由 MCP Serv
242
242
  ### 系统要求
243
243
 
244
244
  - 已安装 [DBX](https://github.com/t8y2/dbx) 并配置了至少一个数据库连接
245
- - Node.js 18+
245
+ - Node.js 22.13.0 or newer
package/dist/index.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { type Backend } from "@dbx-app/node-core";
4
+ export declare const DBX_MCP_PACKAGE_VERSION: string;
5
+ export declare 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, xugu, jdbc, access";
4
6
  export declare function createDbxMcpServer(backend: Backend, options?: {
5
7
  isWebMode?: boolean;
6
8
  }): McpServer;
package/dist/index.js CHANGED
@@ -1,24 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import { createRequire } from "node:module";
4
5
  import { pathToFileURL } from "node:url";
5
6
  import { z } from "zod";
6
- import { buildSchemaContext, createBackend, evaluateSqlSafety, formatSchemaContext, notifyReload, postBridge, sqlSafetyFromEnv, } from "@dbx-app/node-core";
7
+ import { buildSchemaContext, createBackend, evaluateMongoAggregateSafety, evaluateSqlSafety, formatCell, formatSchemaContext, mdTable, notifyReload, parseMongoAggregateCommand, postBridge, sqlSafetyFromEnv, } from "@dbx-app/node-core";
8
+ const require = createRequire(import.meta.url);
9
+ const packageJson = require("../package.json");
10
+ export const DBX_MCP_PACKAGE_VERSION = packageJson.version ?? "0.0.0";
7
11
  function text(s) {
8
12
  return { content: [{ type: "text", text: s }] };
9
13
  }
10
- function mdTable(headers, rows) {
11
- const widths = headers.map((h, i) => Math.max(h.length, ...rows.map((r) => (r[i] || "").length), 3));
12
- const header = `| ${headers.map((h, i) => h.padEnd(widths[i])).join(" | ")} |`;
13
- const sep = `| ${widths.map((w) => "-".repeat(w)).join(" | ")} |`;
14
- const body = rows.map((r) => `| ${r.map((c, i) => (c || "").padEnd(widths[i])).join(" | ")} |`).join("\n");
15
- return `${header}\n${sep}\n${body}`;
14
+ function toolError(code, message) {
15
+ return { ...text(`${code}: ${message}`), isError: true };
16
16
  }
17
+ function withDatabase(config, database) {
18
+ return database === undefined ? config : { ...config, database };
19
+ }
20
+ 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, xugu, jdbc, access";
17
21
  export function createDbxMcpServer(backend, options = {}) {
18
22
  const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL;
19
23
  const server = new McpServer({
20
24
  name: "dbx",
21
- version: "0.4.2",
25
+ version: DBX_MCP_PACKAGE_VERSION,
22
26
  });
23
27
  server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
24
28
  const connections = await backend.loadConnections();
@@ -29,12 +33,13 @@ export function createDbxMcpServer(backend, options = {}) {
29
33
  });
30
34
  server.tool("dbx_list_tables", "List tables and views for a database connection", {
31
35
  connection_name: z.string().describe("Name of the DBX connection"),
36
+ database: z.string().optional().describe("Database name"),
32
37
  schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
33
- }, async ({ connection_name, schema }) => {
38
+ }, async ({ connection_name, database, schema }) => {
34
39
  const config = await backend.findConnection(connection_name);
35
40
  if (!config)
36
- return text(`Connection "${connection_name}" not found`);
37
- const tables = await backend.listTables(config, schema);
41
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
42
+ const tables = await backend.listTables(withDatabase(config, database), schema);
38
43
  if (tables.length === 0)
39
44
  return text("No tables found.");
40
45
  const rows = tables.map((t) => [t.name, t.type]);
@@ -43,12 +48,13 @@ export function createDbxMcpServer(backend, options = {}) {
43
48
  server.tool("dbx_describe_table", "Get column definitions for a table", {
44
49
  connection_name: z.string().describe("Name of the DBX connection"),
45
50
  table: z.string().describe("Table name"),
51
+ database: z.string().optional().describe("Database name"),
46
52
  schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
47
- }, async ({ connection_name, table, schema }) => {
53
+ }, async ({ connection_name, table, database, schema }) => {
48
54
  const config = await backend.findConnection(connection_name);
49
55
  if (!config)
50
- return text(`Connection "${connection_name}" not found`);
51
- const columns = await backend.describeTable(config, table, schema);
56
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
57
+ const columns = await backend.describeTable(withDatabase(config, database), table, schema);
52
58
  if (columns.length === 0)
53
59
  return text("No columns found.");
54
60
  const rows = columns.map((c) => [
@@ -62,16 +68,21 @@ export function createDbxMcpServer(backend, options = {}) {
62
68
  });
63
69
  server.tool("dbx_execute_query", "Execute a SQL query on a database connection (max 100 rows returned)", {
64
70
  connection_name: z.string().describe("Name of the DBX connection"),
71
+ database: z.string().optional().describe("Database name"),
65
72
  sql: z.string().describe("SQL query to execute"),
66
- }, async ({ connection_name, sql }) => {
73
+ }, async ({ connection_name, database, sql }) => {
67
74
  const config = await backend.findConnection(connection_name);
68
75
  if (!config)
69
- return text(`Connection "${connection_name}" not found`);
70
- const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
71
- if (!safety.allowed)
72
- return text(`Query blocked: ${safety.reason}`);
76
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
77
+ if (config.db_type !== "mongodb") {
78
+ const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
79
+ if (!safety.allowed)
80
+ return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
81
+ }
82
+ // MongoDB shell commands don't fit the SQL safety evaluator; the backend
83
+ // (node-core executeQuery) applies command-aware read/write gating.
73
84
  try {
74
- const result = await backend.executeQuery(config, sql);
85
+ const result = await backend.executeQuery(withDatabase(config, database), sql);
75
86
  if (result.columns.length === 0)
76
87
  return text(`Query executed. ${result.row_count} row(s) affected.`);
77
88
  const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
@@ -79,30 +90,33 @@ export function createDbxMcpServer(backend, options = {}) {
79
90
  }
80
91
  catch (e) {
81
92
  const msg = e instanceof Error ? e.message : String(e);
82
- return text(`Query error: ${msg}`);
93
+ return toolError("QUERY_ERROR", msg);
83
94
  }
84
95
  });
85
96
  server.tool("dbx_get_schema_context", "Get compact table and column context for writing SQL", {
86
97
  connection_name: z.string().describe("Name of the DBX connection"),
98
+ database: z.string().optional().describe("Database name"),
87
99
  schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
88
100
  tables: z.array(z.string()).optional().describe("Specific table names to include"),
89
101
  max_tables: z.number().int().min(1).max(20).default(8).describe("Maximum number of tables to include"),
90
- }, async ({ connection_name, schema, tables, max_tables }) => {
102
+ }, async ({ connection_name, database, schema, tables, max_tables }) => {
91
103
  const config = await backend.findConnection(connection_name);
92
104
  if (!config)
93
- return text(`Connection "${connection_name}" not found`);
94
- const context = await buildSchemaContext(backend, config, { schema, tables, maxTables: max_tables });
105
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
106
+ const context = await buildSchemaContext(backend, withDatabase(config, database), {
107
+ schema,
108
+ tables,
109
+ maxTables: max_tables,
110
+ });
95
111
  if (context.tables.length === 0)
96
112
  return text("No matching tables found.");
97
113
  return text(formatSchemaContext(context));
98
114
  });
99
115
  server.tool("dbx_add_connection", "Add a new database connection to DBX", {
100
116
  name: z.string().describe("Connection name"),
101
- db_type: z
102
- .string()
103
- .describe("Database type: postgres, mysql, sqlite, redis, duckdb, clickhouse, sqlserver, mongodb, oracle, elasticsearch, doris, starrocks, redshift, dameng, kingbase, highgo, vastbase, goldendb, gaussdb, h2, snowflake, trino, hive, db2, informix, neo4j, cassandra, bigquery, kylin, sundb, tdengine, jdbc, access"),
117
+ db_type: z.string().describe(DBX_CONNECTION_TYPE_DESCRIPTION),
104
118
  host: z.string().describe("Database host"),
105
- port: z.number().optional().describe("Database port (TDengine defaults to 6041)"),
119
+ port: z.number().optional().describe("Database port (TDengine defaults to 6041, XuguDB defaults to 5138)"),
106
120
  username: z.string().default("").describe("Username"),
107
121
  password: z.string().default("").describe("Password"),
108
122
  database: z.string().optional().describe("Default database name"),
@@ -112,7 +126,8 @@ export function createDbxMcpServer(backend, options = {}) {
112
126
  if (existing)
113
127
  return text(`Connection "${name}" already exists.`);
114
128
  const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
115
- const resolvedPort = port ?? (db_type === "tdengine" ? 6041 : FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
129
+ const DEFAULT_PORTS = { tdengine: 6041, xugu: 5138 };
130
+ const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
116
131
  if (resolvedPort === undefined)
117
132
  return text("Port is required for this database type.");
118
133
  const config = await backend.addConnection({
@@ -134,7 +149,7 @@ export function createDbxMcpServer(backend, options = {}) {
134
149
  }, async ({ connection_name }) => {
135
150
  const removed = await backend.removeConnection(connection_name);
136
151
  if (!removed)
137
- return text(`Connection "${connection_name}" not found.`);
152
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
138
153
  await notifyReload();
139
154
  return text(`Connection "${connection_name}" removed.`);
140
155
  });
@@ -146,6 +161,9 @@ export function createDbxMcpServer(backend, options = {}) {
146
161
  database: z.string().optional().describe("Database name"),
147
162
  schema: z.string().optional().describe("Schema name"),
148
163
  }, async ({ connection_name, table, database, schema }) => {
164
+ const config = await backend.findConnection(connection_name);
165
+ if (!config)
166
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
149
167
  return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
150
168
  });
151
169
  server.tool("dbx_execute_and_show", "Execute a SQL query in DBX desktop app UI and show results there. Requires DBX to be running.", {
@@ -153,26 +171,42 @@ export function createDbxMcpServer(backend, options = {}) {
153
171
  sql: z.string().describe("SQL query to execute"),
154
172
  database: z.string().optional().describe("Database name"),
155
173
  }, async ({ connection_name, sql, database }) => {
156
- const safety = evaluateSqlSafety(sql, sqlSafetyFromEnv());
157
- if (!safety.allowed)
158
- return text(`Query blocked: ${safety.reason}`);
159
- return bridgeRequest("/execute-query", { connection_name, sql, database }, "Query sent to DBX");
174
+ const config = await backend.findConnection(connection_name);
175
+ if (!config)
176
+ return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
177
+ const safetyOptions = sqlSafetyFromEnv();
178
+ if (config?.db_type === "mongodb") {
179
+ const aggregate = parseMongoAggregateCommand(sql);
180
+ if (aggregate) {
181
+ const safety = evaluateMongoAggregateSafety(aggregate, safetyOptions);
182
+ if (!safety.allowed)
183
+ return toolError("SQL_BLOCKED", safety.reason ?? "Query blocked.");
184
+ }
185
+ }
186
+ else {
187
+ const safety = evaluateSqlSafety(sql, safetyOptions);
188
+ if (!safety.allowed)
189
+ return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
190
+ }
191
+ // MongoDB shell commands bypass the SQL safety evaluator; pass MCP
192
+ // safety flags to the desktop executor for command-aware gating.
193
+ return bridgeRequest("/execute-query", {
194
+ connection_name,
195
+ sql,
196
+ database,
197
+ allow_writes: safetyOptions.allowWrites,
198
+ allow_dangerous: safetyOptions.allowDangerous,
199
+ }, "Query sent to DBX");
160
200
  });
161
201
  }
162
202
  return server;
163
203
  }
164
- function formatCell(value) {
165
- if (value === null || value === undefined)
166
- return "NULL";
167
- if (typeof value === "object")
168
- return JSON.stringify(value);
169
- return String(value);
170
- }
171
204
  async function bridgeRequest(path, body, successMsg) {
172
205
  const res = await postBridge(path, body);
173
206
  if (res.ok)
174
207
  return text(successMsg);
175
- return text(res.text.startsWith("DBX is not running") ? res.text : `Failed: ${res.text}`);
208
+ const message = res.text.startsWith("DBX is not running") ? res.text : `Failed: ${res.text}`;
209
+ return toolError("DBX_NOT_RUNNING", message);
176
210
  }
177
211
  async function main() {
178
212
  const backend = await createBackend();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dbx-app/mcp-server",
3
- "version": "0.4.3",
3
+ "version": "0.4.4",
4
4
  "mcpName": "io.github.t8y2/dbx",
5
5
  "description": "MCP server for DBX — query databases from Claude Code, Cursor, and other AI agents",
6
6
  "type": "module",
@@ -34,7 +34,7 @@
34
34
  "dependencies": {
35
35
  "@modelcontextprotocol/sdk": "^1.12.1",
36
36
  "zod": "^3.25.20",
37
- "@dbx-app/node-core": "^0.4.3"
37
+ "@dbx-app/node-core": "^0.4.4"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.15.21",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/t8y2/dbx",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.4.3",
9
+ "version": "0.4.4",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "@dbx-app/mcp-server",
14
- "version": "0.4.3",
14
+ "version": "0.4.4",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  }