@dbx-app/mcp-server 0.4.3 → 0.4.5
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 +4 -4
- package/dist/index.d.ts +2 -0
- package/dist/index.js +91 -48
- package/package.json +2 -2
- package/server.json +2 -2
package/README.md
CHANGED
|
@@ -92,7 +92,7 @@ See the [DBX CLI README](../cli/README.md) for command details.
|
|
|
92
92
|
|
|
93
93
|
## SQL Safety
|
|
94
94
|
|
|
95
|
-
`dbx_execute_query` is read-only by default. To allow write statements such as `INSERT` or `UPDATE`, set:
|
|
95
|
+
`dbx_execute_query` accepts multiple SQL statements and executes them one at a time after checking each statement. It is read-only by default. To allow write statements such as `INSERT` or `UPDATE`, set:
|
|
96
96
|
|
|
97
97
|
```bash
|
|
98
98
|
DBX_MCP_ALLOW_WRITES=1
|
|
@@ -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
|
|
130
|
+
- Node.js 22.13.0 或更高版本
|
|
131
131
|
|
|
132
132
|
## License
|
|
133
133
|
|
|
@@ -213,7 +213,7 @@ dbx query local "select 1" --json
|
|
|
213
213
|
|
|
214
214
|
### SQL 安全
|
|
215
215
|
|
|
216
|
-
`dbx_execute_query`
|
|
216
|
+
`dbx_execute_query` 支持多条 SQL 语句,会逐条完成安全检查并依次执行。默认只读。若要允许 `INSERT`、`UPDATE` 等写操作,设置:
|
|
217
217
|
|
|
218
218
|
```bash
|
|
219
219
|
DBX_MCP_ALLOW_WRITES=1
|
|
@@ -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
|
|
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,34 @@
|
|
|
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 {
|
|
4
|
+
import { createRequire } from "node:module";
|
|
5
5
|
import { z } from "zod";
|
|
6
|
-
import { buildSchemaContext, createBackend, evaluateSqlSafety, formatSchemaContext, notifyReload, postBridge, sqlSafetyFromEnv, } from "@dbx-app/node-core";
|
|
6
|
+
import { buildSchemaContext, createBackend, evaluateMongoAggregateSafety, evaluateSqlSafety, formatCell, formatSchemaContext, isMainModule, mdTable, notifyReload, parseMongoAggregateCommand, postBridge, sqlSafetyFromEnv, splitSqlStatements, } from "@dbx-app/node-core";
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const packageJson = require("../package.json");
|
|
9
|
+
export const DBX_MCP_PACKAGE_VERSION = packageJson.version ?? "0.0.0";
|
|
7
10
|
function text(s) {
|
|
8
11
|
return { content: [{ type: "text", text: s }] };
|
|
9
12
|
}
|
|
10
|
-
function
|
|
11
|
-
|
|
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}`;
|
|
13
|
+
function toolError(code, message) {
|
|
14
|
+
return { ...text(`${code}: ${message}`), isError: true };
|
|
16
15
|
}
|
|
16
|
+
function withDatabase(config, database) {
|
|
17
|
+
return database === undefined ? config : { ...config, database };
|
|
18
|
+
}
|
|
19
|
+
function formatQueryToolResult(result, title) {
|
|
20
|
+
const prefix = title ? `${title}\n` : "";
|
|
21
|
+
if (result.columns.length === 0)
|
|
22
|
+
return text(`${prefix}Query executed. ${result.row_count} row(s) affected.`);
|
|
23
|
+
const rows = result.rows.map((r) => result.columns.map((c) => formatCell(r[c])));
|
|
24
|
+
return text(`${prefix}${mdTable(result.columns, rows)}\n\n${result.row_count} row(s)`);
|
|
25
|
+
}
|
|
26
|
+
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
27
|
export function createDbxMcpServer(backend, options = {}) {
|
|
18
28
|
const isWebMode = options.isWebMode ?? !!process.env.DBX_WEB_URL;
|
|
19
29
|
const server = new McpServer({
|
|
20
30
|
name: "dbx",
|
|
21
|
-
version:
|
|
31
|
+
version: DBX_MCP_PACKAGE_VERSION,
|
|
22
32
|
});
|
|
23
33
|
server.tool("dbx_list_connections", "List all database connections configured in DBX", {}, async () => {
|
|
24
34
|
const connections = await backend.loadConnections();
|
|
@@ -29,12 +39,13 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
29
39
|
});
|
|
30
40
|
server.tool("dbx_list_tables", "List tables and views for a database connection", {
|
|
31
41
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
42
|
+
database: z.string().optional().describe("Database name"),
|
|
32
43
|
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
33
|
-
}, async ({ connection_name, schema }) => {
|
|
44
|
+
}, async ({ connection_name, database, schema }) => {
|
|
34
45
|
const config = await backend.findConnection(connection_name);
|
|
35
46
|
if (!config)
|
|
36
|
-
return
|
|
37
|
-
const tables = await backend.listTables(config, schema);
|
|
47
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
48
|
+
const tables = await backend.listTables(withDatabase(config, database), schema);
|
|
38
49
|
if (tables.length === 0)
|
|
39
50
|
return text("No tables found.");
|
|
40
51
|
const rows = tables.map((t) => [t.name, t.type]);
|
|
@@ -43,12 +54,13 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
43
54
|
server.tool("dbx_describe_table", "Get column definitions for a table", {
|
|
44
55
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
45
56
|
table: z.string().describe("Table name"),
|
|
57
|
+
database: z.string().optional().describe("Database name"),
|
|
46
58
|
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
47
|
-
}, async ({ connection_name, table, schema }) => {
|
|
59
|
+
}, async ({ connection_name, table, database, schema }) => {
|
|
48
60
|
const config = await backend.findConnection(connection_name);
|
|
49
61
|
if (!config)
|
|
50
|
-
return
|
|
51
|
-
const columns = await backend.describeTable(config, table, schema);
|
|
62
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
63
|
+
const columns = await backend.describeTable(withDatabase(config, database), table, schema);
|
|
52
64
|
if (columns.length === 0)
|
|
53
65
|
return text("No columns found.");
|
|
54
66
|
const rows = columns.map((c) => [
|
|
@@ -62,47 +74,58 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
62
74
|
});
|
|
63
75
|
server.tool("dbx_execute_query", "Execute a SQL query on a database connection (max 100 rows returned)", {
|
|
64
76
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
77
|
+
database: z.string().optional().describe("Database name"),
|
|
65
78
|
sql: z.string().describe("SQL query to execute"),
|
|
66
|
-
}, async ({ connection_name, sql }) => {
|
|
79
|
+
}, async ({ connection_name, database, sql }) => {
|
|
67
80
|
const config = await backend.findConnection(connection_name);
|
|
68
81
|
if (!config)
|
|
69
|
-
return
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
82
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
83
|
+
if (config.db_type !== "mongodb") {
|
|
84
|
+
const safety = evaluateSqlSafety(sql, { ...sqlSafetyFromEnv(), allowMultipleStatements: true });
|
|
85
|
+
if (!safety.allowed)
|
|
86
|
+
return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
|
|
87
|
+
}
|
|
88
|
+
// MongoDB shell commands don't fit the SQL safety evaluator; the backend
|
|
89
|
+
// (node-core executeQuery) applies command-aware read/write gating.
|
|
73
90
|
try {
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
91
|
+
const statements = config.db_type === "mongodb" ? [sql] : splitSqlStatements(sql);
|
|
92
|
+
const results = [];
|
|
93
|
+
for (const statement of statements) {
|
|
94
|
+
results.push(await backend.executeQuery(withDatabase(config, database), statement));
|
|
95
|
+
}
|
|
96
|
+
if (results.length === 1)
|
|
97
|
+
return formatQueryToolResult(results[0]);
|
|
98
|
+
return text(results.map((result, index) => formatQueryToolResult(result, `Statement ${index + 1}`).content[0].text).join("\n\n"));
|
|
79
99
|
}
|
|
80
100
|
catch (e) {
|
|
81
101
|
const msg = e instanceof Error ? e.message : String(e);
|
|
82
|
-
return
|
|
102
|
+
return toolError("QUERY_ERROR", msg);
|
|
83
103
|
}
|
|
84
104
|
});
|
|
85
105
|
server.tool("dbx_get_schema_context", "Get compact table and column context for writing SQL", {
|
|
86
106
|
connection_name: z.string().describe("Name of the DBX connection"),
|
|
107
|
+
database: z.string().optional().describe("Database name"),
|
|
87
108
|
schema: z.string().optional().describe("Schema name (default: public for PostgreSQL)"),
|
|
88
109
|
tables: z.array(z.string()).optional().describe("Specific table names to include"),
|
|
89
110
|
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 }) => {
|
|
111
|
+
}, async ({ connection_name, database, schema, tables, max_tables }) => {
|
|
91
112
|
const config = await backend.findConnection(connection_name);
|
|
92
113
|
if (!config)
|
|
93
|
-
return
|
|
94
|
-
const context = await buildSchemaContext(backend, config,
|
|
114
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
115
|
+
const context = await buildSchemaContext(backend, withDatabase(config, database), {
|
|
116
|
+
schema,
|
|
117
|
+
tables,
|
|
118
|
+
maxTables: max_tables,
|
|
119
|
+
});
|
|
95
120
|
if (context.tables.length === 0)
|
|
96
121
|
return text("No matching tables found.");
|
|
97
122
|
return text(formatSchemaContext(context));
|
|
98
123
|
});
|
|
99
124
|
server.tool("dbx_add_connection", "Add a new database connection to DBX", {
|
|
100
125
|
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"),
|
|
126
|
+
db_type: z.string().describe(DBX_CONNECTION_TYPE_DESCRIPTION),
|
|
104
127
|
host: z.string().describe("Database host"),
|
|
105
|
-
port: z.number().optional().describe("Database port (TDengine defaults to 6041)"),
|
|
128
|
+
port: z.number().optional().describe("Database port (TDengine defaults to 6041, XuguDB defaults to 5138)"),
|
|
106
129
|
username: z.string().default("").describe("Username"),
|
|
107
130
|
password: z.string().default("").describe("Password"),
|
|
108
131
|
database: z.string().optional().describe("Default database name"),
|
|
@@ -112,7 +135,8 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
112
135
|
if (existing)
|
|
113
136
|
return text(`Connection "${name}" already exists.`);
|
|
114
137
|
const FILE_BASED_TYPES = new Set(["sqlite", "duckdb", "access"]);
|
|
115
|
-
const
|
|
138
|
+
const DEFAULT_PORTS = { tdengine: 6041, xugu: 5138 };
|
|
139
|
+
const resolvedPort = port ?? DEFAULT_PORTS[db_type] ?? (FILE_BASED_TYPES.has(db_type) ? 0 : undefined);
|
|
116
140
|
if (resolvedPort === undefined)
|
|
117
141
|
return text("Port is required for this database type.");
|
|
118
142
|
const config = await backend.addConnection({
|
|
@@ -134,7 +158,7 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
134
158
|
}, async ({ connection_name }) => {
|
|
135
159
|
const removed = await backend.removeConnection(connection_name);
|
|
136
160
|
if (!removed)
|
|
137
|
-
return
|
|
161
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
138
162
|
await notifyReload();
|
|
139
163
|
return text(`Connection "${connection_name}" removed.`);
|
|
140
164
|
});
|
|
@@ -146,6 +170,9 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
146
170
|
database: z.string().optional().describe("Database name"),
|
|
147
171
|
schema: z.string().optional().describe("Schema name"),
|
|
148
172
|
}, async ({ connection_name, table, database, schema }) => {
|
|
173
|
+
const config = await backend.findConnection(connection_name);
|
|
174
|
+
if (!config)
|
|
175
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
149
176
|
return bridgeRequest("/open-table", { connection_name, table, database, schema }, `Opened ${table} in DBX`);
|
|
150
177
|
});
|
|
151
178
|
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 +180,42 @@ export function createDbxMcpServer(backend, options = {}) {
|
|
|
153
180
|
sql: z.string().describe("SQL query to execute"),
|
|
154
181
|
database: z.string().optional().describe("Database name"),
|
|
155
182
|
}, async ({ connection_name, sql, database }) => {
|
|
156
|
-
const
|
|
157
|
-
if (!
|
|
158
|
-
return
|
|
159
|
-
|
|
183
|
+
const config = await backend.findConnection(connection_name);
|
|
184
|
+
if (!config)
|
|
185
|
+
return toolError("CONNECTION_NOT_FOUND", `Connection "${connection_name}" not found.`);
|
|
186
|
+
const safetyOptions = sqlSafetyFromEnv();
|
|
187
|
+
if (config?.db_type === "mongodb") {
|
|
188
|
+
const aggregate = parseMongoAggregateCommand(sql);
|
|
189
|
+
if (aggregate) {
|
|
190
|
+
const safety = evaluateMongoAggregateSafety(aggregate, safetyOptions);
|
|
191
|
+
if (!safety.allowed)
|
|
192
|
+
return toolError("SQL_BLOCKED", safety.reason ?? "Query blocked.");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
const safety = evaluateSqlSafety(sql, { ...safetyOptions, allowMultipleStatements: true });
|
|
197
|
+
if (!safety.allowed)
|
|
198
|
+
return toolError("SQL_BLOCKED", safety.reason ?? "SQL blocked.");
|
|
199
|
+
}
|
|
200
|
+
// MongoDB shell commands bypass the SQL safety evaluator; pass MCP
|
|
201
|
+
// safety flags to the desktop executor for command-aware gating.
|
|
202
|
+
return bridgeRequest("/execute-query", {
|
|
203
|
+
connection_name,
|
|
204
|
+
sql,
|
|
205
|
+
database,
|
|
206
|
+
allow_writes: safetyOptions.allowWrites,
|
|
207
|
+
allow_dangerous: safetyOptions.allowDangerous,
|
|
208
|
+
}, "Query sent to DBX");
|
|
160
209
|
});
|
|
161
210
|
}
|
|
162
211
|
return server;
|
|
163
212
|
}
|
|
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
213
|
async function bridgeRequest(path, body, successMsg) {
|
|
172
214
|
const res = await postBridge(path, body);
|
|
173
215
|
if (res.ok)
|
|
174
216
|
return text(successMsg);
|
|
175
|
-
|
|
217
|
+
const message = res.text.startsWith("DBX is not running") ? res.text : `Failed: ${res.text}`;
|
|
218
|
+
return toolError("DBX_NOT_RUNNING", message);
|
|
176
219
|
}
|
|
177
220
|
async function main() {
|
|
178
221
|
const backend = await createBackend();
|
|
@@ -180,7 +223,7 @@ async function main() {
|
|
|
180
223
|
const transport = new StdioServerTransport();
|
|
181
224
|
await server.connect(transport);
|
|
182
225
|
}
|
|
183
|
-
if (
|
|
226
|
+
if (isMainModule(import.meta.url, process.argv[1])) {
|
|
184
227
|
main().catch((e) => {
|
|
185
228
|
console.error("MCP Server failed to start:", e);
|
|
186
229
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dbx-app/mcp-server",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.5",
|
|
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.
|
|
37
|
+
"@dbx-app/node-core": "^0.4.5"
|
|
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.
|
|
9
|
+
"version": "0.4.5",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "@dbx-app/mcp-server",
|
|
14
|
-
"version": "0.4.
|
|
14
|
+
"version": "0.4.5",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
}
|